1 package jsonschema
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "reflect"
8 "strconv"
9 "strings"
10
11 jptr "github.com/qri-io/jsonpointer"
12 )
13
14
15 type Const json.RawMessage
16
17
18 func NewConst() Keyword {
19 return &Const{}
20 }
21
22
23 func (c *Const) Register(uri string, registry *SchemaRegistry) {}
24
25
26 func (c *Const) Resolve(pointer jptr.Pointer, uri string) *Schema {
27 return nil
28 }
29
30
31 func (c Const) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
32 schemaDebug("[Const] Validating")
33 var con interface{}
34 if err := json.Unmarshal(c, &con); err != nil {
35 currentState.AddError(data, err.Error())
36 return
37 }
38
39 if !reflect.DeepEqual(con, data) {
40 currentState.AddError(data, fmt.Sprintf(`must equal %s`, InvalidValueString(con)))
41 }
42 }
43
44
45 func (c Const) JSONProp(name string) interface{} {
46 return nil
47 }
48
49
50 func (c Const) String() string {
51 return string(c)
52 }
53
54
55 func (c *Const) UnmarshalJSON(data []byte) error {
56 *c = data
57 return nil
58 }
59
60
61 func (c Const) MarshalJSON() ([]byte, error) {
62 return json.Marshal(json.RawMessage(c))
63 }
64
65
66 type Enum []Const
67
68
69 func NewEnum() Keyword {
70 return &Enum{}
71 }
72
73
74 func (e *Enum) Register(uri string, registry *SchemaRegistry) {}
75
76
77 func (e *Enum) Resolve(pointer jptr.Pointer, uri string) *Schema {
78 return nil
79 }
80
81
82 func (e Enum) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
83 schemaDebug("[Enum] Validating")
84 subState := currentState.NewSubState()
85 subState.ClearState()
86 for _, v := range e {
87 subState.Errs = &[]KeyError{}
88 v.ValidateKeyword(ctx, subState, data)
89 if subState.IsValid() {
90 return
91 }
92 }
93
94 currentState.AddError(data, fmt.Sprintf("should be one of %s", e.String()))
95 }
96
97
98 func (e Enum) JSONProp(name string) interface{} {
99 idx, err := strconv.Atoi(name)
100 if err != nil {
101 return nil
102 }
103 if idx > len(e) || idx < 0 {
104 return nil
105 }
106 return e[idx]
107 }
108
109
110 func (e Enum) JSONChildren() (res map[string]JSONPather) {
111 res = map[string]JSONPather{}
112 for i, bs := range e {
113 res[strconv.Itoa(i)] = bs
114 }
115 return
116 }
117
118
119 func (e Enum) String() string {
120 str := "["
121 for _, c := range e {
122 str += c.String() + ", "
123 }
124 return str[:len(str)-2] + "]"
125 }
126
127
128 var primitiveTypes = map[string]bool{
129 "null": true,
130 "boolean": true,
131 "object": true,
132 "array": true,
133 "number": true,
134 "string": true,
135 "integer": true,
136 }
137
138
139
140 func DataType(data interface{}) string {
141 if data == nil {
142 return "null"
143 }
144
145 switch reflect.TypeOf(data).Kind() {
146 case reflect.Bool:
147 return "boolean"
148 case reflect.Float64:
149 number := reflect.ValueOf(data).Float()
150 if float64(int(number)) == number {
151 return "integer"
152 }
153 return "number"
154 case reflect.String:
155 return "string"
156 case reflect.Array, reflect.Slice:
157 return "array"
158 case reflect.Map, reflect.Struct:
159 return "object"
160 default:
161 return "unknown"
162 }
163 }
164
165
166
167 func DataTypeWithHint(data interface{}, hint string) string {
168 dt := DataType(data)
169 if dt == "string" {
170 if hint == "boolean" {
171 _, err := strconv.ParseBool(data.(string))
172 if err == nil {
173 return "boolean"
174 }
175 }
176 }
177
178 if dt == "integer" && hint == "number" {
179 return "number"
180 }
181 return dt
182 }
183
184
185 type Type struct {
186 strVal bool
187 vals []string
188 }
189
190
191 func NewType() Keyword {
192 return &Type{}
193 }
194
195
196 func (t *Type) Register(uri string, registry *SchemaRegistry) {}
197
198
199 func (t *Type) Resolve(pointer jptr.Pointer, uri string) *Schema {
200 return nil
201 }
202
203
204 func (t Type) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
205 schemaDebug("[Type] Validating")
206 jt := DataType(data)
207 for _, typestr := range t.vals {
208 if jt == typestr || jt == "integer" && typestr == "number" {
209 return
210 }
211 if jt == "string" && (typestr == "boolean" || typestr == "number" || typestr == "integer") {
212 if DataTypeWithHint(data, typestr) == typestr {
213 return
214 }
215 }
216 if jt == "null" && (typestr == "string") {
217 if DataTypeWithHint(data, typestr) == typestr {
218 return
219 }
220 }
221 }
222 if len(t.vals) == 1 {
223 currentState.AddError(data, fmt.Sprintf(`type should be %s, got %s`, t.vals[0], jt))
224 return
225 }
226
227 str := ""
228 for _, ts := range t.vals {
229 str += ts + ","
230 }
231
232 currentState.AddError(data, fmt.Sprintf(`type should be one of: %s, got %s`, str[:len(str)-1], jt))
233 }
234
235
236 func (t Type) String() string {
237 if len(t.vals) == 0 {
238 return "unknown"
239 }
240 return strings.Join(t.vals, ",")
241 }
242
243
244 func (t Type) JSONProp(name string) interface{} {
245 idx, err := strconv.Atoi(name)
246 if err != nil {
247 return nil
248 }
249 if idx > len(t.vals) || idx < 0 {
250 return nil
251 }
252 return t.vals[idx]
253 }
254
255
256 func (t *Type) UnmarshalJSON(data []byte) error {
257 var single string
258 if err := json.Unmarshal(data, &single); err == nil {
259 *t = Type{strVal: true, vals: []string{single}}
260 } else {
261 var set []string
262 if err := json.Unmarshal(data, &set); err == nil {
263 *t = Type{vals: set}
264 } else {
265 return err
266 }
267 }
268
269 for _, pr := range t.vals {
270 if !primitiveTypes[pr] {
271 return fmt.Errorf(`"%s" is not a valid type`, pr)
272 }
273 }
274 return nil
275 }
276
277
278 func (t Type) MarshalJSON() ([]byte, error) {
279 if t.strVal {
280 return json.Marshal(t.vals[0])
281 }
282 return json.Marshal(t.vals)
283 }
284
View as plain text