-
Notifications
You must be signed in to change notification settings - Fork 0
/
parse.go
250 lines (204 loc) · 6.88 KB
/
parse.go
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package chaff
import (
"encoding/json"
"os"
"regexp/syntax"
"github.com/ryanolee/go-chaff/internal/regen"
)
type (
// Options to take into account when parsing a json schema
ParserOptions struct {
// Options for the regex generator used for generating strings with the "pattern property"
RegexStringOptions *regen.GeneratorArgs
// Options for the regex generator used for pattern properties
RegexPatternPropertyOptions *regen.GeneratorArgs
}
// Struct containing metadata for parse operations within the JSON Schema
parserMetadata struct {
// Used to keep track of every referenceable route
ReferenceHandler *referenceHandler
ParserOptions ParserOptions
Errors map[string]error
// Generators that need to have their structures Re-Parsed once all references have been resolved
ReferenceResolver referenceResolver
RootNode schemaNode
}
schemaNode struct {
// Shared Properties
Type multipleType `json:"type"`
Length int `json:"length"` // Shared by String and Array
// Object Properties
Properties map[string]schemaNode `json:"properties"`
AdditionalProperties additionalData `json:"additionalProperties"`
PatternProperties map[string]schemaNode `json:"patternProperties"`
MinProperties int `json:"minProperties"`
MaxProperties int `json:"maxProperties"`
Required []string `json:"required"`
// String Properties
Pattern string `json:"pattern"`
Format string `json:"format"`
// Number Properties
Minimum float64 `json:"minimum"`
Maximum float64 `json:"maximum"`
ExclusiveMinimum float64 `json:"exclusiveMinimum"`
ExclusiveMaximum float64 `json:"exclusiveMaximum"`
MultipleOf float64 `json:"multipleOf"`
// Array Properties
Items itemsData `json:"items"`
MinItems int `json:"minItems"`
MaxItems int `json:"maxItems"`
Contains *schemaNode `json:"contains"`
MinContains int `json:"minContains"`
MaxContains int `json:"maxContains"`
PrefixItems []schemaNode `json:"prefixItems"`
AdditionalItems *schemaNode `json:"additionalItems"`
// Enum Properties
Enum []interface{} `json:"enum"`
// Constant Properties
Const interface{} `json:"const"`
// Combination Properties
// TODO: Implement these
//Not *SchemaNode `json:"not"`
AllOf []schemaNode `json:"allOf"`
AnyOf []schemaNode `json:"anyOf"`
OneOf []schemaNode `json:"oneOf"`
// Reference Operator
Ref string `json:"$ref"`
Id string `json:"$id"`
Defs map[string]schemaNode `json:"$defs"`
Definitions map[string]schemaNode `json:"definitions"`
}
)
const (
// Data Type Operations
typeObject = "object"
typeArray = "array"
typeNumber = "number"
typeInteger = "integer"
typeString = "string"
typeBoolean = "boolean"
typeNull = "null"
)
// Parses a Json Schema file at the given path. If there is an error reading the file or
// parsing the schema, an error will be returned
func ParseSchemaFile(path string, opts *ParserOptions) (RootGenerator, error) {
data, err := os.ReadFile(path)
if err != nil {
return RootGenerator{
Generator: nullGenerator{},
}, err
}
return ParseSchema(data, opts)
}
// Parses a Json Schema file at the given path with default options. If there is an error reading the file or
// parsing the schema, an error will be returned
func ParseSchemaFileWithDefaults(path string) (RootGenerator, error) {
return ParseSchemaFile(path, &ParserOptions{})
}
// Parses a Json Schema string. If there is an error parsing the schema, an error will be returned.
func ParseSchemaString(schema string, opts *ParserOptions) (RootGenerator, error) {
return ParseSchema([]byte(schema), opts)
}
func ParseSchemaStringWithDefaults(schema string) (RootGenerator, error) {
return ParseSchemaString(schema, &ParserOptions{})
}
// Parses a Json Schema byte array. If there is an error parsing the schema, an error will be returned.
func ParseSchema(schema []byte, opts *ParserOptions) (RootGenerator, error) {
var node schemaNode
err := json.Unmarshal(schema, &node)
if err != nil {
return RootGenerator{
Generator: nullGenerator{},
}, err
}
refHandler := newReferenceHandler()
metadata := &parserMetadata{
ReferenceHandler: &refHandler,
Errors: make(map[string]error),
ParserOptions: withDefaultParseOptions(*opts),
RootNode: node,
}
generator, err := parseRoot(node, metadata)
return generator, err
}
// Parses a Json Schema byte array with default options. If there is an error parsing the schema, an error will be returned.
func ParseSchemaWithDefaults(schema []byte) (RootGenerator, error) {
return ParseSchema(schema, &ParserOptions{})
}
func parseNode(node schemaNode, metadata *parserMetadata) (Generator, error) {
refHandler := metadata.ReferenceHandler
gen, err := parseSchemaNode(node, metadata)
if err != nil {
metadata.Errors[refHandler.CurrentPath] = err
}
if node.Id != "" {
refHandler.AddIdReference(node.Id, node, gen)
}
refHandler.AddReference(node, gen)
return gen, err
}
func parseSchemaNode(node schemaNode, metadata *parserMetadata) (Generator, error) {
// Handle reference nodes
if node.Ref != "" {
return parseReference(node, metadata)
}
if node.AllOf != nil {
return parseAllOf(node, metadata)
}
// Handle combination nodes
if node.OneOf != nil || node.AnyOf != nil {
return parseCombination(node, metadata)
}
// Handle enum nodes
if len(node.Enum) != 0 {
return parseEnum(node)
}
// Handle constant nodes
if node.Const != nil {
return parseConst(node)
}
// Handle multiple type nodes
if node.Type.MultipleTypes != nil {
return parseMultipleType(node, metadata)
}
return parseType(node.Type.SingleType, node, metadata)
}
func parseType(nodeType string, node schemaNode, metadata *parserMetadata) (Generator, error) {
// Handle object nodes
switch nodeType {
case typeObject:
return parseObject(node, metadata)
case typeArray:
return parseArray(node, metadata)
case typeNumber:
return parseNumber(node, generatorTypeNumber)
case typeInteger:
return parseNumber(node, generatorTypeInteger)
case typeString:
return parseString(node, metadata)
case typeBoolean:
return parseBoolean(node)
case typeNull:
return parseNull(node)
default:
return nullGenerator{}, nil
}
}
func withDefaultParseOptions(opts ParserOptions) ParserOptions {
parseOpts := ParserOptions{
RegexStringOptions: opts.RegexStringOptions,
RegexPatternPropertyOptions: opts.RegexPatternPropertyOptions,
}
defaultRegexOpts := ®en.GeneratorArgs{
MaxUnboundedRepeatCount: 10,
SuppressRandomBytes: true,
Flags: syntax.PerlX,
}
if opts.RegexStringOptions == nil {
parseOpts.RegexStringOptions = defaultRegexOpts
}
if opts.RegexPatternPropertyOptions == nil {
parseOpts.RegexPatternPropertyOptions = defaultRegexOpts
}
return parseOpts
}