Skip to content

Commit

Permalink
feat: jsonschema allof validations (#372)
Browse files Browse the repository at this point in the history
Signed-off-by: peefy <[email protected]>
  • Loading branch information
Peefy authored Aug 19, 2024
1 parent 5664cd0 commit ea9c7a5
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 22 deletions.
139 changes: 117 additions & 22 deletions pkg/tools/gen/genkcl_jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,36 +385,124 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
}
case *jsonschema.AllOf:
schs := *v
var validations []*validation
_, req := required[name]
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.Ref:
refSch := v.ResolveRef(ctx.rootSchema)
if refSch == nil || refSch.OrderedKeywords == nil {
logger.GetLogger().Warningf("failed to resolve ref: %s", v.Reference)
continue
switch v := sch.Keywords[key].(type) {
case *jsonschema.Minimum:
validations = append(validations, &validation{
Name: name,
Required: req,
Minimum: (*float64)(v),
ExclusiveMinimum: false,
})
case *jsonschema.Maximum:
validations = append(validations, &validation{
Name: name,
Required: req,
Maximum: (*float64)(v),
ExclusiveMaximum: false,
})
case *jsonschema.ExclusiveMinimum:
validations = append(validations, &validation{
Name: name,
Required: req,
Minimum: (*float64)(v),
ExclusiveMinimum: true,
})
case *jsonschema.ExclusiveMaximum:
validations = append(validations, &validation{
Name: name,
Required: req,
Maximum: (*float64)(v),
ExclusiveMaximum: true,
})
case *jsonschema.MinLength:
validations = append(validations, &validation{
Name: name,
Required: req,
MinLength: (*int)(v),
})
case *jsonschema.MaxLength:
validations = append(validations, &validation{
Name: name,
Required: req,
MaxLength: (*int)(v),
})
case *jsonschema.Pattern:
validations = append(validations, &validation{
Name: name,
Required: req,
Regex: (*regexp.Regexp)(v),
})
ctx.imports["regex"] = struct{}{}
case *jsonschema.MultipleOf:
vInt := int(*v)
if float64(vInt) != float64(*v) {
logger.GetLogger().Warningf("unsupported multipleOf value: %f", *v)
continue
}
result.Validations = append(result.Validations, validation{
Name: name,
Required: req,
MultiplyOf: &vInt,
})
case *jsonschema.UniqueItems:
if *v {
result.Validations = append(result.Validations, validation{
Name: name,
Required: req,
Unique: true,
})
}
case *jsonschema.MinItems:
result.Validations = append(result.Validations, validation{
Name: name,
Required: req,
MinLength: (*int)(v),
})
case *jsonschema.MaxItems:
result.Validations = append(result.Validations, validation{
Name: name,
Required: req,
MaxLength: (*int)(v),
})
default:
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.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
default:
logger.GetLogger().Warningf("failed to merge allOf: unsupported keyword %s", key)
}
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
default:
logger.GetLogger().Warningf("failed to merge allOf: unsupported keyword %s", key)
}
}
}
}
if len(validations) > 0 {
result.Validations = append(result.Validations, validation{
AllOf: validations,
})
}
sort.SliceStable(s.OrderedKeywords[i+1:], func(i, j int) bool {
return jsonschema.GetKeywordOrder(s.OrderedKeywords[i]) < jsonschema.GetKeywordOrder(s.OrderedKeywords[j])
})
Expand Down Expand Up @@ -449,6 +537,13 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
if result.HasIndexSignature && result.IndexSignature.validation != nil {
result.Validations = append(result.Validations, *result.IndexSignature.validation)
}
// Update AllOf validation required fields
for i := range result.Validations {
for j := range result.Validations[i].AllOf {
result.Validations[i].AllOf[j].Name = result.Validations[i].Name
result.Validations[i].AllOf[j].Required = result.Validations[i].Required
}
}
result.property.Name = convertPropertyName(result.Name, ctx.castingOption)
result.property.Description = result.Description
return result
Expand Down
3 changes: 3 additions & 0 deletions pkg/tools/gen/templates/kcl/validator.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@
{{- if .Unique }}
isunique({{ formatName .Name }}){{ if not .Required }} if {{ formatName .Name }}{{ end }}
{{- end }}
{{- if .AllOf }}
{{- template "validator" .AllOf }}
{{- end }}
{{- end -}}
25 changes: 25 additions & 0 deletions pkg/tools/gen/testdata/jsonschema/allof-validation/expect.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
This file was generated by the KCL auto-gen tool. DO NOT EDIT.
Editing this file might prove futile when you re-run the KCL auto-gen generate command.
"""
import regex

schema Config:
r"""
Schema for representing a config information.

Attributes
----------
name : str, required
price : float, optional
"""

name: str
price?: float

check:
regex.match(name, r"198.160")
regex.match(name, r"198.161")
regex.match(name, r"198.162")
price >= 0 if price

29 changes: 29 additions & 0 deletions pkg/tools/gen/testdata/jsonschema/allof-validation/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/schemas/config",
"description": "Schema for representing a config information.",
"type": "object",
"properties": {
"name": {
"type": "string",
"allOf": [
{
"pattern": "198.160"
},
{
"pattern": "198.161"
},
{
"pattern": "198.162"
}
]
},
"price": {
"type": "number",
"minimum": 0
}
},
"required": [
"name"
]
}

0 comments on commit ea9c7a5

Please sign in to comment.