Skip to content

Commit e7260f9

Browse files
authored
Abstract OpenAPI schema with an interface (#73)
* Abstract OpenAPI schema with an interface * Fix DefName conflict in case of multiple callbacks * Fix handling of multiple request structures
1 parent 709f0d7 commit e7260f9

11 files changed

+101
-31
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/bool64/dev v0.2.29
77
github.com/stretchr/testify v1.8.2
88
github.com/swaggest/assertjson v1.9.0
9-
github.com/swaggest/jsonschema-go v0.3.54
9+
github.com/swaggest/jsonschema-go v0.3.55
1010
github.com/swaggest/refl v1.2.0
1111
gopkg.in/yaml.v2 v2.4.0
1212
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ
3333
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
3434
github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
3535
github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
36-
github.com/swaggest/jsonschema-go v0.3.54 h1:kRwXd7tqrub8vmtAVV5IElGTE1hiEuo/n9gHrsX7ySY=
37-
github.com/swaggest/jsonschema-go v0.3.54/go.mod h1:iQdEa2VW62As5W+884vA13TC2pEwnKjlQGaQe2pj+fc=
36+
github.com/swaggest/jsonschema-go v0.3.55 h1:xbDQaLw9NxxkL3meYnUnX5f6Hhav2wNAEfb/We53CkM=
37+
github.com/swaggest/jsonschema-go v0.3.55/go.mod h1:5WFFGBBte5JAWAV8gDpNRJ/tlQnb1AHDdf/ghgsVUik=
3838
github.com/swaggest/refl v1.2.0 h1:Qqqhfwi7REXF6/4cwJmj3gQMzl0Q0cYquxTYdD0kvi0=
3939
github.com/swaggest/refl v1.2.0/go.mod h1:CkC6g7h1PW33KprTuYRSw8UUOslRUt4lF3oe7tTIgNU=
4040
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=

openapi3/_testdata/openapi_req.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"openapi":"3.0.3","info":{"title":"SampleAPI","version":"1.2.3"},
2+
"openapi":"3.0.3","info":{"title":"SampleAPI","description":"This a sample API description.","version":"1.2.3"},
33
"paths":{
44
"/somewhere/{in_path}":{
55
"get":{
@@ -16,6 +16,7 @@
1616
{"name":"in_cookie","in":"cookie","deprecated":true,"schema":{"type":"string","deprecated":true}},
1717
{"name":"in_header","in":"header","schema":{"type":"number"}}
1818
],
19+
"requestBody":{"content":{"text/csv":{"schema":{"type":"string"}}}},
1920
"responses":{"204":{"description":"No Content"}}
2021
}
2122
}

openapi3/_testdata/openapi_req2.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"openapi":"3.0.3","info":{"title":"SampleAPI","description":"This a sample API description.","version":"1.2.3"},
3+
"paths":{
4+
"/somewhere/{in_path}":{
5+
"get":{
6+
"parameters":[
7+
{
8+
"name":"in_query1","in":"query","description":"Query parameter.","required":true,
9+
"schema":{"type":"integer","description":"Query parameter."}
10+
},
11+
{
12+
"name":"in_query3","in":"query","description":"Query parameter.","required":true,
13+
"schema":{"type":"integer","description":"Query parameter."}
14+
},
15+
{"name":"in_path","in":"path","required":true,"schema":{"type":"integer"}},
16+
{"name":"in_cookie","in":"cookie","deprecated":true,"schema":{"type":"string","deprecated":true}},
17+
{"name":"in_header","in":"header","schema":{"type":"number"}}
18+
],
19+
"responses":{"204":{"description":"No Content"}}
20+
}
21+
}
22+
}
23+
}

openapi3/_testdata/swgui/swgui.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,23 @@ package main
33
import (
44
"log"
55
"net/http"
6-
"os"
76

87
swgui "github.com/swaggest/swgui/v5"
98
)
109

1110
func main() {
12-
h := swgui.NewHandler("Foo", "/openapi.json", "/")
11+
urlToSchema := "/openapi.json"
12+
filePathToSchema := "../openapi.json"
13+
14+
swh := swgui.NewHandler("Foo", urlToSchema, "/")
1315
hh := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
14-
if r.URL.Path == "/openapi.json" {
15-
o, err := os.ReadFile("../openapi.json")
16-
if err != nil {
17-
http.Error(rw, err.Error(), 500)
18-
return
19-
}
20-
rw.Header().Set("Content-Type", "application/json")
21-
rw.Write(o)
22-
return
16+
if r.URL.Path == urlToSchema {
17+
http.ServeFile(rw, r, filePathToSchema)
2318
}
2419

25-
h.ServeHTTP(rw, r)
20+
swh.ServeHTTP(rw, r)
2621
})
22+
2723
log.Println("Starting Swagger UI server at http://localhost:8082/")
2824
_ = http.ListenAndServe("localhost:8082", hh)
2925
}

openapi3/helper.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,20 @@ func (o Operation) UnknownParamIsForbidden(in ParameterIn) bool {
127127

128128
return f && ok
129129
}
130+
131+
var _ openapi.SpecSchema = &Spec{}
132+
133+
// SetTitle describes the service.
134+
func (s *Spec) SetTitle(t string) {
135+
s.Info.Title = t
136+
}
137+
138+
// SetDescription describes the service.
139+
func (s *Spec) SetDescription(d string) {
140+
s.Info.WithDescription(d)
141+
}
142+
143+
// SetVersion describes the service.
144+
func (s *Spec) SetVersion(v string) {
145+
s.Info.Version = v
146+
}

openapi3/reflect.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -243,21 +243,27 @@ func (r *Reflector) setupRequest(o *Operation, oc openapi.OperationContext) erro
243243
for _, cu := range oc.Request() {
244244
switch cu.ContentType {
245245
case "":
246-
return joinErrors(
246+
if err := joinErrors(
247247
r.parseParameters(o, oc, cu),
248248
r.parseRequestBody(o, oc, cu, mimeJSON, oc.Method(), nil, tagJSON),
249249
r.parseRequestBody(o, oc, cu, mimeFormUrlencoded, oc.Method(), cu.FieldMapping(openapi.InFormData), tagFormData, tagForm),
250-
)
250+
); err != nil {
251+
return err
252+
}
251253
case mimeJSON:
252-
return joinErrors(
254+
if err := joinErrors(
253255
r.parseParameters(o, oc, cu),
254256
r.parseRequestBody(o, oc, cu, mimeJSON, oc.Method(), nil, tagJSON),
255-
)
257+
); err != nil {
258+
return err
259+
}
256260
case mimeFormUrlencoded, mimeMultipart:
257-
return joinErrors(
261+
if err := joinErrors(
258262
r.parseParameters(o, oc, cu),
259263
r.parseRequestBody(o, oc, cu, mimeFormUrlencoded, oc.Method(), cu.FieldMapping(openapi.InFormData), tagFormData, tagForm),
260-
)
264+
); err != nil {
265+
return err
266+
}
261267
default:
262268
r.stringRequestBody(o, cu.ContentType, cu.Format)
263269
}
@@ -573,9 +579,9 @@ func (r *Reflector) parseParametersIn(
573579
var defNameSanitizer = regexp.MustCompile(`[^a-zA-Z0-9.\-_]+`)
574580

575581
func sanitizeDefName(rc *jsonschema.ReflectContext) {
576-
rc.DefName = func(t reflect.Type, defaultDefName string) string {
582+
jsonschema.InterceptDefName(func(t reflect.Type, defaultDefName string) string {
577583
return defNameSanitizer.ReplaceAllString(defaultDefName, "")
578-
}
584+
})(rc)
579585
}
580586

581587
func (r *Reflector) collectDefinition(namePrefix string) func(name string, schema jsonschema.Schema) {
@@ -790,3 +796,13 @@ func (r *Reflector) parseJSONResponse(resp *Response, oc openapi.OperationContex
790796

791797
return nil
792798
}
799+
800+
// SpecSchema returns OpenAPI spec schema.
801+
func (r *Reflector) SpecSchema() openapi.SpecSchema {
802+
return r.SpecEns()
803+
}
804+
805+
// JSONSchemaReflector provides access to a low-level struct reflector.
806+
func (r *Reflector) JSONSchemaReflector() *jsonschema.Reflector {
807+
return &r.Reflector
808+
}

openapi3/reflect_deprecated_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ func TestReflector_SetRequest(t *testing.T) {
8383
reflector := openapi3.Reflector{}
8484

8585
s := reflector.SpecEns()
86-
s.Info.Title = apiName
87-
s.Info.Version = apiVersion
86+
s.SetTitle(apiName)
87+
s.SetVersion(apiVersion)
88+
s.SetDescription("This a sample API description.")
8889

8990
op := openapi3.Operation{}
9091

@@ -96,9 +97,9 @@ func TestReflector_SetRequest(t *testing.T) {
9697
b, err := assertjson.MarshalIndentCompact(s, "", " ", 120)
9798
assert.NoError(t, err)
9899

99-
require.NoError(t, os.WriteFile("_testdata/openapi_req_last_run.json", b, 0o600))
100+
require.NoError(t, os.WriteFile("_testdata/openapi_req2_last_run.json", b, 0o600))
100101

101-
expected, err := os.ReadFile("_testdata/openapi_req.json")
102+
expected, err := os.ReadFile("_testdata/openapi_req2.json")
102103
require.NoError(t, err)
103104

104105
assertjson.Equal(t, expected, b)

openapi3/reflect_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,18 @@ func TestReflector_AddOperation_uploadInterface(t *testing.T) {
162162
func TestReflector_AddOperation_request(t *testing.T) {
163163
reflector := openapi3.Reflector{}
164164

165-
s := reflector.SpecEns()
166-
s.Info.Title = apiName
167-
s.Info.Version = apiVersion
165+
s := reflector.SpecSchema()
166+
s.SetTitle(apiName)
167+
s.SetVersion(apiVersion)
168+
s.SetDescription("This a sample API description.")
168169

169170
oc, err := reflector.NewOperationContext(http.MethodGet, "/somewhere/{in_path}")
170171
require.NoError(t, err)
171172
oc.AddReqStructure(new(GetReq))
173+
oc.AddReqStructure(nil, func(cu *openapi.ContentUnit) {
174+
cu.ContentType = "text/csv"
175+
cu.Description = "Request body in CSV format."
176+
})
172177

173178
require.NoError(t, reflector.AddOperation(oc))
174179

reflector.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ type Reflector interface {
88

99
NewOperationContext(method, pathPattern string) (OperationContext, error)
1010
AddOperation(oc OperationContext) error
11+
12+
SpecSchema() SpecSchema
13+
JSONSchemaReflector() *jsonschema.Reflector
1114
}
1215

1316
// JSONSchemaCallback is a user function called by JSONSchemaWalker.

spec.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package openapi
2+
3+
// SpecSchema abstracts OpenAPI schema implementation to generalize multiple revisions.
4+
type SpecSchema interface {
5+
SetTitle(t string)
6+
SetDescription(d string)
7+
SetVersion(v string)
8+
}

0 commit comments

Comments
 (0)