Skip to content

Commit

Permalink
Do not use global keyword registry
Browse files Browse the repository at this point in the history
Introduce a keyword registry struct so that most operations are not
acting upon a global package variable.

I have preserved existing behaviors (such as applying the default schema
to the global keyword registry), and kept global functions such as
RegistrKeyword. If breaking changes are allowed, then we could remove
the locks and keyword registry copying which would simplify this patch
quite a bit.

Signed-off-by: Carolyn Van Slyck <[email protected]>
  • Loading branch information
carolynvs committed Jun 2, 2021
1 parent 36b0071 commit a1c05f4
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 100 deletions.
149 changes: 83 additions & 66 deletions draft2019_09_keywords.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,88 +5,105 @@ package jsonschema
// this is also the default keyword set loaded automatically
// if no other is loaded
func LoadDraft2019_09() {
r, release := getGlobalKeywordRegistry()
defer release()

r.LoadDraft2019_09()
}

func (r *KeywordRegistry) DefaultIfEmpty() {
if !r.IsRegistryLoaded() {
r.LoadDraft2019_09()
}
}

// LoadDraft2019_09 loads the keywords for schema validation
// based on draft2019_09
// this is also the default keyword set loaded automatically
// if no other is loaded
func (r *KeywordRegistry) LoadDraft2019_09() {
// core keywords
RegisterKeyword("$schema", NewSchemaURI)
RegisterKeyword("$id", NewID)
RegisterKeyword("description", NewDescription)
RegisterKeyword("title", NewTitle)
RegisterKeyword("$comment", NewComment)
RegisterKeyword("examples", NewExamples)
RegisterKeyword("readOnly", NewReadOnly)
RegisterKeyword("writeOnly", NewWriteOnly)
RegisterKeyword("$ref", NewRef)
RegisterKeyword("$recursiveRef", NewRecursiveRef)
RegisterKeyword("$anchor", NewAnchor)
RegisterKeyword("$recursiveAnchor", NewRecursiveAnchor)
RegisterKeyword("$defs", NewDefs)
RegisterKeyword("default", NewDefault)

SetKeywordOrder("$ref", 0)
SetKeywordOrder("$recursiveRef", 0)
r.RegisterKeyword("$schema", NewSchemaURI)
r.RegisterKeyword("$id", NewID)
r.RegisterKeyword("description", NewDescription)
r.RegisterKeyword("title", NewTitle)
r.RegisterKeyword("$comment", NewComment)
r.RegisterKeyword("examples", NewExamples)
r.RegisterKeyword("readOnly", NewReadOnly)
r.RegisterKeyword("writeOnly", NewWriteOnly)
r.RegisterKeyword("$ref", NewRef)
r.RegisterKeyword("$recursiveRef", NewRecursiveRef)
r.RegisterKeyword("$anchor", NewAnchor)
r.RegisterKeyword("$recursiveAnchor", NewRecursiveAnchor)
r.RegisterKeyword("$defs", NewDefs)
r.RegisterKeyword("default", NewDefault)

r.SetKeywordOrder("$ref", 0)
r.SetKeywordOrder("$recursiveRef", 0)

// standard keywords
RegisterKeyword("type", NewType)
RegisterKeyword("enum", NewEnum)
RegisterKeyword("const", NewConst)
r.RegisterKeyword("type", NewType)
r.RegisterKeyword("enum", NewEnum)
r.RegisterKeyword("const", NewConst)

// numeric keywords
RegisterKeyword("multipleOf", NewMultipleOf)
RegisterKeyword("maximum", NewMaximum)
RegisterKeyword("exclusiveMaximum", NewExclusiveMaximum)
RegisterKeyword("minimum", NewMinimum)
RegisterKeyword("exclusiveMinimum", NewExclusiveMinimum)
r.RegisterKeyword("multipleOf", NewMultipleOf)
r.RegisterKeyword("maximum", NewMaximum)
r.RegisterKeyword("exclusiveMaximum", NewExclusiveMaximum)
r.RegisterKeyword("minimum", NewMinimum)
r.RegisterKeyword("exclusiveMinimum", NewExclusiveMinimum)

// string keywords
RegisterKeyword("maxLength", NewMaxLength)
RegisterKeyword("minLength", NewMinLength)
RegisterKeyword("pattern", NewPattern)
r.RegisterKeyword("maxLength", NewMaxLength)
r.RegisterKeyword("minLength", NewMinLength)
r.RegisterKeyword("pattern", NewPattern)

// boolean keywords
RegisterKeyword("allOf", NewAllOf)
RegisterKeyword("anyOf", NewAnyOf)
RegisterKeyword("oneOf", NewOneOf)
RegisterKeyword("not", NewNot)
r.RegisterKeyword("allOf", NewAllOf)
r.RegisterKeyword("anyOf", NewAnyOf)
r.RegisterKeyword("oneOf", NewOneOf)
r.RegisterKeyword("not", NewNot)

// object keywords
RegisterKeyword("properties", NewProperties)
RegisterKeyword("patternProperties", NewPatternProperties)
RegisterKeyword("additionalProperties", NewAdditionalProperties)
RegisterKeyword("required", NewRequired)
RegisterKeyword("propertyNames", NewPropertyNames)
RegisterKeyword("maxProperties", NewMaxProperties)
RegisterKeyword("minProperties", NewMinProperties)
RegisterKeyword("dependentSchemas", NewDependentSchemas)
RegisterKeyword("dependentRequired", NewDependentRequired)
RegisterKeyword("unevaluatedProperties", NewUnevaluatedProperties)

SetKeywordOrder("properties", 2)
SetKeywordOrder("additionalProperties", 3)
SetKeywordOrder("unevaluatedProperties", 4)
r.RegisterKeyword("properties", NewProperties)
r.RegisterKeyword("patternProperties", NewPatternProperties)
r.RegisterKeyword("additionalProperties", NewAdditionalProperties)
r.RegisterKeyword("required", NewRequired)
r.RegisterKeyword("propertyNames", NewPropertyNames)
r.RegisterKeyword("maxProperties", NewMaxProperties)
r.RegisterKeyword("minProperties", NewMinProperties)
r.RegisterKeyword("dependentSchemas", NewDependentSchemas)
r.RegisterKeyword("dependentRequired", NewDependentRequired)
r.RegisterKeyword("unevaluatedProperties", NewUnevaluatedProperties)

r.SetKeywordOrder("properties", 2)
r.SetKeywordOrder("additionalProperties", 3)
r.SetKeywordOrder("unevaluatedProperties", 4)

// array keywords
RegisterKeyword("items", NewItems)
RegisterKeyword("additionalItems", NewAdditionalItems)
RegisterKeyword("maxItems", NewMaxItems)
RegisterKeyword("minItems", NewMinItems)
RegisterKeyword("uniqueItems", NewUniqueItems)
RegisterKeyword("contains", NewContains)
RegisterKeyword("maxContains", NewMaxContains)
RegisterKeyword("minContains", NewMinContains)
RegisterKeyword("unevaluatedItems", NewUnevaluatedItems)

SetKeywordOrder("maxContains", 2)
SetKeywordOrder("minContains", 2)
SetKeywordOrder("additionalItems", 3)
SetKeywordOrder("unevaluatedItems", 4)
r.RegisterKeyword("items", NewItems)
r.RegisterKeyword("additionalItems", NewAdditionalItems)
r.RegisterKeyword("maxItems", NewMaxItems)
r.RegisterKeyword("minItems", NewMinItems)
r.RegisterKeyword("uniqueItems", NewUniqueItems)
r.RegisterKeyword("contains", NewContains)
r.RegisterKeyword("maxContains", NewMaxContains)
r.RegisterKeyword("minContains", NewMinContains)
r.RegisterKeyword("unevaluatedItems", NewUnevaluatedItems)

r.SetKeywordOrder("maxContains", 2)
r.SetKeywordOrder("minContains", 2)
r.SetKeywordOrder("additionalItems", 3)
r.SetKeywordOrder("unevaluatedItems", 4)

// conditional keywords
RegisterKeyword("if", NewIf)
RegisterKeyword("then", NewThen)
RegisterKeyword("else", NewElse)
r.RegisterKeyword("if", NewIf)
r.RegisterKeyword("then", NewThen)
r.RegisterKeyword("else", NewElse)

SetKeywordOrder("then", 2)
SetKeywordOrder("else", 2)
r.SetKeywordOrder("then", 2)
r.SetKeywordOrder("else", 2)

//optional formats
RegisterKeyword("format", NewFormat)
r.RegisterKeyword("format", NewFormat)
}
102 changes: 81 additions & 21 deletions keyword.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"fmt"
"sync"

jptr "github.com/qri-io/jsonpointer"
)
Expand All @@ -24,66 +25,125 @@ var notSupported = map[string]bool{
"dependencies": true,
}

var (
keywordRegistry = map[string]KeyMaker{}
keywordOrder = map[string]int{}
keywordInsertOrder = map[string]int{}
)
var kr *KeywordRegistry
var krLock sync.Mutex

type KeywordRegistry struct {
keywordRegistry map[string]KeyMaker
keywordOrder map[string]int
keywordInsertOrder map[string]int
}

func getGlobalKeywordRegistry() (*KeywordRegistry, func()) {
krLock.Lock()
if kr == nil {
kr = &KeywordRegistry{
keywordRegistry: make(map[string]KeyMaker, 0),
keywordOrder: make(map[string]int, 0),
keywordInsertOrder: make(map[string]int, 0),
}
}
return kr, func() { krLock.Unlock() }
}

func copyGlobalKeywordRegistry() *KeywordRegistry {
kr, release := getGlobalKeywordRegistry()
defer release()
return kr.Copy()
}

func (r *KeywordRegistry) Copy() *KeywordRegistry {
dest := &KeywordRegistry{
keywordRegistry: make(map[string]KeyMaker, len(r.keywordRegistry)),
keywordOrder: make(map[string]int, len(r.keywordOrder)),
keywordInsertOrder: make(map[string]int, len(r.keywordInsertOrder)),
}

for k, v := range r.keywordRegistry {
dest.keywordRegistry[k] = v
}

for k, v := range r.keywordOrder {
dest.keywordOrder[k] = v
}

for k, v := range r.keywordInsertOrder {
dest.keywordInsertOrder[k] = v
}

return dest
}

// IsRegisteredKeyword validates if a given prop string is a registered keyword
func IsRegisteredKeyword(prop string) bool {
_, ok := keywordRegistry[prop]
func (r *KeywordRegistry) IsRegisteredKeyword(prop string) bool {
_, ok := r.keywordRegistry[prop]
return ok
}

// GetKeyword returns a new instance of the keyword
func GetKeyword(prop string) Keyword {
if !IsRegisteredKeyword(prop) {
func (r *KeywordRegistry) GetKeyword(prop string) Keyword {
if !r.IsRegisteredKeyword(prop) {
return NewVoid()
}
return keywordRegistry[prop]()
return r.keywordRegistry[prop]()
}

// GetKeywordOrder returns the order index of
// the given keyword or defaults to 1
func GetKeywordOrder(prop string) int {
if order, ok := keywordOrder[prop]; ok {
func (r *KeywordRegistry) GetKeywordOrder(prop string) int {
if order, ok := r.keywordOrder[prop]; ok {
return order
}
return 1
}

// GetKeywordInsertOrder returns the insert index of
// the given keyword
func GetKeywordInsertOrder(prop string) int {
if order, ok := keywordInsertOrder[prop]; ok {
func (r *KeywordRegistry) GetKeywordInsertOrder(prop string) int {
if order, ok := r.keywordInsertOrder[prop]; ok {
return order
}
// TODO(arqu): this is an arbitrary max
return 1000
}

// SetKeywordOrder assignes a given order to a keyword
// SetKeywordOrder assigns a given order to a keyword
func (r *KeywordRegistry) SetKeywordOrder(prop string, order int) {
r.keywordOrder[prop] = order
}

// SetKeywordOrder assigns a given order to a keyword
func SetKeywordOrder(prop string, order int) {
keywordOrder[prop] = order
r, release := getGlobalKeywordRegistry()
defer release()

r.SetKeywordOrder(prop, order)
}

// IsNotSupportedKeyword is a utility function to clarify when
// a given keyword, while expected is not supported
func IsNotSupportedKeyword(prop string) bool {
func (r *KeywordRegistry) IsNotSupportedKeyword(prop string) bool {
_, ok := notSupported[prop]
return ok
}

// IsRegistryLoaded checks if any keywords are present
func IsRegistryLoaded() bool {
return keywordRegistry != nil && len(keywordRegistry) > 0
func (r *KeywordRegistry) IsRegistryLoaded() bool {
return r.keywordRegistry != nil && len(r.keywordRegistry) > 0
}

// RegisterKeyword registers a keyword with the registry
func (r *KeywordRegistry) RegisterKeyword(prop string, maker KeyMaker) {
r.keywordRegistry[prop] = maker
r.keywordInsertOrder[prop] = len(r.keywordInsertOrder)
}

// RegisterKeyword registers a keyword with the registry
func RegisterKeyword(prop string, maker KeyMaker) {
keywordRegistry[prop] = maker
keywordInsertOrder[prop] = len(keywordInsertOrder)
r, release := getGlobalKeywordRegistry()
defer release()

r.RegisterKeyword(prop, maker)
}

// MaxKeywordErrStringLen sets how long a value can be before it's length is truncated
Expand Down
Loading

0 comments on commit a1c05f4

Please sign in to comment.