From 0bf8b7562e556df0a97c19b6b2efef00bc1f0a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wi=C4=99cek?= Date: Mon, 17 Jun 2024 13:44:44 +0200 Subject: [PATCH 1/6] fix: prevent Fields from using the same underlying ogen.Schema object --- entoas/generator.go | 68 ++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/entoas/generator.go b/entoas/generator.go index 0a2d6f8ee..bb16941fc 100644 --- a/entoas/generator.go +++ b/entoas/generator.go @@ -534,27 +534,49 @@ var ( max16 int64 = math.MaxInt16 maxu16 int64 = math.MaxUint16 maxu32 int64 = math.MaxUint32 - types = map[string]*ogen.Schema{ - "bool": ogen.Bool(), - "time.Time": ogen.DateTime(), - "string": ogen.String(), - "[]byte": ogen.Bytes(), - "uuid.UUID": ogen.UUID(), - "int": ogen.Int(), - "int8": ogen.Int32().SetMinimum(&min8).SetMaximum(&max8), - "int16": ogen.Int32().SetMinimum(&min16).SetMaximum(&max16), - "int32": ogen.Int32(), - "uint": ogen.Int64().SetMinimum(&zero).SetMaximum(&maxu32), - "uint8": ogen.Int32().SetMinimum(&zero).SetMaximum(&maxu8), - "uint16": ogen.Int32().SetMinimum(&zero).SetMaximum(&maxu16), - "uint32": ogen.Int64().SetMinimum(&zero).SetMaximum(&maxu32), - "int64": ogen.Int64(), - "uint64": ogen.Int64().SetMinimum(&zero), - "float32": ogen.Float(), - "float64": ogen.Double(), - } ) +func types(t string) *ogen.Schema { + switch t { + case "bool": + return ogen.Bool() + case "time.Time": + return ogen.DateTime() + case "string": + return ogen.String() + case "[]byte": + return ogen.Bytes() + case "uuid.UUID": + return ogen.UUID() + case "int": + return ogen.Int() + case "int8": + return ogen.Int32().SetMinimum(&min8).SetMaximum(&max8) + case "int16": + return ogen.Int32().SetMinimum(&min16).SetMaximum(&max16) + case "int32": + return ogen.Int32() + case "uint": + return ogen.Int64().SetMinimum(&zero).SetMaximum(&maxu32) + case "uint8": + return ogen.Int32().SetMinimum(&zero).SetMaximum(&maxu8) + case "uint16": + return ogen.Int32().SetMinimum(&zero).SetMaximum(&maxu16) + case "uint32": + return ogen.Int64().SetMinimum(&zero).SetMaximum(&maxu32) + case "int64": + return ogen.Int64() + case "uint64": + return ogen.Int64().SetMinimum(&zero) + case "float32": + return ogen.Float() + case "float64": + return ogen.Double() + default: + return nil + } +} + // OgenSchema returns the ogen.Schema to use for the given gen.Field. func OgenSchema(f *gen.Field) (*ogen.Schema, error) { // If there is a custom property given on the field use it. @@ -565,6 +587,7 @@ func OgenSchema(f *gen.Field) (*ogen.Schema, error) { if ant.Schema != nil { return ant.Schema, nil } + // Enum values need special case. if f.IsEnum() { var d json.RawMessage @@ -586,12 +609,13 @@ func OgenSchema(f *gen.Field) (*ogen.Schema, error) { s := f.Type.String() // Handle slice types. if strings.HasPrefix(s, "[]") { - if t, ok := types[s[2:]]; ok { + t := types(s[2:]) + if t != nil { return t.AsArray(), nil } } - t, ok := types[s] - if !ok { + t := types(s) + if t == nil { return nil, fmt.Errorf("no OAS-type exists for type %q of field %s", s, f.StructField()) } return t, nil From efc3bc45fad3354bcfe1c97cb0d29c3c98c944a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wi=C4=99cek?= Date: Mon, 17 Jun 2024 13:45:11 +0200 Subject: [PATCH 2/6] feat: respect entoas.Example annotation --- entoas/generator.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/entoas/generator.go b/entoas/generator.go index bb16941fc..5588f60db 100644 --- a/entoas/generator.go +++ b/entoas/generator.go @@ -611,6 +611,11 @@ func OgenSchema(f *gen.Field) (*ogen.Schema, error) { if strings.HasPrefix(s, "[]") { t := types(s[2:]) if t != nil { + jv, err := json.Marshal(ant.Example) + if err != nil { + return nil, fmt.Errorf("cannot marshal example annotation for field %s", f.Name) + } + t.Example = jv return t.AsArray(), nil } } @@ -618,6 +623,15 @@ func OgenSchema(f *gen.Field) (*ogen.Schema, error) { if t == nil { return nil, fmt.Errorf("no OAS-type exists for type %q of field %s", s, f.StructField()) } + + if ant.Example != nil { + jv, err := json.Marshal(ant.Example) + if err != nil { + return nil, fmt.Errorf("cannot marshal example annotation for field %s", f.Name) + } + t.Example = jv + } + return t, nil } From 4f49fa5b936e46bf42751e47f55ec7879442aee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wi=C4=99cek?= Date: Mon, 17 Jun 2024 15:53:07 +0200 Subject: [PATCH 3/6] chore: refactor the code --- entoas/generator.go | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/entoas/generator.go b/entoas/generator.go index 5588f60db..6c6c048dc 100644 --- a/entoas/generator.go +++ b/entoas/generator.go @@ -588,8 +588,11 @@ func OgenSchema(f *gen.Field) (*ogen.Schema, error) { return ant.Schema, nil } - // Enum values need special case. - if f.IsEnum() { + var schema *ogen.Schema + s := f.Type.String() + + switch { + case f.IsEnum(): // Enum values need special case. var d json.RawMessage if f.Default { d, err = json.Marshal(f.DefaultValue().(string)) @@ -604,23 +607,21 @@ func OgenSchema(f *gen.Field) (*ogen.Schema, error) { return nil, err } } - return ogen.String().AsEnum(d, vs...), nil - } - s := f.Type.String() - // Handle slice types. - if strings.HasPrefix(s, "[]") { + schema = ogen.String().AsEnum(d, vs...) + + case strings.HasPrefix(s, "[]"): // Handle slice types. t := types(s[2:]) if t != nil { - jv, err := json.Marshal(ant.Example) - if err != nil { - return nil, fmt.Errorf("cannot marshal example annotation for field %s", f.Name) - } - t.Example = jv - return t.AsArray(), nil + schema = t.AsArray() + break } + fallthrough + + default: + schema = types(s) } - t := types(s) - if t == nil { + + if schema == nil { return nil, fmt.Errorf("no OAS-type exists for type %q of field %s", s, f.StructField()) } @@ -629,10 +630,10 @@ func OgenSchema(f *gen.Field) (*ogen.Schema, error) { if err != nil { return nil, fmt.Errorf("cannot marshal example annotation for field %s", f.Name) } - t.Example = jv + schema.Example = jv } - return t, nil + return schema, nil } // NodeOperations returns the list of operations to expose for this node. From 951374c3db453ca26a323d2795c57c030441b84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wi=C4=99cek?= Date: Mon, 17 Jun 2024 15:53:24 +0200 Subject: [PATCH 4/6] chore: regenerate openapi spec for internal examples --- entoas/internal/pets/openapi.json | 60 +++++++++++++++++++---------- entoas/internal/simple/openapi.json | 18 ++++++--- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/entoas/internal/pets/openapi.json b/entoas/internal/pets/openapi.json index 953547a2f..6f982f02a 100644 --- a/entoas/internal/pets/openapi.json +++ b/entoas/internal/pets/openapi.json @@ -400,7 +400,8 @@ "type": "object", "properties": { "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -409,7 +410,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 }, "categories": { "type": "array", @@ -564,7 +566,8 @@ "type": "object", "properties": { "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -573,7 +576,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 }, "categories": { "type": "array", @@ -1215,7 +1219,8 @@ "type": "integer" }, "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -1224,7 +1229,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 } }, "required": [ @@ -1239,7 +1245,8 @@ "type": "integer" }, "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -1248,7 +1255,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 }, "categories": { "type": "array", @@ -1278,7 +1286,8 @@ "type": "integer" }, "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -1287,7 +1296,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 } }, "required": [ @@ -1302,7 +1312,8 @@ "type": "integer" }, "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -1311,7 +1322,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 } }, "required": [ @@ -1323,7 +1335,8 @@ "type": "object", "properties": { "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -1332,7 +1345,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 }, "owner": { "$ref": "#/components/schemas/PetRead_Owner" @@ -1368,7 +1382,8 @@ "type": "integer" }, "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -1377,7 +1392,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 } }, "required": [ @@ -1407,7 +1423,8 @@ "type": "integer" }, "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -1416,7 +1433,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 } }, "required": [ @@ -1551,7 +1569,8 @@ "type": "integer" }, "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -1560,7 +1579,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 } }, "required": [ diff --git a/entoas/internal/simple/openapi.json b/entoas/internal/simple/openapi.json index 639e2f068..d2e0b359e 100644 --- a/entoas/internal/simple/openapi.json +++ b/entoas/internal/simple/openapi.json @@ -400,7 +400,8 @@ "type": "object", "properties": { "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -409,7 +410,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 }, "categories": { "type": "array", @@ -564,7 +566,8 @@ "type": "object", "properties": { "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -573,7 +576,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 }, "categories": { "type": "array", @@ -1159,7 +1163,8 @@ "type": "integer" }, "name": { - "type": "string" + "type": "string", + "example": "Kuro" }, "nicknames": { "type": "array", @@ -1168,7 +1173,8 @@ } }, "age": { - "type": "integer" + "type": "integer", + "example": 1 }, "categories": { "type": "array", From 6aa128d9789ed553ff5a3bd20d3404c4f263434a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wi=C4=99cek?= Date: Mon, 17 Jun 2024 15:53:40 +0200 Subject: [PATCH 5/6] chore: add test for example annotations testing --- entoas/generator_test.go | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/entoas/generator_test.go b/entoas/generator_test.go index 9d9425a20..fa2279909 100644 --- a/entoas/generator_test.go +++ b/entoas/generator_test.go @@ -102,6 +102,78 @@ func TestOgenSchema(t *testing.T) { } } +func TestOgenSchema_Example(t *testing.T) { + t.Parallel() + entFields := map[*entfield.Descriptor]*ogen.Schema{ + entfield.String("name"). + Annotations(Example("name")).Descriptor(): func() *ogen.Schema { + schema := ogen.String() + v, err := json.Marshal("name") + require.NoError(t, err) + schema.Example = v + return schema + }(), + entfield.Float32("total"). + Annotations(Example("total")).Descriptor(): func() *ogen.Schema { + schema := ogen.Float() + v, err := json.Marshal("total") + require.NoError(t, err) + schema.Example = v + return schema + }(), + } + + for d, ex := range entFields { + t.Run(d.Name, func(t *testing.T) { + f, err := load.NewField(d) + require.NoError(t, err) + gf := &gen.Field{ + Name: f.Name, + Type: f.Info, + Annotations: f.Annotations, + } + ant, err := FieldAnnotation(gf) + require.NoError(t, err) + require.Equal(t, ant.Example, gf.Name) + + ac, err := OgenSchema(gf) + require.NoError(t, err) + require.Equal(t, ex, ac) + }) + } + + // require.Equal(t, expected interface{}, actual interface{}, msgAndArgs ...interface{}) + // for d, ex := range map[*entfield.Descriptor]*ogen.Schema{ + // } { + // t.Run(d.Name, func(t *testing.T) { + // f, err := load.NewField(d) + // require.NoError(t, err) + // ens := make([]gen.Enum, len(f.Enums)) + // for i, e := range f.Enums { + // ens[i] = gen.Enum{Name: e.N, Value: e.V} + // } + // gf := &gen.Field{ + // Name: f.Name, + // Type: f.Info, + // Annotations: f.Annotations, + // Enums: ens, + // } + // ac, err := OgenSchema(gf) + // if ex == nil { + // require.Error(t, err) + // require.EqualError(t, err, fmt.Sprintf( + // "no OAS-type exists for type %q of field %s", + // gf.Type.String(), + // gf.StructField(), + // )) + // } else { + // require.NoError(t, err) + // require.Equal(t, ex, ac) + // } + // }) + // } +} + func TestOperation_Title(t *testing.T) { t.Parallel() require.Equal(t, "Create", OpCreate.Title()) From dfe36664c0b8b58b98e9cf3c3ae3d907c8f1cf75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wi=C4=99cek?= Date: Mon, 17 Jun 2024 16:25:23 +0200 Subject: [PATCH 6/6] chore: cleanup comments --- entoas/generator_test.go | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/entoas/generator_test.go b/entoas/generator_test.go index fa2279909..bc8a8d244 100644 --- a/entoas/generator_test.go +++ b/entoas/generator_test.go @@ -141,37 +141,6 @@ func TestOgenSchema_Example(t *testing.T) { require.Equal(t, ex, ac) }) } - - // require.Equal(t, expected interface{}, actual interface{}, msgAndArgs ...interface{}) - // for d, ex := range map[*entfield.Descriptor]*ogen.Schema{ - // } { - // t.Run(d.Name, func(t *testing.T) { - // f, err := load.NewField(d) - // require.NoError(t, err) - // ens := make([]gen.Enum, len(f.Enums)) - // for i, e := range f.Enums { - // ens[i] = gen.Enum{Name: e.N, Value: e.V} - // } - // gf := &gen.Field{ - // Name: f.Name, - // Type: f.Info, - // Annotations: f.Annotations, - // Enums: ens, - // } - // ac, err := OgenSchema(gf) - // if ex == nil { - // require.Error(t, err) - // require.EqualError(t, err, fmt.Sprintf( - // "no OAS-type exists for type %q of field %s", - // gf.Type.String(), - // gf.StructField(), - // )) - // } else { - // require.NoError(t, err) - // require.Equal(t, ex, ac) - // } - // }) - // } } func TestOperation_Title(t *testing.T) {