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
9 changes: 5 additions & 4 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import (
// SchemaCacheEntry holds a compiled schema and its intermediate representations.
// This is stored in the cache to avoid re-rendering and re-compiling schemas on each request.
type SchemaCacheEntry struct {
Schema *base.Schema
RenderedInline []byte
RenderedJSON []byte
CompiledSchema *jsonschema.Schema
Schema *base.Schema
RenderedInline []byte
ReferenceSchema string // String version of RenderedInline
RenderedJSON []byte
CompiledSchema *jsonschema.Schema
}

// SchemaCache defines the interface for schema caching implementations.
Expand Down
28 changes: 16 additions & 12 deletions requests/validate_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
validationOptions := config.NewValidationOptions(input.Options...)
var validationErrors []*errors.ValidationError
var renderedSchema, jsonSchema []byte
var referenceSchema string
var compiledSchema *jsonschema.Schema

if input.Schema == nil {
Expand All @@ -65,6 +66,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
hash := input.Schema.GoLow().Hash()
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
}
Expand All @@ -73,6 +75,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
// Cache miss or no cache - render and compile
if compiledSchema == nil {
renderedSchema, _ = input.Schema.RenderInline()
referenceSchema = string(renderedSchema)
jsonSchema, _ = utils.ConvertYAMLtoJSON(renderedSchema)

var err error
Expand All @@ -87,7 +90,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
violation := &errors.SchemaValidationFailure{
Reason: fmt.Sprintf("failed to compile JSON schema: %s", err.Error()),
Location: "schema compilation",
ReferenceSchema: string(renderedSchema),
ReferenceSchema: referenceSchema,
}
validationErrors = append(validationErrors, &errors.ValidationError{
ValidationType: helpers.RequestBodyValidation,
Expand All @@ -99,18 +102,19 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
SpecCol: 0,
SchemaValidationErrors: []*errors.SchemaValidationFailure{violation},
HowToFix: "check the request schema for invalid JSON Schema syntax, complex regex patterns, or unsupported schema constructs",
Context: string(renderedSchema),
Context: referenceSchema,
})
return false, validationErrors
}

if validationOptions.SchemaCache != nil {
hash := input.Schema.GoLow().Hash()
validationOptions.SchemaCache.Store(hash, &cache.SchemaCacheEntry{
Schema: input.Schema,
RenderedInline: renderedSchema,
RenderedJSON: jsonSchema,
CompiledSchema: compiledSchema,
Schema: input.Schema,
RenderedInline: renderedSchema,
ReferenceSchema: referenceSchema,
RenderedJSON: jsonSchema,
CompiledSchema: compiledSchema,
})
}
}
Expand All @@ -137,7 +141,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
violation := &errors.SchemaValidationFailure{
Reason: err.Error(),
Location: "unavailable",
ReferenceSchema: string(renderedSchema),
ReferenceSchema: referenceSchema,
ReferenceObject: string(requestBody),
}
validationErrors = append(validationErrors, &errors.ValidationError{
Expand All @@ -150,7 +154,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
SpecCol: 0,
SchemaValidationErrors: []*errors.SchemaValidationFailure{violation},
HowToFix: errors.HowToFixInvalidSchema,
Context: string(renderedSchema), // attach the rendered schema to the error
Context: referenceSchema, // attach the rendered schema to the error
})
return false, validationErrors
}
Expand All @@ -169,7 +173,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
// cannot decode the request body, so it's not valid
violation := &errors.SchemaValidationFailure{
Reason: "request body is empty, but there is a schema defined",
ReferenceSchema: string(renderedSchema),
ReferenceSchema: referenceSchema,
ReferenceObject: string(requestBody),
}
validationErrors = append(validationErrors, &errors.ValidationError{
Expand All @@ -182,7 +186,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
SpecCol: col,
SchemaValidationErrors: []*errors.SchemaValidationFailure{violation},
HowToFix: errors.HowToFixInvalidSchema,
Context: string(renderedSchema), // attach the rendered schema to the error
Context: referenceSchema, // attach the rendered schema to the error
})
return false, validationErrors
}
Expand Down Expand Up @@ -237,7 +241,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
FieldName: helpers.ExtractFieldNameFromStringLocation(er.InstanceLocation),
FieldPath: helpers.ExtractJSONPathFromStringLocation(er.InstanceLocation),
InstancePath: helpers.ConvertStringLocationToPathSegments(er.InstanceLocation),
ReferenceSchema: string(renderedSchema),
ReferenceSchema: referenceSchema,
ReferenceObject: referenceObject,
OriginalError: jk,
}
Expand Down Expand Up @@ -280,7 +284,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
SpecCol: col,
SchemaValidationErrors: schemaValidationErrors,
HowToFix: errors.HowToFixInvalidSchema,
Context: string(renderedSchema), // attach the rendered schema to the error
Context: referenceSchema, // attach the rendered schema to the error
})
}
if len(validationErrors) > 0 {
Expand Down
32 changes: 18 additions & 14 deletions responses/validate_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
validationOptions := config.NewValidationOptions(input.Options...)
var validationErrors []*errors.ValidationError
var renderedSchema, jsonSchema []byte
var referenceSchema string
var compiledSchema *jsonschema.Schema

if input.Schema == nil {
Expand All @@ -69,13 +70,15 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
hash := input.Schema.GoLow().Hash()
if cached, ok := validationOptions.SchemaCache.Load(hash); ok && cached != nil && cached.CompiledSchema != nil {
renderedSchema = cached.RenderedInline
referenceSchema = cached.ReferenceSchema
compiledSchema = cached.CompiledSchema
}
}

// Cache miss or no cache - render and compile
if compiledSchema == nil {
renderedSchema, _ = input.Schema.RenderInline()
referenceSchema = string(renderedSchema)
jsonSchema, _ = utils.ConvertYAMLtoJSON(renderedSchema)

var err error
Expand All @@ -90,7 +93,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
violation := &errors.SchemaValidationFailure{
Reason: fmt.Sprintf("failed to compile JSON schema: %s", err.Error()),
Location: "schema compilation",
ReferenceSchema: string(renderedSchema),
ReferenceSchema: referenceSchema,
}
validationErrors = append(validationErrors, &errors.ValidationError{
ValidationType: helpers.ResponseBodyValidation,
Expand All @@ -103,18 +106,19 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
SpecCol: 0,
SchemaValidationErrors: []*errors.SchemaValidationFailure{violation},
HowToFix: "check the response schema for invalid JSON Schema syntax, complex regex patterns, or unsupported schema constructs",
Context: string(renderedSchema),
Context: referenceSchema,
})
return false, validationErrors
}

if validationOptions.SchemaCache != nil {
hash := input.Schema.GoLow().Hash()
validationOptions.SchemaCache.Store(hash, &cache.SchemaCacheEntry{
Schema: input.Schema,
RenderedInline: renderedSchema,
RenderedJSON: jsonSchema,
CompiledSchema: compiledSchema,
Schema: input.Schema,
RenderedInline: renderedSchema,
ReferenceSchema: referenceSchema,
RenderedJSON: jsonSchema,
CompiledSchema: compiledSchema,
})
}
}
Expand All @@ -128,7 +132,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
violation := &errors.SchemaValidationFailure{
Reason: "response is empty",
Location: "unavailable",
ReferenceSchema: string(renderedSchema),
ReferenceSchema: referenceSchema,
}
validationErrors = append(validationErrors, &errors.ValidationError{
ValidationType: "response",
Expand All @@ -140,7 +144,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
SpecCol: 0,
SchemaValidationErrors: []*errors.SchemaValidationFailure{violation},
HowToFix: "ensure response object has been set",
Context: string(renderedSchema), // attach the rendered schema to the error
Context: referenceSchema, // attach the rendered schema to the error
})
return false, validationErrors
}
Expand All @@ -151,7 +155,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
violation := &errors.SchemaValidationFailure{
Reason: ioErr.Error(),
Location: "unavailable",
ReferenceSchema: string(renderedSchema),
ReferenceSchema: referenceSchema,
ReferenceObject: string(responseBody),
}
validationErrors = append(validationErrors, &errors.ValidationError{
Expand All @@ -164,7 +168,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
SpecCol: 0,
SchemaValidationErrors: []*errors.SchemaValidationFailure{violation},
HowToFix: "ensure body is not empty",
Context: string(renderedSchema), // attach the rendered schema to the error
Context: referenceSchema, // attach the rendered schema to the error
})
return false, validationErrors
}
Expand All @@ -182,7 +186,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
violation := &errors.SchemaValidationFailure{
Reason: err.Error(),
Location: "unavailable",
ReferenceSchema: string(renderedSchema),
ReferenceSchema: referenceSchema,
ReferenceObject: string(responseBody),
}
validationErrors = append(validationErrors, &errors.ValidationError{
Expand All @@ -195,7 +199,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
SpecCol: 0,
SchemaValidationErrors: []*errors.SchemaValidationFailure{violation},
HowToFix: errors.HowToFixInvalidSchema,
Context: string(renderedSchema), // attach the rendered schema to the error
Context: referenceSchema, // attach the rendered schema to the error
})
return false, validationErrors
}
Expand Down Expand Up @@ -252,7 +256,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
FieldName: helpers.ExtractFieldNameFromStringLocation(er.InstanceLocation),
FieldPath: helpers.ExtractJSONPathFromStringLocation(er.InstanceLocation),
InstancePath: helpers.ConvertStringLocationToPathSegments(er.InstanceLocation),
ReferenceSchema: string(renderedSchema),
ReferenceSchema: referenceSchema,
ReferenceObject: referenceObject,
OriginalError: jk,
}
Expand Down Expand Up @@ -295,7 +299,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors
SpecCol: col,
SchemaValidationErrors: schemaValidationErrors,
HowToFix: errors.HowToFixInvalidSchema,
Context: string(renderedSchema), // attach the rendered schema to the error
Context: referenceSchema, // attach the rendered schema to the error
})
}
if len(validationErrors) > 0 {
Expand Down
20 changes: 12 additions & 8 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,15 +468,17 @@ func warmMediaTypeSchema(mediaType *v3.MediaType, schemaCache cache.SchemaCache,
schema := mediaType.Schema.Schema()
if schema != nil {
renderedInline, _ := schema.RenderInline()
referenceSchema := string(renderedInline)
renderedJSON, _ := utils.ConvertYAMLtoJSON(renderedInline)
if len(renderedInline) > 0 {
compiledSchema, _ := helpers.NewCompiledSchema(fmt.Sprintf("%x", hash), renderedJSON, options)

schemaCache.Store(hash, &cache.SchemaCacheEntry{
Schema: schema,
RenderedInline: renderedInline,
RenderedJSON: renderedJSON,
CompiledSchema: compiledSchema,
Schema: schema,
RenderedInline: renderedInline,
ReferenceSchema: referenceSchema,
RenderedJSON: renderedJSON,
CompiledSchema: compiledSchema,
})
}
}
Expand Down Expand Up @@ -513,16 +515,18 @@ func warmParameterSchema(param *v3.Parameter, schemaCache cache.SchemaCache, opt
if schema != nil {
if _, exists := schemaCache.Load(hash); !exists {
renderedInline, _ := schema.RenderInline()
referenceSchema := string(renderedInline)
renderedJSON, _ := utils.ConvertYAMLtoJSON(renderedInline)
if len(renderedInline) > 0 {
compiledSchema, _ := helpers.NewCompiledSchema(fmt.Sprintf("%x", hash), renderedJSON, options)

// Store in cache using the shared SchemaCache type
schemaCache.Store(hash, &cache.SchemaCacheEntry{
Schema: schema,
RenderedInline: renderedInline,
RenderedJSON: renderedJSON,
CompiledSchema: compiledSchema,
Schema: schema,
RenderedInline: renderedInline,
ReferenceSchema: referenceSchema,
RenderedJSON: renderedJSON,
CompiledSchema: compiledSchema,
})
}
}
Expand Down
2 changes: 2 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2013,6 +2013,8 @@ func TestCacheWarming_PopulatesCache(t *testing.T) {
validator.options.SchemaCache.Range(func(key [32]byte, value *cache.SchemaCacheEntry) bool {
count++
assert.NotNil(t, value.CompiledSchema, "Cache entry should have compiled schema")
assert.NotEmpty(t, value.ReferenceSchema, "Cache entry should have pre-converted ReferenceSchema string")
assert.Equal(t, string(value.RenderedInline), value.ReferenceSchema, "ReferenceSchema should match string conversion of RenderedInline")
return true
})
assert.Greater(t, count, 0, "Schema cache should have entries from request and response bodies")
Expand Down