mirror of https://github.com/databricks/cli.git
added tests for primitives and basic objects/arrays
This commit is contained in:
parent
987dd29357
commit
926db54a6d
|
@ -16,19 +16,22 @@ type Schema struct {
|
|||
}
|
||||
|
||||
type Property struct {
|
||||
// TODO: Add a enum for json types
|
||||
Type JsType `json:"type"`
|
||||
Items *Item `json:"item,omitempty"`
|
||||
Properities map[string]*Property `json:"properities,omitempty"`
|
||||
AdditionalProperities *Property `json:"additionalProperities,omitempty"`
|
||||
}
|
||||
|
||||
// TODO: panic for now, add support for adding schemas to $defs in case of cycles
|
||||
|
||||
type Item struct {
|
||||
Type JsType `json:"type"`
|
||||
}
|
||||
|
||||
func NewSchema(goType reflect.Type) (*Schema, error) {
|
||||
rootProp, err := properity(goType)
|
||||
func NewSchema(golangType reflect.Type) (*Schema, error) {
|
||||
traceSet := map[reflect.Type]struct{}{}
|
||||
traceSlice := []reflect.Type{}
|
||||
rootProp, err := toProperity(golangType, traceSet, traceSlice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -50,39 +53,52 @@ const (
|
|||
Array = "array"
|
||||
)
|
||||
|
||||
func jsType(goType reflect.Type) (JsType, error) {
|
||||
switch goType.Kind() {
|
||||
func javascriptType(golangType reflect.Type) (JsType, error) {
|
||||
switch golangType.Kind() {
|
||||
case reflect.Bool:
|
||||
return Boolean, nil
|
||||
case reflect.String:
|
||||
return String, nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64:
|
||||
|
||||
return Number, nil
|
||||
case reflect.Struct:
|
||||
return Object, nil
|
||||
// TODO: add support for pattern properities to account for maps
|
||||
case reflect.Map:
|
||||
if goType.Key().Kind() != reflect.String {
|
||||
return Invalid, fmt.Errorf("only strings map keys are valid. key type: ", goType.Key().Kind())
|
||||
if golangType.Key().Kind() != reflect.String {
|
||||
return Invalid, fmt.Errorf("only strings map keys are valid. key type: %v", golangType.Key().Kind())
|
||||
}
|
||||
return Object, nil
|
||||
case reflect.Array, reflect.Slice:
|
||||
return Array, nil
|
||||
default:
|
||||
return Invalid, fmt.Errorf("unhandled golang type: %s", goType)
|
||||
return Invalid, fmt.Errorf("unhandled golang type: %s", golangType)
|
||||
}
|
||||
}
|
||||
|
||||
func errWithTrace(prefix string, trace []reflect.Type) error {
|
||||
traceString := ""
|
||||
for _, golangType := range trace {
|
||||
traceString += " -> " + golangType.Name()
|
||||
}
|
||||
return fmt.Errorf("[ERROR] " + prefix + " type traveral trace: " + traceString)
|
||||
}
|
||||
|
||||
// TODO: handle case of self referential pointers in structs
|
||||
|
||||
// TODO: add doc string explaining numHistoryOccurances
|
||||
func properity(goType reflect.Type, numHistoryOccurances map[string]int) (*Property, error) {
|
||||
func toProperity(golangType reflect.Type, traceSet map[reflect.Type]struct{}, traceSlice []reflect.Type) (*Property, error) {
|
||||
traceSlice = append(traceSlice, golangType)
|
||||
|
||||
// *Struct and Struct generate identical json schemas
|
||||
if goType.Kind() == reflect.Pointer {
|
||||
return properity(goType.Elem())
|
||||
if golangType.Kind() == reflect.Pointer {
|
||||
return toProperity(golangType.Elem(), traceSet, traceSlice)
|
||||
}
|
||||
|
||||
rootJsType, err := jsType(goType)
|
||||
rootJavascriptType, err := javascriptType(golangType)
|
||||
|
||||
// TODO: recursive debugging can be a pain. Make sure the error localtion
|
||||
// floats up
|
||||
|
@ -91,8 +107,8 @@ func properity(goType reflect.Type, numHistoryOccurances map[string]int) (*Prope
|
|||
}
|
||||
|
||||
var items *Item
|
||||
if goType.Kind() == reflect.Array || goType.Kind() == reflect.Slice {
|
||||
elemJsType, err := jsType(goType.Elem())
|
||||
if golangType.Kind() == reflect.Array || golangType.Kind() == reflect.Slice {
|
||||
elemJsType, err := javascriptType(golangType.Elem())
|
||||
if err != nil {
|
||||
// TODO: float up error in case of deep recursion
|
||||
return nil, err
|
||||
|
@ -104,48 +120,52 @@ func properity(goType reflect.Type, numHistoryOccurances map[string]int) (*Prope
|
|||
}
|
||||
|
||||
properities := map[string]*Property{}
|
||||
var additionalProperities *Property
|
||||
|
||||
// TODO: for reflect.Map case for prop computation
|
||||
|
||||
if goType.Kind() == reflect.Struct {
|
||||
for i := 0; i < goType.NumField(); i++ {
|
||||
field := goType.Field(i)
|
||||
if golangType.Kind() == reflect.Struct {
|
||||
for i := 0; i < golangType.NumField(); i++ {
|
||||
child := golangType.Field(i)
|
||||
|
||||
// compute child properties
|
||||
fieldJsonTag := field.Tag.Get("json")
|
||||
fieldName := strings.Split(fieldJsonTag, ",")[0]
|
||||
childJsonTag := child.Tag.Get("json")
|
||||
childName := strings.Split(childJsonTag, ",")[0]
|
||||
|
||||
// stopgap infinite recursion
|
||||
numHistoryOccurances[fieldName] += 1
|
||||
if numHistoryOccurances[fieldName] > MaxHistoryOccurances {
|
||||
return nil
|
||||
// skip non json annotated fields
|
||||
if childName == "" {
|
||||
continue
|
||||
}
|
||||
fieldProps, err := properity(field.Type)
|
||||
numHistoryOccurances[fieldName] -= 1
|
||||
|
||||
// detect cycles. Fail if a cycle is detected
|
||||
// TODO: Add references here for cycles
|
||||
_, ok := traceSet[child.Type]
|
||||
if ok {
|
||||
fmt.Println("[DEBUG] traceSet: ", traceSet)
|
||||
return nil, errWithTrace("cycle detected", traceSlice)
|
||||
}
|
||||
|
||||
// add current child field to history
|
||||
traceSet[child.Type] = struct{}{}
|
||||
|
||||
// recursively compute properties for this child field
|
||||
fieldProps, err := toProperity(child.Type, traceSet, traceSlice)
|
||||
|
||||
// TODO: make sure this error floats up with context
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fieldJsonTag != "" {
|
||||
properities[fieldName] = fieldProps
|
||||
} else if additionalProperities == nil {
|
||||
// TODO: add error disallowing self referenincing without json tags
|
||||
additionalProperities = fieldProps
|
||||
} else {
|
||||
// TODO: float error up with context
|
||||
return nil, fmt.Errorf("only one non json annotated field allowed")
|
||||
}
|
||||
// traversal complete, delete child from history
|
||||
delete(traceSet, child.Type)
|
||||
|
||||
properities[childName] = fieldProps
|
||||
}
|
||||
}
|
||||
|
||||
return &Property{
|
||||
Type: rootJsType,
|
||||
Items: items,
|
||||
Properities: properities,
|
||||
AdditionalProperities: additionalProperities,
|
||||
}, nil
|
||||
traceSlice = traceSlice[:len(traceSlice)-1]
|
||||
|
||||
return &Property{
|
||||
Type: rootJavascriptType,
|
||||
Items: items,
|
||||
Properities: properities,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TODO: add tests to assert that these are valid json schemas. Maybe validate some
|
||||
// json/yaml documents againts them, by unmarshalling a value
|
||||
|
||||
func TestNumberStringBooleanSchema(t *testing.T) {
|
||||
type Foo struct {
|
||||
IntVal int `json:"int_val"`
|
||||
Int8Val int8 `json:"int8_val"`
|
||||
Int16Val int16 `json:"int16_val"`
|
||||
Int32Val int32 `json:"int32_val"`
|
||||
Int64Val int64 `json:"int64_val"`
|
||||
|
||||
Uint8Val int8 `json:"uint8_val"`
|
||||
Uint16Val int16 `json:"uint16_val"`
|
||||
Uint32Val int32 `json:"uint32_val"`
|
||||
Uint64Val int64 `json:"uint64_val"`
|
||||
|
||||
Float32Val int64 `json:"float32_val"`
|
||||
Float64Val int64 `json:"float64_val"`
|
||||
|
||||
StringVal string `json:"string_val"`
|
||||
|
||||
BoolVal string `json:"bool_val"`
|
||||
}
|
||||
|
||||
elem := Foo{}
|
||||
|
||||
schema, err := NewSchema(reflect.TypeOf(elem))
|
||||
assert.NoError(t, err)
|
||||
|
||||
jsonSchema, err := json.MarshalIndent(schema, " ", " ")
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected :=
|
||||
`{
|
||||
"type": "object",
|
||||
"properities": {
|
||||
"bool_val": {
|
||||
"type": "string"
|
||||
},
|
||||
"float32_val": {
|
||||
"type": "number"
|
||||
},
|
||||
"float64_val": {
|
||||
"type": "number"
|
||||
},
|
||||
"int16_val": {
|
||||
"type": "number"
|
||||
},
|
||||
"int32_val": {
|
||||
"type": "number"
|
||||
},
|
||||
"int64_val": {
|
||||
"type": "number"
|
||||
},
|
||||
"int8_val": {
|
||||
"type": "number"
|
||||
},
|
||||
"int_val": {
|
||||
"type": "number"
|
||||
},
|
||||
"string_val": {
|
||||
"type": "string"
|
||||
},
|
||||
"uint16_val": {
|
||||
"type": "number"
|
||||
},
|
||||
"uint32_val": {
|
||||
"type": "number"
|
||||
},
|
||||
"uint64_val": {
|
||||
"type": "number"
|
||||
},
|
||||
"uint8_val": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
fmt.Println("[DEBUG] actual: ", string(jsonSchema))
|
||||
fmt.Println("[DEBUG] expected: ", expected)
|
||||
assert.Equal(t, expected, string(jsonSchema))
|
||||
}
|
||||
|
||||
func TestObjectSchema(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age,omitempty"`
|
||||
}
|
||||
|
||||
type Plot struct {
|
||||
Stakes []string `json:"stakes"`
|
||||
}
|
||||
|
||||
type Story struct {
|
||||
Hero Person `json:"hero"`
|
||||
Villian Person `json:"villian"`
|
||||
Plot Plot `json:"plot"`
|
||||
}
|
||||
|
||||
elem := Story{}
|
||||
|
||||
schema, err := NewSchema(reflect.TypeOf(elem))
|
||||
assert.NoError(t, err)
|
||||
|
||||
jsonSchema, err := json.MarshalIndent(schema, " ", " ")
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected :=
|
||||
`{
|
||||
"type": "object",
|
||||
"properities": {
|
||||
"hero": {
|
||||
"type": "object",
|
||||
"properities": {
|
||||
"age": {
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"plot": {
|
||||
"type": "object",
|
||||
"properities": {
|
||||
"stakes": {
|
||||
"type": "array",
|
||||
"item": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"villian": {
|
||||
"type": "object",
|
||||
"properities": {
|
||||
"age": {
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
fmt.Println("[DEBUG] actual: ", string(jsonSchema))
|
||||
fmt.Println("[DEBUG] expected: ", expected)
|
||||
assert.Equal(t, expected, string(jsonSchema))
|
||||
}
|
Loading…
Reference in New Issue