...

Source file src/github.com/qri-io/jsonschema/keywords_array.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  
    10  	jptr "github.com/qri-io/jsonpointer"
    11  )
    12  
    13  // Items defines the items JSON Schema keyword
    14  type Items struct {
    15  	single  bool
    16  	Schemas []*Schema
    17  }
    18  
    19  // NewItems allocates a new Items keyword
    20  func NewItems() Keyword {
    21  	return &Items{}
    22  }
    23  
    24  // Register implements the Keyword interface for Items
    25  func (it *Items) Register(uri string, registry *SchemaRegistry) {
    26  	for _, v := range it.Schemas {
    27  		v.Register(uri, registry)
    28  	}
    29  }
    30  
    31  // Resolve implements the Keyword interface for Items
    32  func (it *Items) Resolve(pointer jptr.Pointer, uri string) *Schema {
    33  	if pointer == nil {
    34  		return nil
    35  	}
    36  	current := pointer.Head()
    37  	if current == nil {
    38  		return nil
    39  	}
    40  
    41  	pos, err := strconv.Atoi(*current)
    42  	if err != nil {
    43  		return nil
    44  	}
    45  
    46  	if pos < 0 || pos >= len(it.Schemas) {
    47  		return nil
    48  	}
    49  
    50  	return it.Schemas[pos].Resolve(pointer.Tail(), uri)
    51  
    52  	return nil
    53  }
    54  
    55  // ValidateKeyword implements the Keyword interface for Items
    56  func (it Items) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
    57  	schemaDebug("[Items] Validating")
    58  	if arr, ok := data.([]interface{}); ok {
    59  		if it.single {
    60  			subState := currentState.NewSubState()
    61  			subState.DescendBase("items")
    62  			subState.DescendRelative("items")
    63  			for i, elem := range arr {
    64  				subState.ClearState()
    65  				subState.DescendInstanceFromState(currentState, strconv.Itoa(i))
    66  				it.Schemas[0].ValidateKeyword(ctx, subState, elem)
    67  				subState.SetEvaluatedIndex(i)
    68  				// TODO(arqu): this might clash with additional/unevaluated
    69  				// Properties/Items, should separate out
    70  				currentState.UpdateEvaluatedPropsAndItems(subState)
    71  			}
    72  		} else {
    73  			subState := currentState.NewSubState()
    74  			subState.DescendBase("items")
    75  			for i, vs := range it.Schemas {
    76  				if i < len(arr) {
    77  					subState.ClearState()
    78  					subState.DescendRelativeFromState(currentState, "items", strconv.Itoa(i))
    79  					subState.DescendInstanceFromState(currentState, strconv.Itoa(i))
    80  
    81  					vs.ValidateKeyword(ctx, subState, arr[i])
    82  					subState.SetEvaluatedIndex(i)
    83  					currentState.UpdateEvaluatedPropsAndItems(subState)
    84  				}
    85  			}
    86  		}
    87  	}
    88  }
    89  
    90  // JSONProp implements the JSONPather for Items
    91  func (it Items) JSONProp(name string) interface{} {
    92  	idx, err := strconv.Atoi(name)
    93  	if err != nil {
    94  		return nil
    95  	}
    96  	if idx > len(it.Schemas) || idx < 0 {
    97  		return nil
    98  	}
    99  	return it.Schemas[idx]
   100  }
   101  
   102  // JSONChildren implements the JSONContainer interface for Items
   103  func (it Items) JSONChildren() (res map[string]JSONPather) {
   104  	res = map[string]JSONPather{}
   105  	for i, sch := range it.Schemas {
   106  		res[strconv.Itoa(i)] = sch
   107  	}
   108  	return
   109  }
   110  
   111  // UnmarshalJSON implements the json.Unmarshaler interface for Items
   112  func (it *Items) UnmarshalJSON(data []byte) error {
   113  	s := &Schema{}
   114  	if err := json.Unmarshal(data, s); err == nil {
   115  		*it = Items{single: true, Schemas: []*Schema{s}}
   116  		return nil
   117  	}
   118  	ss := []*Schema{}
   119  	if err := json.Unmarshal(data, &ss); err != nil {
   120  		return err
   121  	}
   122  	*it = Items{Schemas: ss}
   123  	return nil
   124  }
   125  
   126  // MarshalJSON implements the json.Marshaler interface for Items
   127  func (it Items) MarshalJSON() ([]byte, error) {
   128  	if it.single {
   129  		return json.Marshal(it.Schemas[0])
   130  	}
   131  	return json.Marshal([]*Schema(it.Schemas))
   132  }
   133  
   134  // MaxItems defines the maxItems JSON Schema keyword
   135  type MaxItems int
   136  
   137  // NewMaxItems allocates a new MaxItems keyword
   138  func NewMaxItems() Keyword {
   139  	return new(MaxItems)
   140  }
   141  
   142  // Register implements the Keyword interface for MaxItems
   143  func (m *MaxItems) Register(uri string, registry *SchemaRegistry) {}
   144  
   145  // Resolve implements the Keyword interface for MaxItems
   146  func (m *MaxItems) Resolve(pointer jptr.Pointer, uri string) *Schema {
   147  	return nil
   148  }
   149  
   150  // ValidateKeyword implements the Keyword interface for MaxItems
   151  func (m MaxItems) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
   152  	schemaDebug("[MaxItems] Validating")
   153  	if arr, ok := data.([]interface{}); ok {
   154  		if len(arr) > int(m) {
   155  			currentState.AddError(data, fmt.Sprintf("array length %d exceeds %d max", len(arr), m))
   156  			return
   157  		}
   158  	}
   159  }
   160  
   161  // MinItems defines the minItems JSON Schema keyword
   162  type MinItems int
   163  
   164  // NewMinItems allocates a new MinItems keyword
   165  func NewMinItems() Keyword {
   166  	return new(MinItems)
   167  }
   168  
   169  // Register implements the Keyword interface for MinItems
   170  func (m *MinItems) Register(uri string, registry *SchemaRegistry) {}
   171  
   172  // Resolve implements the Keyword interface for MinItems
   173  func (m *MinItems) Resolve(pointer jptr.Pointer, uri string) *Schema {
   174  	return nil
   175  }
   176  
   177  // ValidateKeyword implements the Keyword interface for MinItems
   178  func (m MinItems) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
   179  	schemaDebug("[MinItems] Validating")
   180  	if arr, ok := data.([]interface{}); ok {
   181  		if len(arr) < int(m) {
   182  			currentState.AddError(data, fmt.Sprintf("array length %d below %d minimum items", len(arr), m))
   183  			return
   184  		}
   185  	}
   186  }
   187  
   188  // UniqueItems defines the uniqueItems JSON Schema keyword
   189  type UniqueItems bool
   190  
   191  // NewUniqueItems allocates a new UniqueItems keyword
   192  func NewUniqueItems() Keyword {
   193  	return new(UniqueItems)
   194  }
   195  
   196  // Register implements the Keyword interface for UniqueItems
   197  func (u *UniqueItems) Register(uri string, registry *SchemaRegistry) {}
   198  
   199  // Resolve implements the Keyword interface for UniqueItems
   200  func (u *UniqueItems) Resolve(pointer jptr.Pointer, uri string) *Schema {
   201  	return nil
   202  }
   203  
   204  // ValidateKeyword implements the Keyword interface for UniqueItems
   205  func (u UniqueItems) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
   206  	schemaDebug("[UniqueItems] Validating")
   207  	if arr, ok := data.([]interface{}); ok {
   208  		found := []interface{}{}
   209  		for _, elem := range arr {
   210  			for _, f := range found {
   211  				if reflect.DeepEqual(f, elem) {
   212  					currentState.AddError(data, fmt.Sprintf("array items must be unique. duplicated entry: %v", elem))
   213  					return
   214  				}
   215  			}
   216  			found = append(found, elem)
   217  		}
   218  	}
   219  }
   220  
   221  // Contains defines the contains JSON Schema keyword
   222  type Contains Schema
   223  
   224  // NewContains allocates a new Contains keyword
   225  func NewContains() Keyword {
   226  	return &Contains{}
   227  }
   228  
   229  // Register implements the Keyword interface for Contains
   230  func (c *Contains) Register(uri string, registry *SchemaRegistry) {
   231  	(*Schema)(c).Register(uri, registry)
   232  }
   233  
   234  // Resolve implements the Keyword interface for Contains
   235  func (c *Contains) Resolve(pointer jptr.Pointer, uri string) *Schema {
   236  	return (*Schema)(c).Resolve(pointer, uri)
   237  }
   238  
   239  // ValidateKeyword implements the Keyword interface for Contains
   240  func (c *Contains) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
   241  	schemaDebug("[Contains] Validating")
   242  	v := Schema(*c)
   243  	if arr, ok := data.([]interface{}); ok {
   244  		valid := false
   245  		matchCount := 0
   246  		subState := currentState.NewSubState()
   247  		subState.ClearState()
   248  		subState.DescendBase("contains")
   249  		subState.DescendRelative("contains")
   250  		for i, elem := range arr {
   251  			subState.ClearState()
   252  			subState.DescendInstanceFromState(currentState, strconv.Itoa(i))
   253  			subState.Errs = &[]KeyError{}
   254  			v.ValidateKeyword(ctx, subState, elem)
   255  			if subState.IsValid() {
   256  				valid = true
   257  				matchCount++
   258  			}
   259  		}
   260  		if valid {
   261  			currentState.Misc["containsCount"] = matchCount
   262  		} else {
   263  			currentState.AddError(data, fmt.Sprintf("must contain at least one of: %v", c))
   264  		}
   265  	}
   266  }
   267  
   268  // JSONProp implements the JSONPather for Contains
   269  func (c Contains) JSONProp(name string) interface{} {
   270  	return Schema(c).JSONProp(name)
   271  }
   272  
   273  // JSONChildren implements the JSONContainer interface for Contains
   274  func (c Contains) JSONChildren() (res map[string]JSONPather) {
   275  	return Schema(c).JSONChildren()
   276  }
   277  
   278  // UnmarshalJSON implements the json.Unmarshaler interface for Contains
   279  func (c *Contains) UnmarshalJSON(data []byte) error {
   280  	var sch Schema
   281  	if err := json.Unmarshal(data, &sch); err != nil {
   282  		return err
   283  	}
   284  	*c = Contains(sch)
   285  	return nil
   286  }
   287  
   288  // MaxContains defines the maxContains JSON Schema keyword
   289  type MaxContains int
   290  
   291  // NewMaxContains allocates a new MaxContains keyword
   292  func NewMaxContains() Keyword {
   293  	return new(MaxContains)
   294  }
   295  
   296  // Register implements the Keyword interface for MaxContains
   297  func (m *MaxContains) Register(uri string, registry *SchemaRegistry) {}
   298  
   299  // Resolve implements the Keyword interface for MaxContains
   300  func (m *MaxContains) Resolve(pointer jptr.Pointer, uri string) *Schema {
   301  	return nil
   302  }
   303  
   304  // ValidateKeyword implements the Keyword interface for MaxContains
   305  func (m MaxContains) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
   306  	schemaDebug("[MaxContains] Validating")
   307  	if arr, ok := data.([]interface{}); ok {
   308  		if containsCount, ok := currentState.Misc["containsCount"]; ok {
   309  			if containsCount.(int) > int(m) {
   310  				currentState.AddError(data, fmt.Sprintf("contained items %d exceeds %d max", len(arr), m))
   311  			}
   312  		}
   313  	}
   314  }
   315  
   316  // MinContains defines the minContains JSON Schema keyword
   317  type MinContains int
   318  
   319  // NewMinContains allocates a new MinContains keyword
   320  func NewMinContains() Keyword {
   321  	return new(MinContains)
   322  }
   323  
   324  // Register implements the Keyword interface for MinContains
   325  func (m *MinContains) Register(uri string, registry *SchemaRegistry) {}
   326  
   327  // Resolve implements the Keyword interface for MinContains
   328  func (m *MinContains) Resolve(pointer jptr.Pointer, uri string) *Schema {
   329  	return nil
   330  }
   331  
   332  // ValidateKeyword implements the Keyword interface for MinContains
   333  func (m MinContains) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
   334  	schemaDebug("[MinContains] Validating")
   335  	if arr, ok := data.([]interface{}); ok {
   336  		if containsCount, ok := currentState.Misc["containsCount"]; ok {
   337  			if containsCount.(int) < int(m) {
   338  				currentState.AddError(data, fmt.Sprintf("contained items %d bellow %d min", len(arr), m))
   339  			}
   340  		}
   341  	}
   342  }
   343  
   344  // AdditionalItems defines the additionalItems JSON Schema keyword
   345  type AdditionalItems Schema
   346  
   347  // NewAdditionalItems allocates a new AdditionalItems keyword
   348  func NewAdditionalItems() Keyword {
   349  	return &AdditionalItems{}
   350  }
   351  
   352  // Register implements the Keyword interface for AdditionalItems
   353  func (ai *AdditionalItems) Register(uri string, registry *SchemaRegistry) {
   354  	(*Schema)(ai).Register(uri, registry)
   355  }
   356  
   357  // Resolve implements the Keyword interface for AdditionalItems
   358  func (ai *AdditionalItems) Resolve(pointer jptr.Pointer, uri string) *Schema {
   359  	return (*Schema)(ai).Resolve(pointer, uri)
   360  }
   361  
   362  // ValidateKeyword implements the Keyword interface for AdditionalItems
   363  func (ai *AdditionalItems) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
   364  	schemaDebug("[AdditionalItems] Validating")
   365  	if arr, ok := data.([]interface{}); ok {
   366  		if currentState.LastEvaluatedIndex > -1 && currentState.LastEvaluatedIndex < len(arr) {
   367  			for i := currentState.LastEvaluatedIndex + 1; i < len(arr); i++ {
   368  				if ai.schemaType == schemaTypeFalse {
   369  					currentState.AddError(data, "additional items are not allowed")
   370  					return
   371  				}
   372  				subState := currentState.NewSubState()
   373  				subState.ClearState()
   374  				subState.SetEvaluatedIndex(i)
   375  				subState.DescendBase("additionalItems")
   376  				subState.DescendRelative("additionalItems")
   377  				subState.DescendInstance(strconv.Itoa(i))
   378  
   379  				(*Schema)(ai).ValidateKeyword(ctx, subState, arr[i])
   380  				currentState.UpdateEvaluatedPropsAndItems(subState)
   381  			}
   382  		}
   383  	}
   384  }
   385  
   386  // UnmarshalJSON implements the json.Unmarshaler interface for AdditionalItems
   387  func (ai *AdditionalItems) UnmarshalJSON(data []byte) error {
   388  	sch := &Schema{}
   389  	if err := json.Unmarshal(data, sch); err != nil {
   390  		return err
   391  	}
   392  	*ai = (AdditionalItems)(*sch)
   393  	return nil
   394  }
   395  
   396  // UnevaluatedItems defines the unevaluatedItems JSON Schema keyword
   397  type UnevaluatedItems Schema
   398  
   399  // NewUnevaluatedItems allocates a new UnevaluatedItems keyword
   400  func NewUnevaluatedItems() Keyword {
   401  	return &UnevaluatedItems{}
   402  }
   403  
   404  // Register implements the Keyword interface for UnevaluatedItems
   405  func (ui *UnevaluatedItems) Register(uri string, registry *SchemaRegistry) {
   406  	(*Schema)(ui).Register(uri, registry)
   407  }
   408  
   409  // Resolve implements the Keyword interface for UnevaluatedItems
   410  func (ui *UnevaluatedItems) Resolve(pointer jptr.Pointer, uri string) *Schema {
   411  	return (*Schema)(ui).Resolve(pointer, uri)
   412  }
   413  
   414  // ValidateKeyword implements the Keyword interface for UnevaluatedItems
   415  func (ui *UnevaluatedItems) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
   416  	schemaDebug("[UnevaluatedItems] Validating")
   417  	if arr, ok := data.([]interface{}); ok {
   418  		if currentState.LastEvaluatedIndex < len(arr) {
   419  			for i := currentState.LastEvaluatedIndex + 1; i < len(arr); i++ {
   420  				if ui.schemaType == schemaTypeFalse {
   421  					currentState.AddError(data, "unevaluated items are not allowed")
   422  					return
   423  				}
   424  				subState := currentState.NewSubState()
   425  				subState.ClearState()
   426  				subState.DescendBase("unevaluatedItems")
   427  				subState.DescendRelative("unevaluatedItems")
   428  				subState.DescendInstance(strconv.Itoa(i))
   429  
   430  				(*Schema)(ui).ValidateKeyword(ctx, subState, arr[i])
   431  			}
   432  		}
   433  	}
   434  }
   435  
   436  // UnmarshalJSON implements the json.Unmarshaler interface for UnevaluatedItems
   437  func (ui *UnevaluatedItems) UnmarshalJSON(data []byte) error {
   438  	sch := &Schema{}
   439  	if err := json.Unmarshal(data, sch); err != nil {
   440  		return err
   441  	}
   442  	*ui = (UnevaluatedItems)(*sch)
   443  	return nil
   444  }
   445  

View as plain text