Merge pull request #555 from Beckn-One/543-schema2validator

Issue 552 - Improve schema2validator Performance by Building In-Memory Action→Schema Lookup Map
This commit is contained in:
Mayuresh A Nirhali
2025-11-18 13:34:28 +05:30
committed by GitHub

View File

@@ -32,6 +32,7 @@ type schemav2Validator struct {
// cachedSpec holds a cached OpenAPI spec. // cachedSpec holds a cached OpenAPI spec.
type cachedSpec struct { type cachedSpec struct {
doc *openapi3.T doc *openapi3.T
actionSchemas map[string]*openapi3.SchemaRef // O(1) action lookup
loadedAt time.Time loadedAt time.Time
} }
@@ -95,40 +96,14 @@ func (v *schemav2Validator) Validate(ctx context.Context, reqURL *url.URL, data
} }
action := payloadData.Context.Action action := payloadData.Context.Action
var schema *openapi3.SchemaRef
var matchedPath string
// Search all spec paths for matching action in schema
for path, item := range spec.doc.Paths.Map() {
if item == nil {
continue
}
// Check all HTTP methods for this path
for _, op := range []*openapi3.Operation{item.Post, item.Get, item.Put, item.Patch, item.Delete} {
if op == nil || op.RequestBody == nil || op.RequestBody.Value == nil {
continue
}
content := op.RequestBody.Value.Content.Get("application/json")
if content == nil || content.Schema == nil || content.Schema.Value == nil {
continue
}
// Check if schema has action constraint matching our action
if v.schemaMatchesAction(content.Schema.Value, action) {
schema = content.Schema
matchedPath = path
break
}
}
if schema != nil {
break
}
}
// O(1) lookup from action index
schema := spec.actionSchemas[action]
if schema == nil || schema.Value == nil { if schema == nil || schema.Value == nil {
return model.NewBadReqErr(fmt.Errorf("unsupported action: %s", action)) return model.NewBadReqErr(fmt.Errorf("unsupported action: %s", action))
} }
log.Debugf(ctx, "Validating action: %s, matched path: %s", action, matchedPath) log.Debugf(ctx, "Validating action: %s", action)
var jsonData any var jsonData any
if err := json.Unmarshal(data, &jsonData); err != nil { if err := json.Unmarshal(data, &jsonData); err != nil {
@@ -190,14 +165,18 @@ func (v *schemav2Validator) loadSpec(ctx context.Context) error {
log.Debugf(ctx, "Spec validation passed") log.Debugf(ctx, "Spec validation passed")
} }
// Build action→schema index for O(1) lookup
actionSchemas := v.buildActionIndex(ctx, doc)
v.specMutex.Lock() v.specMutex.Lock()
v.spec = &cachedSpec{ v.spec = &cachedSpec{
doc: doc, doc: doc,
actionSchemas: actionSchemas,
loadedAt: time.Now(), loadedAt: time.Now(),
} }
v.specMutex.Unlock() v.specMutex.Unlock()
log.Debugf(ctx, "Loaded OpenAPI spec from %s: %s", v.config.Type, v.config.Location) log.Debugf(ctx, "Loaded OpenAPI spec from %s: %s with %d actions indexed", v.config.Type, v.config.Location, len(actionSchemas))
return nil return nil
} }
@@ -283,12 +262,42 @@ func (v *schemav2Validator) extractSchemaErrors(err error, schemaErrors *[]model
} }
} }
// schemaMatchesAction checks if a schema has an action constraint matching the given action. // buildActionIndex builds a map of action→schema for O(1) lookup.
func (v *schemav2Validator) schemaMatchesAction(schema *openapi3.Schema, action string) bool { func (v *schemav2Validator) buildActionIndex(ctx context.Context, doc *openapi3.T) map[string]*openapi3.SchemaRef {
actionSchemas := make(map[string]*openapi3.SchemaRef)
for path, item := range doc.Paths.Map() {
if item == nil {
continue
}
// Check all HTTP methods
for _, op := range []*openapi3.Operation{item.Post, item.Get, item.Put, item.Patch, item.Delete} {
if op == nil || op.RequestBody == nil || op.RequestBody.Value == nil {
continue
}
content := op.RequestBody.Value.Content.Get("application/json")
if content == nil || content.Schema == nil || content.Schema.Value == nil {
continue
}
// Extract action from schema
action := v.extractActionFromSchema(content.Schema.Value)
if action != "" {
actionSchemas[action] = content.Schema
log.Debugf(ctx, "Indexed action '%s' from path %s", action, path)
}
}
}
return actionSchemas
}
// extractActionFromSchema extracts the action value from a schema.
func (v *schemav2Validator) extractActionFromSchema(schema *openapi3.Schema) string {
// Check direct properties // Check direct properties
if ctxProp := schema.Properties["context"]; ctxProp != nil && ctxProp.Value != nil { if ctxProp := schema.Properties["context"]; ctxProp != nil && ctxProp.Value != nil {
if v.checkActionEnum(ctxProp.Value, action) { if action := v.getActionValue(ctxProp.Value); action != "" {
return true return action
} }
} }
@@ -296,32 +305,29 @@ func (v *schemav2Validator) schemaMatchesAction(schema *openapi3.Schema, action
for _, allOfSchema := range schema.AllOf { for _, allOfSchema := range schema.AllOf {
if allOfSchema.Value != nil { if allOfSchema.Value != nil {
if ctxProp := allOfSchema.Value.Properties["context"]; ctxProp != nil && ctxProp.Value != nil { if ctxProp := allOfSchema.Value.Properties["context"]; ctxProp != nil && ctxProp.Value != nil {
if v.checkActionEnum(ctxProp.Value, action) { if action := v.getActionValue(ctxProp.Value); action != "" {
return true return action
} }
} }
} }
} }
return false return ""
} }
// checkActionEnum checks if a context schema has action enum or const matching the given action. // getActionValue extracts action value from context schema.
func (v *schemav2Validator) checkActionEnum(contextSchema *openapi3.Schema, action string) bool { func (v *schemav2Validator) getActionValue(contextSchema *openapi3.Schema) string {
// Check direct action property
if actionProp := contextSchema.Properties["action"]; actionProp != nil && actionProp.Value != nil { if actionProp := contextSchema.Properties["action"]; actionProp != nil && actionProp.Value != nil {
// Check const field (stored in Extensions by kin-openapi) // Check const field
if constVal, ok := actionProp.Value.Extensions["const"]; ok { if constVal, ok := actionProp.Value.Extensions["const"]; ok {
if constVal == action { if action, ok := constVal.(string); ok {
return true return action
} }
} }
// Check enum field // Check enum field (return first value)
if len(actionProp.Value.Enum) > 0 { if len(actionProp.Value.Enum) > 0 {
for _, e := range actionProp.Value.Enum { if action, ok := actionProp.Value.Enum[0].(string); ok {
if e == action { return action
return true
}
} }
} }
} }
@@ -329,28 +335,11 @@ func (v *schemav2Validator) checkActionEnum(contextSchema *openapi3.Schema, acti
// Check allOf in context // Check allOf in context
for _, allOfSchema := range contextSchema.AllOf { for _, allOfSchema := range contextSchema.AllOf {
if allOfSchema.Value != nil { if allOfSchema.Value != nil {
if actionProp := allOfSchema.Value.Properties["action"]; actionProp != nil && actionProp.Value != nil { if action := v.getActionValue(allOfSchema.Value); action != "" {
// Check const field (stored in Extensions by kin-openapi) return action
if constVal, ok := actionProp.Value.Extensions["const"]; ok {
if constVal == action {
return true
}
}
// Check enum field
if len(actionProp.Value.Enum) > 0 {
for _, e := range actionProp.Value.Enum {
if e == action {
return true
}
}
}
}
// Recursively check nested allOf
if v.checkActionEnum(allOfSchema.Value, action) {
return true
} }
} }
} }
return false return ""
} }