databricks-cli/bundle/schema/schema.go

152 lines
4.0 KiB
Go
Raw Normal View History

2023-01-13 03:52:25 +00:00
package schema
import (
"fmt"
"reflect"
"strings"
)
2023-01-13 11:09:19 +00:00
const MaxHistoryOccurances = 3
// TODO: should omit empty denote non required fields in the json schema?
2023-01-13 03:52:25 +00:00
type Schema struct {
2023-01-13 11:09:19 +00:00
Type JsType `json:"type"`
Properities map[string]*Property `json:"properities,omitempty"`
AdditionalProperities *Property `json:"additionalProperities,omitempty"`
2023-01-13 03:52:25 +00:00
}
type Property struct {
// TODO: Add a enum for json types
2023-01-13 11:09:19 +00:00
Type JsType `json:"type"`
Items *Item `json:"item,omitempty"`
Properities map[string]*Property `json:"properities,omitempty"`
AdditionalProperities *Property `json:"additionalProperities,omitempty"`
2023-01-13 03:52:25 +00:00
}
type Item struct {
Type JsType `json:"type"`
}
func NewSchema(goType reflect.Type) (*Schema, error) {
rootProp, err := properity(goType)
if err != nil {
return nil, err
}
return &Schema{
2023-01-13 11:09:19 +00:00
Type: rootProp.Type,
Properities: rootProp.Properities,
AdditionalProperities: rootProp.AdditionalProperities,
2023-01-13 03:52:25 +00:00
}, nil
}
type JsType string
const (
Invalid JsType = "invalid"
Boolean = "boolean"
String = "string"
Number = "number"
Object = "object"
Array = "array"
)
func jsType(goType reflect.Type) (JsType, error) {
switch goType.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:
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())
}
return Object, nil
case reflect.Array, reflect.Slice:
return Array, nil
default:
return Invalid, fmt.Errorf("unhandled golang type: %s", goType)
}
}
// TODO: handle case of self referential pointers in structs
2023-01-13 11:09:19 +00:00
// TODO: add doc string explaining numHistoryOccurances
func properity(goType reflect.Type, numHistoryOccurances map[string]int) (*Property, error) {
2023-01-13 03:52:25 +00:00
// *Struct and Struct generate identical json schemas
if goType.Kind() == reflect.Pointer {
return properity(goType.Elem())
}
rootJsType, err := jsType(goType)
// TODO: recursive debugging can be a pain. Make sure the error localtion
// floats up
if err != nil {
return nil, err
}
var items *Item
if goType.Kind() == reflect.Array || goType.Kind() == reflect.Slice {
elemJsType, err := jsType(goType.Elem())
if err != nil {
// TODO: float up error in case of deep recursion
return nil, err
}
items = &Item{
// TODO: what if there is an array of objects?
Type: elemJsType,
}
}
properities := map[string]*Property{}
2023-01-13 11:09:19 +00:00
var additionalProperities *Property
// TODO: for reflect.Map case for prop computation
2023-01-13 03:52:25 +00:00
if goType.Kind() == reflect.Struct {
for i := 0; i < goType.NumField(); i++ {
field := goType.Field(i)
2023-01-13 11:09:19 +00:00
// compute child properties
fieldJsonTag := field.Tag.Get("json")
fieldName := strings.Split(fieldJsonTag, ",")[0]
// stopgap infinite recursion
numHistoryOccurances[fieldName] += 1
if numHistoryOccurances[fieldName] > MaxHistoryOccurances {
return nil
}
2023-01-13 03:52:25 +00:00
fieldProps, err := properity(field.Type)
2023-01-13 11:09:19 +00:00
numHistoryOccurances[fieldName] -= 1
2023-01-13 03:52:25 +00:00
// TODO: make sure this error floats up with context
if err != nil {
return nil, err
}
2023-01-13 11:09:19 +00:00
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")
}
2023-01-13 03:52:25 +00:00
}
}
return &Property{
2023-01-13 11:09:19 +00:00
Type: rootJsType,
Items: items,
Properities: properities,
AdditionalProperities: additionalProperities,
2023-01-13 03:52:25 +00:00
}, nil
}