...

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

Documentation: github.com/qri-io/jsonschema

     1  package jsonschema
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  
     9  	jptr "github.com/qri-io/jsonpointer"
    10  )
    11  
    12  var notSupported = map[string]bool{
    13  	// core
    14  	"$vocabulary": true,
    15  
    16  	// other
    17  	"contentEncoding":  true,
    18  	"contentMediaType": true,
    19  	"contentSchema":    true,
    20  	"deprecated":       true,
    21  
    22  	// backward compatibility with draft7
    23  	"definitions":  true,
    24  	"dependencies": true,
    25  }
    26  
    27  var (
    28  	keywordRegistry    = map[string]KeyMaker{}
    29  	keywordOrder       = map[string]int{}
    30  	keywordInsertOrder = map[string]int{}
    31  )
    32  
    33  // IsRegisteredKeyword validates if a given prop string is a registered keyword
    34  func IsRegisteredKeyword(prop string) bool {
    35  	_, ok := keywordRegistry[prop]
    36  	return ok
    37  }
    38  
    39  // GetKeyword returns a new instance of the keyword
    40  func GetKeyword(prop string) Keyword {
    41  	if !IsRegisteredKeyword(prop) {
    42  		return NewVoid()
    43  	}
    44  	return keywordRegistry[prop]()
    45  }
    46  
    47  // GetKeywordOrder returns the order index of
    48  // the given keyword or defaults to 1
    49  func GetKeywordOrder(prop string) int {
    50  	if order, ok := keywordOrder[prop]; ok {
    51  		return order
    52  	}
    53  	return 1
    54  }
    55  
    56  // GetKeywordInsertOrder returns the insert index of
    57  // the given keyword
    58  func GetKeywordInsertOrder(prop string) int {
    59  	if order, ok := keywordInsertOrder[prop]; ok {
    60  		return order
    61  	}
    62  	// TODO(arqu): this is an arbitrary max
    63  	return 1000
    64  }
    65  
    66  // SetKeywordOrder assignes a given order to a keyword
    67  func SetKeywordOrder(prop string, order int) {
    68  	keywordOrder[prop] = order
    69  }
    70  
    71  // IsNotSupportedKeyword is a utility function to clarify when
    72  // a given keyword, while expected is not supported
    73  func IsNotSupportedKeyword(prop string) bool {
    74  	_, ok := notSupported[prop]
    75  	return ok
    76  }
    77  
    78  // IsRegistryLoaded checks if any keywords are present
    79  func IsRegistryLoaded() bool {
    80  	return keywordRegistry != nil && len(keywordRegistry) > 0
    81  }
    82  
    83  // RegisterKeyword registers a keyword with the registry
    84  func RegisterKeyword(prop string, maker KeyMaker) {
    85  	keywordRegistry[prop] = maker
    86  	keywordInsertOrder[prop] = len(keywordInsertOrder)
    87  }
    88  
    89  // MaxKeywordErrStringLen sets how long a value can be before it's length is truncated
    90  // when printing error strings
    91  // a special value of -1 disables output trimming
    92  var MaxKeywordErrStringLen = 20
    93  
    94  // Keyword is an interface for anything that can validate.
    95  // JSON-Schema keywords are all examples of Keyword
    96  type Keyword interface {
    97  	// ValidateKeyword checks decoded JSON data and writes
    98  	// validation errors (if any) to an outparam slice of KeyErrors
    99  	ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{})
   100  
   101  	// Register builds up the schema tree by evaluating the current key
   102  	// and the current location pointer which is later used with resolve to
   103  	// navigate the schema tree and substitute the propper schema for a given
   104  	// reference.
   105  	Register(uri string, registry *SchemaRegistry)
   106  	// Resolve unraps a pointer to the destination schema
   107  	// It usually starts with a $ref validation call which
   108  	// uses the pointer token by token to navigate the
   109  	// schema tree to get to the last schema in the chain.
   110  	// Since every keyword can have it's specifics around resolving
   111  	// each keyword need to implement it's own version of Resolve.
   112  	// Terminal keywords should respond with nil as it's not a schema
   113  	// Keywords that wrap a schema should return the appropriate schema.
   114  	// In case of a non-existing location it will fail to resolve, return nil
   115  	// on ref resolution and error out.
   116  	Resolve(pointer jptr.Pointer, uri string) *Schema
   117  }
   118  
   119  // KeyMaker is a function that generates instances of a Keyword.
   120  // Calls to KeyMaker will be passed directly to json.Marshal,
   121  // so the returned value should be a pointer
   122  type KeyMaker func() Keyword
   123  
   124  // KeyError represents a single error in an instance of a schema
   125  // The only absolutely-required property is Message.
   126  type KeyError struct {
   127  	// PropertyPath is a string path that leads to the
   128  	// property that produced the error
   129  	PropertyPath string `json:"propertyPath,omitempty"`
   130  	// InvalidValue is the value that returned the error
   131  	InvalidValue interface{} `json:"invalidValue,omitempty"`
   132  	// Message is a human-readable description of the error
   133  	Message string `json:"message"`
   134  }
   135  
   136  // Error implements the error interface for KeyError
   137  func (v KeyError) Error() string {
   138  	if v.PropertyPath != "" && v.InvalidValue != nil {
   139  		return fmt.Sprintf("%s: %s %s", v.PropertyPath, InvalidValueString(v.InvalidValue), v.Message)
   140  	} else if v.PropertyPath != "" {
   141  		return fmt.Sprintf("%s: %s", v.PropertyPath, v.Message)
   142  	}
   143  	return v.Message
   144  }
   145  
   146  // InvalidValueString returns the errored value as a string
   147  func InvalidValueString(data interface{}) string {
   148  	bt, err := json.Marshal(data)
   149  	if err != nil {
   150  		return ""
   151  	}
   152  	bt = bytes.Replace(bt, []byte{'\n', '\r'}, []byte{' '}, -1)
   153  	if MaxKeywordErrStringLen != -1 && len(bt) > MaxKeywordErrStringLen {
   154  		bt = append(bt[:MaxKeywordErrStringLen], []byte("...")...)
   155  	}
   156  	return string(bt)
   157  }
   158  

View as plain text