From 37b853fc197bb413210f2a0fade974873c49cb76 Mon Sep 17 00:00:00 2001 From: Viacheslav Poturaev Date: Tue, 21 May 2024 00:42:50 +0200 Subject: [PATCH 1/2] Fix support for jsonschema.Struct in requests --- internal/json_schema.go | 11 ++++- openapi3/reflect_test.go | 85 +++++++++++++++++++++++++++++++++++++++ openapi31/reflect_test.go | 85 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 2 deletions(-) diff --git a/internal/json_schema.go b/internal/json_schema.go index a7166c9..3547a8d 100644 --- a/internal/json_schema.go +++ b/internal/json_schema.go @@ -66,6 +66,13 @@ func ReflectRequestBody( hasTaggedFields = refl.HasTaggedFields(input, t) } + hasJSONSchemaStruct := false + refl.WalkFieldsRecursively(reflect.ValueOf(input), func(v reflect.Value, sf reflect.StructField, path []reflect.StructField) { + if v.Type() == reflect.TypeOf(jsonschema.Struct{}) { + hasJSONSchemaStruct = true + } + }) + // Form data can not have map or array as body. if !hasTaggedFields && len(mapping) == 0 && tag != tagJSON { return nil, false, nil @@ -74,7 +81,7 @@ func ReflectRequestBody( // If `formData` is defined on a request body `json` is ignored. if tag == tagJSON && (refl.HasTaggedFields(input, tagFormData) || refl.HasTaggedFields(input, tagForm)) && - !forceJSONRequestBody { + !forceJSONRequestBody && !hasJSONSchemaStruct { return nil, false, nil } @@ -89,7 +96,7 @@ func ReflectRequestBody( } // JSON can be a map or array without field tags. - if !hasTaggedFields && len(mapping) == 0 && !refl.IsSliceOrMap(input) && + if !hasTaggedFields && !hasJSONSchemaStruct && len(mapping) == 0 && !refl.IsSliceOrMap(input) && refl.FindEmbeddedSliceOrMap(input) == nil && !isProcessWithoutTags { return nil, false, nil } diff --git a/openapi3/reflect_test.go b/openapi3/reflect_test.go index 6d0dc1e..424ac94 100644 --- a/openapi3/reflect_test.go +++ b/openapi3/reflect_test.go @@ -1123,3 +1123,88 @@ func TestReflector_AddOperation_defName(t *testing.T) { } }`, r.Spec) } + +func TestReflector_AddOperation_jsonschemaStruct(t *testing.T) { + r := openapi3.NewReflector() + + oc, err := r.NewOperationContext(http.MethodPost, "/foo/{id}") + require.NoError(t, err) + + type Req struct { + ID int `path:"id"` + jsonschema.Struct + } + + req := Req{} + req.DefName = "FooStruct" + req.Fields = append(req.Fields, jsonschema.Field{ + Name: "Foo", + Value: "abc", + Tag: `json:"foo" minLength:"3"`, + }) + + type Resp struct { + ID int `json:"id"` + jsonschema.Struct + Nested jsonschema.Struct `json:"nested"` + } + resp := Resp{} + resp.DefName = "BarStruct" + resp.Fields = append(resp.Fields, jsonschema.Field{ + Name: "Bar", + Value: "cba", + Tag: `json:"bar" maxLength:"3"`, + }) + resp.Nested.DefName = "BazStruct" + resp.Nested.Fields = append(resp.Nested.Fields, jsonschema.Field{ + Name: "Baz", + Value: "def", + Tag: `json:"baz" maxLength:"5"`, + }) + + oc.AddReqStructure(req) + oc.AddRespStructure(resp) + + require.NoError(t, r.AddOperation(oc)) + + assertjson.EqMarshal(t, `{ + "openapi":"3.0.3","info":{"title":"","version":""}, + "paths":{ + "/foo/{id}":{ + "post":{ + "parameters":[ + { + "name":"id","in":"path","required":true,"schema":{"type":"integer"} + } + ], + "requestBody":{ + "content":{ + "application/json":{"schema":{"$ref":"#/components/schemas/FooStruct"}} + } + }, + "responses":{ + "200":{ + "description":"OK", + "content":{ + "application/json":{"schema":{"$ref":"#/components/schemas/BarStruct"}} + } + } + } + } + } + }, + "components":{ + "schemas":{ + "BarStruct":{ + "properties":{ + "bar":{"maxLength":3,"type":"string"},"id":{"type":"integer"}, + "nested":{"$ref":"#/components/schemas/BazStruct"} + }, + "type":"object" + }, + "BazStruct":{"properties":{"baz":{"maxLength":5,"type":"string"}},"type":"object"}, + "FooStruct":{"properties":{"foo":{"minLength":3,"type":"string"}},"type":"object"} + } + } + }`, r.SpecSchema()) +} diff --git a/openapi31/reflect_test.go b/openapi31/reflect_test.go index 3cc40f5..968a6ba 100644 --- a/openapi31/reflect_test.go +++ b/openapi31/reflect_test.go @@ -1251,3 +1251,88 @@ func TestReflector_AddOperation_rawSchema(t *testing.T) { } }`, r.SpecSchema()) } + +func TestReflector_AddOperation_jsonschemaStruct(t *testing.T) { + r := openapi31.NewReflector() + + oc, err := r.NewOperationContext(http.MethodPost, "/foo/{id}") + require.NoError(t, err) + + type Req struct { + ID int `path:"id"` + jsonschema.Struct + } + + req := Req{} + req.DefName = "FooStruct" + req.Fields = append(req.Fields, jsonschema.Field{ + Name: "Foo", + Value: "abc", + Tag: `json:"foo" minLength:"3"`, + }) + + type Resp struct { + ID int `json:"id"` + jsonschema.Struct + Nested jsonschema.Struct `json:"nested"` + } + resp := Resp{} + resp.DefName = "BarStruct" + resp.Fields = append(resp.Fields, jsonschema.Field{ + Name: "Bar", + Value: "cba", + Tag: `json:"bar" maxLength:"3"`, + }) + resp.Nested.DefName = "BazStruct" + resp.Nested.Fields = append(resp.Nested.Fields, jsonschema.Field{ + Name: "Baz", + Value: "def", + Tag: `json:"baz" maxLength:"5"`, + }) + + oc.AddReqStructure(req) + oc.AddRespStructure(resp) + + require.NoError(t, r.AddOperation(oc)) + + assertjson.EqMarshal(t, `{ + "openapi":"3.1.0","info":{"title":"","version":""}, + "paths":{ + "/foo/{id}":{ + "post":{ + "parameters":[ + { + "name":"id","in":"path","required":true,"schema":{"type":"integer"} + } + ], + "requestBody":{ + "content":{ + "application/json":{"schema":{"$ref":"#/components/schemas/FooStruct"}} + } + }, + "responses":{ + "200":{ + "description":"OK", + "content":{ + "application/json":{"schema":{"$ref":"#/components/schemas/BarStruct"}} + } + } + } + } + } + }, + "components":{ + "schemas":{ + "BarStruct":{ + "properties":{ + "bar":{"maxLength":3,"type":"string"},"id":{"type":"integer"}, + "nested":{"$ref":"#/components/schemas/BazStruct"} + }, + "type":"object" + }, + "BazStruct":{"properties":{"baz":{"maxLength":5,"type":"string"}},"type":"object"}, + "FooStruct":{"properties":{"foo":{"minLength":3,"type":"string"}},"type":"object"} + } + } + }`, r.SpecSchema()) +} From 95054a7ab2ae3ae60fa056ff1299f2e27e2d2894 Mon Sep 17 00:00:00 2001 From: Viacheslav Poturaev Date: Tue, 21 May 2024 00:53:35 +0200 Subject: [PATCH 2/2] Fix lint --- internal/json_schema.go | 3 ++- openapi3/reflect_test.go | 1 + openapi31/reflect_test.go | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/json_schema.go b/internal/json_schema.go index 3547a8d..f19d55f 100644 --- a/internal/json_schema.go +++ b/internal/json_schema.go @@ -67,7 +67,8 @@ func ReflectRequestBody( } hasJSONSchemaStruct := false - refl.WalkFieldsRecursively(reflect.ValueOf(input), func(v reflect.Value, sf reflect.StructField, path []reflect.StructField) { + + refl.WalkFieldsRecursively(reflect.ValueOf(input), func(v reflect.Value, _ reflect.StructField, _ []reflect.StructField) { if v.Type() == reflect.TypeOf(jsonschema.Struct{}) { hasJSONSchemaStruct = true } diff --git a/openapi3/reflect_test.go b/openapi3/reflect_test.go index 424ac94..6b30525 100644 --- a/openapi3/reflect_test.go +++ b/openapi3/reflect_test.go @@ -1148,6 +1148,7 @@ func TestReflector_AddOperation_jsonschemaStruct(t *testing.T) { jsonschema.Struct Nested jsonschema.Struct `json:"nested"` } + resp := Resp{} resp.DefName = "BarStruct" resp.Fields = append(resp.Fields, jsonschema.Field{ diff --git a/openapi31/reflect_test.go b/openapi31/reflect_test.go index 968a6ba..524e4df 100644 --- a/openapi31/reflect_test.go +++ b/openapi31/reflect_test.go @@ -1276,6 +1276,7 @@ func TestReflector_AddOperation_jsonschemaStruct(t *testing.T) { jsonschema.Struct Nested jsonschema.Struct `json:"nested"` } + resp := Resp{} resp.DefName = "BarStruct" resp.Fields = append(resp.Fields, jsonschema.Field{