package openapi3 import ( "bytes" "context" "encoding/json" "errors" "fmt" "math" "math/big" "regexp" "strconv" "unicode/utf16" "github.com/getkin/kin-openapi/jsoninfo" ) var ( // SchemaErrorDetailsDisabled disables printing of details about schema errors. SchemaErrorDetailsDisabled = false //SchemaFormatValidationDisabled disables validation of schema type formats. SchemaFormatValidationDisabled = false errSchema = errors.New("Input does not match the schema") ErrSchemaInputNaN = errors.New("NaN is not allowed") ErrSchemaInputInf = errors.New("Inf is not allowed") ) // Float64Ptr is a helper for defining OpenAPI schemas. func Float64Ptr(value float64) *float64 { return &value } // BoolPtr is a helper for defining OpenAPI schemas. func BoolPtr(value bool) *bool { return &value } // Int64Ptr is a helper for defining OpenAPI schemas. func Int64Ptr(value int64) *int64 { return &value } // Uint64Ptr is a helper for defining OpenAPI schemas. func Uint64Ptr(value uint64) *uint64 { return &value } // Schema is specified by OpenAPI/Swagger 3.0 standard. type Schema struct { ExtensionProps OneOf []*SchemaRef `json:"oneOf,omitempty" yaml:"oneOf,omitempty"` AnyOf []*SchemaRef `json:"anyOf,omitempty" yaml:"anyOf,omitempty"` AllOf []*SchemaRef `json:"allOf,omitempty" yaml:"allOf,omitempty"` Not *SchemaRef `json:"not,omitempty" yaml:"not,omitempty"` Type string `json:"type,omitempty" yaml:"type,omitempty"` Title string `json:"title,omitempty" yaml:"title,omitempty"` Format string `json:"format,omitempty" yaml:"format,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"` Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"` Default interface{} `json:"default,omitempty" yaml:"default,omitempty"` Example interface{} `json:"example,omitempty" yaml:"example,omitempty"` ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` // Object-related, here for struct compactness AdditionalPropertiesAllowed *bool `json:"-" multijson:"additionalProperties,omitempty" yaml:"-"` // Array-related, here for struct compactness UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` // Number-related, here for struct compactness ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` // Properties Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` WriteOnly bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` XML interface{} `json:"xml,omitempty" yaml:"xml,omitempty"` // Number Min *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` Max *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` // String MinLength uint64 `json:"minLength,omitempty" yaml:"minLength,omitempty"` MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` compiledPattern *compiledPattern // Array MinItems uint64 `json:"minItems,omitempty" yaml:"minItems,omitempty"` MaxItems *uint64 `json:"maxItems,omitempty" yaml:"maxItems,omitempty"` Items *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"` // Object Required []string `json:"required,omitempty" yaml:"required,omitempty"` Properties map[string]*SchemaRef `json:"properties,omitempty" yaml:"properties,omitempty"` MinProps uint64 `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` MaxProps *uint64 `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"` AdditionalProperties *SchemaRef `json:"-" multijson:"additionalProperties,omitempty" yaml:"-"` Discriminator *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"` } func NewSchema() *Schema { return &Schema{} } func (schema *Schema) MarshalJSON() ([]byte, error) { return jsoninfo.MarshalStrictStruct(schema) } func (schema *Schema) UnmarshalJSON(data []byte) error { return jsoninfo.UnmarshalStrictStruct(data, schema) } func (schema *Schema) NewRef() *SchemaRef { return &SchemaRef{ Value: schema, } } func NewOneOfSchema(schemas ...*Schema) *Schema { refs := make([]*SchemaRef, 0, len(schemas)) for _, schema := range schemas { refs = append(refs, &SchemaRef{Value: schema}) } return &Schema{ OneOf: refs, } } func NewAnyOfSchema(schemas ...*Schema) *Schema { refs := make([]*SchemaRef, 0, len(schemas)) for _, schema := range schemas { refs = append(refs, &SchemaRef{Value: schema}) } return &Schema{ AnyOf: refs, } } func NewAllOfSchema(schemas ...*Schema) *Schema { refs := make([]*SchemaRef, 0, len(schemas)) for _, schema := range schemas { refs = append(refs, &SchemaRef{Value: schema}) } return &Schema{ AllOf: refs, } } func NewBoolSchema() *Schema { return &Schema{ Type: "boolean", } } func NewFloat64Schema() *Schema { return &Schema{ Type: "number", } } func NewIntegerSchema() *Schema { return &Schema{ Type: "integer", } } func NewInt32Schema() *Schema { return &Schema{ Type: "integer", Format: "int32", } } func NewInt64Schema() *Schema { return &Schema{ Type: "integer", Format: "int64", } } func NewStringSchema() *Schema { return &Schema{ Type: "string", } } func NewDateTimeSchema() *Schema { return &Schema{ Type: "string", Format: "date-time", } } func NewUUIDSchema() *Schema { return &Schema{ Type: "string", Format: "uuid", } } func NewBytesSchema() *Schema { return &Schema{ Type: "string", Format: "byte", } } func NewArraySchema() *Schema { return &Schema{ Type: "array", } } func NewObjectSchema() *Schema { return &Schema{ Type: "object", Properties: make(map[string]*SchemaRef), } } type compiledPattern struct { Regexp *regexp.Regexp ErrReason string } func (schema *Schema) WithNullable() *Schema { schema.Nullable = true return schema } func (schema *Schema) WithMin(value float64) *Schema { schema.Min = &value return schema } func (schema *Schema) WithMax(value float64) *Schema { schema.Max = &value return schema } func (schema *Schema) WithExclusiveMin(value bool) *Schema { schema.ExclusiveMin = value return schema } func (schema *Schema) WithExclusiveMax(value bool) *Schema { schema.ExclusiveMax = value return schema } func (schema *Schema) WithEnum(values ...interface{}) *Schema { schema.Enum = values return schema } func (schema *Schema) WithDefault(defaultValue interface{}) *Schema { schema.Default = defaultValue return schema } func (schema *Schema) WithFormat(value string) *Schema { schema.Format = value return schema } func (schema *Schema) WithLength(i int64) *Schema { n := uint64(i) schema.MinLength = n schema.MaxLength = &n return schema } func (schema *Schema) WithMinLength(i int64) *Schema { n := uint64(i) schema.MinLength = n return schema } func (schema *Schema) WithMaxLength(i int64) *Schema { n := uint64(i) schema.MaxLength = &n return schema } func (schema *Schema) WithLengthDecodedBase64(i int64) *Schema { n := uint64(i) v := (n*8 + 5) / 6 schema.MinLength = v schema.MaxLength = &v return schema } func (schema *Schema) WithMinLengthDecodedBase64(i int64) *Schema { n := uint64(i) schema.MinLength = (n*8 + 5) / 6 return schema } func (schema *Schema) WithMaxLengthDecodedBase64(i int64) *Schema { n := uint64(i) schema.MinLength = (n*8 + 5) / 6 return schema } func (schema *Schema) WithPattern(pattern string) *Schema { schema.Pattern = pattern return schema } func (schema *Schema) WithItems(value *Schema) *Schema { schema.Items = &SchemaRef{ Value: value, } return schema } func (schema *Schema) WithMinItems(i int64) *Schema { n := uint64(i) schema.MinItems = n return schema } func (schema *Schema) WithMaxItems(i int64) *Schema { n := uint64(i) schema.MaxItems = &n return schema } func (schema *Schema) WithUniqueItems(unique bool) *Schema { schema.UniqueItems = unique return schema } func (schema *Schema) WithProperty(name string, propertySchema *Schema) *Schema { return schema.WithPropertyRef(name, &SchemaRef{ Value: propertySchema, }) } func (schema *Schema) WithPropertyRef(name string, ref *SchemaRef) *Schema { properties := schema.Properties if properties == nil { properties = make(map[string]*SchemaRef) schema.Properties = properties } properties[name] = ref return schema } func (schema *Schema) WithProperties(properties map[string]*Schema) *Schema { result := make(map[string]*SchemaRef, len(properties)) for k, v := range properties { result[k] = &SchemaRef{ Value: v, } } schema.Properties = result return schema } func (schema *Schema) WithMinProperties(i int64) *Schema { n := uint64(i) schema.MinProps = n return schema } func (schema *Schema) WithMaxProperties(i int64) *Schema { n := uint64(i) schema.MaxProps = &n return schema } func (schema *Schema) WithAnyAdditionalProperties() *Schema { schema.AdditionalProperties = nil t := true schema.AdditionalPropertiesAllowed = &t return schema } func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema { if v == nil { schema.AdditionalProperties = nil } else { schema.AdditionalProperties = &SchemaRef{ Value: v, } } return schema } func (schema *Schema) IsEmpty() bool { if schema.Type != "" || schema.Format != "" || len(schema.Enum) != 0 || schema.UniqueItems || schema.ExclusiveMin || schema.ExclusiveMax || !schema.Nullable || schema.Min != nil || schema.Max != nil || schema.MultipleOf != nil || schema.MinLength != 0 || schema.MaxLength != nil || schema.Pattern != "" || schema.MinItems != 0 || schema.MaxItems != nil || len(schema.Required) != 0 || schema.MinProps != 0 || schema.MaxProps != nil { return false } if n := schema.Not; n != nil && !n.Value.IsEmpty() { return false } if ap := schema.AdditionalProperties; ap != nil && !ap.Value.IsEmpty() { return false } if apa := schema.AdditionalPropertiesAllowed; apa != nil && !*apa { return false } if items := schema.Items; items != nil && !items.Value.IsEmpty() { return false } for _, s := range schema.Properties { if !s.Value.IsEmpty() { return false } } for _, s := range schema.OneOf { if !s.Value.IsEmpty() { return false } } for _, s := range schema.AnyOf { if !s.Value.IsEmpty() { return false } } for _, s := range schema.AllOf { if !s.Value.IsEmpty() { return false } } return true } func (schema *Schema) Validate(c context.Context) error { return schema.validate(c, []*Schema{}) } func (schema *Schema) validate(c context.Context, stack []*Schema) (err error) { for _, existing := range stack { if existing == schema { return } } stack = append(stack, schema) for _, item := range schema.OneOf { v := item.Value if v == nil { return foundUnresolvedRef(item.Ref) } if err = v.validate(c, stack); err == nil { return } } for _, item := range schema.AnyOf { v := item.Value if v == nil { return foundUnresolvedRef(item.Ref) } if err = v.validate(c, stack); err != nil { return } } for _, item := range schema.AllOf { v := item.Value if v == nil { return foundUnresolvedRef(item.Ref) } if err = v.validate(c, stack); err != nil { return } } if ref := schema.Not; ref != nil { v := ref.Value if v == nil { return foundUnresolvedRef(ref.Ref) } if err = v.validate(c, stack); err != nil { return } } schemaType := schema.Type switch schemaType { case "": case "boolean": case "number": if format := schema.Format; len(format) > 0 { switch format { case "float", "double": default: if !SchemaFormatValidationDisabled { return unsupportedFormat(format) } } } case "integer": if format := schema.Format; len(format) > 0 { switch format { case "int32", "int64": default: if !SchemaFormatValidationDisabled { return unsupportedFormat(format) } } } case "string": if format := schema.Format; len(format) > 0 { switch format { // Supported by OpenAPIv3.0.1: case "byte", "binary", "date", "date-time", "password": // In JSON Draft-07 (not validated yet though): case "regex": case "time", "email", "idn-email": case "hostname", "idn-hostname", "ipv4", "ipv6": case "uri", "uri-reference", "iri", "iri-reference", "uri-template": case "json-pointer", "relative-json-pointer": default: // Try to check for custom defined formats if _, ok := SchemaStringFormats[format]; !ok && !SchemaFormatValidationDisabled { return unsupportedFormat(format) } } } case "array": if schema.Items == nil { return errors.New("When schema type is 'array', schema 'items' must be non-null") } case "object": default: return fmt.Errorf("Unsupported 'type' value '%s'", schemaType) } if ref := schema.Items; ref != nil { v := ref.Value if v == nil { return foundUnresolvedRef(ref.Ref) } if err = v.validate(c, stack); err != nil { return } } for _, ref := range schema.Properties { v := ref.Value if v == nil { return foundUnresolvedRef(ref.Ref) } if err = v.validate(c, stack); err != nil { return } } if ref := schema.AdditionalProperties; ref != nil { v := ref.Value if v == nil { return foundUnresolvedRef(ref.Ref) } if err = v.validate(c, stack); err != nil { return } } return } func (schema *Schema) IsMatching(value interface{}) bool { return schema.visitJSON(value, true) == nil } func (schema *Schema) IsMatchingJSONBoolean(value bool) bool { return schema.visitJSON(value, true) == nil } func (schema *Schema) IsMatchingJSONNumber(value float64) bool { return schema.visitJSON(value, true) == nil } func (schema *Schema) IsMatchingJSONString(value string) bool { return schema.visitJSON(value, true) == nil } func (schema *Schema) IsMatchingJSONArray(value []interface{}) bool { return schema.visitJSON(value, true) == nil } func (schema *Schema) IsMatchingJSONObject(value map[string]interface{}) bool { return schema.visitJSON(value, true) == nil } func (schema *Schema) VisitJSON(value interface{}) error { return schema.visitJSON(value, false) } func (schema *Schema) visitJSON(value interface{}, fast bool) (err error) { switch value := value.(type) { case nil: return schema.visitJSONNull(fast) case float64: if math.IsNaN(value) { return ErrSchemaInputNaN } if math.IsInf(value, 0) { return ErrSchemaInputInf } } if schema.IsEmpty() { return } if err = schema.visitSetOperations(value, fast); err != nil { return } switch value := value.(type) { case nil: return schema.visitJSONNull(fast) case bool: return schema.visitJSONBoolean(value, fast) case float64: return schema.visitJSONNumber(value, fast) case string: return schema.visitJSONString(value, fast) case []interface{}: return schema.visitJSONArray(value, fast) case map[string]interface{}: return schema.visitJSONObject(value, fast) default: return &SchemaError{ Value: value, Schema: schema, SchemaField: "type", Reason: fmt.Sprintf("Not a JSON value: %T", value), } } } func (schema *Schema) visitSetOperations(value interface{}, fast bool) (err error) { if enum := schema.Enum; len(enum) != 0 { for _, v := range enum { if value == v { return } } if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "enum", Reason: "JSON value is not one of the allowed values", } } if ref := schema.Not; ref != nil { v := ref.Value if v == nil { return foundUnresolvedRef(ref.Ref) } if err := v.visitJSON(value, true); err == nil { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "not", } } } if v := schema.OneOf; len(v) > 0 { ok := 0 for _, item := range v { v := item.Value if v == nil { return foundUnresolvedRef(item.Ref) } if err := v.visitJSON(value, true); err == nil { ok++ } } if ok != 1 { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "oneOf", } } } if v := schema.AnyOf; len(v) > 0 { ok := false for _, item := range v { v := item.Value if v == nil { return foundUnresolvedRef(item.Ref) } if err := v.visitJSON(value, true); err == nil { ok = true break } } if !ok { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "anyOf", } } } for _, item := range schema.AllOf { v := item.Value if v == nil { return foundUnresolvedRef(item.Ref) } if err := v.visitJSON(value, false); err != nil { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "allOf", Origin: err, } } } return } func (schema *Schema) visitJSONNull(fast bool) (err error) { if schema.Nullable { return } if fast { return errSchema } return &SchemaError{ Value: nil, Schema: schema, SchemaField: "nullable", Reason: "Value is not nullable", } } func (schema *Schema) VisitJSONBoolean(value bool) error { return schema.visitJSONBoolean(value, false) } func (schema *Schema) visitJSONBoolean(value bool, fast bool) (err error) { if schemaType := schema.Type; schemaType != "" && schemaType != "boolean" { return schema.expectedType("boolean", fast) } return } func (schema *Schema) VisitJSONNumber(value float64) error { return schema.visitJSONNumber(value, false) } func (schema *Schema) visitJSONNumber(value float64, fast bool) (err error) { schemaType := schema.Type if schemaType == "integer" { if bigFloat := big.NewFloat(value); !bigFloat.IsInt() { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "type", Reason: "Value must be an integer", } } } else if schemaType != "" && schemaType != "number" { return schema.expectedType("number, integer", fast) } // "exclusiveMinimum" if v := schema.ExclusiveMin; v && !(*schema.Min < value) { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "exclusiveMinimum", Reason: fmt.Sprintf("Number must be more than %g", *schema.Min), } } // "exclusiveMaximum" if v := schema.ExclusiveMax; v && !(*schema.Max > value) { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "exclusiveMaximum", Reason: fmt.Sprintf("Number must be less than %g", *schema.Max), } } // "minimum" if v := schema.Min; v != nil && !(*v <= value) { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "minimum", Reason: fmt.Sprintf("Number must be at least %g", *v), } } // "maximum" if v := schema.Max; v != nil && !(*v >= value) { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "maximum", Reason: fmt.Sprintf("Number must be most %g", *v), } } // "multipleOf" if v := schema.MultipleOf; v != nil { // "A numeric instance is valid only if division by this keyword's // value results in an integer." if bigFloat := big.NewFloat(value / *v); !bigFloat.IsInt() { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "multipleOf", } } } return } func (schema *Schema) VisitJSONString(value string) error { return schema.visitJSONString(value, false) } func (schema *Schema) visitJSONString(value string, fast bool) (err error) { if schemaType := schema.Type; schemaType != "" && schemaType != "string" { return schema.expectedType("string", fast) } // "minLength" and "maxLength" minLength := schema.MinLength maxLength := schema.MaxLength if minLength != 0 || maxLength != nil { // JSON schema string lengths are UTF-16, not UTF-8! length := int64(0) for _, r := range value { if utf16.IsSurrogate(r) { length += 2 } else { length++ } } if minLength != 0 && length < int64(minLength) { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "minLength", Reason: fmt.Sprintf("Minimum string length is %d", minLength), } } if maxLength != nil && length > int64(*maxLength) { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "maxLength", Reason: fmt.Sprintf("Maximum string length is %d", *maxLength), } } } // "format" and "pattern" cp := schema.compiledPattern if cp == nil { pattern := schema.Pattern if v := schema.Pattern; len(v) > 0 { // Pattern re, err := regexp.Compile(v) if err != nil { return fmt.Errorf("Error while compiling regular expression '%s': %v", pattern, err) } cp = &compiledPattern{ Regexp: re, ErrReason: "JSON string doesn't match the regular expression '" + v + "'", } schema.compiledPattern = cp } else if v := schema.Format; len(v) > 0 { // No pattern, but does have a format re := SchemaStringFormats[v] if re != nil { cp = &compiledPattern{ Regexp: re, ErrReason: "JSON string doesn't match the format '" + v + " (regular expression `" + re.String() + "`)'", } schema.compiledPattern = cp } } } if cp != nil { if !cp.Regexp.MatchString(value) { field := "format" if schema.Pattern != "" { field = "pattern" } return &SchemaError{ Value: value, Schema: schema, SchemaField: field, Reason: cp.ErrReason, } } } return } func (schema *Schema) VisitJSONArray(value []interface{}) error { return schema.visitJSONArray(value, false) } func (schema *Schema) visitJSONArray(value []interface{}, fast bool) (err error) { if schemaType := schema.Type; schemaType != "" && schemaType != "array" { return schema.expectedType("array", fast) } lenValue := int64(len(value)) // "minItems" if v := schema.MinItems; v != 0 && lenValue < int64(v) { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "minItems", Reason: fmt.Sprintf("Minimum number of items is %d", v), } } // "maxItems" if v := schema.MaxItems; v != nil && lenValue > int64(*v) { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "maxItems", Reason: fmt.Sprintf("Maximum number of items is %d", *v), } } // "uniqueItems" if v := schema.UniqueItems; v && !sliceUniqueItemsChecker(value) { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "uniqueItems", Reason: fmt.Sprintf("Duplicate items found"), } } // "items" if itemSchemaRef := schema.Items; itemSchemaRef != nil { itemSchema := itemSchemaRef.Value if itemSchema == nil { return foundUnresolvedRef(itemSchemaRef.Ref) } for i, item := range value { if err := itemSchema.VisitJSON(item); err != nil { return markSchemaErrorIndex(err, i) } } } return } func (schema *Schema) VisitJSONObject(value map[string]interface{}) error { return schema.visitJSONObject(value, false) } func (schema *Schema) visitJSONObject(value map[string]interface{}, fast bool) (err error) { if schemaType := schema.Type; schemaType != "" && schemaType != "object" { return schema.expectedType("object", fast) } // "properties" properties := schema.Properties lenValue := int64(len(value)) // "minProperties" if v := schema.MinProps; v != 0 && lenValue < int64(v) { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "minProperties", Reason: fmt.Sprintf("There must be at least %d properties", v), } } // "maxProperties" if v := schema.MaxProps; v != nil && lenValue > int64(*v) { if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "maxProperties", Reason: fmt.Sprintf("There must be at most %d properties", *v), } } // "additionalProperties" var additionalProperties *Schema if ref := schema.AdditionalProperties; ref != nil { additionalProperties = ref.Value } for k, v := range value { if properties != nil { propertyRef := properties[k] if propertyRef != nil { p := propertyRef.Value if p == nil { return foundUnresolvedRef(propertyRef.Ref) } if err := p.VisitJSON(v); err != nil { if fast { return errSchema } return markSchemaErrorKey(err, k) } continue } } allowed := schema.AdditionalPropertiesAllowed if additionalProperties != nil || allowed == nil || (allowed != nil && *allowed) { if additionalProperties != nil { if err := additionalProperties.VisitJSON(v); err != nil { if fast { return errSchema } return markSchemaErrorKey(err, k) } } continue } if fast { return errSchema } return &SchemaError{ Value: value, Schema: schema, SchemaField: "properties", Reason: fmt.Sprintf("Property '%s' is unsupported", k), } } for _, k := range schema.Required { if _, ok := value[k]; !ok { if fast { return errSchema } return markSchemaErrorKey(&SchemaError{ Value: value, Schema: schema, SchemaField: "required", Reason: fmt.Sprintf("Property '%s' is missing", k), }, k) } } return } func (schema *Schema) expectedType(typ string, fast bool) error { if fast { return errSchema } return &SchemaError{ Value: typ, Schema: schema, SchemaField: "type", Reason: "Field must be set to " + schema.Type + " or not be present", } } type SchemaError struct { Value interface{} reversePath []string Schema *Schema SchemaField string Reason string Origin error } func markSchemaErrorKey(err error, key string) error { if v, ok := err.(*SchemaError); ok { v.reversePath = append(v.reversePath, key) return v } return err } func markSchemaErrorIndex(err error, index int) error { if v, ok := err.(*SchemaError); ok { v.reversePath = append(v.reversePath, strconv.FormatInt(int64(index), 10)) return v } return err } func (err *SchemaError) JSONPointer() []string { reversePath := err.reversePath path := append([]string(nil), reversePath...) for left, right := 0, len(path)-1; left < right; left, right = left+1, right-1 { path[left], path[right] = path[right], path[left] } return path } func (err *SchemaError) Error() string { if err.Origin != nil { return err.Origin.Error() } buf := bytes.NewBuffer(make([]byte, 0, 256)) if len(err.reversePath) > 0 { buf.WriteString(`Error at "`) reversePath := err.reversePath for i := len(reversePath) - 1; i >= 0; i-- { buf.WriteByte('/') buf.WriteString(reversePath[i]) } buf.WriteString(`":`) } reason := err.Reason if reason == "" { buf.WriteString(`Doesn't match schema "`) buf.WriteString(err.SchemaField) buf.WriteString(`"`) } else { buf.WriteString(reason) } if !SchemaErrorDetailsDisabled { buf.WriteString("\nSchema:\n ") encoder := json.NewEncoder(buf) encoder.SetIndent(" ", " ") if err := encoder.Encode(err.Schema); err != nil { panic(err) } buf.WriteString("\nValue:\n ") if err := encoder.Encode(err.Value); err != nil { panic(err) } } return buf.String() } func isSliceOfUniqueItems(xs []interface{}) bool { s := len(xs) m := make(map[string]struct{}, s) for _, x := range xs { // The input slice is coverted from a JSON string, there shall // have no error when covert it back. key, _ := json.Marshal(&x) m[string(key)] = struct{}{} } return s == len(m) } // SliceUniqueItemsChecker is an function used to check if an given slice // have unique items. type SliceUniqueItemsChecker func(items []interface{}) bool // By default using predefined func isSliceOfUniqueItems which make use of // json.Marshal to generate a key for map used to check if a given slice // have unique items. var sliceUniqueItemsChecker SliceUniqueItemsChecker = isSliceOfUniqueItems // RegisterArrayUniqueItemsChecker is used to register a customized function // used to check if JSON array have unique items. func RegisterArrayUniqueItemsChecker(fn SliceUniqueItemsChecker) { sliceUniqueItemsChecker = fn } func unsupportedFormat(format string) error { return fmt.Errorf("Unsupported 'format' value '%s'", format) }