1 package jsonschema
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/url"
8 "sort"
9 "strings"
10
11 jptr "github.com/qri-io/jsonpointer"
12 )
13
14
15
16 func Must(jsonString string) *Schema {
17 s := &Schema{}
18 if err := s.UnmarshalJSON([]byte(jsonString)); err != nil {
19 panic(err)
20 }
21 return s
22 }
23
24 type schemaType int
25
26 const (
27 schemaTypeObject schemaType = iota
28 schemaTypeFalse
29 schemaTypeTrue
30 )
31
32
33 type Schema struct {
34 schemaType schemaType
35 docPath string
36 hasRegistered bool
37
38 id string
39
40 extraDefinitions map[string]json.RawMessage
41 keywords map[string]Keyword
42 orderedkeywords []string
43 }
44
45
46 func NewSchema() Keyword {
47 return &Schema{}
48 }
49
50
51
52 func (s *Schema) HasKeyword(key string) bool {
53 _, ok := s.keywords[key]
54 return ok
55 }
56
57
58 func (s *Schema) Register(uri string, registry *SchemaRegistry) {
59 schemaDebug("[Schema] Register")
60 if s.hasRegistered {
61 return
62 }
63 s.hasRegistered = true
64 registry.RegisterLocal(s)
65
66
67 if !IsRegistryLoaded() {
68 LoadDraft2019_09()
69 }
70
71 address := s.id
72 if uri != "" && address != "" {
73 address, _ = SafeResolveURL(uri, address)
74 }
75 if s.docPath == "" && address != "" && address[0] != '#' {
76 docURI := ""
77 if u, err := url.Parse(address); err != nil {
78 docURI, _ = SafeResolveURL("https://qri.io", address)
79 } else {
80 docURI = u.String()
81 }
82 s.docPath = docURI
83 GetSchemaRegistry().Register(s)
84 uri = docURI
85 }
86
87 for _, keyword := range s.keywords {
88 keyword.Register(uri, registry)
89 }
90 }
91
92
93 func (s *Schema) Resolve(pointer jptr.Pointer, uri string) *Schema {
94 if pointer.IsEmpty() {
95 if s.docPath != "" {
96 s.docPath, _ = SafeResolveURL(uri, s.docPath)
97 } else {
98 s.docPath = uri
99 }
100 return s
101 }
102
103 current := pointer.Head()
104
105 if s.id != "" {
106 if u, err := url.Parse(s.id); err == nil {
107 if u.IsAbs() {
108 uri = s.id
109 } else {
110 uri, _ = SafeResolveURL(uri, s.id)
111 }
112 }
113 }
114
115 keyword := s.keywords[*current]
116 var keywordSchema *Schema
117 if keyword != nil {
118 keywordSchema = keyword.Resolve(pointer.Tail(), uri)
119 }
120
121 if keywordSchema != nil {
122 return keywordSchema
123 }
124
125 found, err := pointer.Eval(s.extraDefinitions)
126 if err != nil {
127 return nil
128 }
129 if found == nil {
130 return nil
131 }
132
133 if foundSchema, ok := found.(*Schema); ok {
134 return foundSchema
135 }
136
137 return nil
138 }
139
140
141 func (s Schema) JSONProp(name string) interface{} {
142 if keyword, ok := s.keywords[name]; ok {
143 return keyword
144 }
145 return s.extraDefinitions[name]
146 }
147
148
149 func (s Schema) JSONChildren() map[string]JSONPather {
150 ch := map[string]JSONPather{}
151
152 if s.keywords != nil {
153 for key, val := range s.keywords {
154 if jp, ok := val.(JSONPather); ok {
155 ch[key] = jp
156 }
157 }
158 }
159
160 return ch
161 }
162
163
164 type _schema struct {
165 ID string `json:"$id,omitempty"`
166 }
167
168
169 func (s *Schema) UnmarshalJSON(data []byte) error {
170 var b bool
171 if err := json.Unmarshal(data, &b); err == nil {
172 if b {
173
174 *s = Schema{schemaType: schemaTypeTrue}
175 return nil
176 }
177
178 *s = Schema{schemaType: schemaTypeFalse}
179 return nil
180 }
181
182 if !IsRegistryLoaded() {
183 LoadDraft2019_09()
184 }
185
186 _s := _schema{}
187 if err := json.Unmarshal(data, &_s); err != nil {
188 return err
189 }
190
191 sch := &Schema{
192 id: _s.ID,
193 keywords: map[string]Keyword{},
194 }
195
196 valprops := map[string]json.RawMessage{}
197 if err := json.Unmarshal(data, &valprops); err != nil {
198 return err
199 }
200
201 for prop, rawmsg := range valprops {
202 var keyword Keyword
203 if IsRegisteredKeyword(prop) {
204 keyword = GetKeyword(prop)
205 } else if IsNotSupportedKeyword(prop) {
206 schemaDebug(fmt.Sprintf("[Schema] WARN: '%s' is not supported and will be ignored\n", prop))
207 continue
208 } else {
209 if sch.extraDefinitions == nil {
210 sch.extraDefinitions = map[string]json.RawMessage{}
211 }
212 sch.extraDefinitions[prop] = rawmsg
213 continue
214 }
215 if _, ok := keyword.(*Void); !ok {
216 if err := json.Unmarshal(rawmsg, keyword); err != nil {
217 return fmt.Errorf("error unmarshaling %s from json: %s", prop, err.Error())
218 }
219 }
220 sch.keywords[prop] = keyword
221 }
222
223
224 keyOrders := make([]_keyOrder, len(sch.keywords))
225 i := 0
226 for k := range sch.keywords {
227 keyOrders[i] = _keyOrder{
228 Key: k,
229 Order: GetKeywordOrder(k),
230 }
231 i++
232 }
233 sort.SliceStable(keyOrders, func(i, j int) bool {
234 if keyOrders[i].Order == keyOrders[j].Order {
235 return GetKeywordInsertOrder(keyOrders[i].Key) < GetKeywordInsertOrder(keyOrders[j].Key)
236 }
237 return keyOrders[i].Order < keyOrders[j].Order
238 })
239 orderedKeys := make([]string, len(sch.keywords))
240 i = 0
241 for _, keyOrder := range keyOrders {
242 orderedKeys[i] = keyOrder.Key
243 i++
244 }
245 sch.orderedkeywords = orderedKeys
246
247 *s = Schema(*sch)
248 return nil
249 }
250
251
252 type _keyOrder struct {
253 Key string
254 Order int
255 }
256
257
258 func (s *Schema) Validate(ctx context.Context, data interface{}) *ValidationState {
259 currentState := NewValidationState(s)
260 s.ValidateKeyword(ctx, currentState, data)
261 return currentState
262 }
263
264
265
266 func (s *Schema) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
267 schemaDebug("[Schema] Validating")
268 if s == nil {
269 currentState.AddError(data, fmt.Sprintf("schema is nil"))
270 return
271 }
272 if s.schemaType == schemaTypeTrue {
273 return
274 }
275 if s.schemaType == schemaTypeFalse {
276 currentState.AddError(data, fmt.Sprintf("schema is always false"))
277 return
278 }
279
280 s.Register("", currentState.LocalRegistry)
281 currentState.LocalRegistry.RegisterLocal(s)
282
283 currentState.Local = s
284
285 refKeyword := s.keywords["$ref"]
286
287 if refKeyword == nil {
288 if currentState.BaseURI == "" {
289 currentState.BaseURI = s.docPath
290 } else if s.docPath != "" {
291 if u, err := url.Parse(s.docPath); err == nil {
292 if u.IsAbs() {
293 currentState.BaseURI = s.docPath
294 } else {
295 currentState.BaseURI, _ = SafeResolveURL(currentState.BaseURI, s.docPath)
296 }
297 }
298 }
299 }
300
301 if currentState.BaseURI != "" && strings.HasSuffix(currentState.BaseURI, "#") {
302 currentState.BaseURI = strings.TrimRight(currentState.BaseURI, "#")
303 }
304
305
306
307
308
309
310
311 s.validateSchemakeywords(ctx, currentState, data)
312 }
313
314
315 func (s *Schema) validateSchemakeywords(ctx context.Context, currentState *ValidationState, data interface{}) {
316 if s.keywords != nil {
317 for _, keyword := range s.orderedkeywords {
318 s.keywords[keyword].ValidateKeyword(ctx, currentState, data)
319 }
320 }
321 }
322
323
324
325 func (s *Schema) ValidateBytes(ctx context.Context, data []byte) ([]KeyError, error) {
326 var doc interface{}
327 if err := json.Unmarshal(data, &doc); err != nil {
328 return nil, fmt.Errorf("error parsing JSON bytes: %s", err.Error())
329 }
330 vs := s.Validate(ctx, doc)
331 return *vs.Errs, nil
332 }
333
334
335 func (s *Schema) TopLevelType() string {
336 if t, ok := s.keywords["type"].(*Type); ok {
337 return t.String()
338 }
339 return "unknown"
340 }
341
342
343 func (s Schema) MarshalJSON() ([]byte, error) {
344 switch s.schemaType {
345 case schemaTypeFalse:
346 return []byte("false"), nil
347 case schemaTypeTrue:
348 return []byte("true"), nil
349 default:
350 obj := map[string]interface{}{}
351
352 for k, v := range s.keywords {
353 obj[k] = v
354 }
355 for k, v := range s.extraDefinitions {
356 obj[k] = v
357 }
358 return json.Marshal(obj)
359 }
360 }
361
View as plain text