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 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c16605e
Adding basic support for XML structure
DimShadoWWW Dec 30, 2024
ef77eaa
Merge branch 'main' into xml-tags
DimShadoWWW Dec 30, 2024
45b88a0
Updating XML code structure
DimShadoWWW Dec 31, 2024
5cbbcb7
Update openapi.go to use 'XML'
DimShadoWWW Dec 31, 2024
357b6b9
Refactor function 'parseStructTags' logic into separate functions and…
DimShadoWWW Jan 1, 2025
3098336
if struct is omitted for JSON, use field name is id
DimShadoWWW Jan 3, 2025
6309376
Merge branch 'go-fuego:main' into xml-tags
DimShadoWWW Jan 4, 2025
d3ab852
Modifying positions to "append" and "prepend"
DimShadoWWW Jan 5, 2025
8c76e4a
Modifying parser function names
DimShadoWWW Jan 5, 2025
ea83213
Fix golangci-lint issues in openapi_metadata.go
DimShadoWWW Jan 6, 2025
3e1a6da
Fix parameter names and adjust log levels in MetadataParsers
DimShadoWWW Jan 6, 2025
578a6e0
Replacing names of default parsers with constants prefixed with metad…
DimShadoWWW Jan 6, 2025
a178685
Fix parameter names in openapi_metadata_test.go
DimShadoWWW Jan 6, 2025
a31e108
Updating metadata parsing functions to take into account list of stru…
DimShadoWWW Jan 8, 2025
715b39b
Update openapi_metadata.go
DimShadoWWW Jan 8, 2025
5289328
Placing comments above fields
DimShadoWWW Jan 8, 2025
15aa629
Split insertRelative into insertBefore and insertAfter
DimShadoWWW Jan 8, 2025
521bffe
If parser is already registered, remove it and insert it in the new p…
DimShadoWWW Jan 8, 2025
92900b0
Fix usage of openapi3.NewArraySchema
DimShadoWWW Jan 8, 2025
d8702dd
Updated test cases in `openapi_metadata_test.go` to use the `testify`…
DimShadoWWW Jan 8, 2025
2f2ff4e
Update openapi_metadata_test.go
DimShadoWWW Jan 8, 2025
c812879
Merge branch 'main' into xml-tags
DimShadoWWW Jan 11, 2025
2680641
Merge branch 'go-fuego:main' into xml-tags
DimShadoWWW Jan 13, 2025
8ec45e9
Update openapi_metadata.go
DimShadoWWW Jan 13, 2025
c2f6bce
Merge branch 'go-fuego:main' into xml-tags
DimShadoWWW Jan 13, 2025
a581729
Update openapi.go: updating from mp.InitializeMetadataParsers to mp.I…
DimShadoWWW Jan 14, 2025
3eb7bb6
Update openapi.go: updating call InitializeMetadataParsers to Initialize
DimShadoWWW Jan 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
124 changes: 7 additions & 117 deletions openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ import (

func NewOpenAPI() *OpenAPI {
desc := NewOpenApiSpec()

mp := NewMetadataParsers()
mp.InitializeMetadataParsers(DefaultParsers)

return &OpenAPI{
description: &desc,
generator: openapi3gen.NewGenerator(),
globalOpenAPIResponses: []openAPIResponse{},
metadataParsers: mp,
}
}

Expand All @@ -33,6 +38,7 @@ type OpenAPI struct {
description *openapi3.T
generator *openapi3gen.Generator
globalOpenAPIResponses []openAPIResponse
metadataParsers *MetadataParsers
}

func (openAPI *OpenAPI) Description() *openapi3.T {
Expand Down Expand Up @@ -403,129 +409,13 @@ func (openapi *OpenAPI) createSchema(key string, v any) *openapi3.SchemaRef {
schemaRef.Value.Description = descriptionable.Description()
}

parseStructTags(reflect.TypeOf(v), schemaRef)
openapi.metadataParsers.ParseStructTags(reflect.TypeOf(v), schemaRef)

openapi.Description().Components.Schemas[key] = schemaRef

return schemaRef
}

// parseStructTags parses struct tags and modifies the schema accordingly.
// t must be a struct type.
// It adds the following struct tags (tag => OpenAPI schema field):
// - description => description
// - example => example
// - json => nullable (if contains omitempty)
// - validate:
// - required => required
// - min=1 => min=1 (for integers)
// - min=1 => minLength=1 (for strings)
// - max=100 => max=100 (for integers)
// - max=100 => maxLength=100 (for strings)
func parseStructTags(t reflect.Type, schemaRef *openapi3.SchemaRef) {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}

if t.Kind() != reflect.Struct {
return
}

for i := range t.NumField() {
field := t.Field(i)
if field.Anonymous {
fieldType := field.Type
parseStructTags(fieldType, schemaRef)
continue
}

jsonFieldName := field.Tag.Get("json")
jsonFieldName = strings.Split(jsonFieldName, ",")[0] // remove omitempty, etc
if jsonFieldName == "-" {
continue
}
if jsonFieldName == "" {
jsonFieldName = field.Name
}

property := schemaRef.Value.Properties[jsonFieldName]
if property == nil {
slog.Warn("Property not found in schema", "property", jsonFieldName)
continue
}
if field.Type.Kind() == reflect.Struct {
parseStructTags(field.Type, property)
}
propertyCopy := *property
propertyValue := *propertyCopy.Value

// Example
example, ok := field.Tag.Lookup("example")
if ok {
propertyValue.Example = example
if propertyValue.Type.Is(openapi3.TypeInteger) {
exNum, err := strconv.Atoi(example)
if err != nil {
slog.Warn("Example might be incorrect (should be integer)", "error", err)
}
propertyValue.Example = exNum
}
}

// Validation
validateTag, ok := field.Tag.Lookup("validate")
validateTags := strings.Split(validateTag, ",")
if ok && slices.Contains(validateTags, "required") {
schemaRef.Value.Required = append(schemaRef.Value.Required, jsonFieldName)
}
for _, validateTag := range validateTags {
if strings.HasPrefix(validateTag, "min=") {
min, err := strconv.Atoi(strings.Split(validateTag, "=")[1])
if err != nil {
slog.Warn("Min might be incorrect (should be integer)", "error", err)
}

if propertyValue.Type.Is(openapi3.TypeInteger) {
minPtr := float64(min)
propertyValue.Min = &minPtr
} else if propertyValue.Type.Is(openapi3.TypeString) {
//nolint:gosec // disable G115
propertyValue.MinLength = uint64(min)
}
}
if strings.HasPrefix(validateTag, "max=") {
max, err := strconv.Atoi(strings.Split(validateTag, "=")[1])
if err != nil {
slog.Warn("Max might be incorrect (should be integer)", "error", err)
}
if propertyValue.Type.Is(openapi3.TypeInteger) {
maxPtr := float64(max)
propertyValue.Max = &maxPtr
} else if propertyValue.Type.Is(openapi3.TypeString) {
//nolint:gosec // disable G115
maxPtr := uint64(max)
propertyValue.MaxLength = &maxPtr
}
}
}

// Description
description, ok := field.Tag.Lookup("description")
if ok {
propertyValue.Description = description
}
jsonTag, ok := field.Tag.Lookup("json")
if ok {
if strings.Contains(jsonTag, ",omitempty") {
propertyValue.Nullable = true
}
}
propertyCopy.Value = &propertyValue

schemaRef.Value.Properties[jsonFieldName] = &propertyCopy
}
}

type OpenAPIDescriptioner interface {
Description() string
}
Loading