Skip to content

Commit

Permalink
feat: support import complex json schema
Browse files Browse the repository at this point in the history
Signed-off-by: jakezhu9 <[email protected]>
  • Loading branch information
jakezhu9 committed Jan 15, 2024
1 parent 77ed9b0 commit dbbc8c8
Show file tree
Hide file tree
Showing 9 changed files with 3,674 additions and 33 deletions.
1 change: 0 additions & 1 deletion pkg/3rdparty/jsonschema/draft2019_09_keywords.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ func LoadDraft2019_09() {
SetKeywordOrder("properties", 2)
SetKeywordOrder("additionalProperties", 3)
SetKeywordOrder("unevaluatedProperties", 4)
SetKeywordOrder("patternProperties", 5)

// array Keywords
RegisterKeyword("items", NewItems)
Expand Down
111 changes: 86 additions & 25 deletions pkg/tools/gen/genkcl_jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"io"
"path/filepath"
"reflect"
"regexp"
"sort"
"strconv"
Expand All @@ -19,6 +20,9 @@ type convertContext struct {
imports map[string]struct{}
resultMap map[string]convertResult
paths []string
// pathObjects is used to avoid infinite loop when converting recursive schema
// TODO: support recursive schema
pathObjects []*jsonschema.Schema
}

type convertResult struct {
Expand All @@ -41,10 +45,11 @@ func (k *kclGenerator) genSchemaFromJsonSchema(w io.Writer, filename string, src

// convert json schema to kcl schema
ctx := convertContext{
rootSchema: js,
resultMap: make(map[string]convertResult),
imports: make(map[string]struct{}),
paths: []string{},
rootSchema: js,
resultMap: make(map[string]convertResult),
imports: make(map[string]struct{}),
paths: []string{},
pathObjects: []*jsonschema.Schema{},
}
result := convertSchemaFromJsonSchema(&ctx, js,
strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename)))
Expand Down Expand Up @@ -86,7 +91,16 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
name = "MyType"
}
result := convertResult{IsSchema: false, Name: name}
if objectExists(ctx.pathObjects, s) {
result.Type = typePrimitive(typAny)
return result
}
ctx.paths = append(ctx.paths, name)
ctx.pathObjects = append(ctx.pathObjects, s)
defer func() {
ctx.paths = ctx.paths[:len(ctx.paths)-1]
ctx.pathObjects = ctx.pathObjects[:len(ctx.pathObjects)-1]
}()

isArray := false
typeList := typeUnion{}
Expand Down Expand Up @@ -131,6 +145,7 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
required[key] = struct{}{}
}
case *jsonschema.Properties:
result.IsSchema = true
for _, prop := range *v {
key := prop.Key
val := prop.Value
Expand Down Expand Up @@ -172,11 +187,11 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
result.IndexSignature = indexSignature{
Alias: "key",
Type: propSch.property.Type,
validation: &validation{
Name: "key",
Regex: prop.Re,
},
}
result.Validations = append(result.Validations, validation{
Name: "key",
Regex: prop.Re,
})
}
}
case *jsonschema.Default:
Expand Down Expand Up @@ -210,14 +225,46 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
refSch := v.ResolveRef(ctx.rootSchema)
if refSch == nil || refSch.OrderedKeywords == nil {
logger.GetLogger().Warningf("failed to resolve ref: %s", v.Reference)
continue
}
for _, key := range refSch.OrderedKeywords {
if _, ok := s.Keywords[key]; ok {
logger.GetLogger().Warningf("keyword %s already exists when resolving ref %s", key, v.Reference)
continue
schs := []*jsonschema.Schema{refSch}
for i := 0; i < len(schs); i++ {
sch := schs[i]
for _, key := range sch.OrderedKeywords {
if _, ok := s.Keywords[key]; !ok {
s.OrderedKeywords = append(s.OrderedKeywords, key)
s.Keywords[key] = sch.Keywords[key]
} else {
switch v := sch.Keywords[key].(type) {
case *jsonschema.Type:
case *jsonschema.Description:
case *jsonschema.Comment:
case *jsonschema.Ref:
refSch := v.ResolveRef(ctx.rootSchema)
if refSch == nil || refSch.OrderedKeywords == nil {
logger.GetLogger().Warningf("failed to resolve ref: %s.", v.Reference)
continue
}
schs = append(schs, refSch)
case *jsonschema.Properties:
props := *s.Keywords[key].(*jsonschema.Properties)
props = append(props, *v...)
s.Keywords[key] = &props
case *jsonschema.Required:
reqs := *s.Keywords[key].(*jsonschema.Required)
reqs = append(reqs, *v...)
s.Keywords[key] = &reqs
case *jsonschema.Items:
items := *s.Keywords[key].(*jsonschema.Items)
items.Schemas = append(items.Schemas, v.Schemas...)
s.Keywords[key] = &items
case *jsonschema.MinItems:
case *jsonschema.Pattern:
default:
logger.GetLogger().Warningf("failed to merge ref: unsupported keyword %s. Paths: %s", key, strings.Join(ctx.paths, "/"))
}
}
}
s.OrderedKeywords = append(s.OrderedKeywords, key)
s.Keywords[key] = refSch.Keywords[key]
}
sort.SliceStable(s.OrderedKeywords[i+1:], func(i, j int) bool {
return jsonschema.GetKeywordOrder(s.OrderedKeywords[i]) < jsonschema.GetKeywordOrder(s.OrderedKeywords[j])
Expand All @@ -226,6 +273,9 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
switch v.SchemaType {
case jsonschema.SchemaTypeObject:
sch := convertSchemaFromJsonSchema(ctx, (*jsonschema.Schema)(v), "additionalProperties")
if sch.IsSchema {
ctx.resultMap[sch.schema.Name] = sch
}
result.HasIndexSignature = true
result.IndexSignature = indexSignature{
Type: sch.Type,
Expand All @@ -236,7 +286,6 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
Type: typePrimitive(typAny),
}
case jsonschema.SchemaTypeFalse:
result.HasIndexSignature = false
}
case *jsonschema.Minimum:
result.Validations = append(result.Validations, validation{
Expand Down Expand Up @@ -319,6 +368,7 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
refSch := v.ResolveRef(ctx.rootSchema)
if refSch == nil || refSch.OrderedKeywords == nil {
logger.GetLogger().Warningf("failed to resolve ref: %s", v.Reference)
continue
}
schs = append(schs, refSch)
case *jsonschema.Properties:
Expand Down Expand Up @@ -350,25 +400,27 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
}
result.schema.Name = s.String()
result.schema.Description = result.Description
result.Type = typeCustom{Name: strcase.ToCamel(result.schema.Name)}
typeList.Items = append(typeList.Items, typeCustom{Name: result.schema.Name})
if len(result.Properties) == 0 && !result.HasIndexSignature {
result.HasIndexSignature = true
result.IndexSignature = indexSignature{Type: typePrimitive(typAny)}
}
} else {
if len(typeList.Items) != 0 {
if isArray {
result.Type = typeArray{Items: typeList}
} else {
result.Type = typeList
}
}
if len(typeList.Items) != 0 {
if isArray {
result.Type = typeArray{Items: typeList}
} else {
result.Type = typePrimitive(typAny)
result.Type = typeList
}
} else {
result.Type = typePrimitive(typAny)
}

if result.HasIndexSignature && result.IndexSignature.validation != nil {
result.Validations = append(result.Validations, *result.IndexSignature.validation)
}
result.property.Name = strcase.ToSnake(result.Name)
result.property.Description = result.Description
ctx.paths = ctx.paths[:len(ctx.paths)-1]
return result
}

Expand All @@ -395,3 +447,12 @@ func jsonTypeToKclType(t string) typeInterface {
return typePrimitive(typStr)
}
}

func objectExists(objs []*jsonschema.Schema, obj *jsonschema.Schema) bool {
for _, o := range objs {
if reflect.DeepEqual(o, obj) {
return true
}
}
return false
}
9 changes: 9 additions & 0 deletions pkg/tools/gen/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ func formatValue(v interface{}) string {
s.WriteString(fmt.Sprintf("%s: %s", formatValue(key), formatValue(value[key])))
}
return "{" + s.String() + "}"
case []interface{}:
var s strings.Builder
for i, item := range value {
if i != 0 {
s.WriteString(" ")
}
s.WriteString(formatValue(item))
}
return "[" + s.String() + "]"
default:
return fmt.Sprintf("%v", value)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ schema Shop:
products?: ShopProductsOneOf0 | [ShopProductsOneOf1Items0] | "empty"

schema ShopProductsOneOf0:
"""
r"""
ShopProductsOneOf0

Attributes
Expand All @@ -36,7 +36,7 @@ schema ShopProductsOneOf0:
price >= 0

schema ShopProductsOneOf1Items0:
"""
r"""
ShopProductsOneOf1Items0

Attributes
Expand Down
Loading

0 comments on commit dbbc8c8

Please sign in to comment.