-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: generate kcl schema from json schema (#127)
- Loading branch information
Showing
30 changed files
with
4,882 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# jsonschema | ||
|
||
This package is a fork of the [jsonschema](https://github.com/qri-io/jsonschema) | ||
package, which is a Go implementation of the JSON Schema specification. | ||
|
||
Thanks to the original authors for their great work, we are able to use this package to | ||
parse JSON Schema and support keywords in different versions of the specification easily. | ||
We also make some modifications to support our needs, such as make some field public, | ||
use orderedmap in properties keyword to keep the order, etc. | ||
|
||
## License | ||
|
||
``` | ||
The MIT License (MIT) | ||
Copyright (c) 2017 Brendan O'Brien | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package jsonschema | ||
|
||
// 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 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("definitions", NewDefs) | ||
RegisterKeyword("default", NewDefault) | ||
|
||
SetKeywordOrder("$ref", 0) | ||
SetKeywordOrder("$recursiveRef", 0) | ||
|
||
// standard Keywords | ||
RegisterKeyword("type", NewType) | ||
RegisterKeyword("enum", NewEnum) | ||
RegisterKeyword("const", NewConst) | ||
|
||
// numeric Keywords | ||
RegisterKeyword("multipleOf", NewMultipleOf) | ||
RegisterKeyword("maximum", NewMaximum) | ||
RegisterKeyword("exclusiveMaximum", NewExclusiveMaximum) | ||
RegisterKeyword("minimum", NewMinimum) | ||
RegisterKeyword("exclusiveMinimum", NewExclusiveMinimum) | ||
|
||
// string Keywords | ||
RegisterKeyword("maxLength", NewMaxLength) | ||
RegisterKeyword("minLength", NewMinLength) | ||
RegisterKeyword("pattern", NewPattern) | ||
|
||
// boolean Keywords | ||
RegisterKeyword("allOf", NewAllOf) | ||
RegisterKeyword("anyOf", NewAnyOf) | ||
RegisterKeyword("oneOf", NewOneOf) | ||
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) | ||
|
||
// 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) | ||
|
||
// conditional Keywords | ||
RegisterKeyword("if", NewIf) | ||
RegisterKeyword("then", NewThen) | ||
RegisterKeyword("else", NewElse) | ||
|
||
SetKeywordOrder("then", 2) | ||
SetKeywordOrder("else", 2) | ||
|
||
//optional formats | ||
RegisterKeyword("format", NewFormat) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
package jsonschema | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
|
||
jptr "github.com/qri-io/jsonpointer" | ||
) | ||
|
||
var notSupported = map[string]bool{ | ||
// core | ||
"$vocabulary": true, | ||
|
||
// other | ||
"contentEncoding": true, | ||
"contentMediaType": true, | ||
"contentSchema": true, | ||
"deprecated": true, | ||
|
||
// backward compatibility with draft7 | ||
"definitions": true, | ||
"dependencies": true, | ||
} | ||
|
||
var ( | ||
keywordRegistry = map[string]KeyMaker{} | ||
keywordOrder = map[string]int{} | ||
keywordInsertOrder = map[string]int{} | ||
) | ||
|
||
// IsRegisteredKeyword validates if a given prop string is a registered keyword | ||
func IsRegisteredKeyword(prop string) bool { | ||
_, ok := keywordRegistry[prop] | ||
return ok | ||
} | ||
|
||
// GetKeyword returns a new instance of the keyword | ||
func GetKeyword(prop string) Keyword { | ||
if !IsRegisteredKeyword(prop) { | ||
return NewVoid() | ||
} | ||
return 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 { | ||
return order | ||
} | ||
return 1 | ||
} | ||
|
||
// GetKeywordInsertOrder returns the insert index of | ||
// the given keyword | ||
func GetKeywordInsertOrder(prop string) int { | ||
if order, ok := keywordInsertOrder[prop]; ok { | ||
return order | ||
} | ||
// TODO(arqu): this is an arbitrary max | ||
return 1000 | ||
} | ||
|
||
// SetKeywordOrder assignes a given order to a keyword | ||
func SetKeywordOrder(prop string, order int) { | ||
keywordOrder[prop] = order | ||
} | ||
|
||
// IsNotSupportedKeyword is a utility function to clarify when | ||
// a given keyword, while expected is not supported | ||
func 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 | ||
} | ||
|
||
// RegisterKeyword registers a keyword with the registry | ||
func RegisterKeyword(prop string, maker KeyMaker) { | ||
keywordRegistry[prop] = maker | ||
keywordInsertOrder[prop] = len(keywordInsertOrder) | ||
} | ||
|
||
// MaxKeywordErrStringLen sets how long a value can be before it's length is truncated | ||
// when printing error strings | ||
// a special value of -1 disables output trimming | ||
var MaxKeywordErrStringLen = 20 | ||
|
||
// Keyword is an interface for anything that can validate. | ||
// JSON-Schema Keywords are all examples of Keyword | ||
type Keyword interface { | ||
// ValidateKeyword checks decoded JSON data and writes | ||
// validation errors (if any) to an outparam slice of KeyErrors | ||
ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) | ||
|
||
// Register builds up the schema tree by evaluating the current key | ||
// and the current location pointer which is later used with resolve to | ||
// navigate the schema tree and substitute the propper schema for a given | ||
// reference. | ||
Register(uri string, registry *SchemaRegistry) | ||
// Resolve unraps a pointer to the destination schema | ||
// It usually starts with a $ref validation call which | ||
// uses the pointer token by token to navigate the | ||
// schema tree to get to the last schema in the chain. | ||
// Since every keyword can have it's specifics around resolving | ||
// each keyword need to implement it's own version of Resolve. | ||
// Terminal Keywords should respond with nil as it's not a schema | ||
// Keywords that wrap a schema should return the appropriate schema. | ||
// In case of a non-existing location it will fail to resolve, return nil | ||
// on ref resolution and error out. | ||
Resolve(pointer jptr.Pointer, uri string) *Schema | ||
} | ||
|
||
// KeyMaker is a function that generates instances of a Keyword. | ||
// Calls to KeyMaker will be passed directly to json.Marshal, | ||
// so the returned value should be a pointer | ||
type KeyMaker func() Keyword | ||
|
||
// KeyError represents a Single error in an instance of a schema | ||
// The only absolutely-required property is Message. | ||
type KeyError struct { | ||
// PropertyPath is a string path that leads to the | ||
// property that produced the error | ||
PropertyPath string `json:"propertyPath,omitempty"` | ||
// InvalidValue is the value that returned the error | ||
InvalidValue interface{} `json:"invalidValue,omitempty"` | ||
// Message is a human-readable description of the error | ||
Message string `json:"message"` | ||
} | ||
|
||
// Error implements the error interface for KeyError | ||
func (v KeyError) Error() string { | ||
if v.PropertyPath != "" && v.InvalidValue != nil { | ||
return fmt.Sprintf("%s: %s %s", v.PropertyPath, InvalidValueString(v.InvalidValue), v.Message) | ||
} else if v.PropertyPath != "" { | ||
return fmt.Sprintf("%s: %s", v.PropertyPath, v.Message) | ||
} | ||
return v.Message | ||
} | ||
|
||
// InvalidValueString returns the errored value as a string | ||
func InvalidValueString(data interface{}) string { | ||
bt, err := json.Marshal(data) | ||
if err != nil { | ||
return "" | ||
} | ||
bt = bytes.Replace(bt, []byte{'\n', '\r'}, []byte{' '}, -1) | ||
if MaxKeywordErrStringLen != -1 && len(bt) > MaxKeywordErrStringLen { | ||
bt = append(bt[:MaxKeywordErrStringLen], []byte("...")...) | ||
} | ||
return string(bt) | ||
} |
Oops, something went wrong.