diff --git a/docs/internal/sources.md b/docs/internal/sources.md index 73f43a0..3ed9279 100644 --- a/docs/internal/sources.md +++ b/docs/internal/sources.md @@ -151,7 +151,8 @@ control into the rendered prompt. The `internal/validate` provides validators backed by an operating-system filesystem or an `fs.FS`. Invalid generated content is returned as a validation result; inability to load, register, or compile a schema is an operational -error. +error. Basic validation checks body emptiness on the byte slice directly +instead of first making a response-sized string copy. Every preparation operation creates one operation-local validation plan. None, basic, and JSON modes retain the effective output contract without source diff --git a/internal/validate/standard_validator.go b/internal/validate/standard_validator.go index f56d15d..d3b5dd8 100644 --- a/internal/validate/standard_validator.go +++ b/internal/validate/standard_validator.go @@ -188,7 +188,7 @@ func validateArtifact(ctx context.Context, artifact *domain.Artifact, contract d res.IsValid = true return res, nil case domain.ValidationBasic: - empty := strings.TrimSpace(string(artifact.Body)) == "" + empty := len(bytes.TrimSpace(artifact.Body)) == 0 if err := ctx.Err(); err != nil { return domain.ValidationResult{}, err } diff --git a/internal/validate/standard_validator_test.go b/internal/validate/standard_validator_test.go index 2368d0c..b0a88c4 100644 --- a/internal/validate/standard_validator_test.go +++ b/internal/validate/standard_validator_test.go @@ -36,32 +36,51 @@ func TestStandardValidatorNoneSkipped(t *testing.T) { func TestStandardValidatorBasicSuccess(t *testing.T) { v := NewStandardValidator("") - - res, err := v.Validate(context.Background(), &domain.Artifact{Body: []byte("hello")}, domain.OutputContract{ - ValidationMode: domain.ValidationBasic, - }) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - if res.Status != domain.ValidationPassed || !res.IsValid { - t.Fatalf("expected passed/valid, got status=%q valid=%v", res.Status, res.IsValid) + for _, tc := range []struct { + name string + body []byte + }{ + {name: "text", body: []byte("hello")}, + {name: "invalid UTF-8 byte", body: []byte{0xff}}, + } { + t.Run(tc.name, func(t *testing.T) { + res, err := v.Validate(context.Background(), &domain.Artifact{Body: tc.body}, domain.OutputContract{ + ValidationMode: domain.ValidationBasic, + }) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if res.Status != domain.ValidationPassed || !res.IsValid { + t.Fatalf("expected passed/valid, got status=%q valid=%v", res.Status, res.IsValid) + } + }) } } func TestStandardValidatorBasicFailureEmpty(t *testing.T) { v := NewStandardValidator("") - - res, err := v.Validate(context.Background(), &domain.Artifact{Body: []byte(" \n\t ")}, domain.OutputContract{ - ValidationMode: domain.ValidationBasic, - }) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - if res.Status != domain.ValidationFailed || res.IsValid { - t.Fatalf("expected failed/invalid, got status=%q valid=%v", res.Status, res.IsValid) - } - if len(res.Errors) == 0 { - t.Fatal("expected validation errors") + for _, tc := range []struct { + name string + body []byte + }{ + {name: "empty bytes", body: []byte{}}, + {name: "ASCII whitespace", body: []byte(" \n\t ")}, + {name: "Unicode whitespace", body: []byte("\u2003\u00a0\u3000")}, + } { + t.Run(tc.name, func(t *testing.T) { + res, err := v.Validate(context.Background(), &domain.Artifact{Body: tc.body}, domain.OutputContract{ + ValidationMode: domain.ValidationBasic, + }) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if res.Status != domain.ValidationFailed || res.IsValid { + t.Fatalf("expected failed/invalid, got status=%q valid=%v", res.Status, res.IsValid) + } + if len(res.Errors) == 0 { + t.Fatal("expected validation errors") + } + }) } } @@ -947,7 +966,8 @@ func BenchmarkBasicValidation(b *testing.B) { } b.ReportAllocs() - b.SetBytes(int64(len(benchmark.body))) + // Nonempty input can be classified from its boundary bytes without + // scanning the body, so report latency rather than bytes per second. b.ResetTimer() for range b.N { result, err := validator.Validate(ctx, artifact, contract)