Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(jsonschema): move default value type check to generator #877

Merged
merged 2 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions _testdata/negative/wrong_default_type/array_value.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"openapi": "3.0.3",
"info": {
"title": "title",
"version": "v0.1.0"
},
"paths": {
"/foo": {
"get": {
"responses": {
"200": {
"description": "User info",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"not_nullable": {
"type": "integer",
"default": []
}
}
}
}
}
}
}
}
}
}
}
31 changes: 31 additions & 0 deletions _testdata/negative/wrong_default_type/bool_value.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"openapi": "3.0.3",
"info": {
"title": "title",
"version": "v0.1.0"
},
"paths": {
"/foo": {
"get": {
"responses": {
"200": {
"description": "User info",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"not_nullable": {
"type": "integer",
"default": false
}
}
}
}
}
}
}
}
}
}
}
31 changes: 31 additions & 0 deletions _testdata/negative/wrong_default_type/float_value.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"openapi": "3.0.3",
"info": {
"title": "title",
"version": "v0.1.0"
},
"paths": {
"/foo": {
"get": {
"responses": {
"200": {
"description": "User info",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"not_nullable": {
"type": "integer",
"default": 3.14
}
}
}
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
"application/json": {
"schema": {
"type": "object",
"required": [
"not_nullable"
],
"properties": {
"not_nullable": {
"type": "integer",
Expand Down
31 changes: 31 additions & 0 deletions _testdata/negative/wrong_default_type/object_value.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"openapi": "3.0.3",
"info": {
"title": "title",
"version": "v0.1.0"
},
"paths": {
"/foo": {
"get": {
"responses": {
"200": {
"description": "User info",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"not_nullable": {
"type": "integer",
"default": {}
}
}
}
}
}
}
}
}
}
}
}
31 changes: 31 additions & 0 deletions _testdata/negative/wrong_default_type/string_value.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"openapi": "3.0.3",
"info": {
"title": "title",
"version": "v0.1.0"
},
"paths": {
"/foo": {
"get": {
"responses": {
"200": {
"description": "User info",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"not_nullable": {
"type": "integer",
"default": "amongus"
}
}
}
}
}
}
}
}
}
}
}
64 changes: 61 additions & 3 deletions gen/schema_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,25 @@ func (g *schemaGen) generate2(name string, schema *jsonschema.Schema) (ret *ir.T

if schema.DefaultSet {
var implErr error
switch schema.Type {
case jsonschema.Object:
switch {
case schema.Type == jsonschema.Object:
implErr = &ErrNotImplemented{Name: "object defaults"}
case jsonschema.Array:
case schema.Type == jsonschema.Array:
implErr = &ErrNotImplemented{Name: "array defaults"}
case schema.Type == jsonschema.Empty ||
len(schema.AnyOf)+len(schema.OneOf) > 0:
implErr = &ErrNotImplemented{Name: "complex defaults"}
}
// Do not fail schema generation if we cannot handle defaults.
if err := g.fail(implErr); err != nil {
return nil, err
}

if implErr == nil {
if err := g.checkDefaultType(schema, schema.Default); err != nil {
return nil, errors.Wrap(err, "check default type")
}
}
schema.DefaultSet = implErr == nil
}

Expand Down Expand Up @@ -494,3 +503,52 @@ func (g *schemaGen) regtype(name string, t *ir.Type) *ir.Type {

return t
}

func (g *schemaGen) checkDefaultType(s *jsonschema.Schema, val any) error {
if s == nil {
// Schema has no validators.
return nil
}
if val == nil && s.Nullable {
return nil
}

var ok bool
switch s.Type {
case jsonschema.Object:
_, ok = val.(map[string]any)
case jsonschema.Array:
_, ok = val.([]any)
case jsonschema.Integer:
_, ok = val.(int64)
case jsonschema.Number:
_, ok = val.(int64)
if !ok {
_, ok = val.(float64)
}
case jsonschema.String:
_, ok = val.(string)
case jsonschema.Boolean:
_, ok = val.(bool)
case jsonschema.Null:
ok = val == nil
}

if !ok {
err := errors.Errorf("expected schema type is %q, default value is %T", s.Type, val)
p := s.Pointer.Field("default")

pos, ok := p.Position()
if !ok {
return err
}

return &location.Error{
File: p.File(),
Pos: pos,
Err: err,
}
}

return nil
}
6 changes: 1 addition & 5 deletions jsonschema/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,11 @@ func (p *Parser) parse1(schema *RawSchema, ctx *jsonpointer.ResolveCtx, hook fun
}
if d := schema.Default; len(d) > 0 {
if err := func() error {
v, err := parseJSONValue(s, json.RawMessage(d))
v, err := parseJSONValue(nil, json.RawMessage(d))
if err != nil {
return err
}

if v == nil && !s.Nullable {
return errors.New("unexpected default \"null\" value")
}

s.Default = v
s.DefaultSet = true
return nil
Expand Down
69 changes: 29 additions & 40 deletions jsonschema/parser_enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,65 +41,54 @@ func parseEnumValues(s *Schema, rawValues []json.RawMessage) ([]any, error) {
func parseJSONValue(root *Schema, v json.RawMessage) (any, error) {
var parse func(s *Schema, d *jx.Decoder) (any, error)
parse = func(s *Schema, d *jx.Decoder) (any, error) {
next := d.Next()
if next == jx.Null {
// We do not check nullability here because enum with null value is completely valid
// even if it is not nullable.
return nil, nil
}
switch typ := s.Type; typ {
case String:
if next != jx.String {
return nil, errors.Errorf("expected type %q, got %q", typ, next)
}
switch next := d.Next(); next {
case jx.Null:
return nil, d.Null()
case jx.String:
return d.Str()
case Integer:
if next != jx.Number {
return nil, errors.Errorf("expected type %q, got %q", typ, next)
}
case jx.Number:
n, err := d.Num()
if err != nil {
return nil, err
}
if !n.IsInt() {
return nil, errors.New("expected integer, got float")
}
return n.Int64()
case Number:
if next != jx.Number {
return nil, errors.Errorf("expected type %q, got %q", typ, next)
}
n, err := d.Num()
if err != nil {
return nil, err
if n.IsInt() {
return n.Int64()
}
return n.Float64()
case Boolean:
if next != jx.Bool {
return nil, errors.Errorf("expected type %q, got %q", typ, next)
}
case jx.Bool:
return d.Bool()
case Array:
if next != jx.Array {
return nil, errors.Errorf("expected type %q, got %q", typ, next)
}
if s.Item == nil {
return nil, errors.New("can't validate untyped array item")
}
case jx.Array:
var arr []any
if err := d.Arr(func(d *jx.Decoder) error {
v, err := parse(s.Item, d)
var item *Schema
if s != nil {
item = s.Item
}
v, err := parse(item, d)
if err != nil {
return errors.Wrap(err, "validate item")
return errors.Wrap(err, "array item")
}
arr = append(arr, v)
return nil
}); err != nil {
return nil, err
}
return arr, nil
case jx.Object:
obj := map[string]any{}
if err := d.ObjBytes(func(d *jx.Decoder, key []byte) error {
v, err := parse(nil, d)
if err != nil {
return errors.Wrapf(err, "property %q", key)
}
obj[string(key)] = v
return nil
}); err != nil {
return nil, err
}
return obj, nil
default:
return nil, errors.Errorf("unexpected type: %q", typ)
return nil, errors.Errorf("unexpected type: %q", next)
}
}

Expand Down