Skip to content

Commit eaa9ebc

Browse files
committed
feat: const validation support
fixes #472
1 parent cad47e4 commit eaa9ebc

File tree

5 files changed

+297
-2
lines changed

5 files changed

+297
-2
lines changed

pkg/generator/schema_generator.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,10 +392,19 @@ func (g *schemaGenerator) structFieldValidators(
392392
case codegen.PrimitiveType:
393393
if v.Type == schemas.TypeNameString {
394394
hasPattern := len(f.SchemaType.Pattern) != 0
395-
if f.SchemaType.MinLength != 0 || f.SchemaType.MaxLength != 0 || hasPattern {
395+
if f.SchemaType.MinLength != 0 || f.SchemaType.MaxLength != 0 || hasPattern || f.SchemaType.Const != nil {
396396
// Double escape the escape characters so we don't effectively parse the escapes within the value.
397397
escapedPattern := f.SchemaType.Pattern
398398

399+
var constVal *string
400+
if f.SchemaType.Const != nil {
401+
if s, ok := f.SchemaType.Const.(string); ok {
402+
constVal = &s
403+
} else {
404+
g.warner(fmt.Sprintf("Ignoring non string const value: %v", f.SchemaType.Const))
405+
}
406+
}
407+
399408
replaceJSONCharactersBy := []string{"\\b", "\\f", "\\n", "\\r", "\\t"}
400409

401410
replaceJSONCharacters := []string{"\b", "\f", "\n", "\r", "\t"}
@@ -410,6 +419,7 @@ func (g *schemaGenerator) structFieldValidators(
410419
minLength: f.SchemaType.MinLength,
411420
maxLength: f.SchemaType.MaxLength,
412421
pattern: escapedPattern,
422+
constVal: constVal,
413423
isNillable: isNillable,
414424
})
415425
}
@@ -422,7 +432,8 @@ func (g *schemaGenerator) structFieldValidators(
422432
f.SchemaType.Maximum != nil ||
423433
f.SchemaType.ExclusiveMaximum != nil ||
424434
f.SchemaType.Minimum != nil ||
425-
f.SchemaType.ExclusiveMinimum != nil {
435+
f.SchemaType.ExclusiveMinimum != nil ||
436+
f.SchemaType.Const != nil {
426437
validators = append(validators, &numericValidator{
427438
jsonName: f.JSONName,
428439
fieldName: f.Name,
@@ -432,13 +443,31 @@ func (g *schemaGenerator) structFieldValidators(
432443
exclusiveMaximum: f.SchemaType.ExclusiveMaximum,
433444
minimum: f.SchemaType.Minimum,
434445
exclusiveMinimum: f.SchemaType.ExclusiveMinimum,
446+
constVal: f.SchemaType.Const,
435447
roundToInt: strings.Contains(v.Type, "int"),
436448
})
437449
}
438450

439451
if f.SchemaType.MultipleOf != nil && v.Type == float64Type {
440452
g.output.file.Package.AddImport("math", "")
441453
}
454+
} else if v.Type == "bool" {
455+
if f.SchemaType.Const != nil {
456+
var constVal *bool
457+
if f.SchemaType.Const != nil {
458+
if b, ok := f.SchemaType.Const.(bool); ok {
459+
constVal = &b
460+
} else {
461+
g.warner(fmt.Sprintf("Ignoring non boolean const value: %v", f.SchemaType.Const))
462+
}
463+
}
464+
validators = append(validators, &booleanValidator{
465+
jsonName: f.JSONName,
466+
fieldName: f.Name,
467+
isNillable: isNillable,
468+
constVal: constVal,
469+
})
470+
}
442471
}
443472

444473
case *codegen.ArrayType:

pkg/generator/validator.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ type stringValidator struct {
348348
maxLength int
349349
isNillable bool
350350
pattern string
351+
constVal *string
351352
}
352353

353354
func (v *stringValidator) generate(out *codegen.Emitter, unmarshalTemplate string) error {
@@ -385,6 +386,14 @@ func (v *stringValidator) generate(out *codegen.Emitter, unmarshalTemplate strin
385386
}
386387
}
387388

389+
if v.constVal != nil {
390+
out.Printlnf(`if %s%s%s != "%s" {`, checkPointer, pointerPrefix, value, *v.constVal)
391+
out.Indent(1)
392+
out.Printlnf(`return fmt.Errorf("field %%s: must be equal to %%s", "%s", "%s")`, fieldName, *v.constVal)
393+
out.Indent(-1)
394+
out.Printlnf("}")
395+
}
396+
388397
if v.minLength == 0 && v.maxLength == 0 {
389398
return nil
390399
}
@@ -424,6 +433,7 @@ type numericValidator struct {
424433
exclusiveMaximum *any
425434
minimum *float64
426435
exclusiveMinimum *any
436+
constVal any
427437
roundToInt bool
428438
}
429439

@@ -437,6 +447,14 @@ func (v *numericValidator) generate(out *codegen.Emitter, unmarshalTemplate stri
437447
pointerPrefix = "*"
438448
}
439449

450+
if v.constVal != nil {
451+
out.Printlnf(`if %s%s%s != %v {`, checkPointer, pointerPrefix, value, v.constVal)
452+
out.Indent(1)
453+
out.Printlnf(`return fmt.Errorf("field %%s: must be equal to %%v", "%s", %v)`, v.jsonName, v.constVal)
454+
out.Indent(-1)
455+
out.Printlnf("}")
456+
}
457+
440458
if v.multipleOf != nil {
441459
if v.roundToInt {
442460
out.Printlnf(`if %s %s%s %% %v != 0 {`, checkPointer, pointerPrefix, value, v.valueOf(*v.multipleOf))
@@ -519,6 +537,42 @@ func (v *numericValidator) valueOf(val float64) any {
519537
return val
520538
}
521539

540+
type booleanValidator struct {
541+
jsonName string
542+
fieldName string
543+
isNillable bool
544+
constVal *bool
545+
}
546+
547+
func (v *booleanValidator) generate(out *codegen.Emitter, unmarshalTemplate string) error {
548+
value := getPlainName(v.fieldName)
549+
fieldName := v.jsonName
550+
checkPointer := ""
551+
pointerPrefix := ""
552+
553+
if v.isNillable {
554+
checkPointer = fmt.Sprintf("%s != nil && ", value)
555+
pointerPrefix = "*"
556+
}
557+
558+
if v.constVal != nil {
559+
out.Printlnf(`if %s%s%s != %t {`, checkPointer, pointerPrefix, value, *v.constVal)
560+
out.Indent(1)
561+
out.Printlnf(`return fmt.Errorf("field %%s: must be equal to %%t", "%s", %t)`, fieldName, *v.constVal)
562+
out.Indent(-1)
563+
out.Printlnf("}")
564+
}
565+
566+
return nil
567+
}
568+
569+
func (v *booleanValidator) desc() *validatorDesc {
570+
return &validatorDesc{
571+
hasError: true,
572+
beforeJSONUnmarshal: false,
573+
}
574+
}
575+
522576
func getPlainName(fieldName string) string {
523577
if fieldName == "" {
524578
return varNamePlainStruct

pkg/schemas/model.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ type Type struct {
170170
AdditionalProperties *Type `json:"additionalProperties,omitempty"` // Section 5.18.
171171
Enum []interface{} `json:"enum,omitempty"` // Section 5.20.
172172
Type TypeList `json:"type,omitempty"` // Section 5.21.
173+
Const interface{} `json:"const,omitempty"`
173174
// RFC draft-bhutton-json-schema-01, section 10.
174175
AllOf []*Type `json:"allOf,omitempty"` // Section 10.2.1.1.
175176
AnyOf []*Type `json:"anyOf,omitempty"` // Section 10.2.1.2.

tests/data/core/const/const.go

Lines changed: 159 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/data/core/const/const.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"id": "https://example.com/primitives",
4+
"type": "object",
5+
"properties": {
6+
"myString": {
7+
"type": "string",
8+
"const": "foo"
9+
},
10+
"myNumber": {
11+
"type": "number",
12+
"const": 4.2
13+
},
14+
"myInteger": {
15+
"type": "integer",
16+
"const": 42
17+
},
18+
"myBoolean": {
19+
"type": "boolean",
20+
"const": true
21+
}
22+
},
23+
"$defs": {
24+
"Required": {
25+
"type": "object",
26+
"properties": {
27+
"myString": {
28+
"type": "string",
29+
"const": "foo"
30+
},
31+
"myNumber": {
32+
"type": "number",
33+
"const": 4.2
34+
},
35+
"myInteger": {
36+
"type": "integer",
37+
"const": 42
38+
},
39+
"myBoolean": {
40+
"type": "boolean",
41+
"const": true
42+
}
43+
},
44+
"required": [
45+
"myString",
46+
"myNumber",
47+
"myInteger",
48+
"myBoolean"
49+
]
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)