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  }