-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathschema.go
More file actions
99 lines (84 loc) · 3.5 KB
/
Copy pathschema.go
File metadata and controls
99 lines (84 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package chaff
import (
"fmt"
"regexp"
"strings"
"github.com/ryanolee/go-chaff/internal/util"
"github.com/santhosh-tekuri/jsonschema/v6"
jsonschemaV6 "github.com/santhosh-tekuri/jsonschema/v6"
)
type (
schemaManager struct {
rootSchemaCompiler *jsonschemaV6.Compiler
documentResolver *documentResolver
}
internalOnlyLoader struct {
}
)
var pathSanitiseRegex = regexp.MustCompile(`[\/\#\-\.\,\s\.]`)
// Create a new schema manager used to manage sub schema validators required for
// conditional validators where generated values must be validated against the original schema
// to ensure they conform to the original schema constraints
func newSchemaManager(resolver *documentResolver, schemaJson []byte) (*schemaManager, error) {
jsonSchemaCompiler := jsonschema.NewCompiler()
// To prevent external references from being inadvertently loaded or files from the local filesystem
// unless they are explicitly added to the schema manager by the upper level parser
jsonSchemaCompiler.UseLoader(internalOnlyLoader{})
if err := jsonSchemaCompiler.AddResource(resolver.GetCurrentScope(), util.UnmarshalJsonStringToMap(string(schemaJson))); err != nil {
return nil, err
}
return &schemaManager{
rootSchemaCompiler: jsonSchemaCompiler,
documentResolver: resolver,
}, nil
}
// Parses a schema node and creates a subdocument in the root schema compiler
// allowing for reference resolutions back into the original schema from any given sub-schema fragment
func (sm *schemaManager) ParseSchemaNode(parserMetadata *parserMetadata, node schemaNode, field string) (*jsonschemaV6.Schema, error) {
currentPath := sm.normalisePath(fmt.Sprintf("%s/%s", parserMetadata.ReferenceHandler.CurrentPath, field))
deepClonedNode := util.UnmarshalJsonStringToMap(util.MarshalJsonToString(node))
referenceUpdatedNode := sm.replaceRefs(deepClonedNode)
sm.rootSchemaCompiler.AddResource(sm.documentResolver.GetCurrentScope()+currentPath, referenceUpdatedNode)
return sm.CompilePath(currentPath)
}
// Replaces all slashes with underscores and removes any hashtags or punctuation that may interfere with
// the schema compiler's ability to parse the path given it is a "file name" technically
func (sm *schemaManager) normalisePath(path string) string {
return pathSanitiseRegex.ReplaceAllString(path, "_")
}
// Recursively replaces all $ref values in the schema with links back to the root schema
func (sm *schemaManager) replaceRefs(data interface{}) interface{} {
switch v := data.(type) {
case map[string]interface{}:
if ref, ok := v["$ref"]; ok {
if refStr, ok := ref.(string); ok {
if strings.Contains(refStr, "://") {
// External cross-document refs (full URLs) can't be resolved
// by the internal-only compiler. Strip them so the sub-schema
// becomes permissive for that part. The go-chaff parser handles
// cross-document resolution separately at the parse level.
delete(v, "$ref")
} else {
v["$ref"] = sm.documentResolver.GetCurrentScope() + refStr
}
}
}
for key, value := range v {
v[key] = sm.replaceRefs(value)
}
return v
case []interface{}:
for i, item := range v {
v[i] = sm.replaceRefs(item)
}
return v
default:
return v
}
}
func (sm *schemaManager) CompilePath(path string) (*jsonschemaV6.Schema, error) {
return sm.rootSchemaCompiler.Compile(sm.documentResolver.GetCurrentScope() + path)
}
func (l internalOnlyLoader) Load(url string) (any, error) {
return nil, fmt.Errorf("internal-only loader: external resource %q not available for schema validation", url)
}