...

Source file src/github.com/qri-io/jsonschema/keywords_standard.go

Documentation: github.com/qri-io/jsonschema

     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  // Const defines the const JSON Schema keyword
    15  type Const json.RawMessage
    16  
    17  // NewConst allocates a new Const keyword
    18  func NewConst() Keyword {
    19  	return &Const{}
    20  }
    21  
    22  // Register implements the Keyword interface for Const
    23  func (c *Const) Register(uri string, registry *SchemaRegistry) {}
    24  
    25  // Resolve implements the Keyword interface for Const
    26  func (c *Const) Resolve(pointer jptr.Pointer, uri string) *Schema {
    27  	return nil
    28  }
    29  
    30  // ValidateKeyword implements the Keyword interface for Const
    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  // JSONProp implements the JSONPather for Const
    45  func (c Const) JSONProp(name string) interface{} {
    46  	return nil
    47  }
    48  
    49  // String implements the Stringer for Const
    50  func (c Const) String() string {
    51  	return string(c)
    52  }
    53  
    54  // UnmarshalJSON implements the json.Unmarshaler interface for Const
    55  func (c *Const) UnmarshalJSON(data []byte) error {
    56  	*c = data
    57  	return nil
    58  }
    59  
    60  // MarshalJSON implements the json.Marshaler interface for Const
    61  func (c Const) MarshalJSON() ([]byte, error) {
    62  	return json.Marshal(json.RawMessage(c))
    63  }
    64  
    65  // Enum defines the enum JSON Schema keyword
    66  type Enum []Const
    67  
    68  // NewEnum allocates a new Enum keyword
    69  func NewEnum() Keyword {
    70  	return &Enum{}
    71  }
    72  
    73  // Register implements the Keyword interface for Enum
    74  func (e *Enum) Register(uri string, registry *SchemaRegistry) {}
    75  
    76  // Resolve implements the Keyword interface for Enum
    77  func (e *Enum) Resolve(pointer jptr.Pointer, uri string) *Schema {
    78  	return nil
    79  }
    80  
    81  // ValidateKeyword implements the Keyword interface for Enum
    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  // JSONProp implements the JSONPather for Enum
    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  // JSONChildren implements the JSONContainer interface for Enum
   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  // String implements the Stringer for Enum
   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  // List of primitive types supported and used by JSON Schema
   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  // DataType attempts to parse the underlying data type
   139  // from the raw data interface
   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  // DataTypeWithHint attempts to parse the underlying data type
   166  // by leveraging the schema expectations for better results
   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  	// deals with traling 0 floats
   178  	if dt == "integer" && hint == "number" {
   179  		return "number"
   180  	}
   181  	return dt
   182  }
   183  
   184  // Type defines the type JSON Schema keyword
   185  type Type struct {
   186  	strVal bool
   187  	vals   []string
   188  }
   189  
   190  // NewType allocates a new Type keyword
   191  func NewType() Keyword {
   192  	return &Type{}
   193  }
   194  
   195  // Register implements the Keyword interface for Type
   196  func (t *Type) Register(uri string, registry *SchemaRegistry) {}
   197  
   198  // Resolve implements the Keyword interface for Type
   199  func (t *Type) Resolve(pointer jptr.Pointer, uri string) *Schema {
   200  	return nil
   201  }
   202  
   203  // ValidateKeyword implements the Keyword interface for Type
   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  // String implements the Stringer for Type
   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  // JSONProp implements the JSONPather for Type
   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  // UnmarshalJSON implements the json.Unmarshaler interface for Type
   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  // MarshalJSON implements the json.Marshaler interface for Type
   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