92 lines
2.4 KiB
Go
92 lines
2.4 KiB
Go
package validate
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/santhosh-tekuri/jsonschema/v6"
|
|
)
|
|
|
|
// These schema-bearing positions mirror the inherited subschema tables in
|
|
// jsonschema/v6 v6.0.2 draft.go. Annotation objects are deliberately excluded.
|
|
func checkSchemaLocations(ctx context.Context, compiler *jsonschema.Compiler, resourceURL string, document any, pointer string, compiled *jsonschema.Schema) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
object, ok := document.(map[string]any)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
version := compiled.DraftVersion
|
|
mapKeys := []string{"definitions", "properties", "patternProperties", "dependencies"}
|
|
singleKeys := []string{"not", "additionalProperties", "items", "additionalItems"}
|
|
arrayKeys := []string{"allOf", "anyOf", "oneOf", "items"}
|
|
if version >= 6 {
|
|
singleKeys = append(singleKeys, "propertyNames", "contains")
|
|
}
|
|
if version >= 7 {
|
|
singleKeys = append(singleKeys, "if", "then", "else")
|
|
}
|
|
if version >= 2019 {
|
|
mapKeys = append(mapKeys, "$defs", "dependentSchemas")
|
|
singleKeys = append(singleKeys, "unevaluatedProperties", "unevaluatedItems", "contentSchema")
|
|
}
|
|
if version >= 2020 {
|
|
arrayKeys = append(arrayKeys, "prefixItems")
|
|
}
|
|
visit := func(value any, segments ...string) error {
|
|
switch value.(type) {
|
|
case map[string]any, bool:
|
|
default:
|
|
return nil
|
|
}
|
|
childPointer := pointer
|
|
for _, segment := range segments {
|
|
childPointer += "/" + strings.ReplaceAll(strings.ReplaceAll(segment, "~", "~0"), "/", "~1")
|
|
}
|
|
parsed, err := url.Parse(resourceURL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
parsed.Fragment = childPointer
|
|
child, err := compileJSONSchema(ctx, parsed.String(), compiler.Compile)
|
|
if err != nil {
|
|
return fmt.Errorf("compile declared schema %q: %w", childPointer, err)
|
|
}
|
|
return checkSchemaLocations(ctx, compiler, resourceURL, value, childPointer, child)
|
|
}
|
|
for _, key := range mapKeys {
|
|
entries, ok := object[key].(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for name, value := range entries {
|
|
if err := visit(value, key, name); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
for _, key := range singleKeys {
|
|
if value, ok := object[key]; ok {
|
|
if err := visit(value, key); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
for _, key := range arrayKeys {
|
|
entries, ok := object[key].([]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for i, value := range entries {
|
|
if err := visit(value, key, strconv.Itoa(i)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return ctx.Err()
|
|
}
|