Skip to content

Commit ea9c7a5

Browse files
authored
feat: jsonschema allof validations (#372)
Signed-off-by: peefy <[email protected]>
1 parent 5664cd0 commit ea9c7a5

File tree

4 files changed

+174
-22
lines changed

4 files changed

+174
-22
lines changed

pkg/tools/gen/genkcl_jsonschema.go

+117-22
Original file line numberDiff line numberDiff line change
@@ -385,36 +385,124 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
385385
}
386386
case *jsonschema.AllOf:
387387
schs := *v
388+
var validations []*validation
389+
_, req := required[name]
388390
for i := 0; i < len(schs); i++ {
389391
sch := schs[i]
390392
for _, key := range sch.OrderedKeywords {
391-
if _, ok := s.Keywords[key]; !ok {
392-
s.OrderedKeywords = append(s.OrderedKeywords, key)
393-
s.Keywords[key] = sch.Keywords[key]
394-
} else {
395-
switch v := sch.Keywords[key].(type) {
396-
case *jsonschema.Type:
397-
case *jsonschema.Ref:
398-
refSch := v.ResolveRef(ctx.rootSchema)
399-
if refSch == nil || refSch.OrderedKeywords == nil {
400-
logger.GetLogger().Warningf("failed to resolve ref: %s", v.Reference)
401-
continue
393+
switch v := sch.Keywords[key].(type) {
394+
case *jsonschema.Minimum:
395+
validations = append(validations, &validation{
396+
Name: name,
397+
Required: req,
398+
Minimum: (*float64)(v),
399+
ExclusiveMinimum: false,
400+
})
401+
case *jsonschema.Maximum:
402+
validations = append(validations, &validation{
403+
Name: name,
404+
Required: req,
405+
Maximum: (*float64)(v),
406+
ExclusiveMaximum: false,
407+
})
408+
case *jsonschema.ExclusiveMinimum:
409+
validations = append(validations, &validation{
410+
Name: name,
411+
Required: req,
412+
Minimum: (*float64)(v),
413+
ExclusiveMinimum: true,
414+
})
415+
case *jsonschema.ExclusiveMaximum:
416+
validations = append(validations, &validation{
417+
Name: name,
418+
Required: req,
419+
Maximum: (*float64)(v),
420+
ExclusiveMaximum: true,
421+
})
422+
case *jsonschema.MinLength:
423+
validations = append(validations, &validation{
424+
Name: name,
425+
Required: req,
426+
MinLength: (*int)(v),
427+
})
428+
case *jsonschema.MaxLength:
429+
validations = append(validations, &validation{
430+
Name: name,
431+
Required: req,
432+
MaxLength: (*int)(v),
433+
})
434+
case *jsonschema.Pattern:
435+
validations = append(validations, &validation{
436+
Name: name,
437+
Required: req,
438+
Regex: (*regexp.Regexp)(v),
439+
})
440+
ctx.imports["regex"] = struct{}{}
441+
case *jsonschema.MultipleOf:
442+
vInt := int(*v)
443+
if float64(vInt) != float64(*v) {
444+
logger.GetLogger().Warningf("unsupported multipleOf value: %f", *v)
445+
continue
446+
}
447+
result.Validations = append(result.Validations, validation{
448+
Name: name,
449+
Required: req,
450+
MultiplyOf: &vInt,
451+
})
452+
case *jsonschema.UniqueItems:
453+
if *v {
454+
result.Validations = append(result.Validations, validation{
455+
Name: name,
456+
Required: req,
457+
Unique: true,
458+
})
459+
}
460+
case *jsonschema.MinItems:
461+
result.Validations = append(result.Validations, validation{
462+
Name: name,
463+
Required: req,
464+
MinLength: (*int)(v),
465+
})
466+
case *jsonschema.MaxItems:
467+
result.Validations = append(result.Validations, validation{
468+
Name: name,
469+
Required: req,
470+
MaxLength: (*int)(v),
471+
})
472+
default:
473+
if _, ok := s.Keywords[key]; !ok {
474+
s.OrderedKeywords = append(s.OrderedKeywords, key)
475+
s.Keywords[key] = sch.Keywords[key]
476+
} else {
477+
switch v := sch.Keywords[key].(type) {
478+
case *jsonschema.Type:
479+
case *jsonschema.Ref:
480+
refSch := v.ResolveRef(ctx.rootSchema)
481+
if refSch == nil || refSch.OrderedKeywords == nil {
482+
logger.GetLogger().Warningf("failed to resolve ref: %s", v.Reference)
483+
continue
484+
}
485+
schs = append(schs, refSch)
486+
case *jsonschema.Properties:
487+
props := *s.Keywords[key].(*jsonschema.Properties)
488+
props = append(props, *v...)
489+
s.Keywords[key] = &props
490+
case *jsonschema.Required:
491+
reqs := *s.Keywords[key].(*jsonschema.Required)
492+
reqs = append(reqs, *v...)
493+
s.Keywords[key] = &reqs
494+
default:
495+
logger.GetLogger().Warningf("failed to merge allOf: unsupported keyword %s", key)
402496
}
403-
schs = append(schs, refSch)
404-
case *jsonschema.Properties:
405-
props := *s.Keywords[key].(*jsonschema.Properties)
406-
props = append(props, *v...)
407-
s.Keywords[key] = &props
408-
case *jsonschema.Required:
409-
reqs := *s.Keywords[key].(*jsonschema.Required)
410-
reqs = append(reqs, *v...)
411-
s.Keywords[key] = &reqs
412-
default:
413-
logger.GetLogger().Warningf("failed to merge allOf: unsupported keyword %s", key)
414497
}
415498
}
416499
}
417500
}
501+
if len(validations) > 0 {
502+
result.Validations = append(result.Validations, validation{
503+
AllOf: validations,
504+
})
505+
}
418506
sort.SliceStable(s.OrderedKeywords[i+1:], func(i, j int) bool {
419507
return jsonschema.GetKeywordOrder(s.OrderedKeywords[i]) < jsonschema.GetKeywordOrder(s.OrderedKeywords[j])
420508
})
@@ -449,6 +537,13 @@ func convertSchemaFromJsonSchema(ctx *convertContext, s *jsonschema.Schema, name
449537
if result.HasIndexSignature && result.IndexSignature.validation != nil {
450538
result.Validations = append(result.Validations, *result.IndexSignature.validation)
451539
}
540+
// Update AllOf validation required fields
541+
for i := range result.Validations {
542+
for j := range result.Validations[i].AllOf {
543+
result.Validations[i].AllOf[j].Name = result.Validations[i].Name
544+
result.Validations[i].AllOf[j].Required = result.Validations[i].Required
545+
}
546+
}
452547
result.property.Name = convertPropertyName(result.Name, ctx.castingOption)
453548
result.property.Description = result.Description
454549
return result

pkg/tools/gen/templates/kcl/validator.gotmpl

+3
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,7 @@
2020
{{- if .Unique }}
2121
isunique({{ formatName .Name }}){{ if not .Required }} if {{ formatName .Name }}{{ end }}
2222
{{- end }}
23+
{{- if .AllOf }}
24+
{{- template "validator" .AllOf }}
25+
{{- end }}
2326
{{- end -}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""
2+
This file was generated by the KCL auto-gen tool. DO NOT EDIT.
3+
Editing this file might prove futile when you re-run the KCL auto-gen generate command.
4+
"""
5+
import regex
6+
7+
schema Config:
8+
r"""
9+
Schema for representing a config information.
10+
11+
Attributes
12+
----------
13+
name : str, required
14+
price : float, optional
15+
"""
16+
17+
name: str
18+
price?: float
19+
20+
check:
21+
regex.match(name, r"198.160")
22+
regex.match(name, r"198.161")
23+
regex.match(name, r"198.162")
24+
price >= 0 if price
25+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$id": "https://example.com/schemas/config",
4+
"description": "Schema for representing a config information.",
5+
"type": "object",
6+
"properties": {
7+
"name": {
8+
"type": "string",
9+
"allOf": [
10+
{
11+
"pattern": "198.160"
12+
},
13+
{
14+
"pattern": "198.161"
15+
},
16+
{
17+
"pattern": "198.162"
18+
}
19+
]
20+
},
21+
"price": {
22+
"type": "number",
23+
"minimum": 0
24+
}
25+
},
26+
"required": [
27+
"name"
28+
]
29+
}

0 commit comments

Comments
 (0)