From 6bb47458123bc7b3fa2c4bf0e4fb8c76db21558e Mon Sep 17 00:00:00 2001 From: Zach Hamm Date: Tue, 20 Jan 2026 15:00:09 -0800 Subject: [PATCH] Cache YAML Nodes --- cache/cache.go | 2 ++ requests/validate_request.go | 11 ++++++++--- responses/validate_response.go | 10 +++++++--- validator.go | 11 +++++++++++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/cache/cache.go b/cache/cache.go index deedc6b..005b474 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -6,6 +6,7 @@ package cache import ( "github.com/pb33f/libopenapi/datamodel/high/base" "github.com/santhosh-tekuri/jsonschema/v6" + "go.yaml.in/yaml/v4" ) // SchemaCacheEntry holds a compiled schema and its intermediate representations. @@ -16,6 +17,7 @@ type SchemaCacheEntry struct { ReferenceSchema string // String version of RenderedInline RenderedJSON []byte CompiledSchema *jsonschema.Schema + RenderedNode *yaml.Node } // SchemaCache defines the interface for schema caching implementations. diff --git a/requests/validate_request.go b/requests/validate_request.go index 1e7d828..f1ce93c 100644 --- a/requests/validate_request.go +++ b/requests/validate_request.go @@ -47,6 +47,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V var renderedSchema, jsonSchema []byte var referenceSchema string var compiledSchema *jsonschema.Schema + var cachedNode *yaml.Node if input.Schema == nil { return false, []*errors.ValidationError{{ @@ -71,6 +72,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V referenceSchema = cached.ReferenceSchema jsonSchema = cached.RenderedJSON compiledSchema = cached.CompiledSchema + cachedNode = cached.RenderedNode } } @@ -229,9 +231,12 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V schFlatErrs := jk.BasicOutput().Errors var schemaValidationErrors []*errors.SchemaValidationFailure - // re-encode the schema. - var renderedNode yaml.Node - _ = yaml.Unmarshal(renderedSchema, &renderedNode) + // Use cached node if available, otherwise parse + renderedNode := cachedNode + if renderedNode == nil { + renderedNode = new(yaml.Node) + _ = yaml.Unmarshal(renderedSchema, renderedNode) + } for q := range schFlatErrs { er := schFlatErrs[q] diff --git a/responses/validate_response.go b/responses/validate_response.go index 04bc78d..f63a6a3 100644 --- a/responses/validate_response.go +++ b/responses/validate_response.go @@ -51,6 +51,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors var renderedSchema, jsonSchema []byte var referenceSchema string var compiledSchema *jsonschema.Schema + var cachedNode *yaml.Node if input.Schema == nil { return false, []*errors.ValidationError{{ @@ -74,6 +75,7 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors renderedSchema = cached.RenderedInline referenceSchema = cached.ReferenceSchema compiledSchema = cached.CompiledSchema + cachedNode = cached.RenderedNode } } @@ -247,9 +249,11 @@ func ValidateResponseSchema(input *ValidateResponseSchemaInput) (bool, []*errors schFlatErrs := jk.BasicOutput().Errors var schemaValidationErrors []*errors.SchemaValidationFailure - // re-encode the schema once for error reporting - var renderedNode yaml.Node - _ = yaml.Unmarshal(renderedSchema, &renderedNode) + renderedNode := cachedNode + if renderedNode == nil { + renderedNode = new(yaml.Node) + _ = yaml.Unmarshal(renderedSchema, renderedNode) + } for q := range schFlatErrs { er := schFlatErrs[q] diff --git a/validator.go b/validator.go index 29eec15..22a8f10 100644 --- a/validator.go +++ b/validator.go @@ -12,6 +12,7 @@ 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" @@ -491,12 +492,17 @@ func warmMediaTypeSchema(mediaType *v3.MediaType, schemaCache cache.SchemaCache, 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, }) } } @@ -539,6 +545,10 @@ func warmParameterSchema(param *v3.Parameter, schemaCache cache.SchemaCache, opt 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, @@ -546,6 +556,7 @@ func warmParameterSchema(param *v3.Parameter, schemaCache cache.SchemaCache, opt ReferenceSchema: referenceSchema, RenderedJSON: renderedJSON, CompiledSchema: compiledSchema, + RenderedNode: &renderedNode, }) } }