github.com/Datadog/cnab-go@v0.3.3-beta1.0.20191007143216-bba4b7e723d0/bundle/definition/schema.go (about) 1 package definition 2 3 import ( 4 "encoding/json" 5 "strconv" 6 "strings" 7 8 "github.com/pkg/errors" 9 ) 10 11 type Definitions map[string]*Schema 12 13 // Schema represents a JSON Schema compatible CNAB Definition 14 type Schema struct { 15 Schema string `json:"$schema,omitempty" yaml:"$schema,omitempty"` 16 Comment string `json:"$comment,omitempty" yaml:"$comment,omitempty"` 17 ID string `json:"$id,omitempty" yaml:"$id,omitempty"` 18 Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` 19 AdditionalItems interface{} `json:"additionalItems,omitempty" yaml:"additionalItems,omitempty"` 20 AdditionalProperties interface{} `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` 21 AllOf []*Schema `json:"allOf,omitempty" yaml:"allOf,omitempty"` 22 Const interface{} `json:"const,omitempty" yaml:"const,omitempty"` 23 Contains *Schema `json:"contains,omitempty" yaml:"contains,omitempty"` 24 ContentEncoding string `json:"contentEncoding,omitempty" yaml:"contentEncoding,omitempty"` 25 ContentMediaType string `json:"contentMediaType,omitempty" yaml:"contentMediaType,omitempty"` 26 Default interface{} `json:"default,omitempty" yaml:"default,omitempty"` 27 Definitions Definitions `json:"definitions,omitempty" yaml:"definitions,omitempty"` 28 Dependencies map[string]interface{} `json:"dependencies,omitempty" yaml:"dependencies,omitempty"` 29 Description string `json:"description,omitempty" yaml:"description,omitempty"` 30 Else *Schema `json:"else,omitempty" yaml:"else,omitempty"` 31 Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"` 32 Examples []interface{} `json:"examples,omitempty" yaml:"examples,omitempty"` 33 ExclusiveMaximum *int `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"` 34 ExclusiveMinimum *int `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"` 35 Format string `json:"format,omitempty" yaml:"format,omitempty"` 36 If *Schema `json:"if,omitempty" yaml:"if,omitempty"` 37 //Items can be a Schema or an Array of Schema :( 38 Items interface{} `json:"items,omitempty" yaml:"items,omitempty"` 39 Maximum *int `json:"maximum,omitempty" yaml:"maximum,omitempty"` 40 MaxLength *int `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` 41 MinItems *int `json:"minItems,omitempty" yaml:"minItems,omitempty"` 42 MinLength *int `json:"minLength,omitempty" yaml:"minLength,omitempty"` 43 MinProperties *int `json:"minProperties,omitempty" yaml:"minProperties,omitempty"` 44 Minimum *int `json:"minimum,omitempty" yaml:"minimum,omitempty"` 45 MultipleOf *int `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"` 46 Not *Schema `json:"not,omitempty" yaml:"not,omitempty"` 47 OneOf *Schema `json:"oneOf,omitempty" yaml:"oneOf,omitempty"` 48 49 PatternProperties map[string]*Schema `json:"patternProperties,omitempty" yaml:"patternProperties,omitempty"` 50 51 Properties map[string]*Schema `json:"properties,omitempty" yaml:"properties,omitempty"` 52 PropertyNames *Schema `json:"propertyNames,omitempty" yaml:"propertyNames,omitempty"` 53 ReadOnly *bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` 54 Required []string `json:"required,omitempty" yaml:"required,omitempty"` 55 Then *Schema `json:"then,omitempty" yaml:"then,omitempty"` 56 Title string `json:"title,omitempty" yaml:"title,omitempty"` 57 Type interface{} `json:"type,omitempty" yaml:"type,omitempty"` 58 UniqueItems *bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"` 59 WriteOnly *bool `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"` 60 } 61 62 // GetType will return the singular type for a given schema and a success boolean. If the 63 // schema does not have a single type, it will return the false boolean and an error. 64 func (s *Schema) GetType() (string, bool, error) { 65 typeString, ok := s.Type.(string) 66 if !ok { 67 return "", false, errors.Errorf("this schema has multiple types: %v", s.Type) 68 } 69 return typeString, ok, nil 70 } 71 72 // GetTypes will return the types (as a slice) for a given schema and a success boolean. If the 73 // schema has a single type, it will return the false boolean and an error. 74 func (s *Schema) GetTypes() ([]string, bool, error) { 75 data, ok := s.Type.([]interface{}) 76 if !ok { 77 return nil, false, errors.Errorf("this schema does not have multiple types: %v", s.Type) 78 } 79 typeStrings := []string{} 80 for _, val := range data { 81 typeString, ok := val.(string) 82 if !ok { 83 return nil, false, errors.Errorf("unknown type value %T", val) 84 } 85 typeStrings = append(typeStrings, typeString) 86 } 87 return typeStrings, ok, nil 88 } 89 90 // UnmarshalJSON provides an implementation of a JSON unmarshaler that uses the 91 // github.com/qri-io/jsonschema to load and validate a given schema. If it is valid, 92 // then the json is unmarshaled. 93 func (s *Schema) UnmarshalJSON(data []byte) error { 94 95 // Before we unmarshal into the cnab-go bundle/definition/Schema type, unmarshal into 96 // the library struct so we can handle any validation errors in the schema. If there 97 // are any errors, return those. 98 js := NewRootSchema() 99 if err := js.UnmarshalJSON(data); err != nil { 100 return err 101 } 102 // The schema is valid at this point, so now use an indirect wrapper type to actually 103 // unmarshal into our type. 104 type wrapperType Schema 105 wrapper := struct { 106 *wrapperType 107 }{ 108 wrapperType: (*wrapperType)(s), 109 } 110 return json.Unmarshal(data, &wrapper) 111 } 112 113 // ConvertValue attempts to convert the given string value to the type from the 114 // definition. Note: this is only applicable to string, number, integer and boolean types 115 func (s *Schema) ConvertValue(val string) (interface{}, error) { 116 dataType, ok, err := s.GetType() 117 if !ok { 118 return nil, errors.Wrapf(err, "unable to determine type: %v", s.Type) 119 } 120 switch dataType { 121 case "string": 122 return val, nil 123 case "integer": 124 return strconv.Atoi(val) 125 case "boolean": 126 switch strings.ToLower(val) { 127 case "true": 128 return true, nil 129 case "false": 130 return false, nil 131 default: 132 return false, errors.Errorf("%q is not a valid boolean", val) 133 } 134 default: 135 return nil, errors.New("invalid definition") 136 } 137 }