diff --git a/components/definition/definition.go b/components/definition/definition.go index 313715a..443737d 100644 --- a/components/definition/definition.go +++ b/components/definition/definition.go @@ -3,6 +3,7 @@ package definition import ( "fmt" "reflect" + "strings" "github.com/go-swagno/swagno/components/fields" "github.com/go-swagno/swagno/components/http/response" @@ -59,6 +60,7 @@ func (g DefinitionGenerator) CreateDefinition(t interface{}) { if reflectReturn.Kind() == reflect.Struct { properties = g.createStructDefinitions(reflectReturn) } + definitionName, _ = strings.CutPrefix(definitionName, "[]") case reflect.Struct: if reflectReturn == reflect.TypeOf(response.CustomResponse{}) { // if CustomResponseType, use Model struct in it @@ -122,7 +124,10 @@ func (g DefinitionGenerator) createStructDefinitions(structType reflect.Type) ma } else if field.Type.String() == "time.Duration" { properties[fieldJsonTag] = g.durationProperty(field) } else { - properties[fieldJsonTag] = g.refProperty(field) + properties[fieldJsonTag] = DefinitionProperties{ + Example: fields.ExampleTag(field), + Ref: fmt.Sprintf("#/definitions/%s", field.Type.String()), + } g.CreateDefinition(reflect.New(field.Type).Elem().Interface()) } @@ -142,6 +147,28 @@ func (g DefinitionGenerator) createStructDefinitions(structType reflect.Type) ma properties[fieldJsonTag] = g.refProperty(field) g.CreateDefinition(reflect.New(field.Type.Elem()).Elem().Interface()) } + } else if field.Type.Elem().Kind() == reflect.Array || field.Type.Elem().Kind() == reflect.Slice { + if field.Type.Elem().Elem().Kind() == reflect.Struct { + properties[fieldJsonTag] = DefinitionProperties{ + Example: fields.ExampleTag(field), + Type: fields.Type(field.Type.Elem().Kind().String()), + Items: &DefinitionPropertiesItems{ + Ref: fmt.Sprintf("#/definitions/%s", field.Type.Elem().Elem().String()), + }, + } + if structType == field.Type.Elem().Elem() { + continue // prevent recursion + } + g.CreateDefinition(reflect.New(field.Type.Elem().Elem()).Elem().Interface()) + } else { + properties[fieldJsonTag] = DefinitionProperties{ + Example: fields.ExampleTag(field), + Type: fields.Type(field.Type.Elem().Kind().String()), + Items: &DefinitionPropertiesItems{ + Type: fields.Type(field.Type.Elem().Elem().Kind().String()), + }, + } + } } else { properties[fieldJsonTag] = DefinitionProperties{ Example: fields.ExampleTag(field), diff --git a/example/models/complex.go b/example/models/complex.go new file mode 100644 index 0000000..4eb685f --- /dev/null +++ b/example/models/complex.go @@ -0,0 +1,18 @@ +package models + +type Object struct { + Name string `json:"name" example:"John Smith"` +} + +type Nested struct { + Objects *[]Object `json:"objects,omitempty"` + Strings *[]string `json:"strings,omitempty"` +} + +type Deeply struct { + Nested Nested `json:"nested"` +} + +type ComplexSuccessfulResponse struct { + Data *Deeply `json:"deeply"` +} diff --git a/generate_test.go b/generate_test.go index 8a98f11..288de62 100644 --- a/generate_test.go +++ b/generate_test.go @@ -71,6 +71,30 @@ func TestSwaggerGeneration(t *testing.T) { }, file: "testdata/expected_output/bft.json", }, + { + name: "Deeply Nested Model Test", + endpoints: []*endpoint.EndPoint{ + endpoint.New( + endpoint.GET, + "/deeplynested", + endpoint.WithSuccessfulReturns([]response.Response{response.New(models.ComplexSuccessfulResponse{}, "OK", "200")}), + endpoint.WithDescription(desc), + endpoint.WithProduce([]mime.MIME{mime.JSON, mime.XML}), + endpoint.WithConsume([]mime.MIME{mime.JSON}), + endpoint.WithSummary("this is a test summary"), + ), + endpoint.New( + endpoint.GET, + "/arraydeeplynested", + endpoint.WithSuccessfulReturns([]response.Response{response.New([]models.ComplexSuccessfulResponse{}, "OK", "200")}), + endpoint.WithDescription(desc), + endpoint.WithProduce([]mime.MIME{mime.JSON, mime.XML}), + endpoint.WithConsume([]mime.MIME{mime.JSON}), + endpoint.WithSummary("this is a test summary"), + ), + }, + file: "testdata/expected_output/dnmt.json", + }, } // Iterate through test cases diff --git a/testdata/expected_output/dnmt.json b/testdata/expected_output/dnmt.json new file mode 100644 index 0000000..bdc317a --- /dev/null +++ b/testdata/expected_output/dnmt.json @@ -0,0 +1,97 @@ +{ + "swagger": "2.0", + "info": { + "title": "Testing API", + "version": "v1.0.0" + }, + "paths": { + "/deeplynested": { + "get": { + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed id malesuada lorem, et fermentum sapien. Vivamus non pharetra risus, in efficitur leo. Suspendisse sed metus sit amet mi laoreet imperdiet. Donec aliquam eros eu blandit feugiat. Quisque scelerisque justo ac vehicula bibendum. Fusce suscipit arcu nisl, eu maximus odio consequat quis. Curabitur fermentum eleifend tellus, lobortis hendrerit velit varius vitae.", + "consumes": ["application/json"], + "produces": ["application/json", "application/xml"], + "tags": [], + "summary": "this is a test summary", + "operationId": "get-/deeplynested", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.ComplexSuccessfulResponse" + } + } + } + } + }, + "/arraydeeplynested": { + "get": { + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed id malesuada lorem, et fermentum sapien. Vivamus non pharetra risus, in efficitur leo. Suspendisse sed metus sit amet mi laoreet imperdiet. Donec aliquam eros eu blandit feugiat. Quisque scelerisque justo ac vehicula bibendum. Fusce suscipit arcu nisl, eu maximus odio consequat quis. Curabitur fermentum eleifend tellus, lobortis hendrerit velit varius vitae.", + "consumes": ["application/json"], + "produces": ["application/json", "application/xml"], + "tags": [], + "summary": "this is a test summary", + "operationId": "get-/arraydeeplynested", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ComplexSuccessfulResponse" + } + } + } + } + } + } + }, + "basePath": "/", + "host": "localhost", + "definitions": { + "models.ComplexSuccessfulResponse": { + "type": "object", + "properties": { + "deeply": { + "$ref": "#/definitions/models.Deeply" + } + } + }, + "models.Deeply": { + "type": "object", + "properties": { + "nested": { + "$ref": "#/definitions/models.Nested" + } + } + }, + "models.Nested": { + "type": "object", + "properties": { + "objects": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Object" + } + }, + "strings": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "models.Object": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "John Smith" + } + } + } + }, + "schemes": ["http", "https"] +}