Skip to content

Commit

Permalink
Fix support for jsonschema.Struct in requests (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
vearutop authored May 20, 2024
1 parent eaa1394 commit 28402d8
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 2 deletions.
12 changes: 10 additions & 2 deletions internal/json_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ func ReflectRequestBody(
hasTaggedFields = refl.HasTaggedFields(input, t)
}

hasJSONSchemaStruct := false

refl.WalkFieldsRecursively(reflect.ValueOf(input), func(v reflect.Value, _ reflect.StructField, _ []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
Expand All @@ -74,7 +82,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
}

Expand All @@ -89,7 +97,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
}
Expand Down
86 changes: 86 additions & 0 deletions openapi3/reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1123,3 +1123,89 @@ 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())
}
86 changes: 86 additions & 0 deletions openapi31/reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1251,3 +1251,89 @@ 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())
}

0 comments on commit 28402d8

Please sign in to comment.