Skip to content

Commit 339f6b5

Browse files
authored
Add contentType field tag for bodies (#135)
1 parent cdbf0a7 commit 339f6b5

File tree

5 files changed

+145
-0
lines changed

5 files changed

+145
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ For automated HTTP REST service framework built with this library please check [
2222
* `query`, `path` for parameters in URL
2323
* `header`, `cookie`, `formData`, `file` for other parameters
2424
* `form` acts as `query` and `formData`
25+
* `contentType` indicates body content type
2526
* [field tags](https://github.com/swaggest/jsonschema-go#field-tags) named after JSON Schema/OpenAPI 3 Schema constraints
2627
* `collectionFormat` to unpack slices from string
2728
* `csv` comma-separated values,

openapi3/reflect.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ func (r *Reflector) setupRequest(o *Operation, oc openapi.OperationContext) erro
279279
); err != nil {
280280
return err
281281
}
282+
283+
r.parseRawRequestBody(o, cu)
282284
case mimeJSON:
283285
if err := joinErrors(
284286
r.parseParameters(o, oc, cu),
@@ -314,6 +316,7 @@ const (
314316
tagFormData = "formData"
315317
tagForm = "form"
316318
tagHeader = "header"
319+
tagContentType = "contentType"
317320
mimeJSON = "application/json"
318321
mimeFormUrlencoded = "application/x-www-form-urlencoded"
319322
mimeMultipart = "multipart/form-data"
@@ -346,6 +349,16 @@ func (r *Reflector) stringRequestBody(
346349
o.RequestBodyEns().RequestBodyEns().WithContentItem(mime, mediaType(format))
347350
}
348351

352+
func (r *Reflector) parseRawRequestBody(o *Operation, cu openapi.ContentUnit) {
353+
if cu.Structure == nil {
354+
return
355+
}
356+
357+
refl.WalkTaggedFields(reflect.ValueOf(cu.Structure), func(_ reflect.Value, _ reflect.StructField, tag string) {
358+
r.stringRequestBody(o, tag, "")
359+
}, tagContentType)
360+
}
361+
349362
func (r *Reflector) parseRequestBody(
350363
o *Operation,
351364
oc openapi.OperationContext,
@@ -617,6 +630,16 @@ func (r *Reflector) parseResponseHeader(resp *Response, oc openapi.OperationCont
617630
return nil
618631
}
619632

633+
func (r *Reflector) parseRawResponseBody(resp *Response, cu openapi.ContentUnit) {
634+
if cu.Structure == nil {
635+
return
636+
}
637+
638+
refl.WalkTaggedFields(reflect.ValueOf(cu.Structure), func(_ reflect.Value, _ reflect.StructField, tag string) {
639+
r.ensureResponseContentType(resp, tag, "")
640+
}, tagContentType)
641+
}
642+
620643
func (r *Reflector) setupResponse(o *Operation, oc openapi.OperationContext) error {
621644
for _, cu := range oc.Response() {
622645
if cu.HTTPStatus == 0 && !cu.IsDefault {
@@ -654,6 +677,8 @@ func (r *Reflector) setupResponse(o *Operation, oc openapi.OperationContext) err
654677
return err
655678
}
656679

680+
r.parseRawResponseBody(resp, cu)
681+
657682
if cu.ContentType != "" {
658683
r.ensureResponseContentType(resp, cu.ContentType, cu.Format)
659684
}

openapi3/reflect_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,3 +1355,50 @@ func TestWithCustomize(t *testing.T) {
13551355
}
13561356
}`, r.SpecSchema())
13571357
}
1358+
1359+
func TestRawBody(t *testing.T) {
1360+
r := openapi3.NewReflector()
1361+
1362+
oc, err := r.NewOperationContext(http.MethodPost, "/")
1363+
require.NoError(t, err)
1364+
1365+
type req struct {
1366+
TextBody string `contentType:"text/plain"`
1367+
CSVBody string `contentType:"text/csv"`
1368+
}
1369+
1370+
type resp struct {
1371+
TextBody string `contentType:"text/plain"`
1372+
CSVBody string `contentType:"text/csv"`
1373+
}
1374+
1375+
oc.AddReqStructure(req{})
1376+
oc.AddRespStructure(resp{})
1377+
1378+
require.NoError(t, r.AddOperation(oc))
1379+
1380+
assertjson.EqMarshal(t, `{
1381+
"openapi":"3.0.3","info":{"title":"","version":""},
1382+
"paths":{
1383+
"/":{
1384+
"post":{
1385+
"requestBody":{
1386+
"content":{
1387+
"text/csv":{"schema":{"type":"string"}},
1388+
"text/plain":{"schema":{"type":"string"}}
1389+
}
1390+
},
1391+
"responses":{
1392+
"200":{
1393+
"description":"OK",
1394+
"content":{
1395+
"text/csv":{"schema":{"type":"string"}},
1396+
"text/plain":{"schema":{"type":"string"}}
1397+
}
1398+
}
1399+
}
1400+
}
1401+
}
1402+
}
1403+
}`, r.SpecSchema())
1404+
}

openapi31/reflect.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ func (r *Reflector) setupRequest(o *Operation, oc openapi.OperationContext) erro
226226
); err != nil {
227227
return err
228228
}
229+
230+
r.parseRawRequestBody(o, cu)
229231
case mimeJSON:
230232
if err := joinErrors(
231233
r.parseParameters(o, oc, cu),
@@ -260,6 +262,7 @@ const (
260262
tagJSON = "json"
261263
tagFormData = "formData"
262264
tagForm = "form"
265+
tagContentType = "contentType"
263266
mimeJSON = "application/json"
264267
mimeFormUrlencoded = "application/x-www-form-urlencoded"
265268
mimeMultipart = "multipart/form-data"
@@ -293,6 +296,16 @@ func (r *Reflector) stringRequestBody(
293296
o.RequestBodyEns().RequestBodyEns().WithContentItem(mime, mediaType(format))
294297
}
295298

299+
func (r *Reflector) parseRawRequestBody(o *Operation, cu openapi.ContentUnit) {
300+
if cu.Structure == nil {
301+
return
302+
}
303+
304+
refl.WalkTaggedFields(reflect.ValueOf(cu.Structure), func(_ reflect.Value, _ reflect.StructField, tag string) {
305+
r.stringRequestBody(o, tag, "")
306+
}, tagContentType)
307+
}
308+
296309
func (r *Reflector) parseRequestBody(
297310
o *Operation,
298311
oc openapi.OperationContext,
@@ -568,6 +581,16 @@ func (r *Reflector) parseResponseHeader(resp *Response, oc openapi.OperationCont
568581
return nil
569582
}
570583

584+
func (r *Reflector) parseRawResponseBody(resp *Response, cu openapi.ContentUnit) {
585+
if cu.Structure == nil {
586+
return
587+
}
588+
589+
refl.WalkTaggedFields(reflect.ValueOf(cu.Structure), func(_ reflect.Value, _ reflect.StructField, tag string) {
590+
resp.WithContentItem(tag, mediaType(""))
591+
}, tagContentType)
592+
}
593+
571594
func (r *Reflector) setupResponse(o *Operation, oc openapi.OperationContext) error {
572595
for _, cu := range oc.Response() {
573596
if cu.HTTPStatus == 0 && !cu.IsDefault {
@@ -605,6 +628,8 @@ func (r *Reflector) setupResponse(o *Operation, oc openapi.OperationContext) err
605628
return err
606629
}
607630

631+
r.parseRawResponseBody(resp, cu)
632+
608633
if cu.ContentType != "" {
609634
r.ensureResponseContentType(resp, cu.ContentType, cu.Format)
610635
}

openapi31/reflect_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,3 +1483,50 @@ func TestWithCustomize(t *testing.T) {
14831483
}
14841484
}`, r.SpecSchema())
14851485
}
1486+
1487+
func TestRawBody(t *testing.T) {
1488+
r := openapi31.NewReflector()
1489+
1490+
oc, err := r.NewOperationContext(http.MethodPost, "/")
1491+
require.NoError(t, err)
1492+
1493+
type req struct {
1494+
TextBody string `contentType:"text/plain"`
1495+
CSVBody string `contentType:"text/csv"`
1496+
}
1497+
1498+
type resp struct {
1499+
TextBody string `contentType:"text/plain"`
1500+
CSVBody string `contentType:"text/csv"`
1501+
}
1502+
1503+
oc.AddReqStructure(req{})
1504+
oc.AddRespStructure(resp{})
1505+
1506+
require.NoError(t, r.AddOperation(oc))
1507+
1508+
assertjson.EqMarshal(t, `{
1509+
"openapi":"3.1.0","info":{"title":"","version":""},
1510+
"paths":{
1511+
"/":{
1512+
"post":{
1513+
"requestBody":{
1514+
"content":{
1515+
"text/csv":{"schema":{"type":"string"}},
1516+
"text/plain":{"schema":{"type":"string"}}
1517+
}
1518+
},
1519+
"responses":{
1520+
"200":{
1521+
"description":"OK",
1522+
"content":{
1523+
"text/csv":{"schema":{"type":"string"}},
1524+
"text/plain":{"schema":{"type":"string"}}
1525+
}
1526+
}
1527+
}
1528+
}
1529+
}
1530+
}
1531+
}`, r.SpecSchema())
1532+
}

0 commit comments

Comments
 (0)