Skip to content
Open
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
10 changes: 2 additions & 8 deletions parameters/cookie_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package parameters

import (
"encoding/json"
"fmt"
"net/http"
"strconv"
Expand Down Expand Up @@ -70,13 +69,8 @@ func (v *paramValidator) ValidateCookieParamsWithPathItem(request *http.Request,
sch = p.Schema.Schema()
}

// Render schema once for ReferenceSchema field in errors
var renderedSchema string
if sch != nil {
rendered, _ := sch.RenderInline()
schemaBytes, _ := json.Marshal(rendered)
renderedSchema = string(schemaBytes)
}
// Get rendered schema for ReferenceSchema field in errors (uses cache if available)
renderedSchema := GetRenderedSchema(sch, v.options)

pType := sch.Type

Expand Down
19 changes: 4 additions & 15 deletions parameters/header_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package parameters

import (
"encoding/json"
"fmt"
"net/http"
"strconv"
Expand Down Expand Up @@ -60,13 +59,8 @@ func (v *paramValidator) ValidateHeaderParamsWithPathItem(request *http.Request,
sch = p.Schema.Schema()
}

// Render schema once for ReferenceSchema field in errors
var renderedSchema string
if sch != nil {
rendered, _ := sch.RenderInline()
schemaBytes, _ := json.Marshal(rendered)
renderedSchema = string(schemaBytes)
}
// Get rendered schema for ReferenceSchema field in errors (uses cache if available)
renderedSchema := GetRenderedSchema(sch, v.options)

pType := sch.Type

Expand Down Expand Up @@ -204,15 +198,10 @@ func (v *paramValidator) ValidateHeaderParamsWithPathItem(request *http.Request,
}
} else {
if p.Required != nil && *p.Required {
// Render schema for missing required parameter
// Get rendered schema for missing required parameter (uses cache if available)
var renderedSchema string
if p.Schema != nil {
sch := p.Schema.Schema()
if sch != nil {
rendered, _ := sch.RenderInline()
schemaBytes, _ := json.Marshal(rendered)
renderedSchema = string(schemaBytes)
}
renderedSchema = GetRenderedSchema(p.Schema.Schema(), v.options)
}
validationErrors = append(validationErrors, errors.HeaderParameterMissing(p, pathValue, operation, renderedSchema))
}
Expand Down
19 changes: 4 additions & 15 deletions parameters/path_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package parameters

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
Expand Down Expand Up @@ -142,13 +141,8 @@ func (v *paramValidator) ValidatePathParamsWithPathItem(request *http.Request, p
// extract the schema from the parameter
sch := p.Schema.Schema()

// Render schema once for ReferenceSchema field in errors
var renderedSchema string
if sch != nil {
rendered, _ := sch.RenderInline()
schemaBytes, _ := json.Marshal(rendered)
renderedSchema = string(schemaBytes)
}
// Get rendered schema for ReferenceSchema field in errors (uses cache if available)
renderedSchema := GetRenderedSchema(sch, v.options)

// check enum (if present)
enumCheck := func(decodedValue string) {
Expand Down Expand Up @@ -309,13 +303,8 @@ func (v *paramValidator) ValidatePathParamsWithPathItem(request *http.Request, p
if sch.Items != nil && sch.Items.IsA() {
iSch := sch.Items.A.Schema()

// Render items schema once for ReferenceSchema field in array errors
var renderedItemsSchema string
if iSch != nil {
rendered, _ := iSch.RenderInline()
schemaBytes, _ := json.Marshal(rendered)
renderedItemsSchema = string(schemaBytes)
}
// Get rendered items schema for ReferenceSchema field in errors (uses cache if available)
renderedItemsSchema := GetRenderedSchema(iSch, v.options)

for n := range iSch.Type {
// determine how to explode the array
Expand Down
17 changes: 4 additions & 13 deletions parameters/query_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,8 @@ doneLooking:
}
}

// Render schema once for ReferenceSchema field in errors
var renderedSchema string
if sch != nil {
rendered, _ := sch.RenderInline()
schemaBytes, _ := json.Marshal(rendered)
renderedSchema = string(schemaBytes)
}
// Get rendered schema for ReferenceSchema field in errors (uses cache if available)
renderedSchema := GetRenderedSchema(sch, v.options)

pType := sch.Type

Expand Down Expand Up @@ -263,12 +258,8 @@ doneLooking:
break
}
}
var renderedSchema string
if sch != nil {
rendered, _ := sch.RenderInline()
schemaBytes, _ := json.Marshal(rendered)
renderedSchema = string(schemaBytes)
}
// Get rendered schema for ReferenceSchema field in errors (uses cache if available)
renderedSchema := GetRenderedSchema(sch, v.options)
validationErrors = append(validationErrors, errors.QueryParameterMissing(params[p], pathValue, operation, renderedSchema))
}
}
Expand Down
138 changes: 107 additions & 31 deletions parameters/validate_parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

stdError "errors"

"github.com/pb33f/libopenapi-validator/cache"
"github.com/pb33f/libopenapi-validator/config"
"github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi-validator/helpers"
Expand All @@ -35,16 +36,41 @@ func ValidateSingleParameterSchema(
pathTemplate string,
operation string,
) (validationErrors []*errors.ValidationError) {
// Get the JSON Schema for the parameter definition.
jsonSchema, err := buildJsonRender(schema)
if err != nil {
return validationErrors
var jsch *jsonschema.Schema
var jsonSchema []byte

// Try cache lookup first - avoids expensive schema compilation on each request
if o != nil && o.SchemaCache != nil && schema != nil && schema.GoLow() != nil {
hash := schema.GoLow().Hash()
if cached, ok := o.SchemaCache.Load(hash); ok && cached != nil && cached.CompiledSchema != nil {
jsch = cached.CompiledSchema
}
}

// Attempt to compile the JSON Schema
jsch, err := helpers.NewCompiledSchema(name, jsonSchema, o)
if err != nil {
return validationErrors
// Cache miss - compile the schema
if jsch == nil {
// Get the JSON Schema for the parameter definition.
var err error
jsonSchema, err = buildJsonRender(schema)
if err != nil {
return validationErrors
}

// Attempt to compile the JSON Schema
jsch, err = helpers.NewCompiledSchema(name, jsonSchema, o)
if err != nil {
return validationErrors
}

// Store in cache for future requests
if o != nil && o.SchemaCache != nil && schema != nil && schema.GoLow() != nil {
hash := schema.GoLow().Hash()
o.SchemaCache.Store(hash, &cache.SchemaCacheEntry{
Schema: schema,
RenderedJSON: jsonSchema,
CompiledSchema: jsch,
})
}
}

// Validate the object and report any errors.
Expand All @@ -71,6 +97,28 @@ func buildJsonRender(schema *base.Schema) ([]byte, error) {
return utils.ConvertYAMLtoJSON(renderedSchema)
}

// GetRenderedSchema returns a JSON string representation of the schema for error messages.
// It first checks the schema cache for a pre-rendered version, falling back to fresh rendering.
// This avoids expensive re-rendering on each validation when the cache is available.
func GetRenderedSchema(schema *base.Schema, opts *config.ValidationOptions) string {
if schema == nil {
return ""
}

// Try cache lookup first
if opts != nil && opts.SchemaCache != nil && schema.GoLow() != nil {
hash := schema.GoLow().Hash()
if cached, ok := opts.SchemaCache.Load(hash); ok && cached != nil && len(cached.RenderedJSON) > 0 {
return string(cached.RenderedJSON)
}
}

// Cache miss - render fresh
rendered, _ := schema.RenderInline()
schemaBytes, _ := json.Marshal(rendered)
return string(schemaBytes)
}

// ValidateParameterSchema will validate a parameter against a raw object, or a blob of json/yaml.
// It will return a list of validation errors, if any.
//
Expand All @@ -94,13 +142,59 @@ func ValidateParameterSchema(
validationOptions *config.ValidationOptions,
) []*errors.ValidationError {
var validationErrors []*errors.ValidationError
var jsch *jsonschema.Schema
var jsonSchema []byte

// 1. build a JSON render of the schema.
renderCtx := base.NewInlineRenderContextForValidation()
renderedSchema, _ := schema.RenderInlineWithContext(renderCtx)
jsonSchema, _ := utils.ConvertYAMLtoJSON(renderedSchema)
// Try cache lookup first - avoids expensive schema compilation on each request
if validationOptions != nil && validationOptions.SchemaCache != nil && schema != nil && schema.GoLow() != nil {
hash := schema.GoLow().Hash()
if cached, ok := validationOptions.SchemaCache.Load(hash); ok && cached != nil && cached.CompiledSchema != nil {
jsch = cached.CompiledSchema
}
}

// 2. decode the object into a json blob.
// Cache miss - render and compile the schema
if jsch == nil {
// 1. build a JSON render of the schema.
renderCtx := base.NewInlineRenderContextForValidation()
renderedSchema, _ := schema.RenderInlineWithContext(renderCtx)
referenceSchema := string(renderedSchema)
jsonSchema, _ = utils.ConvertYAMLtoJSON(renderedSchema)

// 2. create a new json schema compiler and add the schema to it
var err error
jsch, err = helpers.NewCompiledSchema(name, jsonSchema, validationOptions)
if err != nil {
// schema compilation failed, return validation error instead of panicking
validationErrors = append(validationErrors, &errors.ValidationError{
ValidationType: validationType,
ValidationSubType: subValType,
Message: fmt.Sprintf("%s '%s' failed schema compilation", entity, name),
Reason: fmt.Sprintf("%s '%s' schema compilation failed: %s",
reasonEntity, name, err.Error()),
SpecLine: 1,
SpecCol: 0,
ParameterName: name,
HowToFix: "check the parameter schema for invalid JSON Schema syntax, complex regex patterns, or unsupported schema constructs",
Context: string(jsonSchema),
})
return validationErrors
}

// Store in cache for future requests
if validationOptions != nil && validationOptions.SchemaCache != nil && schema != nil && schema.GoLow() != nil {
hash := schema.GoLow().Hash()
validationOptions.SchemaCache.Store(hash, &cache.SchemaCacheEntry{
Schema: schema,
RenderedInline: renderedSchema,
ReferenceSchema: referenceSchema,
RenderedJSON: jsonSchema,
CompiledSchema: jsch,
})
}
}

// 3. decode the object into a json blob.
var decodedObj interface{}
rawIsMap := false
validEncoding := false
Expand All @@ -125,24 +219,6 @@ func ValidateParameterSchema(
}
validEncoding = true
}
// 3. create a new json schema compiler and add the schema to it
jsch, err := helpers.NewCompiledSchema(name, jsonSchema, validationOptions)
if err != nil {
// schema compilation failed, return validation error instead of panicking
validationErrors = append(validationErrors, &errors.ValidationError{
ValidationType: validationType,
ValidationSubType: subValType,
Message: fmt.Sprintf("%s '%s' failed schema compilation", entity, name),
Reason: fmt.Sprintf("%s '%s' schema compilation failed: %s",
reasonEntity, name, err.Error()),
SpecLine: 1,
SpecCol: 0,
ParameterName: name,
HowToFix: "check the parameter schema for invalid JSON Schema syntax, complex regex patterns, or unsupported schema constructs",
Context: string(jsonSchema),
})
return validationErrors
}

// 4. validate the object against the schema
var scErrs error
Expand Down
Loading
Loading