Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding basic support for XML structure #328

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
35 changes: 28 additions & 7 deletions examples/petstore/lib/testdata/doc/openapi.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"detail": {
"description": "Human readable error message",
"nullable": true,
"type": "string"
"type": "string",
"xml": {
"name": "detail"
}
},
"errors": {
"items": {
Expand All @@ -26,27 +29,42 @@
"type": "object"
},
"nullable": true,
"type": "array"
"type": "array",
"xml": {
"name": "errors"
}
},
"instance": {
"nullable": true,
"type": "string"
"type": "string",
"xml": {
"name": "instance"
}
},
"status": {
"description": "HTTP status code",
"example": 403,
"nullable": true,
"type": "integer"
"type": "integer",
"xml": {
"name": "status"
}
},
"title": {
"description": "Short title of the error",
"nullable": true,
"type": "string"
"type": "string",
"xml": {
"name": "title"
}
},
"type": {
"description": "URL of the error type. Can be used to lookup the error in a documentation",
"nullable": true,
"type": "string"
"type": "string",
"xml": {
"name": "type"
}
}
},
"type": "object"
Expand Down Expand Up @@ -137,7 +155,10 @@
"description": "PetsError schema",
"properties": {
"message": {
"type": "string"
"type": "string",
"xml": {
"name": "message"
}
}
},
"type": "object"
Expand Down
32 changes: 32 additions & 0 deletions openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,38 @@ func parseStructTags(t reflect.Type, schemaRef *openapi3.SchemaRef) {
propertyCopy := *property
propertyValue := *propertyCopy.Value

if field.Type.Kind() == reflect.Struct {
parseStructTags(field.Type, schemaRef.Value.Properties[jsonFieldName])
}

// basic Xml tag support
DimShadoWWW marked this conversation as resolved.
Show resolved Hide resolved
//
// We support the following xml tags:
// - "xml" field tag: specifies the XML element name
// - "xml=attr" field tag: specifies that the field is an XML attribute
// - "xml=wrapped" field tag: specifies that the field is wrapped in an XML element
xmlField := field.Tag.Get("xml")
// if xml tag name is "-", don't add it to the schema
if xmlField != "-" && xmlField != "" {
xmlFieldName := strings.Split(xmlField, ",")[0] // remove omitempty, etc

if xmlFieldName == "" {
xmlFieldName = field.Name
}

propertyValue.XML = &openapi3.XML{
Name: xmlFieldName,
}

xmlFields := strings.Split(xmlField, ",")
if slices.Contains(xmlFields, "attr") {
propertyValue.XML.Attribute = true
}
if slices.Contains(xmlFields, "wrapped") {
propertyValue.XML.Wrapped = true
}
}
DimShadoWWW marked this conversation as resolved.
Show resolved Hide resolved

// Example
example, ok := field.Tag.Lookup("example")
if ok {
Expand Down
29 changes: 29 additions & 0 deletions openapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ type MyOutputStruct struct {
Quantity int `json:"quantity"`
}

type MyOutputStructDetails struct {
IngressDate string `json:"in_date" xml:"date,attr"`
}

type MyOutputStructWithXmlAttribute struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add a test for a struct with just xml attributes. As well as one with json:"-"

Name string `json:"name" xml:"Name"`
Quantity int `json:"quantity" xml:"Quantity"`
Details MyOutputStructDetails `json:"details" xml:"Details"`
}

type InvalidExample struct {
XMLName xml.Name `xml:"TestStruct"`
MyInt int `json:"e" example:"isString" validate:"min=isString,max=isString" `
Expand Down Expand Up @@ -282,6 +292,17 @@ func TestServer_generateOpenAPI(t *testing.T) {
Get(s, "/post/{id}", func(ContextNoBody) (MyOutputStruct, error) {
return MyOutputStruct{}, nil
})

Get(s, "/postxml/{id}", func(ContextNoBody) (MyOutputStructWithXmlAttribute, error) {
return MyOutputStructWithXmlAttribute{
Name: "name",
Quantity: 1,
Details: MyOutputStructDetails{
IngressDate: "2021-01-01",
},
}, nil
})

Post(s, "/multidimensional/post", func(ContextWithBody[MyStruct]) ([][]MyStruct, error) {
return nil, nil
})
Expand All @@ -300,6 +321,14 @@ func TestServer_generateOpenAPI(t *testing.T) {
require.Nil(t, document.Paths.Find("/post/{id}").Get.Responses.Value("200").Value.Content["application/json"].Schema.Value.Properties["unknown"])
require.Equal(t, document.Paths.Find("/post/{id}").Get.Responses.Value("200").Value.Content["application/json"].Schema.Value.Properties["quantity"].Value.Type, &openapi3.Types{"integer"})

require.Nil(t, document.Paths.Find("/postxml/{id}").Get.Responses.Value("200").Value.Content["application/xml"].Schema.Value.Properties["unknown"])
require.Equal(t, document.Paths.Find("/postxml/{id}").Get.Responses.Value("200").Value.Content["application/xml"].Schema.Value.Properties["quantity"].Value.Type, &openapi3.Types{"integer"})
require.Equal(t, document.Paths.Find("/postxml/{id}").Get.Responses.Value("200").Value.Content["application/xml"].Schema.Value.Properties["details"].Value.Type, &openapi3.Types{"object"})
require.Equal(t, document.Paths.Find("/postxml/{id}").Get.Responses.Value("200").Value.Content["application/xml"].Schema.Value.Properties["details"].Value.XML.Name, "Details")
require.Equal(t, document.Paths.Find("/postxml/{id}").Get.Responses.Value("200").Value.Content["application/xml"].Schema.Value.Properties["details"].Value.Properties["in_date"].Value.Type, &openapi3.Types{"string"})
require.Equal(t, document.Paths.Find("/postxml/{id}").Get.Responses.Value("200").Value.Content["application/xml"].Schema.Value.Properties["details"].Value.Properties["in_date"].Value.XML.Attribute, true)
require.Equal(t, document.Paths.Find("/postxml/{id}").Get.Responses.Value("200").Value.Content["application/xml"].Schema.Value.Properties["details"].Value.Properties["in_date"].Value.XML.Name, "date")

t.Run("openapi doc is available through a route", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/swagger/openapi.json", nil)
Expand Down
Loading