Skip to content

Commit 893f0c9

Browse files
authored
Add support for OpenAPI 3.1 (#76)
1 parent c843b39 commit 893f0c9

33 files changed

+12829
-60
lines changed

Makefile

+9-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ ifeq ($(DEVGO_PATH),)
2828
endif
2929

3030
JSON_CLI_VERSION := "v1.8.6"
31+
JSON_CLI_VERSION_31 := "v1.11.1"
3132

3233
-include $(DEVGO_PATH)/makefiles/main.mk
3334
-include $(DEVGO_PATH)/makefiles/lint.mk
@@ -40,7 +41,14 @@ JSON_CLI_VERSION := "v1.8.6"
4041
test: test-unit
4142

4243
## Generate entities from schema
43-
gen:
44+
gen-3.0:
4445
@test -s $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) || (curl -sSfL https://github.com/swaggest/json-cli/releases/download/$(JSON_CLI_VERSION)/json-cli -o $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) && chmod +x $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION))
4546
@cd resources/schema/ && $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) gen-go openapi3.json --output ../../openapi3/entities.go --package-name openapi3 --with-tests --with-zero-values --validate-required --fluent-setters --root-name Spec
4647
@gofmt -w ./openapi3/entities.go ./openapi3/entities_test.go
48+
49+
50+
## Generate entities from schema
51+
gen-3.1:
52+
@test -s $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION_31) || (curl -sSfL https://github.com/swaggest/json-cli/releases/download/$(JSON_CLI_VERSION_31)/json-cli -o $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION_31) && chmod +x $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION_31))
53+
@cd resources/schema/ && $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION_31) gen-go openapi31-patched.json --config openapi31-config.json --output ../../openapi31/entities.go --package-name openapi31 --def-ptr '#/$$defs' --with-zero-values --validate-required --fluent-setters --root-name Spec
54+
@gofmt -w ./openapi31/entities.go

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ For automated HTTP REST service framework built with this library please check [
1616
## Features
1717

1818
* Type safe mapping of OpenAPI 3 documents with Go structures generated from schema.
19-
* Type-based reflection of Go structures to OpenAPI 3 schema.
19+
* Type-based reflection of Go structures to OpenAPI 3.0 or 3.1 schema.
2020
* Schema control with field tags
2121
* `json` for request bodies and responses in JSON
2222
* `query`, `path` for parameters in URL

go.mod

+1-1
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.55
9+
github.com/swaggest/jsonschema-go v0.3.57
1010
github.com/swaggest/refl v1.2.0
1111
gopkg.in/yaml.v2 v2.4.0
1212
)

go.sum

+2-2
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.55 h1:xbDQaLw9NxxkL3meYnUnX5f6Hhav2wNAEfb/We53CkM=
37-
github.com/swaggest/jsonschema-go v0.3.55/go.mod h1:5WFFGBBte5JAWAV8gDpNRJ/tlQnb1AHDdf/ghgsVUik=
36+
github.com/swaggest/jsonschema-go v0.3.57 h1:n6D/2K9557Yqn/NohXoszmjuN0Lp5n0DyHlVLRZXbOM=
37+
github.com/swaggest/jsonschema-go v0.3.57/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/example_security_test.go

+6-33
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,12 @@ import (
99
"github.com/swaggest/openapi-go/openapi3"
1010
)
1111

12-
func ExampleReflector_AddOperation_http_basic_auth() {
12+
func ExampleSpec_SetHTTPBasicSecurity() {
1313
reflector := openapi3.Reflector{}
1414
securityName := "admin"
1515

1616
// Declare security scheme.
17-
reflector.SpecEns().ComponentsEns().SecuritySchemesEns().WithMapOfSecuritySchemeOrRefValuesItem(
18-
securityName,
19-
openapi3.SecuritySchemeOrRef{
20-
SecurityScheme: &openapi3.SecurityScheme{
21-
HTTPSecurityScheme: (&openapi3.HTTPSecurityScheme{}).WithScheme("basic").WithDescription("Admin Access"),
22-
},
23-
},
24-
)
17+
reflector.SpecEns().SetHTTPBasicSecurity(securityName, "Admin Access")
2518

2619
oc, _ := reflector.NewOperationContext(http.MethodGet, "/secure")
2720
oc.AddRespStructure(struct {
@@ -85,22 +78,12 @@ func ExampleReflector_AddOperation_http_basic_auth() {
8578
// type: http
8679
}
8780

88-
func ExampleReflector_AddOperation_api_key_auth() {
81+
func ExampleSpec_SetAPIKeySecurity() {
8982
reflector := openapi3.Reflector{}
9083
securityName := "api_key"
9184

9285
// Declare security scheme.
93-
reflector.SpecEns().ComponentsEns().SecuritySchemesEns().WithMapOfSecuritySchemeOrRefValuesItem(
94-
securityName,
95-
openapi3.SecuritySchemeOrRef{
96-
SecurityScheme: &openapi3.SecurityScheme{
97-
APIKeySecurityScheme: (&openapi3.APIKeySecurityScheme{}).
98-
WithName("Authorization").
99-
WithIn("header").
100-
WithDescription("API Access"),
101-
},
102-
},
103-
)
86+
reflector.SpecEns().SetAPIKeySecurity(securityName, "Authorization", openapi.InHeader, "API Access")
10487

10588
oc, _ := reflector.NewOperationContext(http.MethodGet, "/secure")
10689
oc.AddRespStructure(struct {
@@ -165,22 +148,12 @@ func ExampleReflector_AddOperation_api_key_auth() {
165148
// type: apiKey
166149
}
167150

168-
func ExampleReflector_AddOperation_http_bearer_token_auth() {
151+
func ExampleSpec_SetHTTPBearerTokenSecurity() {
169152
reflector := openapi3.Reflector{}
170153
securityName := "bearer_token"
171154

172155
// Declare security scheme.
173-
reflector.SpecEns().ComponentsEns().SecuritySchemesEns().WithMapOfSecuritySchemeOrRefValuesItem(
174-
securityName,
175-
openapi3.SecuritySchemeOrRef{
176-
SecurityScheme: &openapi3.SecurityScheme{
177-
HTTPSecurityScheme: (&openapi3.HTTPSecurityScheme{}).
178-
WithScheme("bearer").
179-
WithBearerFormat("JWT").
180-
WithDescription("Admin Access"),
181-
},
182-
},
183-
)
156+
reflector.SpecEns().SetHTTPBearerTokenSecurity(securityName, "JWT", "Admin Access")
184157

185158
oc, _ := reflector.NewOperationContext(http.MethodGet, "/secure")
186159
oc.AddRespStructure(struct {

openapi3/example_test.go

+17-18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http"
77
"time"
88

9+
"github.com/swaggest/openapi-go"
910
"github.com/swaggest/openapi-go/openapi3"
1011
)
1112

@@ -339,8 +340,8 @@ components:
339340
// bar
340341
}
341342

342-
func ExampleReflector_SetJSONResponse() {
343-
reflector := openapi3.Reflector{}
343+
func ExampleReflector_AddOperation() {
344+
reflector := openapi3.NewReflector()
344345
reflector.Spec = &openapi3.Spec{Openapi: "3.0.3"}
345346
reflector.Spec.Info.
346347
WithTitle("Things API").
@@ -368,18 +369,17 @@ func ExampleReflector_SetJSONResponse() {
368369
UpdatedAt time.Time `json:"updated_at"`
369370
}
370371

371-
putOp := openapi3.Operation{}
372+
putOp, _ := reflector.NewOperationContext(http.MethodPut, "/things/{id}")
372373

373-
handleError(reflector.SetRequest(&putOp, new(req), http.MethodPut))
374-
handleError(reflector.SetJSONResponse(&putOp, new(resp), http.StatusOK))
375-
handleError(reflector.SetJSONResponse(&putOp, new([]resp), http.StatusConflict))
376-
handleError(reflector.Spec.AddOperation(http.MethodPut, "/things/{id}", putOp))
374+
putOp.AddReqStructure(new(req))
375+
putOp.AddRespStructure(new(resp))
376+
putOp.AddRespStructure(new([]resp), openapi.WithHTTPStatus(http.StatusConflict))
377+
handleError(reflector.AddOperation(putOp))
377378

378-
getOp := openapi3.Operation{}
379-
380-
handleError(reflector.SetRequest(&getOp, new(req), http.MethodGet))
381-
handleError(reflector.SetJSONResponse(&getOp, new(resp), http.StatusOK))
382-
handleError(reflector.Spec.AddOperation(http.MethodGet, "/things/{id}", getOp))
379+
getOp, _ := reflector.NewOperationContext(http.MethodGet, "/things/{id}")
380+
getOp.AddReqStructure(new(req))
381+
getOp.AddRespStructure(new(resp))
382+
handleError(reflector.AddOperation(getOp))
383383

384384
schema, err := reflector.Spec.MarshalYAML()
385385
if err != nil {
@@ -493,9 +493,8 @@ func ExampleReflector_SetJSONResponse() {
493493
// type: object
494494
}
495495

496-
func ExampleReflector_SetRequest_queryObject() {
497-
reflector := openapi3.Reflector{}
498-
reflector.Spec = &openapi3.Spec{Openapi: "3.0.3"}
496+
func ExampleReflector_AddOperation_queryObject() {
497+
reflector := openapi3.NewReflector()
499498
reflector.Spec.Info.
500499
WithTitle("Things API").
501500
WithVersion("1.2.3").
@@ -526,10 +525,10 @@ func ExampleReflector_SetRequest_queryObject() {
526525
DeepObjectFilter deepObjectFilter `query:"deep_object_filter"`
527526
}
528527

529-
getOp := openapi3.Operation{}
528+
getOp, _ := reflector.NewOperationContext(http.MethodGet, "/things/{id}")
530529

531-
_ = reflector.SetRequest(&getOp, new(req), http.MethodGet)
532-
_ = reflector.Spec.AddOperation(http.MethodGet, "/things/{id}", getOp)
530+
getOp.AddReqStructure(new(req))
531+
_ = reflector.AddOperation(getOp)
533532

534533
schema, err := reflector.Spec.MarshalYAML()
535534
if err != nil {

openapi3/helper.go

+66
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,83 @@ func (o Operation) UnknownParamIsForbidden(in ParameterIn) bool {
130130

131131
var _ openapi.SpecSchema = &Spec{}
132132

133+
// Title returns service title.
134+
func (s *Spec) Title() string {
135+
return s.Info.Title
136+
}
137+
133138
// SetTitle describes the service.
134139
func (s *Spec) SetTitle(t string) {
135140
s.Info.Title = t
136141
}
137142

143+
// Description returns service description.
144+
func (s *Spec) Description() string {
145+
if s.Info.Description != nil {
146+
return *s.Info.Description
147+
}
148+
149+
return ""
150+
}
151+
138152
// SetDescription describes the service.
139153
func (s *Spec) SetDescription(d string) {
140154
s.Info.WithDescription(d)
141155
}
142156

157+
// Version returns service version.
158+
func (s *Spec) Version() string {
159+
return s.Info.Version
160+
}
161+
143162
// SetVersion describes the service.
144163
func (s *Spec) SetVersion(v string) {
145164
s.Info.Version = v
146165
}
166+
167+
// SetHTTPBasicSecurity sets security definition.
168+
func (s *Spec) SetHTTPBasicSecurity(securityName string, description string) {
169+
s.ComponentsEns().SecuritySchemesEns().WithMapOfSecuritySchemeOrRefValuesItem(
170+
securityName,
171+
SecuritySchemeOrRef{
172+
SecurityScheme: &SecurityScheme{
173+
HTTPSecurityScheme: (&HTTPSecurityScheme{}).WithScheme("basic").WithDescription(description),
174+
},
175+
},
176+
)
177+
}
178+
179+
// SetAPIKeySecurity sets security definition.
180+
func (s *Spec) SetAPIKeySecurity(securityName string, fieldName string, fieldIn openapi.In, description string) {
181+
s.ComponentsEns().SecuritySchemesEns().WithMapOfSecuritySchemeOrRefValuesItem(
182+
securityName,
183+
SecuritySchemeOrRef{
184+
SecurityScheme: &SecurityScheme{
185+
APIKeySecurityScheme: (&APIKeySecurityScheme{}).
186+
WithName(fieldName).
187+
WithIn(APIKeySecuritySchemeIn(fieldIn)).
188+
WithDescription(description),
189+
},
190+
},
191+
)
192+
}
193+
194+
// SetHTTPBearerTokenSecurity sets security definition.
195+
func (s *Spec) SetHTTPBearerTokenSecurity(securityName string, format string, description string) {
196+
ss := &SecurityScheme{
197+
HTTPSecurityScheme: (&HTTPSecurityScheme{}).
198+
WithScheme("bearer").
199+
WithDescription(description),
200+
}
201+
202+
if format != "" {
203+
ss.HTTPSecurityScheme.WithBearerFormat(format)
204+
}
205+
206+
s.ComponentsEns().SecuritySchemesEns().WithMapOfSecuritySchemeOrRefValuesItem(
207+
securityName,
208+
SecuritySchemeOrRef{
209+
SecurityScheme: ss,
210+
},
211+
)
212+
}

openapi3/jsonschema.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func (s *SchemaOrRef) ToJSONSchema(spec *Spec) jsonschema.SchemaOrBool {
2525

2626
// Inline root reference without recursions.
2727
if s.SchemaReference != nil {
28-
dstName := strings.TrimPrefix(s.SchemaReference.Ref, "#/components/schemas/")
28+
dstName := strings.TrimPrefix(s.SchemaReference.Ref, componentsSchemas)
2929
if ctx.refsCount[dstName] == 1 {
3030
js = ctx.refsProcessed[dstName]
3131
delete(ctx.refsProcessed, dstName)
@@ -49,8 +49,8 @@ func (s *SchemaOrRef) toJSONSchema(ctx toJSONSchemaContext) jsonschema.SchemaOrB
4949
if s.SchemaReference != nil {
5050
jso.WithRef(s.SchemaReference.Ref)
5151

52-
if strings.HasPrefix(s.SchemaReference.Ref, "#/components/schemas/") {
53-
dstName := strings.TrimPrefix(s.SchemaReference.Ref, "#/components/schemas/")
52+
if strings.HasPrefix(s.SchemaReference.Ref, componentsSchemas) {
53+
dstName := strings.TrimPrefix(s.SchemaReference.Ref, componentsSchemas)
5454

5555
if _, alreadyProcessed := ctx.refsProcessed[dstName]; !alreadyProcessed {
5656
ctx.refsProcessed[dstName] = jsonschema.SchemaOrBool{}

openapi3/yaml.go

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ func (s *Spec) UnmarshalYAML(data []byte) error {
3030

3131
// MarshalYAML produces YAML bytes.
3232
func (s *Spec) MarshalYAML() ([]byte, error) {
33-
// return ya.Marshal(s)
3433
jsonData, err := s.MarshalJSON()
3534
if err != nil {
3635
return nil, err

0 commit comments

Comments
 (0)