Skip to content

Commit c843b39

Browse files
authored
Add more controls to ContentUnit (#75)
* Add default response support to content unit * Add ContentUnitPreparer * Add ContentUnit options * Add more tests, fix description and no content handling
1 parent 56b9d50 commit c843b39

File tree

7 files changed

+129
-13
lines changed

7 files changed

+129
-13
lines changed

internal/operation_context.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,12 @@ func (o *OperationContext) SetPathPattern(pattern string) {
7575
// AddReqStructure adds request content schema.
7676
func (o *OperationContext) AddReqStructure(s interface{}, options ...openapi.ContentOption) {
7777
c := openapi.ContentUnit{}
78-
c.Structure = s
78+
79+
if cp, ok := s.(openapi.ContentUnitPreparer); ok {
80+
cp.SetupContentUnit(&c)
81+
} else {
82+
c.Structure = s
83+
}
7984

8085
for _, o := range options {
8186
o(&c)
@@ -87,7 +92,12 @@ func (o *OperationContext) AddReqStructure(s interface{}, options ...openapi.Con
8792
// AddRespStructure adds response content schema.
8893
func (o *OperationContext) AddRespStructure(s interface{}, options ...openapi.ContentOption) {
8994
c := openapi.ContentUnit{}
90-
c.Structure = s
95+
96+
if cp, ok := s.(openapi.ContentUnitPreparer); ok {
97+
cp.SetupContentUnit(&c)
98+
} else {
99+
c.Structure = s
100+
}
91101

92102
for _, o := range options {
93103
o(&c)

openapi3/_testdata/openapi_req.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +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"}}}},
19+
"requestBody":{"description":"Request body in CSV format.","content":{"text/csv":{"schema":{"type":"string"}}}},
2020
"responses":{"204":{"description":"No Content"}}
2121
}
2222
}

openapi3/helper.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func (s *Spec) AddOperation(method, path string, operation Operation) error {
106106
}
107107

108108
// Add "No Content" response if there are no responses configured.
109-
if len(operation.Responses.MapOfResponseOrRefValues) == 0 {
109+
if len(operation.Responses.MapOfResponseOrRefValues) == 0 && operation.Responses.Default == nil {
110110
operation.Responses.WithMapOfResponseOrRefValuesItem(strconv.Itoa(http.StatusNoContent), ResponseOrRef{
111111
Response: &Response{
112112
Description: http.StatusText(http.StatusNoContent),

openapi3/reflect.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,10 @@ func (r *Reflector) setupRequest(o *Operation, oc openapi.OperationContext) erro
267267
default:
268268
r.stringRequestBody(o, cu.ContentType, cu.Format)
269269
}
270+
271+
if cu.Description != "" && o.RequestBody != nil && o.RequestBody.RequestBody != nil {
272+
o.RequestBody.RequestBody.WithDescription(cu.Description)
273+
}
270274
}
271275

272276
return nil
@@ -690,15 +694,29 @@ func (r *Reflector) hasJSONBody(output interface{}) (bool, error) {
690694

691695
func (r *Reflector) setupResponse(o *Operation, oc openapi.OperationContext) error {
692696
for _, cu := range oc.Response() {
693-
if cu.HTTPStatus == 0 {
694-
cu.HTTPStatus = 200
697+
if cu.HTTPStatus == 0 && !cu.IsDefault {
698+
cu.HTTPStatus = http.StatusOK
695699
}
696700

697701
cu.ContentType = strings.Split(cu.ContentType, ";")[0]
698702

699703
httpStatus := strconv.Itoa(cu.HTTPStatus)
700704
resp := o.Responses.MapOfResponseOrRefValues[httpStatus].Response
701705

706+
switch {
707+
case cu.IsDefault:
708+
httpStatus = "default"
709+
710+
if o.Responses.Default == nil {
711+
o.Responses.Default = &ResponseOrRef{}
712+
}
713+
714+
resp = o.Responses.Default.Response
715+
case cu.HTTPStatus > 0 && cu.HTTPStatus < 6:
716+
httpStatus = strconv.Itoa(cu.HTTPStatus) + "XX"
717+
resp = o.Responses.MapOfResponseOrRefValues[httpStatus].Response
718+
}
719+
702720
if resp == nil {
703721
resp = &Response{}
704722
}
@@ -721,13 +739,19 @@ func (r *Reflector) setupResponse(o *Operation, oc openapi.OperationContext) err
721739
}
722740
}
723741

742+
if cu.Description != "" {
743+
resp.Description = cu.Description
744+
}
745+
724746
if resp.Description == "" {
725747
resp.Description = http.StatusText(cu.HTTPStatus)
726748
}
727749

728-
o.Responses.WithMapOfResponseOrRefValuesItem(httpStatus, ResponseOrRef{
729-
Response: resp,
730-
})
750+
if cu.IsDefault {
751+
o.Responses.Default = &ResponseOrRef{Response: resp}
752+
} else {
753+
o.Responses.WithMapOfResponseOrRefValuesItem(httpStatus, ResponseOrRef{Response: resp})
754+
}
731755
}
732756

733757
return nil

openapi3/reflect_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,3 +989,42 @@ func TestReflector_AddOperation_request_queryObject_deepObject(t *testing.T) {
989989
}
990990
}`, reflector.Spec)
991991
}
992+
993+
type textCSV struct{}
994+
995+
func (t textCSV) SetupContentUnit(cu *openapi.ContentUnit) {
996+
cu.ContentType = "text/csv"
997+
cu.Description = "This is CSV."
998+
cu.IsDefault = true
999+
}
1000+
1001+
func TestReflector_AddOperation_contentUnitPreparer(t *testing.T) {
1002+
r := openapi3.NewReflector()
1003+
oc, err := r.NewOperationContext(http.MethodPost, "/foo")
1004+
require.NoError(t, err)
1005+
1006+
oc.AddReqStructure(textCSV{})
1007+
oc.AddRespStructure(textCSV{})
1008+
1009+
require.NoError(t, r.AddOperation(oc))
1010+
1011+
assertjson.EqMarshal(t, `{
1012+
"openapi":"3.0.3","info":{"title":"","version":""},
1013+
"paths":{
1014+
"/foo":{
1015+
"post":{
1016+
"requestBody":{
1017+
"description":"This is CSV.",
1018+
"content":{"text/csv":{"schema":{"type":"string"}}}
1019+
},
1020+
"responses":{
1021+
"default":{
1022+
"description":"This is CSV.",
1023+
"content":{"text/csv":{"schema":{"type":"string"}}}
1024+
}
1025+
}
1026+
}
1027+
}
1028+
}
1029+
}`, r.SpecSchema())
1030+
}

operation.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,39 @@ type ContentOption func(cu *ContentUnit)
2727

2828
// ContentUnit defines HTTP content.
2929
type ContentUnit struct {
30-
Structure interface{}
31-
ContentType string
32-
Format string
33-
HTTPStatus int
30+
Structure interface{}
31+
ContentType string
32+
Format string
33+
34+
// HTTPStatus can have values 100-599 for single status, or 1-5 for status families (e.g. 2XX)
35+
HTTPStatus int
36+
37+
// IsDefault indicates default response.
38+
IsDefault bool
39+
3440
Description string
3541
fieldMapping map[In]map[string]string
3642
}
3743

44+
// ContentUnitPreparer defines self-contained ContentUnit.
45+
type ContentUnitPreparer interface {
46+
SetupContentUnit(cu *ContentUnit)
47+
}
48+
49+
// WithContentType is a ContentUnit option.
50+
func WithContentType(contentType string) func(cu *ContentUnit) {
51+
return func(cu *ContentUnit) {
52+
cu.ContentType = contentType
53+
}
54+
}
55+
56+
// WithHTTPStatus is a ContentUnit option.
57+
func WithHTTPStatus(httpStatus int) func(cu *ContentUnit) {
58+
return func(cu *ContentUnit) {
59+
cu.HTTPStatus = httpStatus
60+
}
61+
}
62+
3863
// SetFieldMapping sets custom field mapping.
3964
func (c *ContentUnit) SetFieldMapping(in In, fieldToParamName map[string]string) {
4065
if len(fieldToParamName) == 0 {

operation_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package openapi_test
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/swaggest/openapi-go"
9+
)
10+
11+
func TestContentUnit_options(t *testing.T) {
12+
cu := openapi.ContentUnit{}
13+
openapi.WithContentType("text/csv")(&cu)
14+
openapi.WithHTTPStatus(http.StatusConflict)(&cu)
15+
16+
assert.Equal(t, "text/csv", cu.ContentType)
17+
assert.Equal(t, http.StatusConflict, cu.HTTPStatus)
18+
}

0 commit comments

Comments
 (0)