Skip to content

Commit

Permalink
feat(gen): add minProperties/maxProperties support
Browse files Browse the repository at this point in the history
  • Loading branch information
tdakkota committed Feb 4, 2022
1 parent d3f85ed commit 777ddb6
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 1 deletion.
23 changes: 22 additions & 1 deletion gen/_template/json/encoders_map.tmpl
@@ -1,6 +1,7 @@
{{- /*gotype: github.com/ogen-go/ogen/internal/ir.Type*/ -}}
{{- define "json/encoders_map" }}
{{- $fields := $.JSON.Fields }}
{{- $va := $.Validators }}
// Encode implements json.Marshaler.
func (s {{ $.Name }}) Encode(e *jx.Writer) {
e.ObjStart()
Expand Down Expand Up @@ -41,6 +42,9 @@ func (s {{ if $fields.NotEmpty }}*{{ end }}{{ $.Name }}) Decode(d *jx.Decoder) e
{{- if $fields.HasRequired }}
var requiredBitSet [{{ len $requiredMask }}]uint8
{{- end }}
{{- if $va.Object.Set }}
var propertiesCount int
{{- end }}

m := s{{ if $fields.NotEmpty }}.{{ $.AdditionalPropsField }}
if m == nil {
Expand All @@ -49,6 +53,9 @@ func (s {{ if $fields.NotEmpty }}*{{ end }}{{ $.Name }}) Decode(d *jx.Decoder) e
}
{{- end }}
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
{{- if $va.Object.Set }}
propertiesCount++
{{- end }}
switch string(k) {
{{- range $i, $f := $fields }}
case {{ quote $f.Tag.JSON }}:
Expand All @@ -75,10 +82,24 @@ func (s {{ if $fields.NotEmpty }}*{{ end }}{{ $.Name }}) Decode(d *jx.Decoder) e
}
return nil
}); err != nil {
return err
return errors.Wrap(err, {{ printf "decode %s" $.Name | quote }})
}

{{- if $va.Object.Set }}
{{- $v := $va.Object }}
// Validate properties count.
if err := (validate.Object{
MinProperties: {{ $v.MinProperties }},
MinPropertiesSet: {{ $v.MinPropertiesSet }},
MaxProperties: {{ $v.MaxProperties }},
MaxPropertiesSet: {{ $v.MaxPropertiesSet }},
}).ValidateProperties(propertiesCount); err != nil {
return errors.Wrap(err, "object")
}
{{- end }}

{{- if $fields.HasRequired }}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [{{ len $requiredMask }}]uint8{
{{- range $mask := $requiredMask }}
Expand Down
21 changes: 21 additions & 0 deletions gen/_template/json/encoders_struct.tmpl
Expand Up @@ -25,6 +25,7 @@
{{- define "json/encoders_struct" }}
{{- /*gotype: github.com/ogen-go/ogen/internal/ir.Type*/ -}}
{{- $fields := $.JSON.Fields }}
{{- $va := $.Validators }}
// Encode implements json.Marshaler.
func (s {{ $.Name }}) Encode(e *jx.Writer) {
e.ObjStart()
Expand Down Expand Up @@ -52,8 +53,14 @@ func (s *{{ $.Name }}) Decode(d *jx.Decoder) error {
{{- if $fields.HasRequired }}
var requiredBitSet [{{ len $requiredMask }}]uint8
{{- end }}
{{- if $va.Object.Set }}
var propertiesCount int
{{- end }}

if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
{{- if $va.Object.Set }}
propertiesCount++
{{- end }}
switch string(k) {
{{- range $i, $f := $fields }}
case {{ quote $f.Tag.JSON }}:
Expand All @@ -76,7 +83,21 @@ func (s *{{ $.Name }}) Decode(d *jx.Decoder) error {
return errors.Wrap(err, {{ printf "decode %s" $.Name | quote }})
}

{{- if $va.Object.Set }}
{{- $v := $va.Object }}
// Validate properties count.
if err := (validate.Object{
MinProperties: {{ $v.MinProperties }},
MinPropertiesSet: {{ $v.MinPropertiesSet }},
MaxProperties: {{ $v.MaxProperties }},
MaxPropertiesSet: {{ $v.MaxPropertiesSet }},
}).ValidateProperties(propertiesCount); err != nil {
return errors.Wrap(err, "object")
}
{{- end }}

{{- if $fields.HasRequired }}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [{{ len $requiredMask }}]uint8{
{{- range $mask := $requiredMask }}
Expand Down
1 change: 1 addition & 0 deletions gen/schema_gen.go
Expand Up @@ -104,6 +104,7 @@ func (g *schemaGen) generate(name string, schema *oas.Schema) (_ *ir.Type, err e
Name: name,
Schema: schema,
})
s.Validators.SetObject(schema)

for i := range schema.Properties {
prop := schema.Properties[i]
Expand Down
4 changes: 4 additions & 0 deletions gen/schema_gen_sum.go
Expand Up @@ -84,6 +84,10 @@ func (g *schemaGen) anyOf(name string, schema *oas.Schema) (*ir.Type, error) {
if !v.Validators.Array.Set() {
v.Validators.SetArray(schema)
}
case ir.KindMap, ir.KindStruct:
if !v.Validators.Object.Set() {
v.Validators.SetObject(schema)
}
}
}
return sum, nil
Expand Down
13 changes: 13 additions & 0 deletions internal/ir/validation.go
Expand Up @@ -13,6 +13,7 @@ type Validators struct {
String validate.String
Int validate.Int
Array validate.Array
Object validate.Object
}

func (v *Validators) SetString(schema *oas.Schema) (err error) {
Expand Down Expand Up @@ -60,6 +61,15 @@ func (v *Validators) SetArray(schema *oas.Schema) {
}
}

func (v *Validators) SetObject(schema *oas.Schema) {
if schema.MaxProperties != nil {
v.Object.SetMaxProperties(int(*schema.MaxProperties))
}
if schema.MinProperties != nil {
v.Object.SetMinProperties(int(*schema.MinProperties))
}
}

func (t *Type) NeedValidation() bool {
return t.needValidation(&walkpath{})
}
Expand Down Expand Up @@ -122,6 +132,9 @@ func (t *Type) needValidation(path *walkpath) (result bool) {
}
return false
case KindMap:
if t.Validators.Object.Set() {
return true
}
return t.Item.needValidation(path)
case KindStream, KindAny:
// FIXME(tdakkota): try to validate Any.
Expand Down
40 changes: 40 additions & 0 deletions validate/object.go
@@ -0,0 +1,40 @@
package validate

import "github.com/go-faster/errors"

// Object validates map length.
type Object struct {
MinProperties int
MinPropertiesSet bool
MaxProperties int
MaxPropertiesSet bool
}

// SetMinProperties sets MinProperties validation.
func (t *Object) SetMinProperties(v int) {
t.MinPropertiesSet = true
t.MinProperties = v
}

// SetMaxProperties sets MaxProperties validation.
func (t *Object) SetMaxProperties(v int) {
t.MaxPropertiesSet = true
t.MaxProperties = v
}

// Set reports whether any validations are set.
func (t Object) Set() bool {
return t.MaxPropertiesSet || t.MinPropertiesSet
}

// ValidateProperties returns error if object length (properties number) v is invalid.
func (t Object) ValidateProperties(v int) error {
if t.MaxPropertiesSet && v > t.MaxProperties {
return errors.Errorf("len %d greater than maximum %d", v, t.MaxProperties)
}
if t.MinPropertiesSet && v < t.MinProperties {
return errors.Errorf("len %d less than minimum %d", v, t.MinProperties)
}

return nil
}
99 changes: 99 additions & 0 deletions validate/object_test.go
@@ -0,0 +1,99 @@
package validate

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestObject_Set(t *testing.T) {
var v Object
v.SetMaxProperties(10)
v.SetMinProperties(5)
require.True(t, v.Set())
require.Equal(t, Object{
MinProperties: 5,
MinPropertiesSet: true,
MaxProperties: 10,
MaxPropertiesSet: true,
}, v)
require.NoError(t, v.ValidateProperties(7))
}

func TestObject_ValidateProperties(t *testing.T) {
for _, tc := range []struct {
Name string
Validator Object
Value int
Valid bool
}{
{Name: "Zero", Valid: true},
{
Name: "MaxPropertiesOk",
Validator: Object{MaxProperties: 10, MaxPropertiesSet: true},
Value: 5,
Valid: true,
},
{
Name: "MaxPropertiesErr",
Validator: Object{MaxProperties: 10, MaxPropertiesSet: true},
Value: 15,
Valid: false,
},
{
Name: "MinPropertiesOk",
Validator: Object{MinProperties: 10, MinPropertiesSet: true},
Value: 15,
Valid: true,
},
{
Name: "MinPropertiesErr",
Validator: Object{MinProperties: 10, MinPropertiesSet: true},
Value: 5,
Valid: false,
},
{
Name: "BothOk",
Validator: Object{
MinProperties: 10,
MinPropertiesSet: true,
MaxProperties: 15,
MaxPropertiesSet: true,
},
Value: 12,
Valid: true,
},
{
Name: "BothErrMax",
Validator: Object{
MinProperties: 10,
MinPropertiesSet: true,
MaxProperties: 15,
MaxPropertiesSet: true,
},
Value: 17,
Valid: false,
},
{
Name: "BothErrMin",
Validator: Object{
MinProperties: 10,
MinPropertiesSet: true,
MaxProperties: 15,
MaxPropertiesSet: true,
},
Value: 7,
Valid: false,
},
} {
t.Run(tc.Name, func(t *testing.T) {
valid := tc.Validator.ValidateProperties(tc.Value) == nil
assert.Equal(t, tc.Valid, valid, "max: %d, min: %d, v: %d",
tc.Validator.MaxProperties,
tc.Validator.MinProperties,
tc.Value,
)
})
}
}

0 comments on commit 777ddb6

Please sign in to comment.