From 6604b054365f5676fff086ae6b208962c75b2542 Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Sun, 14 Jun 2026 11:38:12 -0400 Subject: [PATCH 1/6] bump deps! --- go.mod | 10 +++--- go.sum | 16 ++++----- schema_validation/validate_document.go | 36 +++++++++++++++----- schema_validation/validate_document_test.go | 37 +++++++++++++++------ 4 files changed, 67 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index 87c3653..909b7a3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/pb33f/libopenapi-validator -go 1.25.0 +go 1.25.7 require ( github.com/basgys/goxml2json v1.1.1-0.20231018121955-e66ee54ceaad @@ -8,11 +8,11 @@ require ( github.com/go-openapi/jsonpointer v0.23.1 github.com/goccy/go-yaml v1.19.2 github.com/pb33f/jsonpath v0.8.2 - github.com/pb33f/libopenapi v0.37.2 + github.com/pb33f/libopenapi v0.38.0 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/stretchr/testify v1.11.1 - go.yaml.in/yaml/v4 v4.0.0-rc.4 - golang.org/x/text v0.37.0 + go.yaml.in/yaml/v4 v4.0.0-rc.5 + golang.org/x/text v0.38.0 ) require ( @@ -24,7 +24,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect golang.org/x/net v0.50.0 // indirect - golang.org/x/sync v0.20.0 // indirect + golang.org/x/sync v0.21.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d8a0ec2..640f371 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pb33f/jsonpath v0.8.2 h1:Ou4C7zjYClBm97dfZjDCjdZGusJoynv/vrtiEKNfj2Y= github.com/pb33f/jsonpath v0.8.2/go.mod h1:zBV5LJW4OQOPatmQE2QdKpGQJvhDTlE5IEj6ASaRNTo= -github.com/pb33f/libopenapi v0.37.2 h1:4Kb4w/h2BVKb099oYIZqeDxEBhUioWA+z6WJhBOk2r8= -github.com/pb33f/libopenapi v0.37.2/go.mod h1:MsDdUlQ1CdrIDO5v26JfgBxQs7kcaOUEpMP3EqU6bI4= +github.com/pb33f/libopenapi v0.38.0 h1:OG+AMr2dMeB0BZdmm+j6iFy3Uxyl+W0HZpdD72zcyGs= +github.com/pb33f/libopenapi v0.38.0/go.mod h1:HrXjcGH/igq+/Af5l5/hmDD+yQRBRiTHgl0FmLdCZjg= github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -43,8 +43,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= -go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +go.yaml.in/yaml/v4 v4.0.0-rc.5 h1:JVliQq9EGOYaTgMi+k8BhUJyqcGk4ZqeuiN1Cirba9c= +go.yaml.in/yaml/v4 v4.0.0-rc.5/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= @@ -61,8 +61,8 @@ golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -82,8 +82,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/schema_validation/validate_document.go b/schema_validation/validate_document.go index d539db4..c98d92d 100644 --- a/schema_validation/validate_document.go +++ b/schema_validation/validate_document.go @@ -1,4 +1,4 @@ -// Copyright 2023-2025 Princess Beef Heavy Industries, LLC / Dave Shanley +// Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley // SPDX-License-Identifier: MIT package schema_validation @@ -235,8 +235,17 @@ func ValidateOpenAPIDocumentWithPrecompiled(doc libopenapi.Document, compiledSch loadedSchema := info.APISchema var validationErrors []*liberrors.ValidationError - // Check if both JSON representations are nil before proceeding - if info.SpecJSON == nil && info.SpecJSONBytes == nil { + // libopenapi builds the JSON view of the document lazily: the deprecated + // SpecJSON / SpecJSONBytes fields stay nil until an accessor runs, and the + // accessors return nil when conversion is disabled (SkipJSONConversion) or + // the document cannot be represented as JSON. + specJSON := info.GetSpecJSON() + specJSONBytes := info.GetSpecJSONBytes() + + // Check if both JSON representations are unavailable before proceeding. + // Empty bytes count as unavailable: neither branch of the normalization + // ladder below would fire, and a nil document must not reach Validate. + if specJSON == nil && (specJSONBytes == nil || len(*specJSONBytes) == 0) { validationErrors = append(validationErrors, &liberrors.ValidationError{ ValidationType: helpers.Schema, ValidationSubType: "document", @@ -279,13 +288,13 @@ func ValidateOpenAPIDocumentWithPrecompiled(doc libopenapi.Document, compiledSch // Build the normalized document value for validation. // Prefer SpecJSONBytes (single unmarshal) over SpecJSON (marshal+unmarshal round-trip). var normalized any - if info.SpecJSONBytes != nil && len(*info.SpecJSONBytes) > 0 { + if specJSONBytes != nil && len(*specJSONBytes) > 0 { var err error - normalized, err = jsonschema.UnmarshalJSON(bytes.NewReader(*info.SpecJSONBytes)) + normalized, err = jsonschema.UnmarshalJSON(bytes.NewReader(*specJSONBytes)) if err != nil { // Fall back to normalizeJSON if UnmarshalJSON fails - if info.SpecJSON != nil { - normalized, err = normalizeJSON(*info.SpecJSON) + if specJSON != nil { + normalized, err = normalizeJSON(*specJSON) if err != nil { return false, []*liberrors.ValidationError{buildDocumentDecodeError( fmt.Sprintf("The OpenAPI document cannot be converted to JSON: %s", err.Error()), @@ -299,9 +308,9 @@ func ValidateOpenAPIDocumentWithPrecompiled(doc libopenapi.Document, compiledSch )} } } - } else if info.SpecJSON != nil { + } else if specJSON != nil { var err error - normalized, err = normalizeJSON(*info.SpecJSON) + normalized, err = normalizeJSON(*specJSON) if err != nil { return false, []*liberrors.ValidationError{buildDocumentDecodeError( fmt.Sprintf("The OpenAPI document cannot be converted to JSON: %s", err.Error()), @@ -310,6 +319,15 @@ func ValidateOpenAPIDocumentWithPrecompiled(doc libopenapi.Document, compiledSch } } + // belt and braces: never validate a nil document - it produces misleading + // "got null, want object" schema errors instead of a clear reason. + if normalized == nil { + return false, []*liberrors.ValidationError{buildDocumentDecodeError( + "The document has no usable JSON representation to validate", + "SpecJSON", + )} + } + // Validate the document scErrs := jsch.Validate(normalized) diff --git a/schema_validation/validate_document_test.go b/schema_validation/validate_document_test.go index 6f8e2f1..cd79658 100644 --- a/schema_validation/validate_document_test.go +++ b/schema_validation/validate_document_test.go @@ -1,4 +1,4 @@ -// Copyright 2023-2025 Princess Beef Heavy Industries, LLC / Dave Shanley +// Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley // SPDX-License-Identifier: MIT package schema_validation @@ -9,6 +9,7 @@ import ( "testing" "github.com/pb33f/libopenapi" + "github.com/pb33f/libopenapi/datamodel" "github.com/stretchr/testify/assert" "go.yaml.in/yaml/v4" @@ -210,6 +211,10 @@ paths: {}` }, } info := doc.GetSpecInfo() + // the JSON view is built lazily behind a sync.Once; latch it before + // injecting the poisoned values, otherwise the first accessor call inside + // the validator rebuilds the real JSON view and overwrites the injection. + _ = info.GetSpecJSONBytes() info.SpecJSON = &badSpecJSON info.SpecJSONBytes = nil @@ -240,6 +245,8 @@ paths: {}` } corrupt := []byte(`{not valid json!!!}`) info := doc.GetSpecInfo() + // latch the lazy JSON build before injecting (see note in the test above) + _ = info.GetSpecJSONBytes() info.SpecJSON = &badSpecJSON info.SpecJSONBytes = &corrupt @@ -427,12 +434,10 @@ info: title: Test ` - doc, _ := libopenapi.NewDocument([]byte(spec)) - - // Simulate the nil SpecJSON scenario by setting both to nil - info := doc.GetSpecInfo() - info.SpecJSON = nil - info.SpecJSONBytes = nil + // SkipJSONConversion disables the JSON view entirely: the lazy accessors + // return nil, which is the production scenario this guard protects against. + docConfig := &datamodel.DocumentConfiguration{SkipJSONConversion: true} + doc, _ := libopenapi.NewDocumentWithConfiguration([]byte(spec), docConfig) // validate! valid, errors := ValidateOpenAPIDocument(doc) @@ -507,8 +512,11 @@ func TestValidateDocument_SpecJSONBytesPath(t *testing.T) { info := doc.GetSpecInfo() + // The JSON view builds lazily; latch it before manipulating the fields so + // the validator's accessor calls don't rebuild and overwrite the setup. + assert.NotNil(t, info.GetSpecJSONBytes(), "SpecJSONBytes should be populated by libopenapi") + // Nil out SpecJSON but leave SpecJSONBytes intact — forces the SpecJSONBytes path - assert.NotNil(t, info.SpecJSONBytes, "SpecJSONBytes should be populated by libopenapi") info.SpecJSON = nil valid, errs := ValidateOpenAPIDocument(doc) @@ -522,6 +530,10 @@ func TestValidateDocument_SpecJSONBytesCorrupt_NilSpecJSON(t *testing.T) { info := doc.GetSpecInfo() + // latch the lazy JSON build before injecting, so the validator's accessor + // calls return the injected values instead of rebuilding the real view. + _ = info.GetSpecJSONBytes() + // Put corrupt bytes in SpecJSONBytes so UnmarshalJSON fails, // and nil out SpecJSON so the fallback normalizeJSON path is skipped. // This exercises the nil guard on SpecJSON inside the error branch. @@ -543,6 +555,10 @@ func TestValidateDocument_SpecJSONBytesCorrupt_FallbackToSpecJSON(t *testing.T) info := doc.GetSpecInfo() + // latch the lazy JSON build before injecting, so the corrupt bytes + // actually reach the validator instead of being rebuilt over. + _ = info.GetSpecJSONBytes() + // Put corrupt bytes in SpecJSONBytes so UnmarshalJSON fails, // but leave SpecJSON intact so the fallback to normalizeJSON executes. corrupt := []byte(`{not valid json!!!}`) @@ -560,8 +576,9 @@ func TestValidateDocument_SpecJSONBytesPath_Invalid(t *testing.T) { info := doc.GetSpecInfo() - // Nil out SpecJSON but leave SpecJSONBytes intact - assert.NotNil(t, info.SpecJSONBytes, "SpecJSONBytes should be populated by libopenapi") + // latch the lazy JSON build, then nil out SpecJSON but leave + // SpecJSONBytes intact + assert.NotNil(t, info.GetSpecJSONBytes(), "SpecJSONBytes should be populated by libopenapi") info.SpecJSON = nil valid, errs := ValidateOpenAPIDocument(doc) From d597f960fbfd0a88b1a5f0d7835494fa1fd7e6be Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Mon, 15 Jun 2026 11:58:15 -0400 Subject: [PATCH 2/6] honor readOnly/writeOnly in required validation by direction Add schema_validation/directional_schema.go providing direction-aware schema rendering. readOnly properties are dropped from `required` when validating request bodies, and writeOnly properties are dropped when validating response bodies, so a schema shared between a request and response no longer fails required checks for properties that cannot be present in that direction. Introduce SchemaValidationPurpose and SchemaCacheKey so request, response, and generic compilations of the same schema get distinct cache entries keyed by hash, version, and purpose. Wire request/response body validation and cache warming through RenderSchemaForValidation and the new cache keys, caching the rendered node alongside the compiled schema. Pruning recurses through properties, composition (allOf/anyOf/oneOf), arrays, and conditional subschemas, and empty required lists are removed entirely. Adds tests covering nested, allOf, and shared request/response schemas. #281 --- requests/validate_request.go | 34 ++- requests/validate_request_test.go | 97 ++++++- responses/validate_response.go | 33 ++- responses/validate_response_test.go | 101 ++++++- schema_validation/directional_schema.go | 211 +++++++++++++++ schema_validation/directional_schema_test.go | 261 +++++++++++++++++++ validator.go | 129 ++++----- validator_test.go | 129 +++++++++ 8 files changed, 912 insertions(+), 83 deletions(-) create mode 100644 schema_validation/directional_schema.go create mode 100644 schema_validation/directional_schema_test.go diff --git a/requests/validate_request.go b/requests/validate_request.go index b43387f..66d3cc9 100644 --- a/requests/validate_request.go +++ b/requests/validate_request.go @@ -15,7 +15,6 @@ import ( "strconv" "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/pb33f/libopenapi/utils" "github.com/santhosh-tekuri/jsonschema/v6" "go.yaml.in/yaml/v4" "golang.org/x/text/language" @@ -83,7 +82,7 @@ func requestBodyReader(body io.ReadCloser) io.Reader { } value := reflect.ValueOf(body) - if value.Kind() == reflect.Ptr { + if value.Kind() == reflect.Pointer { if value.IsNil() { return nil } @@ -159,7 +158,11 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*liberror } if validationOptions.SchemaCache != nil { - hash := input.Schema.GoLow().Hash() + hash := schema_validation.SchemaCacheKey( + input.Schema.GoLow().Hash(), + input.Version, + schema_validation.SchemaValidationPurposeRequestBody, + ) if cached, ok := validationOptions.SchemaCache.Load(hash); ok && cached != nil && cached.CompiledSchema != nil { renderedSchema = cached.RenderedInline referenceSchema = cached.ReferenceSchema @@ -171,10 +174,16 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*liberror // Cache miss or no cache - render and compile if compiledSchema == nil { - renderCtx := base.NewInlineRenderContextForValidation() - var renderErr error - renderedSchema, renderErr = input.Schema.RenderInlineWithContext(renderCtx) - referenceSchema = string(renderedSchema) + rendered, renderErr := schema_validation.RenderSchemaForValidation( + input.Schema, + schema_validation.SchemaValidationPurposeRequestBody, + ) + if rendered != nil { + renderedSchema = rendered.RenderedInline + referenceSchema = rendered.ReferenceSchema + jsonSchema = rendered.RenderedJSON + cachedNode = rendered.RenderedNode + } // If rendering failed (e.g., circular reference), return the render error if renderErr != nil { @@ -198,10 +207,13 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*liberror return false, validationErrors } - jsonSchema, _ = utils.ConvertYAMLtoJSON(renderedSchema) - var err error - schemaName := fmt.Sprintf("%x", input.Schema.GoLow().Hash()) + hash := schema_validation.SchemaCacheKey( + input.Schema.GoLow().Hash(), + input.Version, + schema_validation.SchemaValidationPurposeRequestBody, + ) + schemaName := fmt.Sprintf("%x", hash) compiledSchema, err = helpers.NewCompiledSchemaWithVersion( schemaName, jsonSchema, @@ -224,13 +236,13 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*liberror } if validationOptions.SchemaCache != nil { - hash := input.Schema.GoLow().Hash() validationOptions.SchemaCache.Store(hash, &cache.SchemaCacheEntry{ Schema: input.Schema, RenderedInline: renderedSchema, ReferenceSchema: referenceSchema, RenderedJSON: jsonSchema, CompiledSchema: compiledSchema, + RenderedNode: cachedNode, }) } } diff --git a/requests/validate_request_test.go b/requests/validate_request_test.go index fd4ea8d..3beb140 100644 --- a/requests/validate_request_test.go +++ b/requests/validate_request_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/pb33f/libopenapi-validator/config" + "github.com/pb33f/libopenapi-validator/schema_validation" ) func TestValidateRequestSchema(t *testing.T) { @@ -153,7 +154,8 @@ properties: assert.Len(t, errors, 0) // Verify cache was populated - hash := schema.GoLow().Hash() + hash := schema_validation.SchemaCacheKey(schema.GoLow().Hash(), openAPIVersion, + schema_validation.SchemaValidationPurposeRequestBody) cached, ok := opts.SchemaCache.Load(hash) assert.True(t, ok, "Schema should be in cache") assert.NotNil(t, cached, "Cached entry should not be nil") @@ -162,6 +164,99 @@ properties: assert.NotNil(t, cached.RenderedJSON, "JSON schema should be cached") } +func TestValidateRequestSchema_ReadOnlyRequiredIgnored(t *testing.T) { + schema := parseSchemaFromSpec(t, `type: object +required: + - id + - name +properties: + id: + type: string + readOnly: true + name: + type: string`, 3.1) + + valid, errors := ValidateRequestSchema(&ValidateRequestSchemaInput{ + Request: postRequestWithBody(`{"name":"John"}`), + Schema: schema, + Version: 3.1, + }) + + assert.True(t, valid) + assert.Empty(t, errors) +} + +func TestValidateRequestSchema_WriteOnlyRequiredStillApplies(t *testing.T) { + schema := parseSchemaFromSpec(t, `type: object +required: + - password +properties: + password: + type: string + writeOnly: true`, 3.1) + + valid, errors := ValidateRequestSchema(&ValidateRequestSchemaInput{ + Request: postRequestWithBody(`{}`), + Schema: schema, + Version: 3.1, + }) + + assert.False(t, valid) + require.Len(t, errors, 1) + require.Len(t, errors[0].SchemaValidationErrors, 1) + assert.Equal(t, "missing property 'password'", errors[0].SchemaValidationErrors[0].Reason) +} + +func TestValidateRequestSchema_NestedReadOnlyRequiredIgnored(t *testing.T) { + schema := parseSchemaFromSpec(t, `type: object +required: + - profile +properties: + profile: + type: object + required: + - id + - email + properties: + id: + type: string + readOnly: true + email: + type: string`, 3.1) + + valid, errors := ValidateRequestSchema(&ValidateRequestSchemaInput{ + Request: postRequestWithBody(`{"profile":{"email":"john@example.com"}}`), + Schema: schema, + Version: 3.1, + }) + + assert.True(t, valid) + assert.Empty(t, errors) +} + +func TestValidateRequestSchema_AllOfReadOnlyRequiredIgnored(t *testing.T) { + schema := parseSchemaFromSpec(t, `allOf: + - type: object + required: + - id + - name + properties: + id: + type: string + readOnly: true + name: + type: string`, 3.1) + + valid, errors := ValidateRequestSchema(&ValidateRequestSchemaInput{ + Request: postRequestWithBody(`{"name":"John"}`), + Schema: schema, + Version: 3.1, + }) + + assert.True(t, valid) + assert.Empty(t, errors) +} + func TestValidateRequestSchema_NilSchema(t *testing.T) { // Test when schema is nil valid, errors := ValidateRequestSchema(&ValidateRequestSchemaInput{ diff --git a/responses/validate_response.go b/responses/validate_response.go index 93527e5..c57f10d 100644 --- a/responses/validate_response.go +++ b/responses/validate_response.go @@ -15,7 +15,6 @@ import ( "strconv" "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/pb33f/libopenapi/utils" "github.com/santhosh-tekuri/jsonschema/v6" "go.yaml.in/yaml/v4" "golang.org/x/text/language" @@ -71,10 +70,15 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*liberr } if validationOptions.SchemaCache != nil { - hash := input.Schema.GoLow().Hash() + hash := schema_validation.SchemaCacheKey( + input.Schema.GoLow().Hash(), + input.Version, + schema_validation.SchemaValidationPurposeResponseBody, + ) if cached, ok := validationOptions.SchemaCache.Load(hash); ok && cached != nil && cached.CompiledSchema != nil { renderedSchema = cached.RenderedInline referenceSchema = cached.ReferenceSchema + jsonSchema = cached.RenderedJSON compiledSchema = cached.CompiledSchema cachedNode = cached.RenderedNode } @@ -82,10 +86,16 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*liberr // Cache miss or no cache - render and compile if compiledSchema == nil { - renderCtx := base.NewInlineRenderContextForValidation() - var renderErr error - renderedSchema, renderErr = input.Schema.RenderInlineWithContext(renderCtx) - referenceSchema = string(renderedSchema) + rendered, renderErr := schema_validation.RenderSchemaForValidation( + input.Schema, + schema_validation.SchemaValidationPurposeResponseBody, + ) + if rendered != nil { + renderedSchema = rendered.RenderedInline + referenceSchema = rendered.ReferenceSchema + jsonSchema = rendered.RenderedJSON + cachedNode = rendered.RenderedNode + } // If rendering failed (e.g., circular reference), return the render error if renderErr != nil { @@ -109,10 +119,13 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*liberr return false, validationErrors } - jsonSchema, _ = utils.ConvertYAMLtoJSON(renderedSchema) - var err error - schemaName := fmt.Sprintf("%x", input.Schema.GoLow().Hash()) + hash := schema_validation.SchemaCacheKey( + input.Schema.GoLow().Hash(), + input.Version, + schema_validation.SchemaValidationPurposeResponseBody, + ) + schemaName := fmt.Sprintf("%x", hash) compiledSchema, err = helpers.NewCompiledSchemaWithVersion( schemaName, jsonSchema, @@ -136,13 +149,13 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*liberr } if validationOptions.SchemaCache != nil { - hash := input.Schema.GoLow().Hash() validationOptions.SchemaCache.Store(hash, &cache.SchemaCacheEntry{ Schema: input.Schema, RenderedInline: renderedSchema, ReferenceSchema: referenceSchema, RenderedJSON: jsonSchema, CompiledSchema: compiledSchema, + RenderedNode: cachedNode, }) } } diff --git a/responses/validate_response_test.go b/responses/validate_response_test.go index 91e3aae..3a184b3 100644 --- a/responses/validate_response_test.go +++ b/responses/validate_response_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/pb33f/libopenapi-validator/config" + "github.com/pb33f/libopenapi-validator/schema_validation" ) func TestValidateResponseSchema(t *testing.T) { @@ -161,7 +162,8 @@ properties: assert.Len(t, errors, 0) // Verify cache was populated - hash := schema.GoLow().Hash() + hash := schema_validation.SchemaCacheKey(schema.GoLow().Hash(), 3.1, + schema_validation.SchemaValidationPurposeResponseBody) cached, ok := opts.SchemaCache.Load(hash) assert.True(t, ok, "Schema should be in cache") assert.NotNil(t, cached, "Cached entry should not be nil") @@ -170,6 +172,103 @@ properties: assert.NotNil(t, cached.RenderedJSON, "JSON schema should be cached") } +func TestValidateResponseSchema_WriteOnlyRequiredIgnored(t *testing.T) { + schema := parseSchemaFromSpec(t, `type: object +required: + - password + - name +properties: + name: + type: string + password: + type: string + writeOnly: true`, 3.1) + + valid, errors := ValidateResponseSchema(&ValidateResponseSchemaInput{ + Request: postRequest(), + Response: responseWithBody(`{"name":"John"}`), + Schema: schema, + Version: 3.1, + }) + + assert.True(t, valid) + assert.Empty(t, errors) +} + +func TestValidateResponseSchema_ReadOnlyRequiredStillApplies(t *testing.T) { + schema := parseSchemaFromSpec(t, `type: object +required: + - id +properties: + id: + type: string + readOnly: true`, 3.1) + + valid, errors := ValidateResponseSchema(&ValidateResponseSchemaInput{ + Request: postRequest(), + Response: responseWithBody(`{}`), + Schema: schema, + Version: 3.1, + }) + + assert.False(t, valid) + require.Len(t, errors, 1) + require.Len(t, errors[0].SchemaValidationErrors, 1) + assert.Equal(t, "missing property 'id'", errors[0].SchemaValidationErrors[0].Reason) +} + +func TestValidateResponseSchema_NestedWriteOnlyRequiredIgnored(t *testing.T) { + schema := parseSchemaFromSpec(t, `type: object +required: + - profile +properties: + profile: + type: object + required: + - password + - email + properties: + password: + type: string + writeOnly: true + email: + type: string`, 3.1) + + valid, errors := ValidateResponseSchema(&ValidateResponseSchemaInput{ + Request: postRequest(), + Response: responseWithBody(`{"profile":{"email":"john@example.com"}}`), + Schema: schema, + Version: 3.1, + }) + + assert.True(t, valid) + assert.Empty(t, errors) +} + +func TestValidateResponseSchema_AllOfWriteOnlyRequiredIgnored(t *testing.T) { + schema := parseSchemaFromSpec(t, `allOf: + - type: object + required: + - password + - name + properties: + password: + type: string + writeOnly: true + name: + type: string`, 3.1) + + valid, errors := ValidateResponseSchema(&ValidateResponseSchemaInput{ + Request: postRequest(), + Response: responseWithBody(`{"name":"John"}`), + Schema: schema, + Version: 3.1, + }) + + assert.True(t, valid) + assert.Empty(t, errors) +} + func postRequest() *http.Request { req, _ := http.NewRequest(http.MethodPost, "/test", io.NopCloser(strings.NewReader(""))) return req diff --git a/schema_validation/directional_schema.go b/schema_validation/directional_schema.go new file mode 100644 index 0000000..c3ecfb2 --- /dev/null +++ b/schema_validation/directional_schema.go @@ -0,0 +1,211 @@ +// Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley +// SPDX-License-Identifier: MIT + +package schema_validation + +import ( + "fmt" + "math" + + "github.com/pb33f/libopenapi/datamodel/high/base" + "github.com/pb33f/libopenapi/utils" + "go.yaml.in/yaml/v4" +) + +// SchemaValidationPurpose identifies the context in which a schema is compiled. +// Request and response bodies need distinct cache entries because readOnly and +// writeOnly annotations change required-property semantics by direction. +type SchemaValidationPurpose uint64 + +const ( + SchemaValidationPurposeGeneric SchemaValidationPurpose = iota + SchemaValidationPurposeRequestBody + SchemaValidationPurposeResponseBody +) + +const schemaCachePurposeSalt uint64 = 0x9e3779b97f4a7c15 + +// RenderedValidationSchema contains a rendered schema and its JSON equivalent. +type RenderedValidationSchema struct { + RenderedInline []byte + ReferenceSchema string + RenderedJSON []byte + RenderedNode *yaml.Node +} + +// SchemaCacheKey returns a cache key for a schema compiled in a validation context. +func SchemaCacheKey(schemaHash uint64, version float32, purpose SchemaValidationPurpose) uint64 { + if purpose == SchemaValidationPurposeGeneric { + return schemaHash + } + versionBits := uint64(math.Float32bits(version)) + return schemaHash ^ (versionBits << 32) ^ versionBits ^ (uint64(purpose) * schemaCachePurposeSalt) +} + +// RenderSchemaForValidation renders schema for the supplied validation purpose. +// For request bodies it removes readOnly properties from required lists, and for +// response bodies it removes writeOnly properties from required lists. +func RenderSchemaForValidation(schema *base.Schema, purpose SchemaValidationPurpose) (*RenderedValidationSchema, error) { + if schema == nil { + return nil, nil + } + + renderCtx := base.NewInlineRenderContextForValidation() + renderedInline, err := schema.RenderInlineWithContext(renderCtx) + if err != nil { + return &RenderedValidationSchema{ + RenderedInline: renderedInline, + ReferenceSchema: string(renderedInline), + }, err + } + + return renderSchemaBytesForValidation(renderedInline, purpose) +} + +func renderSchemaBytesForValidation(renderedInline []byte, purpose SchemaValidationPurpose) (*RenderedValidationSchema, error) { + renderedNode := new(yaml.Node) + if err := yaml.Unmarshal(renderedInline, renderedNode); err != nil { + return nil, fmt.Errorf("schema render decode failed: %w", err) + } + + if len(renderedNode.Content) > 0 { + pruneDirectionalRequired(renderedNode.Content[0], purpose) + } + + if purpose != SchemaValidationPurposeGeneric { + renderedInline, _ = yaml.Marshal(renderedNode) + } + + renderedJSON, _ := utils.ConvertYAMLtoJSON(renderedInline) + + return &RenderedValidationSchema{ + RenderedInline: renderedInline, + ReferenceSchema: string(renderedInline), + RenderedJSON: renderedJSON, + RenderedNode: renderedNode, + }, nil +} + +func pruneDirectionalRequired(schemaNode *yaml.Node, purpose SchemaValidationPurpose) { + if schemaNode == nil || schemaNode.Kind != yaml.MappingNode { + return + } + + pruneRequiredAtSchema(schemaNode, purpose) + + for _, key := range []string{"properties", "patternProperties", "$defs", "definitions", "dependentSchemas"} { + if childMap := mappingValue(schemaNode, key); childMap != nil && childMap.Kind == yaml.MappingNode { + for i := 1; i < len(childMap.Content); i += 2 { + pruneDirectionalRequired(childMap.Content[i], purpose) + } + } + } + + for _, key := range []string{"items", "contains", "additionalProperties", "unevaluatedProperties", "propertyNames", "not", "if", "then", "else"} { + pruneDirectionalRequired(mappingValue(schemaNode, key), purpose) + } + + for _, key := range []string{"prefixItems", "allOf", "anyOf", "oneOf"} { + if childSeq := mappingValue(schemaNode, key); childSeq != nil && childSeq.Kind == yaml.SequenceNode { + for _, item := range childSeq.Content { + pruneDirectionalRequired(item, purpose) + } + } + } +} + +func pruneRequiredAtSchema(schemaNode *yaml.Node, purpose SchemaValidationPurpose) { + if purpose != SchemaValidationPurposeRequestBody && purpose != SchemaValidationPurposeResponseBody { + return + } + + requiredIndex, requiredNode := mappingPair(schemaNode, "required") + if requiredNode == nil || requiredNode.Kind != yaml.SequenceNode { + return + } + propertiesNode := mappingValue(schemaNode, "properties") + if propertiesNode == nil || propertiesNode.Kind != yaml.MappingNode { + return + } + + prunedRequired := make([]*yaml.Node, 0, len(requiredNode.Content)) + for _, item := range requiredNode.Content { + if item == nil || item.Kind != yaml.ScalarNode { + prunedRequired = append(prunedRequired, item) + continue + } + propertySchema := mappingValue(propertiesNode, item.Value) + if propertySchemaHasDirectionalAnnotation(propertySchema, purpose) { + continue + } + prunedRequired = append(prunedRequired, item) + } + + if len(prunedRequired) == 0 { + removeMappingPair(schemaNode, requiredIndex) + return + } + requiredNode.Content = prunedRequired +} + +func propertySchemaHasDirectionalAnnotation(schemaNode *yaml.Node, purpose SchemaValidationPurpose) bool { + if schemaNode == nil || schemaNode.Kind != yaml.MappingNode { + return false + } + + switch purpose { + case SchemaValidationPurposeRequestBody: + if boolMappingValue(schemaNode, "readOnly") { + return true + } + case SchemaValidationPurposeResponseBody: + if boolMappingValue(schemaNode, "writeOnly") { + return true + } + } + + for _, key := range []string{"allOf", "anyOf", "oneOf"} { + if childSeq := mappingValue(schemaNode, key); childSeq != nil && childSeq.Kind == yaml.SequenceNode { + for _, item := range childSeq.Content { + if propertySchemaHasDirectionalAnnotation(item, purpose) { + return true + } + } + } + } + + return false +} + +func mappingPair(node *yaml.Node, key string) (int, *yaml.Node) { + if node == nil || node.Kind != yaml.MappingNode { + return -1, nil + } + for i := 0; i+1 < len(node.Content); i += 2 { + keyNode := node.Content[i] + if keyNode != nil && keyNode.Value == key { + return i, node.Content[i+1] + } + } + return -1, nil +} + +func mappingValue(node *yaml.Node, key string) *yaml.Node { + _, value := mappingPair(node, key) + return value +} + +func boolMappingValue(node *yaml.Node, key string) bool { + value := mappingValue(node, key) + if value == nil || value.Kind != yaml.ScalarNode { + return false + } + return value.Tag == "!!bool" && value.Value == "true" +} + +func removeMappingPair(node *yaml.Node, keyIndex int) { + if node == nil || keyIndex < 0 || keyIndex+1 >= len(node.Content) { + return + } + node.Content = append(node.Content[:keyIndex], node.Content[keyIndex+2:]...) +} diff --git a/schema_validation/directional_schema_test.go b/schema_validation/directional_schema_test.go new file mode 100644 index 0000000..c54f79b --- /dev/null +++ b/schema_validation/directional_schema_test.go @@ -0,0 +1,261 @@ +// Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley +// SPDX-License-Identifier: MIT + +package schema_validation + +import ( + "encoding/json" + "fmt" + "strings" + "testing" + + "github.com/pb33f/libopenapi" + "github.com/pb33f/libopenapi/datamodel/high/base" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v4" +) + +func TestRenderSchemaForValidation_DirectionalRequiredProperties(t *testing.T) { + schema := parseDirectionalTestSchema(t, `type: object +required: + - id + - name + - password +properties: + id: + type: string + readOnly: true + name: + type: string + password: + type: string + writeOnly: true`) + + for _, tc := range []struct { + name string + purpose SchemaValidationPurpose + expected []string + }{ + { + name: "generic keeps all required properties", + purpose: SchemaValidationPurposeGeneric, + expected: []string{"id", "name", "password"}, + }, + { + name: "request removes readOnly required properties", + purpose: SchemaValidationPurposeRequestBody, + expected: []string{"name", "password"}, + }, + { + name: "response removes writeOnly required properties", + purpose: SchemaValidationPurposeResponseBody, + expected: []string{"id", "name"}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + rendered, err := RenderSchemaForValidation(schema, tc.purpose) + require.NoError(t, err) + require.NotNil(t, rendered) + + assert.Equal(t, tc.expected, renderedRequired(t, rendered.RenderedJSON)) + }) + } +} + +func TestSchemaCacheKey_DirectionalKeysAreDistinct(t *testing.T) { + const schemaHash = uint64(100) + const version = float32(3.1) + + genericKey := SchemaCacheKey(schemaHash, version, SchemaValidationPurposeGeneric) + requestKey := SchemaCacheKey(schemaHash, version, SchemaValidationPurposeRequestBody) + responseKey := SchemaCacheKey(schemaHash, version, SchemaValidationPurposeResponseBody) + request30Key := SchemaCacheKey(schemaHash, 3.0, SchemaValidationPurposeRequestBody) + + assert.Equal(t, schemaHash, genericKey) + assert.NotEqual(t, genericKey, requestKey) + assert.NotEqual(t, genericKey, responseKey) + assert.NotEqual(t, requestKey, responseKey) + assert.NotEqual(t, requestKey, request30Key) +} + +func TestRenderSchemaForValidation_EdgeCases(t *testing.T) { + rendered, err := RenderSchemaForValidation(nil, SchemaValidationPurposeRequestBody) + require.NoError(t, err) + assert.Nil(t, rendered) + + schema := parseDirectionalSpecSchema(t, `openapi: 3.1.0 +info: + title: Test + version: 1.0.0 +components: + schemas: + Error: + type: object + properties: + details: + type: array + items: + $ref: '#/components/schemas/Error'`, "Error") + + rendered, err = RenderSchemaForValidation(schema, SchemaValidationPurposeRequestBody) + require.Error(t, err) + require.NotNil(t, rendered) +} + +func TestRenderSchemaBytesForValidation_Errors(t *testing.T) { + rendered, err := renderSchemaBytesForValidation([]byte(":\n"), SchemaValidationPurposeRequestBody) + require.Error(t, err) + assert.Nil(t, rendered) + assert.Contains(t, err.Error(), "schema render decode failed") +} + +func TestRenderSchemaBytesForValidation_RemovesEmptyRequired(t *testing.T) { + rendered, err := renderSchemaBytesForValidation([]byte(`type: object +required: + - id +properties: + id: + type: string + readOnly: true +`), SchemaValidationPurposeRequestBody) + require.NoError(t, err) + require.NotNil(t, rendered) + + assert.Nil(t, renderedRequired(t, rendered.RenderedJSON)) +} + +func TestRenderSchemaBytesForValidation_PrunesPrefixItems(t *testing.T) { + rendered, err := renderSchemaBytesForValidation([]byte(`type: array +prefixItems: + - type: object + required: + - id + properties: + id: + type: string + readOnly: true +`), SchemaValidationPurposeRequestBody) + require.NoError(t, err) + require.NotNil(t, rendered) + + var renderedMap map[string]any + require.NoError(t, json.Unmarshal(rendered.RenderedJSON, &renderedMap)) + prefixItems := renderedMap["prefixItems"].([]any) + firstItem := prefixItems[0].(map[string]any) + assert.NotContains(t, firstItem, "required") +} + +func TestDirectionalSchemaHelpers_EdgeCases(t *testing.T) { + pruneDirectionalRequired(nil, SchemaValidationPurposeRequestBody) + pruneDirectionalRequired(&yaml.Node{Kind: yaml.ScalarNode, Value: "scalar"}, SchemaValidationPurposeRequestBody) + + pruneRequiredAtSchema(&yaml.Node{ + Kind: yaml.MappingNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "required"}, + {Kind: yaml.SequenceNode, Content: []*yaml.Node{{Kind: yaml.ScalarNode, Value: "id"}}}, + }, + }, SchemaValidationPurposeRequestBody) + + pruneRequiredAtSchema(&yaml.Node{ + Kind: yaml.MappingNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "required"}, + {Kind: yaml.SequenceNode, Content: []*yaml.Node{nil, {Kind: yaml.MappingNode}}}, + {Kind: yaml.ScalarNode, Value: "properties"}, + {Kind: yaml.MappingNode}, + }, + }, SchemaValidationPurposeRequestBody) + + assert.False(t, propertySchemaHasDirectionalAnnotation(nil, SchemaValidationPurposeRequestBody)) + assert.False(t, propertySchemaHasDirectionalAnnotation(&yaml.Node{Kind: yaml.ScalarNode}, SchemaValidationPurposeRequestBody)) + + composed := &yaml.Node{ + Kind: yaml.MappingNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "oneOf"}, + { + Kind: yaml.SequenceNode, + Content: []*yaml.Node{{ + Kind: yaml.MappingNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "writeOnly"}, + {Kind: yaml.ScalarNode, Tag: "!!bool", Value: "true"}, + }, + }}, + }, + }, + } + assert.True(t, propertySchemaHasDirectionalAnnotation(composed, SchemaValidationPurposeResponseBody)) + + index, value := mappingPair(nil, "missing") + assert.Equal(t, -1, index) + assert.Nil(t, value) + + removeMappingPair(nil, -1) + node := &yaml.Node{ + Kind: yaml.MappingNode, + Content: []*yaml.Node{ + {Kind: yaml.ScalarNode, Value: "required"}, + {Kind: yaml.SequenceNode}, + }, + } + removeMappingPair(node, 0) + assert.Empty(t, node.Content) +} + +func renderedRequired(t *testing.T, renderedJSON []byte) []string { + t.Helper() + + var rendered map[string]any + require.NoError(t, json.Unmarshal(renderedJSON, &rendered)) + + required, ok := rendered["required"].([]any) + if !ok { + return nil + } + + values := make([]string, 0, len(required)) + for _, item := range required { + values = append(values, item.(string)) + } + return values +} + +func parseDirectionalTestSchema(t *testing.T, schemaYAML string) *base.Schema { + t.Helper() + + return parseDirectionalSpecSchema(t, fmt.Sprintf(`openapi: 3.1.0 +info: + title: Test + version: 1.0.0 +components: + schemas: + TestSchema: +%s`, indentDirectionalSchemaLines(schemaYAML, " ")), "TestSchema") +} + +func parseDirectionalSpecSchema(t *testing.T, spec string, schemaName string) *base.Schema { + t.Helper() + + doc, err := libopenapi.NewDocument([]byte(spec)) + require.NoError(t, err) + + model, errs := doc.BuildV3Model() + require.Empty(t, errs) + + schema := model.Model.Components.Schemas.GetOrZero(schemaName) + require.NotNil(t, schema) + return schema.Schema() +} + +func indentDirectionalSchemaLines(s string, indent string) string { + lines := strings.Split(strings.TrimSpace(s), "\n") + for i, line := range lines { + if line != "" { + lines[i] = indent + line + } + } + return strings.Join(lines, "\n") +} diff --git a/validator.go b/validator.go index 96b9f89..8987a16 100644 --- a/validator.go +++ b/validator.go @@ -11,8 +11,6 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/pb33f/libopenapi/utils" - "go.yaml.in/yaml/v4" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" @@ -408,6 +406,7 @@ func warmSchemaCaches( } schemaCache := options.SchemaCache + version := helpers.VersionToFloat(doc.Version) // Walk through all paths and operations for pathPair := doc.Paths.PathItems.First(); pathPair != nil; pathPair = pathPair.Next() { @@ -430,7 +429,8 @@ func warmSchemaCaches( for contentPair := operation.RequestBody.Content.First(); contentPair != nil; contentPair = contentPair.Next() { mediaType := contentPair.Value() if mediaType.Schema != nil { - warmMediaTypeSchema(mediaType, schemaCache, options) + warmMediaTypeSchema(mediaType, schemaCache, options, version, + schema_validation.SchemaValidationPurposeRequestBody) } } } @@ -445,7 +445,8 @@ func warmSchemaCaches( for contentPair := response.Content.First(); contentPair != nil; contentPair = contentPair.Next() { mediaType := contentPair.Value() if mediaType.Schema != nil { - warmMediaTypeSchema(mediaType, schemaCache, options) + warmMediaTypeSchema(mediaType, schemaCache, options, version, + schema_validation.SchemaValidationPurposeResponseBody) } } } @@ -457,7 +458,8 @@ func warmSchemaCaches( for contentPair := operation.Responses.Default.Content.First(); contentPair != nil; contentPair = contentPair.Next() { mediaType := contentPair.Value() if mediaType.Schema != nil { - warmMediaTypeSchema(mediaType, schemaCache, options) + warmMediaTypeSchema(mediaType, schemaCache, options, version, + schema_validation.SchemaValidationPurposeResponseBody) } } } @@ -467,7 +469,7 @@ func warmSchemaCaches( if operation.Parameters != nil { for _, param := range operation.Parameters { if param != nil { - warmParameterSchema(param, schemaCache, options) + warmParameterSchema(param, schemaCache, options, version) } } } @@ -477,7 +479,7 @@ func warmSchemaCaches( if pathItem.Parameters != nil { for _, param := range pathItem.Parameters { if param != nil { - warmParameterSchema(param, schemaCache, options) + warmParameterSchema(param, schemaCache, options, version) } } } @@ -485,87 +487,94 @@ func warmSchemaCaches( } // warmMediaTypeSchema warms the cache for a media type schema -func warmMediaTypeSchema(mediaType *v3.MediaType, schemaCache cache.SchemaCache, options *config.ValidationOptions) { +func warmMediaTypeSchema( + mediaType *v3.MediaType, + schemaCache cache.SchemaCache, + options *config.ValidationOptions, + version float32, + purpose schema_validation.SchemaValidationPurpose, +) { if mediaType != nil && mediaType.Schema != nil { - hash := mediaType.GoLow().Schema.Value.Hash() + schema := mediaType.Schema.Schema() + if schema == nil || schema.GoLow() == nil { + return + } + hash := schema_validation.SchemaCacheKey(schema.GoLow().Hash(), version, purpose) if _, exists := schemaCache.Load(hash); !exists { - schema := mediaType.Schema.Schema() - if schema != nil { - renderCtx := base.NewInlineRenderContextForValidation() - renderedInline, _ := schema.RenderInlineWithContext(renderCtx) - referenceSchema := string(renderedInline) - renderedJSON, _ := utils.ConvertYAMLtoJSON(renderedInline) - if len(renderedInline) > 0 { - compiledSchema, _ := helpers.NewCompiledSchema(fmt.Sprintf("%x", hash), renderedJSON, options) - - // Pre-parse YAML node for error reporting (avoids re-parsing on each error) - var renderedNode yaml.Node - _ = yaml.Unmarshal(renderedInline, &renderedNode) - - schemaCache.Store(hash, &cache.SchemaCacheEntry{ - Schema: schema, - RenderedInline: renderedInline, - ReferenceSchema: referenceSchema, - RenderedJSON: renderedJSON, - CompiledSchema: compiledSchema, - RenderedNode: &renderedNode, - }) - } + rendered, err := schema_validation.RenderSchemaForValidation(schema, purpose) + if err != nil || rendered == nil || len(rendered.RenderedInline) == 0 { + return + } + compiledSchema, compileErr := helpers.NewCompiledSchemaWithVersion( + fmt.Sprintf("%x", hash), + rendered.RenderedJSON, + options, + version, + ) + if compileErr != nil || compiledSchema == nil { + return } + + schemaCache.Store(hash, &cache.SchemaCacheEntry{ + Schema: schema, + RenderedInline: rendered.RenderedInline, + ReferenceSchema: rendered.ReferenceSchema, + RenderedJSON: rendered.RenderedJSON, + CompiledSchema: compiledSchema, + RenderedNode: rendered.RenderedNode, + }) } } } // warmParameterSchema warms the cache for a parameter schema -func warmParameterSchema(param *v3.Parameter, schemaCache cache.SchemaCache, options *config.ValidationOptions) { +func warmParameterSchema(param *v3.Parameter, schemaCache cache.SchemaCache, options *config.ValidationOptions, version float32) { if param != nil { var schema *base.Schema - var hash uint64 // Parameters can have schemas in two places: schema property or content property if param.Schema != nil { schema = param.Schema.Schema() - if schema != nil { - hash = param.GoLow().Schema.Value.Hash() - } } else if param.Content != nil { // Check content for schema for contentPair := param.Content.First(); contentPair != nil; contentPair = contentPair.Next() { mediaType := contentPair.Value() if mediaType.Schema != nil { schema = mediaType.Schema.Schema() - if schema != nil { - hash = mediaType.GoLow().Schema.Value.Hash() - } break // Only process first content type } } } - if schema != nil { + if schema != nil && schema.GoLow() != nil { + hash := schema_validation.SchemaCacheKey(schema.GoLow().Hash(), version, + schema_validation.SchemaValidationPurposeGeneric) if _, exists := schemaCache.Load(hash); !exists { - renderCtx := base.NewInlineRenderContextForValidation() - renderedInline, _ := schema.RenderInlineWithContext(renderCtx) - referenceSchema := string(renderedInline) - renderedJSON, _ := utils.ConvertYAMLtoJSON(renderedInline) - if len(renderedInline) > 0 { - compiledSchema, _ := helpers.NewCompiledSchema(fmt.Sprintf("%x", hash), renderedJSON, options) - - // Pre-parse YAML node for error reporting (avoids re-parsing on each error) - var renderedNode yaml.Node - _ = yaml.Unmarshal(renderedInline, &renderedNode) - - // Store in cache using the shared SchemaCache type - schemaCache.Store(hash, &cache.SchemaCacheEntry{ - Schema: schema, - RenderedInline: renderedInline, - ReferenceSchema: referenceSchema, - RenderedJSON: renderedJSON, - CompiledSchema: compiledSchema, - RenderedNode: &renderedNode, - }) + rendered, err := schema_validation.RenderSchemaForValidation(schema, + schema_validation.SchemaValidationPurposeGeneric) + if err != nil || rendered == nil || len(rendered.RenderedInline) == 0 { + return + } + compiledSchema, compileErr := helpers.NewCompiledSchemaWithVersion( + fmt.Sprintf("%x", hash), + rendered.RenderedJSON, + options, + version, + ) + if compileErr != nil || compiledSchema == nil { + return } + + // Store in cache using the shared SchemaCache type + schemaCache.Store(hash, &cache.SchemaCacheEntry{ + Schema: schema, + RenderedInline: rendered.RenderedInline, + ReferenceSchema: rendered.ReferenceSchema, + RenderedJSON: rendered.RenderedJSON, + CompiledSchema: compiledSchema, + RenderedNode: rendered.RenderedNode, + }) } } } diff --git a/validator_test.go b/validator_test.go index 126bb63..2018e95 100644 --- a/validator_test.go +++ b/validator_test.go @@ -29,6 +29,7 @@ import ( "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/errors" "github.com/pb33f/libopenapi-validator/helpers" + "github.com/pb33f/libopenapi-validator/schema_validation" ) func TestNewValidator(t *testing.T) { @@ -2160,6 +2161,134 @@ func TestCacheWarming_PopulatesCache(t *testing.T) { assert.Greater(t, count, 0, "Schema cache should have entries from request and response bodies") } +func TestDirectionalRequiredProperties_RequestResponseSharedSchema(t *testing.T) { + spec := `openapi: 3.1.0 +info: + title: Test + version: 1.0.0 +paths: + /users/123: + post: + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/User' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/User' +components: + schemas: + User: + type: object + required: + - id + - name + - password + properties: + id: + type: string + readOnly: true + name: + type: string + password: + type: string + writeOnly: true` + + for _, tc := range []struct { + name string + run func(t *testing.T, v Validator) + }{ + { + name: "request then response", + run: func(t *testing.T, v Validator) { + req := issue281Request(t, `{"name":"John","password":"secret"}`) + valid, errs := v.ValidateHttpRequest(req) + require.True(t, valid) + require.Empty(t, errs) + + res := issue281Response(`{"id":"123","name":"John"}`) + valid, errs = v.ValidateHttpResponse(req, res) + require.True(t, valid) + require.Empty(t, errs) + }, + }, + { + name: "response then request", + run: func(t *testing.T, v Validator) { + req := issue281Request(t, "") + res := issue281Response(`{"id":"123","name":"John"}`) + valid, errs := v.ValidateHttpResponse(req, res) + require.True(t, valid) + require.Empty(t, errs) + + req = issue281Request(t, `{"name":"John","password":"secret"}`) + valid, errs = v.ValidateHttpRequest(req) + require.True(t, valid) + require.Empty(t, errs) + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + doc, err := libopenapi.NewDocument([]byte(spec)) + require.NoError(t, err) + + v, errs := NewValidator(doc) + require.Nil(t, errs) + + validator := v.(*validator) + userSchema := validator.v3Model.Components.Schemas.GetOrZero("User") + require.NotNil(t, userSchema) + require.NotNil(t, userSchema.Schema()) + + requestKey := schema_validation.SchemaCacheKey( + userSchema.Schema().GoLow().Hash(), + 3.1, + schema_validation.SchemaValidationPurposeRequestBody, + ) + responseKey := schema_validation.SchemaCacheKey( + userSchema.Schema().GoLow().Hash(), + 3.1, + schema_validation.SchemaValidationPurposeResponseBody, + ) + + _, requestWarmed := validator.options.SchemaCache.Load(requestKey) + _, responseWarmed := validator.options.SchemaCache.Load(responseKey) + require.True(t, requestWarmed, "request schema variant should be warmed") + require.True(t, responseWarmed, "response schema variant should be warmed") + require.NotEqual(t, requestKey, responseKey) + + tc.run(t, v) + }) + } +} + +func issue281Request(t *testing.T, payload string) *http.Request { + var body io.Reader = http.NoBody + if payload != "" { + body = strings.NewReader(payload) + } + req, err := http.NewRequest(http.MethodPost, "https://things.com/users/123", body) + require.NoError(t, err) + req.Header.Set(helpers.ContentTypeHeader, "application/json") + return req +} + +func issue281Response(payload string) *http.Response { + return &http.Response{ + StatusCode: http.StatusCreated, + Header: http.Header{ + helpers.ContentTypeHeader: []string{"application/json"}, + }, + Body: io.NopCloser(strings.NewReader(payload)), + } +} + func TestCacheWarming_EdgeCases(t *testing.T) { // Test nil document warmSchemaCaches(nil, nil) From 7ba9351f75791679d09b916750a2bd204269813c Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Mon, 15 Jun 2026 13:10:35 -0400 Subject: [PATCH 3/6] coverage bumps --- schema_validation/validate_document_test.go | 20 +++++ validator.go | 8 +- validator_test.go | 90 ++++++++++++++++++++- 3 files changed, 110 insertions(+), 8 deletions(-) diff --git a/schema_validation/validate_document_test.go b/schema_validation/validate_document_test.go index cd79658..3b5ac0d 100644 --- a/schema_validation/validate_document_test.go +++ b/schema_validation/validate_document_test.go @@ -549,6 +549,26 @@ func TestValidateDocument_SpecJSONBytesCorrupt_NilSpecJSON(t *testing.T) { assert.Empty(t, errs[0].SchemaValidationErrors) } +func TestValidateDocument_SpecJSONBytesNullDoesNotValidateNil(t *testing.T) { + petstore, _ := os.ReadFile("../test_specs/petstorev3.json") + doc, _ := libopenapi.NewDocument(petstore) + + info := doc.GetSpecInfo() + _ = info.GetSpecJSONBytes() + + nullJSON := []byte("null") + info.SpecJSONBytes = &nullJSON + info.SpecJSON = nil + + valid, errs := ValidateOpenAPIDocument(doc) + + assert.False(t, valid) + assert.Len(t, errs, 1) + assert.Contains(t, errs[0].Reason, "no usable JSON representation") + assert.NotContains(t, errs[0].Reason, "got null, want object") + assert.Empty(t, errs[0].SchemaValidationErrors) +} + func TestValidateDocument_SpecJSONBytesCorrupt_FallbackToSpecJSON(t *testing.T) { petstore, _ := os.ReadFile("../test_specs/petstorev3.json") doc, _ := libopenapi.NewDocument(petstore) diff --git a/validator.go b/validator.go index 8987a16..820fafd 100644 --- a/validator.go +++ b/validator.go @@ -1,4 +1,4 @@ -// Copyright 2023-2025 Princess Beef Heavy Industries, LLC / Dave Shanley +// Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley // SPDX-License-Identifier: MIT package validator @@ -414,15 +414,9 @@ func warmSchemaCaches( // Get all operations for this path (handles all HTTP methods including OpenAPI 3.2+ extensions) operations := pathItem.GetOperations() - if operations == nil { - continue - } for opPair := operations.First(); opPair != nil; opPair = opPair.Next() { operation := opPair.Value() - if operation == nil { - continue - } // Warm request body schemas if operation.RequestBody != nil && operation.RequestBody.Content != nil { diff --git a/validator_test.go b/validator_test.go index 2018e95..2779c51 100644 --- a/validator_test.go +++ b/validator_test.go @@ -1,4 +1,4 @@ -// Copyright 2023-2025 Princess Beef Heavy Industries, LLC / Dave Shanley +// Copyright 2023-2026 Princess Beef Heavy Industries, LLC / Dave Shanley // SPDX-License-Identifier: MIT package validator @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/pb33f/libopenapi/datamodel/high/base" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" "github.com/pb33f/libopenapi-validator/cache" @@ -2383,6 +2384,51 @@ paths: assert.NotNil(t, v) } +func TestCacheWarming_MediaTypeSchemaWithNilGoLow(t *testing.T) { + options := config.NewValidationOptions() + mediaType := &v3.MediaType{ + Schema: base.CreateSchemaProxy(&base.Schema{ + Type: []string{"object"}, + }), + } + + warmMediaTypeSchema(mediaType, options.SchemaCache, options, 3.1, + schema_validation.SchemaValidationPurposeRequestBody) + + assert.Equal(t, 0, schemaCacheEntryCount(options.SchemaCache)) +} + +func TestCacheWarming_ParameterRenderFailure(t *testing.T) { + param := cacheWarmingTestParameter(t, `schema: + $ref: '#/components/schemas/Error'`, ` +components: + schemas: + Error: + type: object + properties: + code: + type: string + details: + type: array + items: + $ref: '#/components/schemas/Error'`) + options := config.NewValidationOptions() + + warmParameterSchema(param, options.SchemaCache, options, 3.1) + + assert.Equal(t, 0, schemaCacheEntryCount(options.SchemaCache)) +} + +func TestCacheWarming_ParameterCompileFailure(t *testing.T) { + param := cacheWarmingTestParameter(t, `schema: + type: invalid-type-that-does-not-exist`, "") + options := config.NewValidationOptions() + + warmParameterSchema(param, options.SchemaCache, options, 3.1) + + assert.Equal(t, 0, schemaCacheEntryCount(options.SchemaCache)) +} + func TestCacheWarming_DefaultResponse(t *testing.T) { spec := `openapi: 3.1.0 paths: @@ -2525,6 +2571,48 @@ paths: assert.Greater(t, count, 0, "Schema cache should have entries from path-level parameters") } +func cacheWarmingTestParameter(t *testing.T, schemaYAML string, componentsYAML string) *v3.Parameter { + t.Helper() + + spec := fmt.Sprintf(`openapi: 3.1.0 +info: + title: Test + version: 1.0.0 +paths: + /test: + get: + parameters: + - name: filter + in: query + %s + responses: + '200': + description: Success +%s`, schemaYAML, componentsYAML) + + doc, err := libopenapi.NewDocument([]byte(spec)) + require.NoError(t, err) + + model, errs := doc.BuildV3Model() + require.Empty(t, errs) + + pathItem := model.Model.Paths.PathItems.GetOrZero("/test") + require.NotNil(t, pathItem) + require.NotNil(t, pathItem.Get) + require.Len(t, pathItem.Get.Parameters, 1) + + return pathItem.Get.Parameters[0] +} + +func schemaCacheEntryCount(schemaCache cache.SchemaCache) int { + count := 0 + schemaCache.Range(func(key uint64, value *cache.SchemaCacheEntry) bool { + count++ + return true + }) + return count +} + // TestSortValidationErrors tests that validation errors are sorted deterministically func TestSortValidationErrors(t *testing.T) { // Create errors in random order From b1129dac9d04efcd85ed45d789a8055e446ff83d Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Mon, 15 Jun 2026 16:36:08 -0400 Subject: [PATCH 4/6] fixed pipeline --- schema_validation/validate_document_test.go | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/schema_validation/validate_document_test.go b/schema_validation/validate_document_test.go index 3b5ac0d..33177cd 100644 --- a/schema_validation/validate_document_test.go +++ b/schema_validation/validate_document_test.go @@ -215,8 +215,8 @@ paths: {}` // injecting the poisoned values, otherwise the first accessor call inside // the validator rebuilds the real JSON view and overwrites the injection. _ = info.GetSpecJSONBytes() - info.SpecJSON = &badSpecJSON - info.SpecJSONBytes = nil + info.SpecJSON = &badSpecJSON //nolint:staticcheck // test intentionally poisons lazy JSON cache fields + info.SpecJSONBytes = nil //nolint:staticcheck // test intentionally poisons lazy JSON cache fields valid, errors := ValidateOpenAPIDocument(doc) @@ -247,8 +247,8 @@ paths: {}` info := doc.GetSpecInfo() // latch the lazy JSON build before injecting (see note in the test above) _ = info.GetSpecJSONBytes() - info.SpecJSON = &badSpecJSON - info.SpecJSONBytes = &corrupt + info.SpecJSON = &badSpecJSON //nolint:staticcheck // test intentionally poisons lazy JSON cache fields + info.SpecJSONBytes = &corrupt //nolint:staticcheck // test intentionally poisons lazy JSON cache fields valid, errors := ValidateOpenAPIDocument(doc) @@ -517,7 +517,7 @@ func TestValidateDocument_SpecJSONBytesPath(t *testing.T) { assert.NotNil(t, info.GetSpecJSONBytes(), "SpecJSONBytes should be populated by libopenapi") // Nil out SpecJSON but leave SpecJSONBytes intact — forces the SpecJSONBytes path - info.SpecJSON = nil + info.SpecJSON = nil //nolint:staticcheck // test intentionally poisons lazy JSON cache fields valid, errs := ValidateOpenAPIDocument(doc) assert.True(t, valid) @@ -538,8 +538,8 @@ func TestValidateDocument_SpecJSONBytesCorrupt_NilSpecJSON(t *testing.T) { // and nil out SpecJSON so the fallback normalizeJSON path is skipped. // This exercises the nil guard on SpecJSON inside the error branch. corrupt := []byte(`{not valid json!!!}`) - info.SpecJSONBytes = &corrupt - info.SpecJSON = nil + info.SpecJSONBytes = &corrupt //nolint:staticcheck // test intentionally poisons lazy JSON cache fields + info.SpecJSON = nil //nolint:staticcheck // test intentionally poisons lazy JSON cache fields // Validation should fail before JSON Schema validation instead of validating nil. valid, errs := ValidateOpenAPIDocument(doc) @@ -557,8 +557,8 @@ func TestValidateDocument_SpecJSONBytesNullDoesNotValidateNil(t *testing.T) { _ = info.GetSpecJSONBytes() nullJSON := []byte("null") - info.SpecJSONBytes = &nullJSON - info.SpecJSON = nil + info.SpecJSONBytes = &nullJSON //nolint:staticcheck // test intentionally poisons lazy JSON cache fields + info.SpecJSON = nil //nolint:staticcheck // test intentionally poisons lazy JSON cache fields valid, errs := ValidateOpenAPIDocument(doc) @@ -582,7 +582,7 @@ func TestValidateDocument_SpecJSONBytesCorrupt_FallbackToSpecJSON(t *testing.T) // Put corrupt bytes in SpecJSONBytes so UnmarshalJSON fails, // but leave SpecJSON intact so the fallback to normalizeJSON executes. corrupt := []byte(`{not valid json!!!}`) - info.SpecJSONBytes = &corrupt + info.SpecJSONBytes = &corrupt //nolint:staticcheck // test intentionally poisons lazy JSON cache fields // Should still validate successfully via the SpecJSON fallback valid, errs := ValidateOpenAPIDocument(doc) @@ -599,7 +599,7 @@ func TestValidateDocument_SpecJSONBytesPath_Invalid(t *testing.T) { // latch the lazy JSON build, then nil out SpecJSON but leave // SpecJSONBytes intact assert.NotNil(t, info.GetSpecJSONBytes(), "SpecJSONBytes should be populated by libopenapi") - info.SpecJSON = nil + info.SpecJSON = nil //nolint:staticcheck // test intentionally poisons lazy JSON cache fields valid, errs := ValidateOpenAPIDocument(doc) assert.False(t, valid) From 137abc72c339487d888180584968c4851149ce09 Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Mon, 15 Jun 2026 17:51:48 -0400 Subject: [PATCH 5/6] move to our own fork of testify --- cache/cache_test.go | 4 ++-- config/config_test.go | 2 +- errors/error_utilities_test.go | 2 +- errors/parameter_errors_test.go | 2 +- errors/request_errors_test.go | 2 +- errors/response_errors_test.go | 2 +- errors/strict_errors_test.go | 2 +- errors/urlencoded_errors_test.go | 2 +- errors/validation_error_test.go | 2 +- errors/xml_errors_test.go | 2 +- go.mod | 7 +------ go.sum | 13 ++----------- helpers/json_pointer_test.go | 2 +- helpers/operation_utilities_test.go | 2 +- helpers/parameter_utilities_test.go | 2 +- helpers/path_finder_test.go | 2 +- helpers/regex_maker_test.go | 2 +- helpers/schema_compiler_test.go | 4 ++-- helpers/url_loader_test.go | 2 +- helpers/version_test.go | 2 +- openapi_vocabulary/coercion_simple_test.go | 2 +- openapi_vocabulary/vocabulary_test.go | 2 +- parameters/cookie_parameters_test.go | 4 ++-- parameters/header_parameters_test.go | 2 +- parameters/path_parameters_test.go | 4 ++-- parameters/query_parameters_test.go | 4 ++-- parameters/validate_parameter_test.go | 4 ++-- parameters/validate_security_test.go | 2 +- paths/paths_test.go | 2 +- paths/specificity_test.go | 2 +- radix/path_tree_test.go | 4 ++-- radix/tree_test.go | 4 ++-- requests/validate_body_test.go | 4 ++-- requests/validate_request_test.go | 4 ++-- responses/validate_body_test.go | 4 ++-- responses/validate_headers_test.go | 2 +- responses/validate_response_test.go | 4 ++-- schema_validation/directional_schema_test.go | 4 ++-- schema_validation/locate_schema_property_test.go | 2 +- .../openapi_schemas/load_schema_test.go | 4 ++-- schema_validation/property_locator_test.go | 2 +- schema_validation/validate_document_test.go | 2 +- schema_validation/validate_schema_coercion_test.go | 2 +- .../validate_schema_extract_errors_test.go | 2 +- schema_validation/validate_schema_openapi_test.go | 2 +- schema_validation/validate_schema_test.go | 2 +- schema_validation/validate_urlencoded_test.go | 2 +- schema_validation/validate_xml_test.go | 2 +- strict/types_test.go | 4 ++-- strict/utils_test.go | 2 +- strict/validator_test.go | 4 ++-- validator_nullable_enum_test.go | 4 ++-- validator_test.go | 4 ++-- 53 files changed, 72 insertions(+), 86 deletions(-) diff --git a/cache/cache_test.go b/cache/cache_test.go index 16be42e..ea3f678 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -7,9 +7,9 @@ import ( "testing" "github.com/pb33f/libopenapi/datamodel/high/base" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "github.com/santhosh-tekuri/jsonschema/v6" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestNewDefaultCache(t *testing.T) { diff --git a/config/config_test.go b/config/config_test.go index c1cea05..7177620 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -9,8 +9,8 @@ import ( "sync" "testing" + "github.com/pb33f/testify/assert" "github.com/santhosh-tekuri/jsonschema/v6" - "github.com/stretchr/testify/assert" ) func TestNewValidationOptions_Defaults(t *testing.T) { diff --git a/errors/error_utilities_test.go b/errors/error_utilities_test.go index b45e370..ce1f6f5 100644 --- a/errors/error_utilities_test.go +++ b/errors/error_utilities_test.go @@ -7,7 +7,7 @@ import ( "net/http" "testing" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/require" ) // Helper function to create a mock ValidationError diff --git a/errors/parameter_errors_test.go b/errors/parameter_errors_test.go index e9a5e51..0e9e83c 100644 --- a/errors/parameter_errors_test.go +++ b/errors/parameter_errors_test.go @@ -10,7 +10,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/orderedmap" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/require" "go.yaml.in/yaml/v4" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" diff --git a/errors/request_errors_test.go b/errors/request_errors_test.go index 033f2d5..cc41d72 100644 --- a/errors/request_errors_test.go +++ b/errors/request_errors_test.go @@ -9,7 +9,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/orderedmap" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/require" "go.yaml.in/yaml/v4" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" diff --git a/errors/response_errors_test.go b/errors/response_errors_test.go index 69323a0..1cd4b1c 100644 --- a/errors/response_errors_test.go +++ b/errors/response_errors_test.go @@ -9,7 +9,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/orderedmap" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/require" "go.yaml.in/yaml/v4" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" diff --git a/errors/strict_errors_test.go b/errors/strict_errors_test.go index 127fd82..b12d5e9 100644 --- a/errors/strict_errors_test.go +++ b/errors/strict_errors_test.go @@ -6,7 +6,7 @@ package errors import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func TestUndeclaredPropertyError(t *testing.T) { diff --git a/errors/urlencoded_errors_test.go b/errors/urlencoded_errors_test.go index d6ad984..d190266 100644 --- a/errors/urlencoded_errors_test.go +++ b/errors/urlencoded_errors_test.go @@ -6,7 +6,7 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi-validator/helpers" "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func getURLEncodingTestSchema() *base.Schema { diff --git a/errors/validation_error_test.go b/errors/validation_error_test.go index 82e0d6d..57b1d69 100644 --- a/errors/validation_error_test.go +++ b/errors/validation_error_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/pb33f/libopenapi-validator/helpers" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/require" ) func TestSchemaValidationFailure_Error(t *testing.T) { diff --git a/errors/xml_errors_test.go b/errors/xml_errors_test.go index 3b05da0..2f4ce1f 100644 --- a/errors/xml_errors_test.go +++ b/errors/xml_errors_test.go @@ -9,7 +9,7 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi-validator/helpers" "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func getTestSchema() *base.Schema { diff --git a/go.mod b/go.mod index 909b7a3..44538ac 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/goccy/go-yaml v1.19.2 github.com/pb33f/jsonpath v0.8.2 github.com/pb33f/libopenapi v0.38.0 + github.com/pb33f/testify v0.1.0 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 - github.com/stretchr/testify v1.11.1 go.yaml.in/yaml/v4 v4.0.0-rc.5 golang.org/x/text v0.38.0 ) @@ -18,13 +18,8 @@ require ( require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-openapi/swag/jsonname v0.26.0 // indirect github.com/pb33f/ordered-map/v2 v2.3.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.21.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 640f371..ce01eca 100644 --- a/go.sum +++ b/go.sum @@ -19,23 +19,16 @@ github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLu github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pb33f/jsonpath v0.8.2 h1:Ou4C7zjYClBm97dfZjDCjdZGusJoynv/vrtiEKNfj2Y= github.com/pb33f/jsonpath v0.8.2/go.mod h1:zBV5LJW4OQOPatmQE2QdKpGQJvhDTlE5IEj6ASaRNTo= github.com/pb33f/libopenapi v0.38.0 h1:OG+AMr2dMeB0BZdmm+j6iFy3Uxyl+W0HZpdD72zcyGs= github.com/pb33f/libopenapi v0.38.0/go.mod h1:HrXjcGH/igq+/Af5l5/hmDD+yQRBRiTHgl0FmLdCZjg= github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= +github.com/pb33f/testify v0.1.0 h1:g48/HDU/jn2COspS4nM0scptxiKTJ4DnbX/4ehK6IZ8= +github.com/pb33f/testify v0.1.0/go.mod h1:nq283P/jJ8hXMmdhAqfj7BJIz0y+6IOHj9q0044rKt4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -90,8 +83,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/helpers/json_pointer_test.go b/helpers/json_pointer_test.go index 5e08b2d..b5f5e45 100644 --- a/helpers/json_pointer_test.go +++ b/helpers/json_pointer_test.go @@ -6,7 +6,7 @@ package helpers import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func TestEscapeJSONPointerSegment(t *testing.T) { diff --git a/helpers/operation_utilities_test.go b/helpers/operation_utilities_test.go index c6433ad..fcb28ec 100644 --- a/helpers/operation_utilities_test.go +++ b/helpers/operation_utilities_test.go @@ -9,7 +9,7 @@ import ( "testing" "github.com/pb33f/libopenapi/datamodel/high/v3" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/require" ) // Test ExtractOperation for each HTTP method diff --git a/helpers/parameter_utilities_test.go b/helpers/parameter_utilities_test.go index e390866..29776e0 100644 --- a/helpers/parameter_utilities_test.go +++ b/helpers/parameter_utilities_test.go @@ -9,7 +9,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/orderedmap" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/require" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" ) diff --git a/helpers/path_finder_test.go b/helpers/path_finder_test.go index 8f9c4be..f6d78c6 100644 --- a/helpers/path_finder_test.go +++ b/helpers/path_finder_test.go @@ -6,8 +6,8 @@ package helpers import ( "testing" + "github.com/pb33f/testify/assert" "github.com/santhosh-tekuri/jsonschema/v6" - "github.com/stretchr/testify/assert" ) func TestDiveIntoValidationError(t *testing.T) { diff --git a/helpers/regex_maker_test.go b/helpers/regex_maker_test.go index b2cff44..99315d1 100644 --- a/helpers/regex_maker_test.go +++ b/helpers/regex_maker_test.go @@ -3,7 +3,7 @@ package helpers import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func TestGetRegexForPath(t *testing.T) { diff --git a/helpers/schema_compiler_test.go b/helpers/schema_compiler_test.go index 72cbbe2..c78cbab 100644 --- a/helpers/schema_compiler_test.go +++ b/helpers/schema_compiler_test.go @@ -6,8 +6,8 @@ import ( "testing" "unicode" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "github.com/pb33f/libopenapi-validator/config" ) diff --git a/helpers/url_loader_test.go b/helpers/url_loader_test.go index 18276c6..4c4e6eb 100644 --- a/helpers/url_loader_test.go +++ b/helpers/url_loader_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/require" ) // Test the Load function for a successful case diff --git a/helpers/version_test.go b/helpers/version_test.go index 56bae29..2254ee5 100644 --- a/helpers/version_test.go +++ b/helpers/version_test.go @@ -3,7 +3,7 @@ package helpers import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func TestVersionToFloat(t *testing.T) { diff --git a/openapi_vocabulary/coercion_simple_test.go b/openapi_vocabulary/coercion_simple_test.go index bf3a6f0..736c9e7 100644 --- a/openapi_vocabulary/coercion_simple_test.go +++ b/openapi_vocabulary/coercion_simple_test.go @@ -8,8 +8,8 @@ import ( "strings" "testing" + "github.com/pb33f/testify/assert" "github.com/santhosh-tekuri/jsonschema/v6" - "github.com/stretchr/testify/assert" ) func TestCoercion_Vocabulary_CompilationSuccess(t *testing.T) { diff --git a/openapi_vocabulary/vocabulary_test.go b/openapi_vocabulary/vocabulary_test.go index 750ef3d..a951f16 100644 --- a/openapi_vocabulary/vocabulary_test.go +++ b/openapi_vocabulary/vocabulary_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" + "github.com/pb33f/testify/assert" "github.com/santhosh-tekuri/jsonschema/v6" - "github.com/stretchr/testify/assert" "golang.org/x/text/message" ) diff --git a/parameters/cookie_parameters_test.go b/parameters/cookie_parameters_test.go index b4b7479..4d8139e 100644 --- a/parameters/cookie_parameters_test.go +++ b/parameters/cookie_parameters_test.go @@ -9,8 +9,8 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/helpers" diff --git a/parameters/header_parameters_test.go b/parameters/header_parameters_test.go index 2e27689..62cb7be 100644 --- a/parameters/header_parameters_test.go +++ b/parameters/header_parameters_test.go @@ -9,7 +9,7 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/paths" diff --git a/parameters/path_parameters_test.go b/parameters/path_parameters_test.go index 5972439..c295315 100644 --- a/parameters/path_parameters_test.go +++ b/parameters/path_parameters_test.go @@ -10,8 +10,8 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/helpers" diff --git a/parameters/query_parameters_test.go b/parameters/query_parameters_test.go index 1bc9ca7..9cff869 100644 --- a/parameters/query_parameters_test.go +++ b/parameters/query_parameters_test.go @@ -11,8 +11,8 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/paths" diff --git a/parameters/validate_parameter_test.go b/parameters/validate_parameter_test.go index 6f58e6e..f358547 100644 --- a/parameters/validate_parameter_test.go +++ b/parameters/validate_parameter_test.go @@ -9,8 +9,8 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel/high/base" lowv3 "github.com/pb33f/libopenapi/datamodel/low/v3" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "github.com/pb33f/libopenapi-validator/cache" "github.com/pb33f/libopenapi-validator/config" diff --git a/parameters/validate_security_test.go b/parameters/validate_security_test.go index 4ec236a..60837f8 100644 --- a/parameters/validate_security_test.go +++ b/parameters/validate_security_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/paths" diff --git a/paths/paths_test.go b/paths/paths_test.go index c3bfbdc..e46ea49 100644 --- a/paths/paths_test.go +++ b/paths/paths_test.go @@ -14,7 +14,7 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/radix" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func TestNewValidator_BadParam(t *testing.T) { diff --git a/paths/specificity_test.go b/paths/specificity_test.go index 4c1f52c..c5902a3 100644 --- a/paths/specificity_test.go +++ b/paths/specificity_test.go @@ -7,7 +7,7 @@ import ( "testing" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func TestComputeSpecificityScore(t *testing.T) { diff --git a/radix/path_tree_test.go b/radix/path_tree_test.go index d8dabf4..9f3cc1d 100644 --- a/radix/path_tree_test.go +++ b/radix/path_tree_test.go @@ -8,8 +8,8 @@ import ( "github.com/pb33f/libopenapi" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" ) func TestNewPathTree(t *testing.T) { diff --git a/radix/tree_test.go b/radix/tree_test.go index 6246783..0f6d3df 100644 --- a/radix/tree_test.go +++ b/radix/tree_test.go @@ -8,8 +8,8 @@ import ( "sort" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" ) func TestNew(t *testing.T) { diff --git a/requests/validate_body_test.go b/requests/validate_body_test.go index 36daa63..21e5946 100644 --- a/requests/validate_body_test.go +++ b/requests/validate_body_test.go @@ -13,8 +13,8 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/helpers" diff --git a/requests/validate_request_test.go b/requests/validate_request_test.go index 3beb140..0394c6f 100644 --- a/requests/validate_request_test.go +++ b/requests/validate_request_test.go @@ -10,8 +10,8 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/schema_validation" diff --git a/responses/validate_body_test.go b/responses/validate_body_test.go index 1a0a51a..1e11b3a 100644 --- a/responses/validate_body_test.go +++ b/responses/validate_body_test.go @@ -16,8 +16,8 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/helpers" diff --git a/responses/validate_headers_test.go b/responses/validate_headers_test.go index 290d7d1..b8bf5c3 100644 --- a/responses/validate_headers_test.go +++ b/responses/validate_headers_test.go @@ -9,7 +9,7 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" "github.com/pb33f/libopenapi-validator/config" ) diff --git a/responses/validate_response_test.go b/responses/validate_response_test.go index 3a184b3..3f54b66 100644 --- a/responses/validate_response_test.go +++ b/responses/validate_response_test.go @@ -10,8 +10,8 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "github.com/pb33f/libopenapi-validator/config" "github.com/pb33f/libopenapi-validator/schema_validation" diff --git a/schema_validation/directional_schema_test.go b/schema_validation/directional_schema_test.go index c54f79b..ff23486 100644 --- a/schema_validation/directional_schema_test.go +++ b/schema_validation/directional_schema_test.go @@ -11,8 +11,8 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "go.yaml.in/yaml/v4" ) diff --git a/schema_validation/locate_schema_property_test.go b/schema_validation/locate_schema_property_test.go index a589069..452c1b7 100644 --- a/schema_validation/locate_schema_property_test.go +++ b/schema_validation/locate_schema_property_test.go @@ -6,7 +6,7 @@ package schema_validation import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func TestLocateSchemaPropertyNodeByJSONPath_BadNode(t *testing.T) { diff --git a/schema_validation/openapi_schemas/load_schema_test.go b/schema_validation/openapi_schemas/load_schema_test.go index 76444f4..d5e8220 100644 --- a/schema_validation/openapi_schemas/load_schema_test.go +++ b/schema_validation/openapi_schemas/load_schema_test.go @@ -9,8 +9,8 @@ import ( "net/http/httptest" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" ) // Mock server to simulate fetching remote files diff --git a/schema_validation/property_locator_test.go b/schema_validation/property_locator_test.go index 56b74ad..d7aa2df 100644 --- a/schema_validation/property_locator_test.go +++ b/schema_validation/property_locator_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" "go.yaml.in/yaml/v4" ) diff --git a/schema_validation/validate_document_test.go b/schema_validation/validate_document_test.go index 33177cd..a02ef59 100644 --- a/schema_validation/validate_document_test.go +++ b/schema_validation/validate_document_test.go @@ -10,7 +10,7 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" "go.yaml.in/yaml/v4" "github.com/pb33f/libopenapi-validator/config" diff --git a/schema_validation/validate_schema_coercion_test.go b/schema_validation/validate_schema_coercion_test.go index a8036dc..a0a99bd 100644 --- a/schema_validation/validate_schema_coercion_test.go +++ b/schema_validation/validate_schema_coercion_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" "github.com/pb33f/libopenapi-validator/config" ) diff --git a/schema_validation/validate_schema_extract_errors_test.go b/schema_validation/validate_schema_extract_errors_test.go index 825bffa..1f263fe 100644 --- a/schema_validation/validate_schema_extract_errors_test.go +++ b/schema_validation/validate_schema_extract_errors_test.go @@ -6,8 +6,8 @@ package schema_validation import ( "testing" + "github.com/pb33f/testify/assert" "github.com/santhosh-tekuri/jsonschema/v6" - "github.com/stretchr/testify/assert" "go.yaml.in/yaml/v4" "golang.org/x/text/message" ) diff --git a/schema_validation/validate_schema_openapi_test.go b/schema_validation/validate_schema_openapi_test.go index a193248..482e7e5 100644 --- a/schema_validation/validate_schema_openapi_test.go +++ b/schema_validation/validate_schema_openapi_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" "github.com/pb33f/libopenapi-validator/config" ) diff --git a/schema_validation/validate_schema_test.go b/schema_validation/validate_schema_test.go index 29ca82b..6ed66df 100644 --- a/schema_validation/validate_schema_test.go +++ b/schema_validation/validate_schema_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" "go.yaml.in/yaml/v4" ) diff --git a/schema_validation/validate_urlencoded_test.go b/schema_validation/validate_urlencoded_test.go index 4fb928c..59ecd4e 100644 --- a/schema_validation/validate_urlencoded_test.go +++ b/schema_validation/validate_urlencoded_test.go @@ -9,7 +9,7 @@ import ( "github.com/pb33f/libopenapi/datamodel/high/base" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" "github.com/pb33f/libopenapi/orderedmap" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func TestIsURLEncodedContentType(t *testing.T) { diff --git a/schema_validation/validate_xml_test.go b/schema_validation/validate_xml_test.go index 1b6292f..48f62f5 100644 --- a/schema_validation/validate_xml_test.go +++ b/schema_validation/validate_xml_test.go @@ -9,7 +9,7 @@ import ( "github.com/pb33f/libopenapi-validator/helpers" "github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/orderedmap" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func TestValidateXML_Issue346_BasicXMLWithName(t *testing.T) { diff --git a/strict/types_test.go b/strict/types_test.go index fcd3091..15cd146 100644 --- a/strict/types_test.go +++ b/strict/types_test.go @@ -8,8 +8,8 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" ) func TestExtractSchemaLocation_NilSchema(t *testing.T) { diff --git a/strict/utils_test.go b/strict/utils_test.go index cdf4be1..d2bf3c3 100644 --- a/strict/utils_test.go +++ b/strict/utils_test.go @@ -6,7 +6,7 @@ package strict import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/pb33f/testify/assert" ) func TestCompilePattern_EscapedDoubleAsterisk(t *testing.T) { diff --git a/strict/validator_test.go b/strict/validator_test.go index 6e88b43..0f82bba 100644 --- a/strict/validator_test.go +++ b/strict/validator_test.go @@ -12,8 +12,8 @@ import ( "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel/high/base" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" libcache "github.com/pb33f/libopenapi-validator/cache" "github.com/pb33f/libopenapi-validator/config" diff --git a/validator_nullable_enum_test.go b/validator_nullable_enum_test.go index 2693165..feedff4 100644 --- a/validator_nullable_enum_test.go +++ b/validator_nullable_enum_test.go @@ -12,8 +12,8 @@ import ( "testing" "github.com/pb33f/libopenapi" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" ) // TestNullableEnum_ResponseValidation_NullValue tests that nullable enum fields diff --git a/validator_test.go b/validator_test.go index 2779c51..3567c37 100644 --- a/validator_test.go +++ b/validator_test.go @@ -19,9 +19,9 @@ import ( "github.com/dlclark/regexp2" "github.com/pb33f/libopenapi" + "github.com/pb33f/testify/assert" + "github.com/pb33f/testify/require" "github.com/santhosh-tekuri/jsonschema/v6" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/pb33f/libopenapi/datamodel/high/base" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" From d77b2f7a382c190a10cb70da74b42d2f8c5ecc8a Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Mon, 15 Jun 2026 17:52:32 -0400 Subject: [PATCH 6/6] deps --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 44538ac..60b38f7 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-openapi/jsonpointer v0.23.1 github.com/goccy/go-yaml v1.19.2 github.com/pb33f/jsonpath v0.8.2 - github.com/pb33f/libopenapi v0.38.0 + github.com/pb33f/libopenapi v0.38.1 github.com/pb33f/testify v0.1.0 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 go.yaml.in/yaml/v4 v4.0.0-rc.5 diff --git a/go.sum b/go.sum index ce01eca..09e5a07 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/pb33f/jsonpath v0.8.2 h1:Ou4C7zjYClBm97dfZjDCjdZGusJoynv/vrtiEKNfj2Y= github.com/pb33f/jsonpath v0.8.2/go.mod h1:zBV5LJW4OQOPatmQE2QdKpGQJvhDTlE5IEj6ASaRNTo= -github.com/pb33f/libopenapi v0.38.0 h1:OG+AMr2dMeB0BZdmm+j6iFy3Uxyl+W0HZpdD72zcyGs= -github.com/pb33f/libopenapi v0.38.0/go.mod h1:HrXjcGH/igq+/Af5l5/hmDD+yQRBRiTHgl0FmLdCZjg= +github.com/pb33f/libopenapi v0.38.1 h1:F4mlPaex6MugO1DoGjIy8lnevyzclfGX5lWZrT9LszE= +github.com/pb33f/libopenapi v0.38.1/go.mod h1:OIh31Zxvw3z0OnLGKqhnVlSQ80swwddph1+xedWZjdU= github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= github.com/pb33f/testify v0.1.0 h1:g48/HDU/jn2COspS4nM0scptxiKTJ4DnbX/4ehK6IZ8=