diff --git a/requests/reference_object_copy_test.go b/requests/reference_object_copy_test.go new file mode 100644 index 0000000..c61fd16 --- /dev/null +++ b/requests/reference_object_copy_test.go @@ -0,0 +1,73 @@ +// Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley +// SPDX-License-Identifier: MIT + +package requests + +import ( + "fmt" + "strings" + "testing" + "unsafe" + + liberrors "github.com/pb33f/libopenapi-validator/errors" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" +) + +func TestReferenceObjectCopiesRequestBodyPerViolation(t *testing.T) { + const ( + violations = 80 + paddingLen = 256 * 1024 + ) + + body := largeObjectWithTypedFields(paddingLen, violations) + schema := parseSchemaFromSpec(t, objectSchemaRequiringStrings(violations), 3.1) + + valid, errs := ValidateRequestSchema(&ValidateRequestSchemaInput{ + Request: postRequestWithBody(body), + Schema: schema, + Version: 3.1, + }) + require.False(t, valid) + require.Len(t, errs, 1) + require.GreaterOrEqual(t, len(errs[0].SchemaValidationErrors), violations) + + ptrs := uniqueReferenceObjectBackings(errs[0].SchemaValidationErrors) + t.Logf("body_bytes=%d schema_violations=%d unique_ReferenceObject_backings=%d", + len(body), len(errs[0].SchemaValidationErrors), len(ptrs)) + + assert.Equal(t, 1, len(ptrs)) +} + +func objectSchemaRequiringStrings(fields int) string { + var b strings.Builder + b.WriteString("type: object\nproperties:\n pad:\n type: string\n") + for i := 0; i < fields; i++ { + fmt.Fprintf(&b, " f%d:\n type: string\n", i) + } + return b.String() +} + +func largeObjectWithTypedFields(paddingLen, fields int) string { + var b strings.Builder + b.Grow(paddingLen + fields*16 + 32) + b.WriteString(`{"pad":"`) + b.WriteString(strings.Repeat("x", paddingLen)) + b.WriteString(`"`) + for i := 0; i < fields; i++ { + fmt.Fprintf(&b, `,"f%d":1`, i) + } + b.WriteString(`}`) + return b.String() +} + +func uniqueReferenceObjectBackings(fails []*liberrors.SchemaValidationFailure) map[uintptr]struct{} { + seen := make(map[uintptr]struct{}, len(fails)) + for _, fail := range fails { + if fail == nil || fail.ReferenceObject == "" { + continue + } + seen[uintptr(unsafe.Pointer(unsafe.StringData(fail.ReferenceObject)))] = struct{}{} + } + return seen +} diff --git a/requests/validate_request.go b/requests/validate_request.go index e760f85..9f74197 100644 --- a/requests/validate_request.go +++ b/requests/validate_request.go @@ -206,6 +206,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*liberror schFlatErrs := helpers.FlattenSchemaOutputErrors(jk.DetailedOutput()) renderedNode, resourceNodes := schema_validation.DiagnosticLocationNodes(renderedSchema, cachedNode, resourceNodes) + var fallbackReferenceObject string for q := range schFlatErrs { er := schFlatErrs[q] @@ -240,7 +241,10 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*liberror } } if referenceObject == "" { - referenceObject = string(requestBody) + if fallbackReferenceObject == "" { + fallbackReferenceObject = string(requestBody) + } + referenceObject = fallbackReferenceObject } errMsg := er.Error.Kind.LocalizedString(message.NewPrinter(language.Tag{})) diff --git a/responses/validate_response.go b/responses/validate_response.go index e307b86..6032ffb 100644 --- a/responses/validate_response.go +++ b/responses/validate_response.go @@ -244,6 +244,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*liberr _ = yaml.Unmarshal(renderedSchema, renderedNode) } + var fallbackReferenceObject string for q := range schFlatErrs { er := schFlatErrs[q] @@ -276,7 +277,10 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*liberr } } if referenceObject == "" { - referenceObject = string(responseBody) + if fallbackReferenceObject == "" { + fallbackReferenceObject = string(responseBody) + } + referenceObject = fallbackReferenceObject } violation := &liberrors.SchemaValidationFailure{ diff --git a/schema_validation/validate_schema.go b/schema_validation/validate_schema.go index 425eb59..8e988b7 100644 --- a/schema_validation/validate_schema.go +++ b/schema_validation/validate_schema.go @@ -258,6 +258,8 @@ func extractBasicErrors(schFlatErrs []jsonschema.OutputUnit, propertyInfo := extractPropertyNameFromError(jk) rootNode, resourceNodes := DiagnosticLocationNodes(renderedSchema, renderedNode, resourceNodes) + var fallbackReferenceObject string + referenceSchema := string(renderedSchema) for q := range schFlatErrs { er := schFlatErrs[q] @@ -292,7 +294,10 @@ func extractBasicErrors(schFlatErrs []jsonschema.OutputUnit, } } if referenceObject == "" { - referenceObject = string(payload) + if fallbackReferenceObject == "" { + fallbackReferenceObject = string(payload) + } + referenceObject = fallbackReferenceObject } violation := &liberrors.SchemaValidationFailure{ @@ -301,7 +306,7 @@ func extractBasicErrors(schFlatErrs []jsonschema.OutputUnit, FieldPath: helpers.ExtractJSONPathFromStringLocation(er.InstanceLocation), InstancePath: helpers.ConvertStringLocationToPathSegments(er.InstanceLocation), KeywordLocation: er.KeywordLocation, - ReferenceSchema: string(renderedSchema), + ReferenceSchema: referenceSchema, ReferenceObject: referenceObject, OriginalJsonSchemaError: jk, }