From b31b4168c86df0c34a69226959adc429c23f793d Mon Sep 17 00:00:00 2001 From: candiduslynx Date: Mon, 25 Sep 2023 09:13:42 +0300 Subject: [PATCH 1/6] tidy up --- .gitignore | 2 ++ go.mod | 1 - go.sum | 2 -- reflect.go | 12 ++++++------ 4 files changed, 8 insertions(+), 9 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ef0e14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor/ +.idea/ diff --git a/go.mod b/go.mod index dcf7130..49c0e35 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/invopop/jsonschema go 1.18 require ( - github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 github.com/stretchr/testify v1.8.1 github.com/wk8/go-ordered-map/v2 v2.1.8 ) diff --git a/go.sum b/go.sum index 2cfaaf2..025db2e 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,6 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= -github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= diff --git a/reflect.go b/reflect.go index acccd07..3f7a862 100644 --- a/reflect.go +++ b/reflect.go @@ -676,7 +676,7 @@ func (t *Schema) structKeywordsFromTags(f reflect.StructField, parent *Schema, p t.extraKeywords(extras) } -// read struct tags for generic keyworks +// read struct tags for generic keywords func (t *Schema) genericKeywords(tags []string, parent *Schema, propertyName string) { //nolint:gocyclo for _, tag := range tags { nameValue := strings.Split(tag, "=") @@ -789,7 +789,7 @@ func (t *Schema) genericKeywords(tags []string, parent *Schema, propertyName str } } -// read struct tags for boolean type keyworks +// read struct tags for boolean type keywords func (t *Schema) booleanKeywords(tags []string) { for _, tag := range tags { nameValue := strings.Split(tag, "=") @@ -807,7 +807,7 @@ func (t *Schema) booleanKeywords(tags []string) { } } -// read struct tags for string type keyworks +// read struct tags for string type keywords func (t *Schema) stringKeywords(tags []string) { for _, tag := range tags { nameValue := strings.Split(tag, "=") @@ -842,7 +842,7 @@ func (t *Schema) stringKeywords(tags []string) { } } -// read struct tags for numerical type keyworks +// read struct tags for numerical type keywords func (t *Schema) numericalKeywords(tags []string) { for _, tag := range tags { nameValue := strings.Split(tag, "=") @@ -876,7 +876,7 @@ func (t *Schema) numericalKeywords(tags []string) { } } -// read struct tags for object type keyworks +// read struct tags for object type keywords // func (t *Type) objectKeywords(tags []string) { // for _, tag := range tags{ // nameValue := strings.Split(tag, "=") @@ -892,7 +892,7 @@ func (t *Schema) numericalKeywords(tags []string) { // } // } -// read struct tags for array type keyworks +// read struct tags for array type keywords func (t *Schema) arrayKeywords(tags []string) { var defaultValues []interface{} for _, tag := range tags { From c7f38646b92fca1ea992af2d8f7ccc21f81d3913 Mon Sep 17 00:00:00 2001 From: candiduslynx Date: Mon, 25 Sep 2023 09:19:46 +0300 Subject: [PATCH 2/6] move 6.2 elements to json.Number --- reflect.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/reflect.go b/reflect.go index 3f7a862..1014e98 100644 --- a/reflect.go +++ b/reflect.go @@ -56,11 +56,11 @@ type Schema struct { Type string `json:"type,omitempty"` // section 6.1.1 Enum []interface{} `json:"enum,omitempty"` // section 6.1.2 Const interface{} `json:"const,omitempty"` // section 6.1.3 - MultipleOf int `json:"multipleOf,omitempty"` // section 6.2.1 - Maximum int `json:"maximum,omitempty"` // section 6.2.2 - ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` // section 6.2.3 - Minimum int `json:"minimum,omitempty"` // section 6.2.4 - ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` // section 6.2.5 + MultipleOf json.Number `json:"multipleOf,omitempty"` // section 6.2.1 + Maximum json.Number `json:"maximum,omitempty"` // section 6.2.2 + ExclusiveMaximum json.Number `json:"exclusiveMaximum,omitempty"` // section 6.2.3 + Minimum json.Number `json:"minimum,omitempty"` // section 6.2.4 + ExclusiveMinimum json.Number `json:"exclusiveMinimum,omitempty"` // section 6.2.5 MaxLength int `json:"maxLength,omitempty"` // section 6.3.1 MinLength int `json:"minLength,omitempty"` // section 6.3.2 Pattern string `json:"pattern,omitempty"` // section 6.3.3 @@ -850,20 +850,15 @@ func (t *Schema) numericalKeywords(tags []string) { name, val := nameValue[0], nameValue[1] switch name { case "multipleOf": - i, _ := strconv.Atoi(val) - t.MultipleOf = i + t.MultipleOf = json.Number(val) case "minimum": - i, _ := strconv.Atoi(val) - t.Minimum = i + t.Minimum = json.Number(val) case "maximum": - i, _ := strconv.Atoi(val) - t.Maximum = i + t.Maximum = json.Number(val) case "exclusiveMaximum": - b, _ := strconv.ParseBool(val) - t.ExclusiveMaximum = b + t.ExclusiveMaximum = json.Number(val) case "exclusiveMinimum": - b, _ := strconv.ParseBool(val) - t.ExclusiveMinimum = b + t.ExclusiveMinimum = json.Number(val) case "default": n, _ := strconv.ParseFloat(val, 64) t.Default = n From b0206aa42ef7921e95277fe9f3dc01e1807e0b86 Mon Sep 17 00:00:00 2001 From: candiduslynx Date: Mon, 25 Sep 2023 09:41:32 +0300 Subject: [PATCH 3/6] move to *json.Number --- fixtures/allow_additional_props.json | 4 +-- fixtures/custom_base_schema_id.json | 4 +-- fixtures/defaults_expanded_toplevel.json | 4 +-- fixtures/ignore_type.json | 4 +-- fixtures/no_reference.json | 4 +-- fixtures/no_reference_anchor.json | 4 +-- fixtures/required_from_jsontags.json | 4 +-- fixtures/test_user.json | 4 +-- fixtures/test_user_assign_anchor.json | 4 +-- reflect.go | 43 ++++++++++++++++-------- reflect_test.go | 4 +-- 11 files changed, 49 insertions(+), 34 deletions(-) diff --git a/fixtures/allow_additional_props.json b/fixtures/allow_additional_props.json index bb4aa2c..7e89cf4 100644 --- a/fixtures/allow_additional_props.json +++ b/fixtures/allow_additional_props.json @@ -118,9 +118,9 @@ "age": { "type": "integer", "maximum": 120, - "exclusiveMaximum": true, + "exclusiveMaximum": 121, "minimum": 18, - "exclusiveMinimum": true + "exclusiveMinimum": 17 }, "email": { "type": "string", diff --git a/fixtures/custom_base_schema_id.json b/fixtures/custom_base_schema_id.json index 77f8f84..479abc6 100644 --- a/fixtures/custom_base_schema_id.json +++ b/fixtures/custom_base_schema_id.json @@ -107,9 +107,9 @@ "age": { "type": "integer", "maximum": 120, - "exclusiveMaximum": true, + "exclusiveMaximum": 121, "minimum": 18, - "exclusiveMinimum": true + "exclusiveMinimum": 17 }, "email": { "type": "string", diff --git a/fixtures/defaults_expanded_toplevel.json b/fixtures/defaults_expanded_toplevel.json index cc51d10..09d592c 100644 --- a/fixtures/defaults_expanded_toplevel.json +++ b/fixtures/defaults_expanded_toplevel.json @@ -118,9 +118,9 @@ "age": { "type": "integer", "maximum": 120, - "exclusiveMaximum": true, + "exclusiveMaximum": 121, "minimum": 18, - "exclusiveMinimum": true + "exclusiveMinimum": 17 }, "email": { "type": "string", diff --git a/fixtures/ignore_type.json b/fixtures/ignore_type.json index 1b4da5c..6e7f997 100644 --- a/fixtures/ignore_type.json +++ b/fixtures/ignore_type.json @@ -112,9 +112,9 @@ "age": { "type": "integer", "maximum": 120, - "exclusiveMaximum": true, + "exclusiveMaximum": 121, "minimum": 18, - "exclusiveMinimum": true + "exclusiveMinimum": 17 }, "email": { "type": "string", diff --git a/fixtures/no_reference.json b/fixtures/no_reference.json index eab2dce..6efab45 100644 --- a/fixtures/no_reference.json +++ b/fixtures/no_reference.json @@ -107,9 +107,9 @@ "age": { "type": "integer", "maximum": 120, - "exclusiveMaximum": true, + "exclusiveMaximum": 121, "minimum": 18, - "exclusiveMinimum": true + "exclusiveMinimum": 17 }, "email": { "type": "string", diff --git a/fixtures/no_reference_anchor.json b/fixtures/no_reference_anchor.json index 03963b2..d544d93 100644 --- a/fixtures/no_reference_anchor.json +++ b/fixtures/no_reference_anchor.json @@ -109,9 +109,9 @@ "age": { "type": "integer", "maximum": 120, - "exclusiveMaximum": true, + "exclusiveMaximum": 121, "minimum": 18, - "exclusiveMinimum": true + "exclusiveMinimum": 17 }, "email": { "type": "string", diff --git a/fixtures/required_from_jsontags.json b/fixtures/required_from_jsontags.json index a3494af..7173eaa 100644 --- a/fixtures/required_from_jsontags.json +++ b/fixtures/required_from_jsontags.json @@ -119,9 +119,9 @@ "age": { "type": "integer", "maximum": 120, - "exclusiveMaximum": true, + "exclusiveMaximum": 121, "minimum": 18, - "exclusiveMinimum": true + "exclusiveMinimum": 17 }, "email": { "type": "string", diff --git a/fixtures/test_user.json b/fixtures/test_user.json index bd32c02..97d3b3b 100644 --- a/fixtures/test_user.json +++ b/fixtures/test_user.json @@ -119,9 +119,9 @@ "age": { "type": "integer", "maximum": 120, - "exclusiveMaximum": true, + "exclusiveMaximum": 121, "minimum": 18, - "exclusiveMinimum": true + "exclusiveMinimum": 17 }, "email": { "type": "string", diff --git a/fixtures/test_user_assign_anchor.json b/fixtures/test_user_assign_anchor.json index 406c881..e5691bc 100644 --- a/fixtures/test_user_assign_anchor.json +++ b/fixtures/test_user_assign_anchor.json @@ -121,9 +121,9 @@ "age": { "type": "integer", "maximum": 120, - "exclusiveMaximum": true, + "exclusiveMaximum": 121, "minimum": 18, - "exclusiveMinimum": true + "exclusiveMinimum": 17 }, "email": { "type": "string", diff --git a/reflect.go b/reflect.go index 1014e98..af70461 100644 --- a/reflect.go +++ b/reflect.go @@ -56,11 +56,11 @@ type Schema struct { Type string `json:"type,omitempty"` // section 6.1.1 Enum []interface{} `json:"enum,omitempty"` // section 6.1.2 Const interface{} `json:"const,omitempty"` // section 6.1.3 - MultipleOf json.Number `json:"multipleOf,omitempty"` // section 6.2.1 - Maximum json.Number `json:"maximum,omitempty"` // section 6.2.2 - ExclusiveMaximum json.Number `json:"exclusiveMaximum,omitempty"` // section 6.2.3 - Minimum json.Number `json:"minimum,omitempty"` // section 6.2.4 - ExclusiveMinimum json.Number `json:"exclusiveMinimum,omitempty"` // section 6.2.5 + MultipleOf *json.Number `json:"multipleOf,omitempty"` // section 6.2.1 + Maximum *json.Number `json:"maximum,omitempty"` // section 6.2.2 + ExclusiveMaximum *json.Number `json:"exclusiveMaximum,omitempty"` // section 6.2.3 + Minimum *json.Number `json:"minimum,omitempty"` // section 6.2.4 + ExclusiveMinimum *json.Number `json:"exclusiveMinimum,omitempty"` // section 6.2.5 MaxLength int `json:"maxLength,omitempty"` // section 6.3.1 MinLength int `json:"minLength,omitempty"` // section 6.3.2 Pattern string `json:"pattern,omitempty"` // section 6.3.3 @@ -850,21 +850,23 @@ func (t *Schema) numericalKeywords(tags []string) { name, val := nameValue[0], nameValue[1] switch name { case "multipleOf": - t.MultipleOf = json.Number(val) + t.MultipleOf = toJSONNumber(val) case "minimum": - t.Minimum = json.Number(val) + t.Minimum = toJSONNumber(val) case "maximum": - t.Maximum = json.Number(val) + t.Maximum = toJSONNumber(val) case "exclusiveMaximum": - t.ExclusiveMaximum = json.Number(val) + t.ExclusiveMaximum = toJSONNumber(val) case "exclusiveMinimum": - t.ExclusiveMinimum = json.Number(val) + t.ExclusiveMinimum = toJSONNumber(val) case "default": - n, _ := strconv.ParseFloat(val, 64) - t.Default = n + if num := toJSONNumber(val); num != nil { + // toJSONNumber returns typed nil, so we need to account for that + t.Default = num + } case "example": - if i, err := strconv.Atoi(val); err == nil { - t.Examples = append(t.Examples, i) + if num := toJSONNumber(val); num != nil { + t.Examples = append(t.Examples, num) } } } @@ -1015,6 +1017,19 @@ func ignoredByJSONSchemaTags(tags []string) bool { return tags[0] == "-" } +// toJSONNumber converts string to *json.Number +// it'll return nil if the number is invalid +func toJSONNumber(s string) *json.Number { + num := json.Number(s) + if _, err := num.Int64(); err == nil { + return &num + } + if _, err := num.Float64(); err == nil { + return &num + } + return nil +} + func (r *Reflector) fieldNameTag() string { if r.FieldNameTag != "" { return r.FieldNameTag diff --git a/reflect_test.go b/reflect_test.go index 47cbddb..3dd622f 100644 --- a/reflect_test.go +++ b/reflect_test.go @@ -64,7 +64,7 @@ type TestUser struct { nonExported MapType - ID int `json:"id" jsonschema:"required"` + ID int `json:"id" jsonschema:"required,minimum=bad,maximum=bad,exclusiveMinimum=bad,exclusiveMaximum=bad,default=bad"` Name string `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex,readOnly=true"` Password string `json:"password" jsonschema:"writeOnly=true"` Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"` @@ -88,7 +88,7 @@ type TestUser struct { // Tests for jsonpb enum support Feeling ProtoEnum `json:"feeling,omitempty"` - Age int `json:"age" jsonschema:"minimum=18,maximum=120,exclusiveMaximum=true,exclusiveMinimum=true"` + Age int `json:"age" jsonschema:"minimum=18,maximum=120,exclusiveMaximum=121,exclusiveMinimum=17"` Email string `json:"email" jsonschema:"format=email"` UUID string `json:"uuid" jsonschema:"format=uuid"` From 4e3ccc03014b9d4e9edcd7f5e5ba50496d6d57ca Mon Sep 17 00:00:00 2001 From: candiduslynx Date: Mon, 25 Sep 2023 09:44:35 +0300 Subject: [PATCH 4/6] interface{} -> any --- examples/user.go | 6 ++--- examples_test.go | 16 ++++++------ reflect.go | 32 +++++++++++------------ reflect_test.go | 68 ++++++++++++++++++++++++------------------------ 4 files changed, 61 insertions(+), 61 deletions(-) diff --git a/examples/user.go b/examples/user.go index 710f12e..4c5b484 100644 --- a/examples/user.go +++ b/examples/user.go @@ -10,9 +10,9 @@ type User struct { // Unique sequential identifier. ID int `json:"id" jsonschema:"required"` // This comment will be ignored - Name string `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex"` - Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"` - Tags map[string]interface{} `json:"tags,omitempty"` + Name string `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex"` + Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"` + Tags map[string]any `json:"tags,omitempty"` // An array of pets the user cares for. Pets nested.Pets `json:"pets"` diff --git a/examples_test.go b/examples_test.go index 82cae05..5363458 100644 --- a/examples_test.go +++ b/examples_test.go @@ -9,14 +9,14 @@ import ( ) type SampleUser struct { - ID int `json:"id"` - Name string `json:"name" jsonschema:"title=the name,description=The name of a friend,example=joe,example=lucy,default=alex"` - Friends []int `json:"friends,omitempty" jsonschema_description:"The list of IDs, omitted when empty"` - Tags map[string]interface{} `json:"tags,omitempty" jsonschema_extras:"a=b,foo=bar,foo=bar1"` - BirthDate time.Time `json:"birth_date,omitempty" jsonschema:"oneof_required=date"` - YearOfBirth string `json:"year_of_birth,omitempty" jsonschema:"oneof_required=year"` - Metadata interface{} `json:"metadata,omitempty" jsonschema:"oneof_type=string;array"` - FavColor string `json:"fav_color,omitempty" jsonschema:"enum=red,enum=green,enum=blue"` + ID int `json:"id"` + Name string `json:"name" jsonschema:"title=the name,description=The name of a friend,example=joe,example=lucy,default=alex"` + Friends []int `json:"friends,omitempty" jsonschema_description:"The list of IDs, omitted when empty"` + Tags map[string]any `json:"tags,omitempty" jsonschema_extras:"a=b,foo=bar,foo=bar1"` + BirthDate time.Time `json:"birth_date,omitempty" jsonschema:"oneof_required=date"` + YearOfBirth string `json:"year_of_birth,omitempty" jsonschema:"oneof_required=year"` + Metadata any `json:"metadata,omitempty" jsonschema:"oneof_type=string;array"` + FavColor string `json:"fav_color,omitempty" jsonschema:"enum=red,enum=green,enum=blue"` } func ExampleReflect() { diff --git a/reflect.go b/reflect.go index af70461..ee0ef81 100644 --- a/reflect.go +++ b/reflect.go @@ -54,8 +54,8 @@ type Schema struct { PropertyNames *Schema `json:"propertyNames,omitempty"` // section 10.3.2.4 // RFC draft-bhutton-json-schema-validation-00, section 6 Type string `json:"type,omitempty"` // section 6.1.1 - Enum []interface{} `json:"enum,omitempty"` // section 6.1.2 - Const interface{} `json:"const,omitempty"` // section 6.1.3 + Enum []any `json:"enum,omitempty"` // section 6.1.2 + Const any `json:"const,omitempty"` // section 6.1.3 MultipleOf *json.Number `json:"multipleOf,omitempty"` // section 6.2.1 Maximum *json.Number `json:"maximum,omitempty"` // section 6.2.2 ExclusiveMaximum *json.Number `json:"exclusiveMaximum,omitempty"` // section 6.2.3 @@ -80,15 +80,15 @@ type Schema struct { ContentMediaType string `json:"contentMediaType,omitempty"` // section 8.4 ContentSchema *Schema `json:"contentSchema,omitempty"` // section 8.5 // RFC draft-bhutton-json-schema-validation-00, section 9 - Title string `json:"title,omitempty"` // section 9.1 - Description string `json:"description,omitempty"` // section 9.1 - Default interface{} `json:"default,omitempty"` // section 9.2 - Deprecated bool `json:"deprecated,omitempty"` // section 9.3 - ReadOnly bool `json:"readOnly,omitempty"` // section 9.4 - WriteOnly bool `json:"writeOnly,omitempty"` // section 9.4 - Examples []interface{} `json:"examples,omitempty"` // section 9.5 + Title string `json:"title,omitempty"` // section 9.1 + Description string `json:"description,omitempty"` // section 9.1 + Default any `json:"default,omitempty"` // section 9.2 + Deprecated bool `json:"deprecated,omitempty"` // section 9.3 + ReadOnly bool `json:"readOnly,omitempty"` // section 9.4 + WriteOnly bool `json:"writeOnly,omitempty"` // section 9.4 + Examples []any `json:"examples,omitempty"` // section 9.5 - Extras map[string]interface{} `json:"-"` + Extras map[string]any `json:"-"` // Special boolean representation of the Schema - section 4.3.2 boolean *bool @@ -127,7 +127,7 @@ type customGetFieldDocString func(fieldName string) string var customStructGetFieldDocString = reflect.TypeOf((*customSchemaGetFieldDocString)(nil)).Elem() // Reflect reflects to Schema from a value using the default Reflector -func Reflect(v interface{}) *Schema { +func Reflect(v any) *Schema { return ReflectFromType(reflect.TypeOf(v)) } @@ -188,7 +188,7 @@ type Reflector struct { // IgnoredTypes defines a slice of types that should be ignored in the schema, // switching to just allowing additional properties instead. - IgnoredTypes []interface{} + IgnoredTypes []any // Lookup allows a function to be defined that will provide a custom mapping of // types to Schema IDs. This allows existing schema documents to be referenced @@ -229,7 +229,7 @@ type Reflector struct { } // Reflect reflects to Schema from a value. -func (r *Reflector) Reflect(v interface{}) *Schema { +func (r *Reflector) Reflect(v any) *Schema { return r.ReflectFromType(reflect.TypeOf(v)) } @@ -891,7 +891,7 @@ func (t *Schema) numericalKeywords(tags []string) { // read struct tags for array type keywords func (t *Schema) arrayKeywords(tags []string) { - var defaultValues []interface{} + var defaultValues []any for _, tag := range tags { nameValue := strings.Split(tag, "=") if len(nameValue) == 2 { @@ -941,7 +941,7 @@ func (t *Schema) extraKeywords(tags []string) { func (t *Schema) setExtra(key, val string) { if t.Extras == nil { - t.Extras = map[string]interface{}{} + t.Extras = map[string]any{} } if existingVal, ok := t.Extras[key]; ok { switch existingVal := existingVal.(type) { @@ -959,7 +959,7 @@ func (t *Schema) setExtra(key, val string) { case "minimum": t.Extras[key], _ = strconv.Atoi(val) default: - var x interface{} + var x any if val == "true" { x = true } else if val == "false" { diff --git a/reflect_test.go b/reflect_test.go index 3dd622f..53ea6e8 100644 --- a/reflect_test.go +++ b/reflect_test.go @@ -41,7 +41,7 @@ type SomeBaseType struct { someUnexportedUntaggedBaseProperty bool //nolint:unused } -type MapType map[string]interface{} +type MapType map[string]any type ArrayType []string @@ -64,12 +64,12 @@ type TestUser struct { nonExported MapType - ID int `json:"id" jsonschema:"required,minimum=bad,maximum=bad,exclusiveMinimum=bad,exclusiveMaximum=bad,default=bad"` - Name string `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex,readOnly=true"` - Password string `json:"password" jsonschema:"writeOnly=true"` - Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"` - Tags map[string]string `json:"tags,omitempty"` - Options map[string]interface{} `json:"options,omitempty"` + ID int `json:"id" jsonschema:"required,minimum=bad,maximum=bad,exclusiveMinimum=bad,exclusiveMaximum=bad,default=bad"` + Name string `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex,readOnly=true"` + Password string `json:"password" jsonschema:"writeOnly=true"` + Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"` + Tags map[string]string `json:"tags,omitempty"` + Options map[string]any `json:"options,omitempty"` TestFlag bool TestFlagFalse bool `json:",omitempty" jsonschema:"default=false"` @@ -107,7 +107,7 @@ type TestUser struct { Offsets []float64 `json:"offsets,omitempty" jsonschema:"enum=1.570796,enum=3.141592,enum=6.283185"` // Test for raw JSON - Anything interface{} `json:"anything,omitempty"` + Anything any `json:"anything,omitempty"` Raw json.RawMessage `json:"raw"` } @@ -131,34 +131,34 @@ func (CustomTimeWithInterface) JSONSchema() *Schema { } type RootOneOf struct { - Field1 string `json:"field1" jsonschema:"oneof_required=group1"` - Field2 string `json:"field2" jsonschema:"oneof_required=group2"` - Field3 interface{} `json:"field3" jsonschema:"oneof_type=string;array"` - Field4 string `json:"field4" jsonschema:"oneof_required=group1"` - Field5 ChildOneOf `json:"child"` - Field6 interface{} `json:"field6" jsonschema:"oneof_ref=Outer;OuterNamed;OuterPtr"` + Field1 string `json:"field1" jsonschema:"oneof_required=group1"` + Field2 string `json:"field2" jsonschema:"oneof_required=group2"` + Field3 any `json:"field3" jsonschema:"oneof_type=string;array"` + Field4 string `json:"field4" jsonschema:"oneof_required=group1"` + Field5 ChildOneOf `json:"child"` + Field6 any `json:"field6" jsonschema:"oneof_ref=Outer;OuterNamed;OuterPtr"` } type ChildOneOf struct { - Child1 string `json:"child1" jsonschema:"oneof_required=group1"` - Child2 string `json:"child2" jsonschema:"oneof_required=group2"` - Child3 interface{} `json:"child3" jsonschema:"oneof_required=group2,oneof_type=string;array"` - Child4 string `json:"child4" jsonschema:"oneof_required=group1"` + Child1 string `json:"child1" jsonschema:"oneof_required=group1"` + Child2 string `json:"child2" jsonschema:"oneof_required=group2"` + Child3 any `json:"child3" jsonschema:"oneof_required=group2,oneof_type=string;array"` + Child4 string `json:"child4" jsonschema:"oneof_required=group1"` } type RootAnyOf struct { - Field1 string `json:"field1" jsonschema:"anyof_required=group1"` - Field2 string `json:"field2" jsonschema:"anyof_required=group2"` - Field3 interface{} `json:"field3" jsonschema:"anyof_type=string;array"` - Field4 string `json:"field4" jsonschema:"anyof_required=group1"` - Field5 ChildAnyOf `json:"child"` + Field1 string `json:"field1" jsonschema:"anyof_required=group1"` + Field2 string `json:"field2" jsonschema:"anyof_required=group2"` + Field3 any `json:"field3" jsonschema:"anyof_type=string;array"` + Field4 string `json:"field4" jsonschema:"anyof_required=group1"` + Field5 ChildAnyOf `json:"child"` } type ChildAnyOf struct { - Child1 string `json:"child1" jsonschema:"anyof_required=group1"` - Child2 string `json:"child2" jsonschema:"anyof_required=group2"` - Child3 interface{} `json:"child3" jsonschema:"anyof_required=group2,oneof_type=string;array"` - Child4 string `json:"child4" jsonschema:"anyof_required=group1"` + Child1 string `json:"child1" jsonschema:"anyof_required=group1"` + Child2 string `json:"child2" jsonschema:"anyof_required=group2"` + Child3 any `json:"child3" jsonschema:"anyof_required=group2,oneof_type=string;array"` + Child4 string `json:"child4" jsonschema:"anyof_required=group1"` } type Text string @@ -359,7 +359,7 @@ func TestReflectFromType(t *testing.T) { func TestSchemaGeneration(t *testing.T) { tests := []struct { - typ interface{} + typ any reflector *Reflector fixture string }{ @@ -369,7 +369,7 @@ func TestSchemaGeneration(t *testing.T) { {&TestUser{}, &Reflector{AllowAdditionalProperties: true}, "fixtures/allow_additional_props.json"}, {&TestUser{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/required_from_jsontags.json"}, {&TestUser{}, &Reflector{ExpandedStruct: true}, "fixtures/defaults_expanded_toplevel.json"}, - {&TestUser{}, &Reflector{IgnoredTypes: []interface{}{GrandfatherType{}}}, "fixtures/ignore_type.json"}, + {&TestUser{}, &Reflector{IgnoredTypes: []any{GrandfatherType{}}}, "fixtures/ignore_type.json"}, {&TestUser{}, &Reflector{DoNotReference: true}, "fixtures/no_reference.json"}, {&TestUser{}, &Reflector{DoNotReference: true, AssignAnchor: true}, "fixtures/no_reference_anchor.json"}, {&RootOneOf{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/oneof.json"}, @@ -489,7 +489,7 @@ func TestBaselineUnmarshal(t *testing.T) { compareSchemaOutput(t, "fixtures/test_user.json", r, &TestUser{}) } -func compareSchemaOutput(t *testing.T, f string, r *Reflector, obj interface{}) { +func compareSchemaOutput(t *testing.T, f string, r *Reflector, obj any) { t.Helper() expectedJSON, err := os.ReadFile(f) require.NoError(t, err) @@ -567,10 +567,10 @@ func TestFieldNameTag(t *testing.T) { func TestFieldOneOfRef(t *testing.T) { type Server struct { - IPAddress interface{} `json:"ip_address,omitempty" jsonschema:"oneof_ref=#/$defs/ipv4;#/$defs/ipv6"` - IPAddresses []interface{} `json:"ip_addresses,omitempty" jsonschema:"oneof_ref=#/$defs/ipv4;#/$defs/ipv6"` - IPAddressAny interface{} `json:"ip_address_any,omitempty" jsonschema:"anyof_ref=#/$defs/ipv4;#/$defs/ipv6"` - IPAddressesAny []interface{} `json:"ip_addresses_any,omitempty" jsonschema:"anyof_ref=#/$defs/ipv4;#/$defs/ipv6"` + IPAddress any `json:"ip_address,omitempty" jsonschema:"oneof_ref=#/$defs/ipv4;#/$defs/ipv6"` + IPAddresses []any `json:"ip_addresses,omitempty" jsonschema:"oneof_ref=#/$defs/ipv4;#/$defs/ipv6"` + IPAddressAny any `json:"ip_address_any,omitempty" jsonschema:"anyof_ref=#/$defs/ipv4;#/$defs/ipv6"` + IPAddressesAny []any `json:"ip_addresses_any,omitempty" jsonschema:"anyof_ref=#/$defs/ipv4;#/$defs/ipv6"` } r := &Reflector{} From 75ac2d7f43d3ad59423d5e2f2d86ac6749412e43 Mon Sep 17 00:00:00 2001 From: candiduslynx Date: Mon, 25 Sep 2023 09:52:14 +0300 Subject: [PATCH 5/6] add lint rule for `any` --- .golangci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 905f122..3dac8a3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -43,6 +43,12 @@ linters-settings: gocritic: disabled-checks: - ifElseChain + gofmt: + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' + - pattern: 'a[b:len(a)]' + replacement: 'a[b:]' issues: max-per-linter: 0 From 372fbaa99d22ebbe3c128521b48a7be325ff5810 Mon Sep 17 00:00:00 2001 From: candiduslynx Date: Mon, 25 Sep 2023 13:42:49 +0300 Subject: [PATCH 6/6] move away from pointers --- reflect.go | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/reflect.go b/reflect.go index ee0ef81..c4f170a 100644 --- a/reflect.go +++ b/reflect.go @@ -56,11 +56,11 @@ type Schema struct { Type string `json:"type,omitempty"` // section 6.1.1 Enum []any `json:"enum,omitempty"` // section 6.1.2 Const any `json:"const,omitempty"` // section 6.1.3 - MultipleOf *json.Number `json:"multipleOf,omitempty"` // section 6.2.1 - Maximum *json.Number `json:"maximum,omitempty"` // section 6.2.2 - ExclusiveMaximum *json.Number `json:"exclusiveMaximum,omitempty"` // section 6.2.3 - Minimum *json.Number `json:"minimum,omitempty"` // section 6.2.4 - ExclusiveMinimum *json.Number `json:"exclusiveMinimum,omitempty"` // section 6.2.5 + MultipleOf json.Number `json:"multipleOf,omitempty"` // section 6.2.1 + Maximum json.Number `json:"maximum,omitempty"` // section 6.2.2 + ExclusiveMaximum json.Number `json:"exclusiveMaximum,omitempty"` // section 6.2.3 + Minimum json.Number `json:"minimum,omitempty"` // section 6.2.4 + ExclusiveMinimum json.Number `json:"exclusiveMinimum,omitempty"` // section 6.2.5 MaxLength int `json:"maxLength,omitempty"` // section 6.3.1 MinLength int `json:"minLength,omitempty"` // section 6.3.2 Pattern string `json:"pattern,omitempty"` // section 6.3.3 @@ -850,22 +850,21 @@ func (t *Schema) numericalKeywords(tags []string) { name, val := nameValue[0], nameValue[1] switch name { case "multipleOf": - t.MultipleOf = toJSONNumber(val) + t.MultipleOf, _ = toJSONNumber(val) case "minimum": - t.Minimum = toJSONNumber(val) + t.Minimum, _ = toJSONNumber(val) case "maximum": - t.Maximum = toJSONNumber(val) + t.Maximum, _ = toJSONNumber(val) case "exclusiveMaximum": - t.ExclusiveMaximum = toJSONNumber(val) + t.ExclusiveMaximum, _ = toJSONNumber(val) case "exclusiveMinimum": - t.ExclusiveMinimum = toJSONNumber(val) + t.ExclusiveMinimum, _ = toJSONNumber(val) case "default": - if num := toJSONNumber(val); num != nil { - // toJSONNumber returns typed nil, so we need to account for that + if num, ok := toJSONNumber(val); ok { t.Default = num } case "example": - if num := toJSONNumber(val); num != nil { + if num, ok := toJSONNumber(val); ok { t.Examples = append(t.Examples, num) } } @@ -1017,17 +1016,17 @@ func ignoredByJSONSchemaTags(tags []string) bool { return tags[0] == "-" } -// toJSONNumber converts string to *json.Number -// it'll return nil if the number is invalid -func toJSONNumber(s string) *json.Number { +// toJSONNumber converts string to *json.Number. +// It'll aso return whether the number is valid. +func toJSONNumber(s string) (json.Number, bool) { num := json.Number(s) if _, err := num.Int64(); err == nil { - return &num + return num, true } if _, err := num.Float64(); err == nil { - return &num + return num, true } - return nil + return json.Number(""), false } func (r *Reflector) fieldNameTag() string {