Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/dlclark/regexp2 v1.11.5
github.com/goccy/go-yaml v1.19.1
github.com/pb33f/jsonpath v0.7.0
github.com/pb33f/libopenapi v0.31.1
github.com/pb33f/libopenapi v0.31.2
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.3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,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.7.0 h1:3oG6yu1RqNoMZpqnRjBMqi8fSIXWoDAKDrsB0QGTcoU=
github.com/pb33f/jsonpath v0.7.0/go.mod h1:/+JlSIjWA2ijMVYGJ3IQPF4Q1nLMYbUTYNdk0exCDPQ=
github.com/pb33f/libopenapi v0.31.1 h1:smGr45U2Y+hHWYKiEV13oS2tP9IUnscqNb5qsvT9+YI=
github.com/pb33f/libopenapi v0.31.1/go.mod h1:oaebeA5l58AFbZ7qRKTtMnu15JEiPlaBas1vLDcw9vs=
github.com/pb33f/libopenapi v0.31.2 h1:dcFG9cPH7LvSejbemqqpSa3yrHYZs8eBHNdMx8ayIVc=
github.com/pb33f/libopenapi v0.31.2/go.mod h1:oaebeA5l58AFbZ7qRKTtMnu15JEiPlaBas1vLDcw9vs=
github.com/pb33f/ordered-map/v2 v2.3.0 h1:k2OhVEQkhTCQMhAicQ3Z6iInzoZNQ7L9MVomwKBZ5WQ=
github.com/pb33f/ordered-map/v2 v2.3.0/go.mod h1:oe5ue+6ZNhy7QN9cPZvPA23Hx0vMHnNVeMg4fGdCANw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
6 changes: 3 additions & 3 deletions schema_validation/validate_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,15 @@ func (s *schemaValidator) validateSchemaWithVersion(schema *base.Schema, payload
return false, validationErrors
}

// extract index of schema, and check the version
// schemaIndex := schema.GoLow().Index
var renderedSchema []byte

// render the schema, to be used for validation, stop this from running concurrently, mutations are made to state
// and, it will cause async issues.
// Create isolated render context for this validation to prevent false positive cycle detection
// when multiple validations run concurrently.
renderCtx := base.NewInlineRenderContext()
// Use validation mode to force full inlining of discriminator refs - the JSON schema compiler
// needs a self-contained schema without unresolved $refs.
renderCtx := base.NewInlineRenderContextForValidation()
s.lock.Lock()
var e error
renderedSchema, e = schema.RenderInlineWithContext(renderCtx)
Expand Down
215 changes: 215 additions & 0 deletions schema_validation/validate_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1053,3 +1053,218 @@ components:
})
}
}

// TestValidateSchema_Discriminator_OneOf_WithRefs_Issue788 tests that validation works correctly
// when a schema has discriminator + oneOf with $ref to component schemas.
// This was a regression in vacuum v0.21.2+ where the validator would fail with
// "JSON schema compile failed: json-pointer not found" because discriminator refs
// were being preserved instead of inlined.
// https://github.com/daveshanley/vacuum/issues/788
func TestValidateSchema_Discriminator_OneOf_WithRefs_Issue788(t *testing.T) {
spec := `openapi: 3.1.0
info:
title: Test Discriminator OneOf With Refs
version: 1.0.0
components:
schemas:
ProductWidget:
type: object
required:
- productName
- quantity
- color
properties:
productName:
type: string
enum:
- Widget
quantity:
type: integer
minimum: 1
color:
type: string
enum:
- Red
- Blue
- Green
ProductGadget:
type: object
required:
- productName
- quantity
- size
properties:
productName:
type: string
enum:
- Gadget
quantity:
type: integer
minimum: 1
size:
type: string
enum:
- Small
- Medium
- Large
Product:
oneOf:
- $ref: '#/components/schemas/ProductWidget'
- $ref: '#/components/schemas/ProductGadget'
discriminator:
propertyName: productName`

doc, err := libopenapi.NewDocument([]byte(spec))
assert.NoError(t, err)

model, errs := doc.BuildV3Model()
assert.Empty(t, errs)

productSchema := model.Model.Components.Schemas.GetOrZero("Product").Schema()

// Valid Widget product
validWidget := map[string]interface{}{
"productName": "Widget",
"quantity": 1,
"color": "Green",
}

validator := NewSchemaValidator()
valid, validationErrors := validator.ValidateSchemaObject(productSchema, validWidget)

// This should pass without "json-pointer not found" error
assert.True(t, valid, "validation should pass for valid product with discriminator oneOf refs")
assert.Empty(t, validationErrors, "no validation errors should be present")
}

// TestValidateSchema_Discriminator_AnyOf_WithRefs tests anyOf with discriminator and $refs
func TestValidateSchema_Discriminator_AnyOf_WithRefs(t *testing.T) {
spec := `openapi: 3.1.0
info:
title: Test Discriminator AnyOf With Refs
version: 1.0.0
components:
schemas:
Cat:
type: object
required:
- petType
- meow
properties:
petType:
type: string
const: cat
meow:
type: boolean
Dog:
type: object
required:
- petType
- bark
properties:
petType:
type: string
const: dog
bark:
type: boolean
Pet:
anyOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
discriminator:
propertyName: petType`

doc, err := libopenapi.NewDocument([]byte(spec))
assert.NoError(t, err)

model, errs := doc.BuildV3Model()
assert.Empty(t, errs)

petSchema := model.Model.Components.Schemas.GetOrZero("Pet").Schema()

// Valid cat
validCat := map[string]interface{}{
"petType": "cat",
"meow": true,
}

validator := NewSchemaValidator()
valid, validationErrors := validator.ValidateSchemaObject(petSchema, validCat)

assert.True(t, valid, "validation should pass for valid cat with discriminator anyOf refs")
assert.Empty(t, validationErrors, "no validation errors should be present")
}

// TestValidateSchema_Discriminator_OneOf_WithRefs_InvalidData tests that invalid data
// still fails validation correctly (not a false negative)
func TestValidateSchema_Discriminator_OneOf_WithRefs_InvalidData(t *testing.T) {
spec := `openapi: 3.1.0
info:
title: Test Discriminator OneOf Invalid
version: 1.0.0
components:
schemas:
ProductWidget:
type: object
required:
- productName
- quantity
- color
properties:
productName:
type: string
enum:
- Widget
quantity:
type: integer
minimum: 1
color:
type: string
enum:
- Red
- Blue
ProductGadget:
type: object
required:
- productName
- quantity
- size
properties:
productName:
type: string
enum:
- Gadget
quantity:
type: integer
minimum: 1
size:
type: string
Product:
oneOf:
- $ref: '#/components/schemas/ProductWidget'
- $ref: '#/components/schemas/ProductGadget'
discriminator:
propertyName: productName`

doc, err := libopenapi.NewDocument([]byte(spec))
assert.NoError(t, err)

model, errs := doc.BuildV3Model()
assert.Empty(t, errs)

productSchema := model.Model.Components.Schemas.GetOrZero("Product").Schema()

// Invalid product - missing required field 'color' for Widget
invalidProduct := map[string]interface{}{
"productName": "Widget",
"quantity": 1,
// missing required 'color' field
}

validator := NewSchemaValidator()
valid, validationErrors := validator.ValidateSchemaObject(productSchema, invalidProduct)

// This should fail because 'color' is required for Widget
assert.False(t, valid, "validation should fail for invalid product")
assert.NotEmpty(t, validationErrors, "validation errors should be present")
}