Skip to content

Commit 01501dd

Browse files
committed
feat: openapi enhancements:
* Correct name for properties, now fields with tag "name,omitempty" are exported as "name" instead of "name,omitempty" * Handle anonymous structs * Paths with no methods are discarded * Resources or Actions can be marked to not export
1 parent d6e9454 commit 01501dd

File tree

2 files changed

+95
-25
lines changed

2 files changed

+95
-25
lines changed

boxopenapi/openapi.go

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@ import (
1111

1212
type JSON = map[string]any
1313

14+
func isFalse(a any) bool {
15+
16+
if v, ok := a.(string); ok && v == "false" {
17+
return true
18+
}
19+
20+
if v, ok := a.(bool); ok && v == false {
21+
return true
22+
}
23+
24+
return false
25+
}
26+
1427
// Spec returns a valid OpenAPI spec from a box router ready to be used.
1528
func Spec(api *box.B) OpenAPI {
1629

@@ -19,6 +32,10 @@ func Spec(api *box.B) OpenAPI {
1932

2033
walk(api.R, func(r *box.R) {
2134

35+
if isFalse(r.GetAttribute("openapi")) {
36+
return
37+
}
38+
2239
if len(r.GetActions()) == 0 {
2340
return
2441
}
@@ -30,6 +47,11 @@ func Spec(api *box.B) OpenAPI {
3047
parameters := getParams(r)
3148

3249
for _, action := range r.GetActions() {
50+
51+
if isFalse(action.GetAttribute("openapi")) {
52+
return
53+
}
54+
3355
method := strings.ToLower(action.HttpMethod)
3456

3557
inputs, outputs, _ := describeHandler(action.GetHandler())
@@ -75,7 +97,9 @@ func Spec(api *box.B) OpenAPI {
7597
}
7698
}
7799

78-
paths[path] = methods
100+
if len(methods) > 0 {
101+
paths[path] = methods
102+
}
79103
})
80104

81105
return OpenAPI{
@@ -96,31 +120,9 @@ func Spec(api *box.B) OpenAPI {
96120
}
97121
}
98122

99-
func schemaStruct(schemas JSON, item reflect.Type) JSON {
100-
101-
name := item.Name()
102-
{
103-
// Normalize name:
104-
name = strings.TrimRight(name, "]")
105-
parts := strings.Split(name, ".")
106-
name = parts[len(parts)-1]
107-
}
108-
109-
result := JSON{
110-
"$ref": "#/components/schemas/" + name,
111-
}
112-
113-
if _, exists := schemas[name]; exists {
114-
return result
115-
}
123+
func schemaStructFields(schemas JSON, item reflect.Type) JSON {
116124

117125
properties := JSON{}
118-
definition := JSON{
119-
"type": "object",
120-
"required": []string{},
121-
"properties": properties,
122-
}
123-
schemas[name] = definition
124126

125127
// follow pointers
126128
for item.Kind() == reflect.Ptr {
@@ -130,20 +132,61 @@ func schemaStruct(schemas JSON, item reflect.Type) JSON {
130132
for i := 0; i < item.NumField(); i++ {
131133
f := item.Field(i)
132134

135+
if f.Anonymous {
136+
anonymousProperties := schemaStructFields(schemas, f.Type)
137+
for k, v := range anonymousProperties {
138+
properties[k] = v
139+
}
140+
continue
141+
}
142+
133143
definition := schemaAny(schemas, f.Type)
134144

135145
if v, ok := f.Tag.Lookup("description"); ok {
136146
definition["description"] = v
137147
}
138148

139149
name := f.Tag.Get("json")
150+
name = strings.Split(name, ",")[0]
140151
if name == "" {
141152
name = f.Name
142153
}
143154

144155
properties[name] = definition
145156
}
146157

158+
return properties
159+
}
160+
161+
func schemaStruct(schemas JSON, item reflect.Type) JSON {
162+
163+
name := item.Name()
164+
{
165+
// Normalize name:
166+
name = strings.TrimRight(name, "]")
167+
parts := strings.Split(name, ".")
168+
name = parts[len(parts)-1]
169+
}
170+
171+
result := JSON{
172+
"$ref": "#/components/schemas/" + name,
173+
}
174+
175+
if _, exists := schemas[name]; exists {
176+
return result
177+
}
178+
179+
schemas[name] = JSON{} // Avoid infinite loop in recursive objects
180+
181+
properties := schemaStructFields(schemas, item)
182+
183+
definition := JSON{
184+
"type": "object",
185+
"required": []string{},
186+
"properties": properties,
187+
}
188+
schemas[name] = definition
189+
147190
return result
148191
}
149192

boxopenapi/openapi_test.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,26 @@ func newApiExample() *box.B {
2222
b.Handle("GET", "/scalarValues", GetScalarValues)
2323
b.Handle("GET", "/time.Time", GetTypeTime)
2424

25+
b.Handle("GET", "/hidden-action", func() {}).SetAttribute("openapi", false)
26+
27+
b.Group("/hidden-resource").SetAttribute("openapi", false)
28+
b.Handle("GET", "/hidden-resource", func() {})
29+
2530
return b
2631
}
2732

2833
type User struct {
2934
Id string `json:"id" description:"User identifier"`
3035
Name string `json:"name" description:"User name"`
31-
Tags []string `json:"tags" description:"User tags"`
36+
Tags []string `json:"tags,omitempty" description:"User tags"`
3237
Age int `json:"age" description:"User age"`
3338
Active bool `json:"active" description:"User active"`
39+
AnonymousFields
40+
}
41+
42+
type AnonymousFields struct {
43+
CreationDate time.Time `json:"creation_date" description:"Creation date"`
44+
ModifiedDate time.Time `json:"modifiedd_date" description:"Modification date"`
3445
}
3546

3647
func ListUsers() []*User {
@@ -235,6 +246,22 @@ func TestOpenApi(t *testing.T) {
235246
},
236247
"type": "array",
237248
},
249+
"creation_date": JSON{
250+
"description": "Creation date",
251+
"examples": []string{
252+
"2006-01-02T15:04:05Z07:00",
253+
},
254+
"format": "date-time",
255+
"type": "string",
256+
},
257+
"modifiedd_date": JSON{
258+
"description": "Modification date",
259+
"examples": []string{
260+
"2006-01-02T15:04:05Z07:00",
261+
},
262+
"format": "date-time",
263+
"type": "string",
264+
},
238265
},
239266
"required": []JSON{},
240267
"type": "object",

0 commit comments

Comments
 (0)