| // Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // author xeipuuv |
| // author-github https://github.com/xeipuuv |
| // author-mail xeipuuv@gmail.com |
| // |
| // repository-name gojsonschema |
| // repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. |
| // |
| // description Defines Schema, the main entry to every subSchema. |
| // Contains the parsing logic and error checking. |
| // |
| // created 26-02-2013 |
| |
| package gojsonschema |
| |
| import ( |
| "errors" |
| "math/big" |
| "reflect" |
| "regexp" |
| "text/template" |
| |
| "github.com/xeipuuv/gojsonreference" |
| ) |
| |
| var ( |
| // Locale is the default locale to use |
| // Library users can overwrite with their own implementation |
| Locale locale = DefaultLocale{} |
| |
| // ErrorTemplateFuncs allows you to define custom template funcs for use in localization. |
| ErrorTemplateFuncs template.FuncMap |
| ) |
| |
| func NewSchema(l JSONLoader) (*Schema, error) { |
| return NewSchemaLoader().Compile(l) |
| } |
| |
| type Schema struct { |
| documentReference gojsonreference.JsonReference |
| rootSchema *subSchema |
| pool *schemaPool |
| referencePool *schemaReferencePool |
| } |
| |
| func (d *Schema) parse(document interface{}, draft Draft) error { |
| d.rootSchema = &subSchema{property: STRING_ROOT_SCHEMA_PROPERTY, draft: &draft} |
| return d.parseSchema(document, d.rootSchema) |
| } |
| |
| func (d *Schema) SetRootSchemaName(name string) { |
| d.rootSchema.property = name |
| } |
| |
| // Parses a subSchema |
| // |
| // Pretty long function ( sorry :) )... but pretty straight forward, repetitive and boring |
| // Not much magic involved here, most of the job is to validate the key names and their values, |
| // then the values are copied into subSchema struct |
| // |
| func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) error { |
| |
| if currentSchema.draft == nil { |
| if currentSchema.parent == nil { |
| return errors.New("Draft not set") |
| } |
| currentSchema.draft = currentSchema.parent.draft |
| } |
| |
| // As of draft 6 "true" is equivalent to an empty schema "{}" and false equals "{"not":{}}" |
| if *currentSchema.draft >= Draft6 && isKind(documentNode, reflect.Bool) { |
| b := documentNode.(bool) |
| if b { |
| documentNode = map[string]interface{}{} |
| } else { |
| documentNode = map[string]interface{}{"not": true} |
| } |
| } |
| |
| if !isKind(documentNode, reflect.Map) { |
| return errors.New(formatErrorDescription( |
| Locale.ParseError(), |
| ErrorDetails{ |
| "expected": STRING_SCHEMA, |
| }, |
| )) |
| } |
| |
| m := documentNode.(map[string]interface{}) |
| |
| if currentSchema.parent == nil { |
| currentSchema.ref = &d.documentReference |
| currentSchema.id = &d.documentReference |
| } |
| |
| if currentSchema.id == nil && currentSchema.parent != nil { |
| currentSchema.id = currentSchema.parent.id |
| } |
| |
| // In draft 6 the id keyword was renamed to $id |
| // Hybrid mode uses the old id by default |
| var keyID string |
| |
| switch *currentSchema.draft { |
| case Draft4: |
| keyID = KEY_ID |
| case Hybrid: |
| keyID = KEY_ID_NEW |
| if existsMapKey(m, KEY_ID) { |
| keyID = KEY_ID |
| } |
| default: |
| keyID = KEY_ID_NEW |
| } |
| if existsMapKey(m, keyID) && !isKind(m[keyID], reflect.String) { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_STRING, |
| "given": keyID, |
| }, |
| )) |
| } |
| if k, ok := m[keyID].(string); ok { |
| jsonReference, err := gojsonreference.NewJsonReference(k) |
| if err != nil { |
| return err |
| } |
| if currentSchema == d.rootSchema { |
| currentSchema.id = &jsonReference |
| } else { |
| ref, err := currentSchema.parent.id.Inherits(jsonReference) |
| if err != nil { |
| return err |
| } |
| currentSchema.id = ref |
| } |
| } |
| |
| // definitions |
| if existsMapKey(m, KEY_DEFINITIONS) { |
| if isKind(m[KEY_DEFINITIONS], reflect.Map, reflect.Bool) { |
| for _, dv := range m[KEY_DEFINITIONS].(map[string]interface{}) { |
| if isKind(dv, reflect.Map, reflect.Bool) { |
| |
| newSchema := &subSchema{property: KEY_DEFINITIONS, parent: currentSchema} |
| |
| err := d.parseSchema(dv, newSchema) |
| |
| if err != nil { |
| return err |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": STRING_ARRAY_OF_SCHEMAS, |
| "given": KEY_DEFINITIONS, |
| }, |
| )) |
| } |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": STRING_ARRAY_OF_SCHEMAS, |
| "given": KEY_DEFINITIONS, |
| }, |
| )) |
| } |
| |
| } |
| |
| // title |
| if existsMapKey(m, KEY_TITLE) && !isKind(m[KEY_TITLE], reflect.String) { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_STRING, |
| "given": KEY_TITLE, |
| }, |
| )) |
| } |
| if k, ok := m[KEY_TITLE].(string); ok { |
| currentSchema.title = &k |
| } |
| |
| // description |
| if existsMapKey(m, KEY_DESCRIPTION) && !isKind(m[KEY_DESCRIPTION], reflect.String) { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_STRING, |
| "given": KEY_DESCRIPTION, |
| }, |
| )) |
| } |
| if k, ok := m[KEY_DESCRIPTION].(string); ok { |
| currentSchema.description = &k |
| } |
| |
| // $ref |
| if existsMapKey(m, KEY_REF) && !isKind(m[KEY_REF], reflect.String) { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_STRING, |
| "given": KEY_REF, |
| }, |
| )) |
| } |
| |
| if k, ok := m[KEY_REF].(string); ok { |
| |
| jsonReference, err := gojsonreference.NewJsonReference(k) |
| if err != nil { |
| return err |
| } |
| |
| currentSchema.ref = &jsonReference |
| |
| if sch, ok := d.referencePool.Get(currentSchema.ref.String()); ok { |
| currentSchema.refSchema = sch |
| } else { |
| err := d.parseReference(documentNode, currentSchema) |
| |
| if err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| } |
| |
| // type |
| if existsMapKey(m, KEY_TYPE) { |
| if isKind(m[KEY_TYPE], reflect.String) { |
| if k, ok := m[KEY_TYPE].(string); ok { |
| err := currentSchema.types.Add(k) |
| if err != nil { |
| return err |
| } |
| } |
| } else { |
| if isKind(m[KEY_TYPE], reflect.Slice) { |
| arrayOfTypes := m[KEY_TYPE].([]interface{}) |
| for _, typeInArray := range arrayOfTypes { |
| if reflect.ValueOf(typeInArray).Kind() != reflect.String { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_STRING + "/" + STRING_ARRAY_OF_STRINGS, |
| "given": KEY_TYPE, |
| }, |
| )) |
| } else { |
| currentSchema.types.Add(typeInArray.(string)) |
| } |
| } |
| |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_STRING + "/" + STRING_ARRAY_OF_STRINGS, |
| "given": KEY_TYPE, |
| }, |
| )) |
| } |
| } |
| } |
| |
| // properties |
| if existsMapKey(m, KEY_PROPERTIES) { |
| err := d.parseProperties(m[KEY_PROPERTIES], currentSchema) |
| if err != nil { |
| return err |
| } |
| } |
| |
| // additionalProperties |
| if existsMapKey(m, KEY_ADDITIONAL_PROPERTIES) { |
| if isKind(m[KEY_ADDITIONAL_PROPERTIES], reflect.Bool) { |
| currentSchema.additionalProperties = m[KEY_ADDITIONAL_PROPERTIES].(bool) |
| } else if isKind(m[KEY_ADDITIONAL_PROPERTIES], reflect.Map) { |
| newSchema := &subSchema{property: KEY_ADDITIONAL_PROPERTIES, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.additionalProperties = newSchema |
| err := d.parseSchema(m[KEY_ADDITIONAL_PROPERTIES], newSchema) |
| if err != nil { |
| return errors.New(err.Error()) |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_BOOLEAN + "/" + STRING_SCHEMA, |
| "given": KEY_ADDITIONAL_PROPERTIES, |
| }, |
| )) |
| } |
| } |
| |
| // patternProperties |
| if existsMapKey(m, KEY_PATTERN_PROPERTIES) { |
| if isKind(m[KEY_PATTERN_PROPERTIES], reflect.Map) { |
| patternPropertiesMap := m[KEY_PATTERN_PROPERTIES].(map[string]interface{}) |
| if len(patternPropertiesMap) > 0 { |
| currentSchema.patternProperties = make(map[string]*subSchema) |
| for k, v := range patternPropertiesMap { |
| _, err := regexp.MatchString(k, "") |
| if err != nil { |
| return errors.New(formatErrorDescription( |
| Locale.RegexPattern(), |
| ErrorDetails{"pattern": k}, |
| )) |
| } |
| newSchema := &subSchema{property: k, parent: currentSchema, ref: currentSchema.ref} |
| err = d.parseSchema(v, newSchema) |
| if err != nil { |
| return errors.New(err.Error()) |
| } |
| currentSchema.patternProperties[k] = newSchema |
| } |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": STRING_SCHEMA, |
| "given": KEY_PATTERN_PROPERTIES, |
| }, |
| )) |
| } |
| } |
| |
| // propertyNames |
| if existsMapKey(m, KEY_PROPERTY_NAMES) && *currentSchema.draft >= Draft6 { |
| if isKind(m[KEY_PROPERTY_NAMES], reflect.Map, reflect.Bool) { |
| newSchema := &subSchema{property: KEY_PROPERTY_NAMES, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.propertyNames = newSchema |
| err := d.parseSchema(m[KEY_PROPERTY_NAMES], newSchema) |
| if err != nil { |
| return err |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": STRING_SCHEMA, |
| "given": KEY_PATTERN_PROPERTIES, |
| }, |
| )) |
| } |
| } |
| |
| // dependencies |
| if existsMapKey(m, KEY_DEPENDENCIES) { |
| err := d.parseDependencies(m[KEY_DEPENDENCIES], currentSchema) |
| if err != nil { |
| return err |
| } |
| } |
| |
| // items |
| if existsMapKey(m, KEY_ITEMS) { |
| if isKind(m[KEY_ITEMS], reflect.Slice) { |
| for _, itemElement := range m[KEY_ITEMS].([]interface{}) { |
| if isKind(itemElement, reflect.Map, reflect.Bool) { |
| newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS} |
| newSchema.ref = currentSchema.ref |
| currentSchema.AddItemsChild(newSchema) |
| err := d.parseSchema(itemElement, newSchema) |
| if err != nil { |
| return err |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": STRING_SCHEMA + "/" + STRING_ARRAY_OF_SCHEMAS, |
| "given": KEY_ITEMS, |
| }, |
| )) |
| } |
| currentSchema.itemsChildrenIsSingleSchema = false |
| } |
| } else if isKind(m[KEY_ITEMS], reflect.Map, reflect.Bool) { |
| newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS} |
| newSchema.ref = currentSchema.ref |
| currentSchema.AddItemsChild(newSchema) |
| err := d.parseSchema(m[KEY_ITEMS], newSchema) |
| if err != nil { |
| return err |
| } |
| currentSchema.itemsChildrenIsSingleSchema = true |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": STRING_SCHEMA + "/" + STRING_ARRAY_OF_SCHEMAS, |
| "given": KEY_ITEMS, |
| }, |
| )) |
| } |
| } |
| |
| // additionalItems |
| if existsMapKey(m, KEY_ADDITIONAL_ITEMS) { |
| if isKind(m[KEY_ADDITIONAL_ITEMS], reflect.Bool) { |
| currentSchema.additionalItems = m[KEY_ADDITIONAL_ITEMS].(bool) |
| } else if isKind(m[KEY_ADDITIONAL_ITEMS], reflect.Map) { |
| newSchema := &subSchema{property: KEY_ADDITIONAL_ITEMS, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.additionalItems = newSchema |
| err := d.parseSchema(m[KEY_ADDITIONAL_ITEMS], newSchema) |
| if err != nil { |
| return errors.New(err.Error()) |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_BOOLEAN + "/" + STRING_SCHEMA, |
| "given": KEY_ADDITIONAL_ITEMS, |
| }, |
| )) |
| } |
| } |
| |
| // validation : number / integer |
| |
| if existsMapKey(m, KEY_MULTIPLE_OF) { |
| multipleOfValue := mustBeNumber(m[KEY_MULTIPLE_OF]) |
| if multipleOfValue == nil { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": STRING_NUMBER, |
| "given": KEY_MULTIPLE_OF, |
| }, |
| )) |
| } |
| if multipleOfValue.Cmp(big.NewRat(0, 1)) <= 0 { |
| return errors.New(formatErrorDescription( |
| Locale.GreaterThanZero(), |
| ErrorDetails{"number": KEY_MULTIPLE_OF}, |
| )) |
| } |
| currentSchema.multipleOf = multipleOfValue |
| } |
| |
| if existsMapKey(m, KEY_MINIMUM) { |
| minimumValue := mustBeNumber(m[KEY_MINIMUM]) |
| if minimumValue == nil { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfA(), |
| ErrorDetails{"x": KEY_MINIMUM, "y": STRING_NUMBER}, |
| )) |
| } |
| currentSchema.minimum = minimumValue |
| } |
| |
| if existsMapKey(m, KEY_EXCLUSIVE_MINIMUM) { |
| switch *currentSchema.draft { |
| case Draft4: |
| if !isKind(m[KEY_EXCLUSIVE_MINIMUM], reflect.Bool) { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_BOOLEAN, |
| "given": KEY_EXCLUSIVE_MINIMUM, |
| }, |
| )) |
| } |
| if currentSchema.minimum == nil { |
| return errors.New(formatErrorDescription( |
| Locale.CannotBeUsedWithout(), |
| ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": KEY_MINIMUM}, |
| )) |
| } |
| if m[KEY_EXCLUSIVE_MINIMUM].(bool) { |
| currentSchema.exclusiveMinimum = currentSchema.minimum |
| currentSchema.minimum = nil |
| } |
| case Hybrid: |
| if isKind(m[KEY_EXCLUSIVE_MINIMUM], reflect.Bool) { |
| if currentSchema.minimum == nil { |
| return errors.New(formatErrorDescription( |
| Locale.CannotBeUsedWithout(), |
| ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": KEY_MINIMUM}, |
| )) |
| } |
| if m[KEY_EXCLUSIVE_MINIMUM].(bool) { |
| currentSchema.exclusiveMinimum = currentSchema.minimum |
| currentSchema.minimum = nil |
| } |
| } else if isJsonNumber(m[KEY_EXCLUSIVE_MINIMUM]) { |
| currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM]) |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_BOOLEAN + "/" + TYPE_NUMBER, |
| "given": KEY_EXCLUSIVE_MINIMUM, |
| }, |
| )) |
| } |
| default: |
| if isJsonNumber(m[KEY_EXCLUSIVE_MINIMUM]) { |
| currentSchema.exclusiveMinimum = mustBeNumber(m[KEY_EXCLUSIVE_MINIMUM]) |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_NUMBER, |
| "given": KEY_EXCLUSIVE_MINIMUM, |
| }, |
| )) |
| } |
| } |
| } |
| |
| if existsMapKey(m, KEY_MAXIMUM) { |
| maximumValue := mustBeNumber(m[KEY_MAXIMUM]) |
| if maximumValue == nil { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfA(), |
| ErrorDetails{"x": KEY_MAXIMUM, "y": STRING_NUMBER}, |
| )) |
| } |
| currentSchema.maximum = maximumValue |
| } |
| |
| if existsMapKey(m, KEY_EXCLUSIVE_MAXIMUM) { |
| switch *currentSchema.draft { |
| case Draft4: |
| if !isKind(m[KEY_EXCLUSIVE_MAXIMUM], reflect.Bool) { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_BOOLEAN, |
| "given": KEY_EXCLUSIVE_MAXIMUM, |
| }, |
| )) |
| } |
| if currentSchema.maximum == nil { |
| return errors.New(formatErrorDescription( |
| Locale.CannotBeUsedWithout(), |
| ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": KEY_MAXIMUM}, |
| )) |
| } |
| if m[KEY_EXCLUSIVE_MAXIMUM].(bool) { |
| currentSchema.exclusiveMaximum = currentSchema.maximum |
| currentSchema.maximum = nil |
| } |
| case Hybrid: |
| if isKind(m[KEY_EXCLUSIVE_MAXIMUM], reflect.Bool) { |
| if currentSchema.maximum == nil { |
| return errors.New(formatErrorDescription( |
| Locale.CannotBeUsedWithout(), |
| ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": KEY_MAXIMUM}, |
| )) |
| } |
| if m[KEY_EXCLUSIVE_MAXIMUM].(bool) { |
| currentSchema.exclusiveMaximum = currentSchema.maximum |
| currentSchema.maximum = nil |
| } |
| } else if isJsonNumber(m[KEY_EXCLUSIVE_MAXIMUM]) { |
| currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM]) |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_BOOLEAN + "/" + TYPE_NUMBER, |
| "given": KEY_EXCLUSIVE_MAXIMUM, |
| }, |
| )) |
| } |
| default: |
| if isJsonNumber(m[KEY_EXCLUSIVE_MAXIMUM]) { |
| currentSchema.exclusiveMaximum = mustBeNumber(m[KEY_EXCLUSIVE_MAXIMUM]) |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.InvalidType(), |
| ErrorDetails{ |
| "expected": TYPE_NUMBER, |
| "given": KEY_EXCLUSIVE_MAXIMUM, |
| }, |
| )) |
| } |
| } |
| } |
| |
| // validation : string |
| |
| if existsMapKey(m, KEY_MIN_LENGTH) { |
| minLengthIntegerValue := mustBeInteger(m[KEY_MIN_LENGTH]) |
| if minLengthIntegerValue == nil { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_MIN_LENGTH, "y": TYPE_INTEGER}, |
| )) |
| } |
| if *minLengthIntegerValue < 0 { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeGTEZero(), |
| ErrorDetails{"key": KEY_MIN_LENGTH}, |
| )) |
| } |
| currentSchema.minLength = minLengthIntegerValue |
| } |
| |
| if existsMapKey(m, KEY_MAX_LENGTH) { |
| maxLengthIntegerValue := mustBeInteger(m[KEY_MAX_LENGTH]) |
| if maxLengthIntegerValue == nil { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_MAX_LENGTH, "y": TYPE_INTEGER}, |
| )) |
| } |
| if *maxLengthIntegerValue < 0 { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeGTEZero(), |
| ErrorDetails{"key": KEY_MAX_LENGTH}, |
| )) |
| } |
| currentSchema.maxLength = maxLengthIntegerValue |
| } |
| |
| if currentSchema.minLength != nil && currentSchema.maxLength != nil { |
| if *currentSchema.minLength > *currentSchema.maxLength { |
| return errors.New(formatErrorDescription( |
| Locale.CannotBeGT(), |
| ErrorDetails{"x": KEY_MIN_LENGTH, "y": KEY_MAX_LENGTH}, |
| )) |
| } |
| } |
| |
| if existsMapKey(m, KEY_PATTERN) { |
| if isKind(m[KEY_PATTERN], reflect.String) { |
| regexpObject, err := regexp.Compile(m[KEY_PATTERN].(string)) |
| if err != nil { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeValidRegex(), |
| ErrorDetails{"key": KEY_PATTERN}, |
| )) |
| } |
| currentSchema.pattern = regexpObject |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfA(), |
| ErrorDetails{"x": KEY_PATTERN, "y": TYPE_STRING}, |
| )) |
| } |
| } |
| |
| if existsMapKey(m, KEY_FORMAT) { |
| formatString, ok := m[KEY_FORMAT].(string) |
| if ok && FormatCheckers.Has(formatString) { |
| currentSchema.format = formatString |
| } |
| } |
| |
| // validation : object |
| |
| if existsMapKey(m, KEY_MIN_PROPERTIES) { |
| minPropertiesIntegerValue := mustBeInteger(m[KEY_MIN_PROPERTIES]) |
| if minPropertiesIntegerValue == nil { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_MIN_PROPERTIES, "y": TYPE_INTEGER}, |
| )) |
| } |
| if *minPropertiesIntegerValue < 0 { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeGTEZero(), |
| ErrorDetails{"key": KEY_MIN_PROPERTIES}, |
| )) |
| } |
| currentSchema.minProperties = minPropertiesIntegerValue |
| } |
| |
| if existsMapKey(m, KEY_MAX_PROPERTIES) { |
| maxPropertiesIntegerValue := mustBeInteger(m[KEY_MAX_PROPERTIES]) |
| if maxPropertiesIntegerValue == nil { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_MAX_PROPERTIES, "y": TYPE_INTEGER}, |
| )) |
| } |
| if *maxPropertiesIntegerValue < 0 { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeGTEZero(), |
| ErrorDetails{"key": KEY_MAX_PROPERTIES}, |
| )) |
| } |
| currentSchema.maxProperties = maxPropertiesIntegerValue |
| } |
| |
| if currentSchema.minProperties != nil && currentSchema.maxProperties != nil { |
| if *currentSchema.minProperties > *currentSchema.maxProperties { |
| return errors.New(formatErrorDescription( |
| Locale.KeyCannotBeGreaterThan(), |
| ErrorDetails{"key": KEY_MIN_PROPERTIES, "y": KEY_MAX_PROPERTIES}, |
| )) |
| } |
| } |
| |
| if existsMapKey(m, KEY_REQUIRED) { |
| if isKind(m[KEY_REQUIRED], reflect.Slice) { |
| requiredValues := m[KEY_REQUIRED].([]interface{}) |
| for _, requiredValue := range requiredValues { |
| if isKind(requiredValue, reflect.String) { |
| err := currentSchema.AddRequired(requiredValue.(string)) |
| if err != nil { |
| return err |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.KeyItemsMustBeOfType(), |
| ErrorDetails{"key": KEY_REQUIRED, "type": TYPE_STRING}, |
| )) |
| } |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_REQUIRED, "y": TYPE_ARRAY}, |
| )) |
| } |
| } |
| |
| // validation : array |
| |
| if existsMapKey(m, KEY_MIN_ITEMS) { |
| minItemsIntegerValue := mustBeInteger(m[KEY_MIN_ITEMS]) |
| if minItemsIntegerValue == nil { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_MIN_ITEMS, "y": TYPE_INTEGER}, |
| )) |
| } |
| if *minItemsIntegerValue < 0 { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeGTEZero(), |
| ErrorDetails{"key": KEY_MIN_ITEMS}, |
| )) |
| } |
| currentSchema.minItems = minItemsIntegerValue |
| } |
| |
| if existsMapKey(m, KEY_MAX_ITEMS) { |
| maxItemsIntegerValue := mustBeInteger(m[KEY_MAX_ITEMS]) |
| if maxItemsIntegerValue == nil { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_MAX_ITEMS, "y": TYPE_INTEGER}, |
| )) |
| } |
| if *maxItemsIntegerValue < 0 { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeGTEZero(), |
| ErrorDetails{"key": KEY_MAX_ITEMS}, |
| )) |
| } |
| currentSchema.maxItems = maxItemsIntegerValue |
| } |
| |
| if existsMapKey(m, KEY_UNIQUE_ITEMS) { |
| if isKind(m[KEY_UNIQUE_ITEMS], reflect.Bool) { |
| currentSchema.uniqueItems = m[KEY_UNIQUE_ITEMS].(bool) |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfA(), |
| ErrorDetails{"x": KEY_UNIQUE_ITEMS, "y": TYPE_BOOLEAN}, |
| )) |
| } |
| } |
| |
| if existsMapKey(m, KEY_CONTAINS) && *currentSchema.draft >= Draft6 { |
| newSchema := &subSchema{property: KEY_CONTAINS, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.contains = newSchema |
| err := d.parseSchema(m[KEY_CONTAINS], newSchema) |
| if err != nil { |
| return err |
| } |
| } |
| |
| // validation : all |
| |
| if existsMapKey(m, KEY_CONST) && *currentSchema.draft >= Draft6 { |
| err := currentSchema.AddConst(m[KEY_CONST]) |
| if err != nil { |
| return err |
| } |
| } |
| |
| if existsMapKey(m, KEY_ENUM) { |
| if isKind(m[KEY_ENUM], reflect.Slice) { |
| for _, v := range m[KEY_ENUM].([]interface{}) { |
| err := currentSchema.AddEnum(v) |
| if err != nil { |
| return err |
| } |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_ENUM, "y": TYPE_ARRAY}, |
| )) |
| } |
| } |
| |
| // validation : subSchema |
| |
| if existsMapKey(m, KEY_ONE_OF) { |
| if isKind(m[KEY_ONE_OF], reflect.Slice) { |
| for _, v := range m[KEY_ONE_OF].([]interface{}) { |
| newSchema := &subSchema{property: KEY_ONE_OF, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.AddOneOf(newSchema) |
| err := d.parseSchema(v, newSchema) |
| if err != nil { |
| return err |
| } |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_ONE_OF, "y": TYPE_ARRAY}, |
| )) |
| } |
| } |
| |
| if existsMapKey(m, KEY_ANY_OF) { |
| if isKind(m[KEY_ANY_OF], reflect.Slice) { |
| for _, v := range m[KEY_ANY_OF].([]interface{}) { |
| newSchema := &subSchema{property: KEY_ANY_OF, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.AddAnyOf(newSchema) |
| err := d.parseSchema(v, newSchema) |
| if err != nil { |
| return err |
| } |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_ANY_OF, "y": TYPE_ARRAY}, |
| )) |
| } |
| } |
| |
| if existsMapKey(m, KEY_ALL_OF) { |
| if isKind(m[KEY_ALL_OF], reflect.Slice) { |
| for _, v := range m[KEY_ALL_OF].([]interface{}) { |
| newSchema := &subSchema{property: KEY_ALL_OF, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.AddAllOf(newSchema) |
| err := d.parseSchema(v, newSchema) |
| if err != nil { |
| return err |
| } |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_ANY_OF, "y": TYPE_ARRAY}, |
| )) |
| } |
| } |
| |
| if existsMapKey(m, KEY_NOT) { |
| if isKind(m[KEY_NOT], reflect.Map, reflect.Bool) { |
| newSchema := &subSchema{property: KEY_NOT, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.SetNot(newSchema) |
| err := d.parseSchema(m[KEY_NOT], newSchema) |
| if err != nil { |
| return err |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_NOT, "y": TYPE_OBJECT}, |
| )) |
| } |
| } |
| |
| if *currentSchema.draft >= Draft7 { |
| if existsMapKey(m, KEY_IF) { |
| if isKind(m[KEY_IF], reflect.Map, reflect.Bool) { |
| newSchema := &subSchema{property: KEY_IF, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.SetIf(newSchema) |
| err := d.parseSchema(m[KEY_IF], newSchema) |
| if err != nil { |
| return err |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_IF, "y": TYPE_OBJECT}, |
| )) |
| } |
| } |
| |
| if existsMapKey(m, KEY_THEN) { |
| if isKind(m[KEY_THEN], reflect.Map, reflect.Bool) { |
| newSchema := &subSchema{property: KEY_THEN, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.SetThen(newSchema) |
| err := d.parseSchema(m[KEY_THEN], newSchema) |
| if err != nil { |
| return err |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_THEN, "y": TYPE_OBJECT}, |
| )) |
| } |
| } |
| |
| if existsMapKey(m, KEY_ELSE) { |
| if isKind(m[KEY_ELSE], reflect.Map, reflect.Bool) { |
| newSchema := &subSchema{property: KEY_ELSE, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.SetElse(newSchema) |
| err := d.parseSchema(m[KEY_ELSE], newSchema) |
| if err != nil { |
| return err |
| } |
| } else { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfAn(), |
| ErrorDetails{"x": KEY_ELSE, "y": TYPE_OBJECT}, |
| )) |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSchema) error { |
| var ( |
| refdDocumentNode interface{} |
| dsp *schemaPoolDocument |
| err error |
| ) |
| |
| newSchema := &subSchema{property: KEY_REF, parent: currentSchema, ref: currentSchema.ref} |
| |
| d.referencePool.Add(currentSchema.ref.String(), newSchema) |
| |
| dsp, err = d.pool.GetDocument(*currentSchema.ref) |
| if err != nil { |
| return err |
| } |
| newSchema.id = currentSchema.ref |
| |
| refdDocumentNode = dsp.Document |
| newSchema.draft = dsp.Draft |
| |
| if err != nil { |
| return err |
| } |
| |
| if !isKind(refdDocumentNode, reflect.Map, reflect.Bool) { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfType(), |
| ErrorDetails{"key": STRING_SCHEMA, "type": TYPE_OBJECT}, |
| )) |
| } |
| |
| err = d.parseSchema(refdDocumentNode, newSchema) |
| if err != nil { |
| return err |
| } |
| |
| currentSchema.refSchema = newSchema |
| |
| return nil |
| |
| } |
| |
| func (d *Schema) parseProperties(documentNode interface{}, currentSchema *subSchema) error { |
| |
| if !isKind(documentNode, reflect.Map) { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfType(), |
| ErrorDetails{"key": STRING_PROPERTIES, "type": TYPE_OBJECT}, |
| )) |
| } |
| |
| m := documentNode.(map[string]interface{}) |
| for k := range m { |
| schemaProperty := k |
| newSchema := &subSchema{property: schemaProperty, parent: currentSchema, ref: currentSchema.ref} |
| currentSchema.AddPropertiesChild(newSchema) |
| err := d.parseSchema(m[k], newSchema) |
| if err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func (d *Schema) parseDependencies(documentNode interface{}, currentSchema *subSchema) error { |
| |
| if !isKind(documentNode, reflect.Map) { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfType(), |
| ErrorDetails{"key": KEY_DEPENDENCIES, "type": TYPE_OBJECT}, |
| )) |
| } |
| |
| m := documentNode.(map[string]interface{}) |
| currentSchema.dependencies = make(map[string]interface{}) |
| |
| for k := range m { |
| switch reflect.ValueOf(m[k]).Kind() { |
| |
| case reflect.Slice: |
| values := m[k].([]interface{}) |
| var valuesToRegister []string |
| |
| for _, value := range values { |
| if !isKind(value, reflect.String) { |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfType(), |
| ErrorDetails{ |
| "key": STRING_DEPENDENCY, |
| "type": STRING_SCHEMA_OR_ARRAY_OF_STRINGS, |
| }, |
| )) |
| } else { |
| valuesToRegister = append(valuesToRegister, value.(string)) |
| } |
| currentSchema.dependencies[k] = valuesToRegister |
| } |
| |
| case reflect.Map, reflect.Bool: |
| depSchema := &subSchema{property: k, parent: currentSchema, ref: currentSchema.ref} |
| err := d.parseSchema(m[k], depSchema) |
| if err != nil { |
| return err |
| } |
| currentSchema.dependencies[k] = depSchema |
| |
| default: |
| return errors.New(formatErrorDescription( |
| Locale.MustBeOfType(), |
| ErrorDetails{ |
| "key": STRING_DEPENDENCY, |
| "type": STRING_SCHEMA_OR_ARRAY_OF_STRINGS, |
| }, |
| )) |
| } |
| |
| } |
| |
| return nil |
| } |