Repository: swaggest/openapi-go
Pull request #13 (2020-10-05 08:25:36)

Add API to configure response headers mapping

codecov[bot]: # [Codecov](https://codecov.io/gh/swaggest/openapi-go/pull/13?src=pr&el=h1) Report
> Merging [#13](https://codecov.io/gh/swaggest/openapi-go/pull/13?src=pr&el=desc) into [master](https://codecov.io/gh/swaggest/openapi-go/commit/8e0f77c4b989741f3e0cc8569d53532543dd6016?el=desc) will **increase** coverage by `0.92%`.
> The diff coverage is `84.05%`.

[![Impacted file tree graph](https://codecov.io/gh/swaggest/openapi-go/pull/13/graphs/tree.svg?width=650&height=150&src=pr&token=3uETjeijpQ)](https://codecov.io/gh/swaggest/openapi-go/pull/13?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #13      +/-   ##
==========================================
+ Coverage   12.36%   13.29%   +0.92%     
==========================================
  Files           5        5              
  Lines        3332     3370      +38     
==========================================
+ Hits          412      448      +36     
- Misses       2863     2864       +1     
- Partials       57       58       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| #unittests | `13.29% <84.05%> (+0.92%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/swaggest/openapi-go/pull/13?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [openapi3/reflect.go](https://codecov.io/gh/swaggest/openapi-go/pull/13/diff?src=pr&el=tree#diff-b3BlbmFwaTMvcmVmbGVjdC5nbw==) | `84.61% <82.53%> (+1.17%)` | :arrow_up: |
| [openapi3/helper.go](https://codecov.io/gh/swaggest/openapi-go/pull/13/diff?src=pr&el=tree#diff-b3BlbmFwaTMvaGVscGVyLmdv) | `86.53% <100.00%> (+1.75%)` | :arrow_up: |
| [openapi3/entities.go](https://codecov.io/gh/swaggest/openapi-go/pull/13/diff?src=pr&el=tree#diff-b3BlbmFwaTMvZW50aXRpZXMuZ28=) | `3.51% <0.00%> (+0.03%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/swaggest/openapi-go/pull/13?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/swaggest/openapi-go/pull/13?src=pr&el=footer). Last update [8e0f77c...0b6f161](https://codecov.io/gh/swaggest/openapi-go/pull/13?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/go.mod b/go.mod
index ac9e7a7..645fd77 100644
--- a/go.mod
+++ b/go.mod
@@ -4,9 +4,9 @@ go 1.13
 
 require (
 	github.com/bool64/dev v0.1.0
-	github.com/stretchr/testify v1.5.1
-	github.com/swaggest/assertjson v1.0.0
-	github.com/swaggest/jsonschema-go v0.3.7
+	github.com/stretchr/testify v1.6.1
+	github.com/swaggest/assertjson v1.2.0
+	github.com/swaggest/jsonschema-go v0.3.8
 	github.com/swaggest/refl v0.1.3
 	github.com/swaggest/swgen v0.6.23
 	gopkg.in/yaml.v2 v2.3.0
diff --git a/go.sum b/go.sum
index 3e30c2a..6bbc666 100644
--- a/go.sum
+++ b/go.sum
@@ -14,6 +14,8 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk=
+github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -46,18 +48,24 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/swaggest/assertjson v1.0.0 h1:Dyc+uF7h9nkS6+jHHIERRakQe9V7KyufKHmkUl6czNM=
 github.com/swaggest/assertjson v1.0.0/go.mod h1:mE5ltBbrB+Ya8Xar5OMITxya76vwLZMoPG8aXF7sVQc=
+github.com/swaggest/assertjson v1.2.0 h1:5o3xwits8nn2xw3bDpH80CN4YHJUMuVVNh4HO1azuls=
+github.com/swaggest/assertjson v1.2.0/go.mod h1:VR1/jzZHtROewu/SBEdFrBOBuNm1FO9LDjvP9fLf6+I=
 github.com/swaggest/jsonschema-go v0.2.1 h1:SVg+zXQ46w6ewR7jZDRVbpKKC+V7Y6Xn/teC2pRS2bM=
 github.com/swaggest/jsonschema-go v0.2.1/go.mod h1:QFauBdPTrU1UltwocM5FzOWnVjVVtcWkJWG3NlK9sV0=
 github.com/swaggest/jsonschema-go v0.2.4/go.mod h1:m4VV88Gbi7lCrt9ckJzigK1rMlEeFjdZUkJr1o5MnDE=
-github.com/swaggest/jsonschema-go v0.3.7 h1:EgSPKzzjRrMKGdq7PVF/ziMilQag8HUD8XYCZoii8S8=
-github.com/swaggest/jsonschema-go v0.3.7/go.mod h1:TrWgbug4p2ZgcxnHDz+CvYvEtJ5KckL/XOV4mSR6FGw=
+github.com/swaggest/jsonschema-go v0.3.8 h1:QkRQXoO1jp9otq5i0blji6T2gVbsF0OfdiMHFuKeUwM=
+github.com/swaggest/jsonschema-go v0.3.8/go.mod h1:AQijowS82ZcUId9RStqvyGTxyhzYUiVDxelwQxEdOy8=
 github.com/swaggest/openapi-go v0.1.3/go.mod h1:Zx4ZgJ7XvlFH9wCOHE7u8RAjLfiHAnCHeaD5kUDujVM=
 github.com/swaggest/refl v0.1.0 h1:mz3skba8ewcPmxDryM9rETv4fSuAX4GqNxTuDfPyUM0=
 github.com/swaggest/refl v0.1.0/go.mod h1:kmYWhxNEvjfRDdMRqpaR/vLULk/SotJs9HFUCIVMK8o=
@@ -116,3 +124,6 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/openapi3/helper.go b/openapi3/helper.go
index 6b3a5eb..5f223f9 100644
--- a/openapi3/helper.go
+++ b/openapi3/helper.go
@@ -3,7 +3,9 @@ package openapi3
 import (
 	"errors"
 	"fmt"
+	"net/http"
 	"regexp"
+	"strconv"
 	"strings"
 )
 
@@ -109,6 +111,15 @@ func (s *Spec) AddOperation(method, path string, operation Operation) error {
 		return errors.New("operation with method and path already exists")
 	}
 
+	// Add "No Content" response if there are no responses configured.
+	if len(operation.Responses.MapOfResponseOrRefValues) == 0 {
+		operation.Responses.WithMapOfResponseOrRefValuesItem(strconv.Itoa(http.StatusNoContent), ResponseOrRef{
+			Response: &Response{
+				Description: http.StatusText(http.StatusNoContent),
+			},
+		})
+	}
+
 	return s.SetupOperation(method, path, func(op *Operation) error {
 		*op = operation
 
diff --git a/openapi3/reflect.go b/openapi3/reflect.go
index 9b8e6c2..fa6d3ce 100644
--- a/openapi3/reflect.go
+++ b/openapi3/reflect.go
@@ -44,18 +44,44 @@ func (r *Reflector) SpecEns() *Spec {
 	return r.Spec
 }
 
-// SetRequest sets up operation parameters.
-func (r *Reflector) SetRequest(o *Operation, input interface{}, httpMethod string) error {
+// OperationContext describes operation.
+type OperationContext struct {
+	Operation  *Operation
+	Input      interface{}
+	HTTPMethod string
+
+	ReqQueryMapping    map[string]string
+	ReqPathMapping     map[string]string
+	ReqCookieMapping   map[string]string
+	ReqHeaderMapping   map[string]string
+	ReqFormDataMapping map[string]string
+
+	Output            interface{}
+	HTTPStatus        int
+	RespHeaderMapping map[string]string
+}
+
+// SetupRequest sets up operation parameters.
+func (r *Reflector) SetupRequest(oc OperationContext) error {
 	return joinErrors(
-		r.parseParametersIn(o, input, ParameterInQuery),
-		r.parseParametersIn(o, input, ParameterInPath),
-		r.parseParametersIn(o, input, ParameterInCookie),
-		r.parseParametersIn(o, input, ParameterInHeader),
-		r.parseRequestBody(o, input, tagJSON, mimeJSON, httpMethod),
-		r.parseRequestBody(o, input, tagFormData, mimeFormUrlencoded, httpMethod),
+		r.parseParametersIn(oc.Operation, oc.Input, ParameterInQuery, oc.ReqQueryMapping),
+		r.parseParametersIn(oc.Operation, oc.Input, ParameterInPath, oc.ReqPathMapping),
+		r.parseParametersIn(oc.Operation, oc.Input, ParameterInCookie, oc.ReqCookieMapping),
+		r.parseParametersIn(oc.Operation, oc.Input, ParameterInHeader, oc.ReqHeaderMapping),
+		r.parseRequestBody(oc.Operation, oc.Input, tagJSON, mimeJSON, oc.HTTPMethod, nil),
+		r.parseRequestBody(oc.Operation, oc.Input, tagFormData, mimeFormUrlencoded, oc.HTTPMethod, oc.ReqFormDataMapping),
 	)
 }
 
+// SetRequest sets up operation parameters.
+func (r *Reflector) SetRequest(o *Operation, input interface{}, httpMethod string) error {
+	return r.SetupRequest(OperationContext{
+		Operation:  o,
+		Input:      input,
+		HTTPMethod: httpMethod,
+	})
+}
+
 var (
 	typeOfMultipartFile       = reflect.TypeOf((*multipart.File)(nil)).Elem()
 	typeOfMultipartFileHeader = reflect.TypeOf((*multipart.FileHeader)(nil)).Elem()
@@ -77,7 +103,9 @@ type RequestBodyEnforcer interface {
 	ForceRequestBody()
 }
 
-func (r *Reflector) parseRequestBody(o *Operation, input interface{}, tag, mime string, httpMethod string) error {
+func (r *Reflector) parseRequestBody(
+	o *Operation, input interface{}, tag, mime string, httpMethod string, mapping map[string]string,
+) error {
 	httpMethod = strings.ToUpper(httpMethod)
 	_, forceRequestBody := input.(RequestBodyEnforcer)
 
@@ -89,12 +117,12 @@ func (r *Reflector) parseRequestBody(o *Operation, input interface{}, tag, mime
 	hasTaggedFields := refl.HasTaggedFields(input, tag)
 
 	// Form data can not have map or array as body.
-	if !hasTaggedFields && tag != tagJSON {
+	if !hasTaggedFields && len(mapping) == 0 && tag != tagJSON {
 		return nil
 	}
 
 	// JSON can be a map or array without field tags.
-	if !hasTaggedFields && !refl.IsSliceOrMap(input) && refl.FindEmbeddedSliceOrMap(input) == nil {
+	if !hasTaggedFields && len(mapping) == 0 && !refl.IsSliceOrMap(input) && refl.FindEmbeddedSliceOrMap(input) == nil {
 		return nil
 	}
 
@@ -108,6 +136,7 @@ func (r *Reflector) parseRequestBody(o *Operation, input interface{}, tag, mime
 	schema, err := r.Reflect(input,
 		jsonschema.DefinitionsPrefix("#/components/schemas/"+definitionPrefix),
 		jsonschema.RootRef,
+		jsonschema.PropertyNameMapping(mapping),
 		jsonschema.PropertyNameTag(tag),
 		jsonschema.InterceptType(func(v reflect.Value, s *jsonschema.Schema) (bool, error) {
 			vv := v.Interface()
@@ -166,7 +195,9 @@ func (r *Reflector) parseRequestBody(o *Operation, input interface{}, tag, mime
 	return nil
 }
 
-func (r *Reflector) parseParametersIn(o *Operation, input interface{}, in ParameterIn) error {
+func (r *Reflector) parseParametersIn(
+	o *Operation, input interface{}, in ParameterIn, propertyMapping map[string]string,
+) error {
 	if refl.IsSliceOrMap(input) {
 		return nil
 	}
@@ -174,6 +205,7 @@ func (r *Reflector) parseParametersIn(o *Operation, input interface{}, in Parame
 	_, err := r.Reflect(input,
 		jsonschema.DefinitionsPrefix("#/components/schemas/"),
 		jsonschema.CollectDefinitions(r.collectDefinition),
+		jsonschema.PropertyNameMapping(propertyMapping),
 		jsonschema.PropertyNameTag(string(in)),
 		jsonschema.SkipEmbeddedMapsSlices,
 		jsonschema.InterceptProperty(func(name string, field reflect.StructField, propertySchema *jsonschema.Schema) error {
@@ -236,11 +268,12 @@ func (r *Reflector) collectDefinition(name string, schema jsonschema.Schema) {
 	r.SpecEns().ComponentsEns().SchemasEns().WithMapOfSchemaOrRefValuesItem(name, s)
 }
 
-func (r *Reflector) parseResponseHeader(output interface{}) (map[string]HeaderOrRef, error) {
+func (r *Reflector) parseResponseHeader(resp *Response, output interface{}, mapping map[string]string) error {
 	res := make(map[string]HeaderOrRef)
 
-	_, err := r.Reflect(output,
+	schema, err := r.Reflect(output,
 		jsonschema.InlineRefs,
+		jsonschema.PropertyNameMapping(mapping),
 		jsonschema.PropertyNameTag("header"),
 		jsonschema.InterceptProperty(func(name string, field reflect.StructField, propertySchema *jsonschema.Schema) error {
 			s := SchemaOrRef{}
@@ -269,14 +302,77 @@ func (r *Reflector) parseResponseHeader(output interface{}) (map[string]HeaderOr
 		}),
 	)
 	if err != nil {
-		return nil, err
+		return err
+	}
+
+	resp.Headers = res
+
+	if schema.Description != nil && resp.Description == "" {
+		resp.Description = *schema.Description
 	}
 
-	return res, nil
+	return nil
 }
 
 // SetJSONResponse sets up operation JSON response.
 func (r *Reflector) SetJSONResponse(o *Operation, output interface{}, httpStatus int) error {
+	return r.SetupResponse(OperationContext{
+		Operation:  o,
+		Output:     output,
+		HTTPStatus: httpStatus,
+	})
+}
+
+func (r *Reflector) hasJSONBody(output interface{}) (bool, error) {
+	schema, err := r.Reflect(output)
+	if err != nil {
+		return false, err
+	}
+
+	if schema.Type == nil {
+		return false, nil
+	}
+
+	if schema.HasType(jsonschema.Object) && schema.AdditionalProperties == nil && len(schema.Properties) == 0 {
+		return false, nil
+	}
+
+	return true, nil
+}
+
+// SetupResponse sets up operation response.
+func (r *Reflector) SetupResponse(oc OperationContext) error {
+	resp := Response{}
+
+	if oc.Output != nil {
+		err := r.parseJSONResponse(&resp, oc.Output)
+		if err != nil {
+			return err
+		}
+
+		err = r.parseResponseHeader(&resp, oc.Output, oc.RespHeaderMapping)
+		if err != nil {
+			return err
+		}
+	}
+
+	if resp.Description == "" {
+		resp.Description = http.StatusText(oc.HTTPStatus)
+	}
+
+	oc.Operation.Responses.WithMapOfResponseOrRefValuesItem(strconv.Itoa(oc.HTTPStatus), ResponseOrRef{
+		Response: &resp,
+	})
+
+	return nil
+}
+
+func (r *Reflector) parseJSONResponse(resp *Response, output interface{}) error {
+	// Check if output structure exposes meaningful schema.
+	if hasJSONBody, err := r.hasJSONBody(output); err == nil && !hasJSONBody {
+		return nil
+	}
+
 	schema, err := r.Reflect(output,
 		jsonschema.RootRef,
 		jsonschema.DefinitionsPrefix("#/components/schemas/"),
@@ -293,32 +389,21 @@ func (r *Reflector) SetJSONResponse(o *Operation, output interface{}, httpStatus
 		oaiSchema.Schema.Nullable = nil
 	}
 
-	resp := Response{
-		Content: map[string]MediaType{
-			"application/json": {
-				Schema:        &oaiSchema,
-				Example:       nil,
-				Examples:      nil,
-				Encoding:      nil,
-				MapOfAnything: nil,
-			},
-		},
+	if resp.Content == nil {
+		resp.Content = map[string]MediaType{}
 	}
 
-	if schema.Description != nil {
-		resp.Description = *schema.Description
-	} else {
-		resp.Description = http.StatusText(httpStatus)
+	resp.Content[mimeJSON] = MediaType{
+		Schema:        &oaiSchema,
+		Example:       nil,
+		Examples:      nil,
+		Encoding:      nil,
+		MapOfAnything: nil,
 	}
 
-	resp.Headers, err = r.parseResponseHeader(output)
-	if err != nil {
-		return err
+	if schema.Description != nil && resp.Description == "" {
+		resp.Description = *schema.Description
 	}
 
-	o.Responses.WithMapOfResponseOrRefValuesItem(strconv.Itoa(httpStatus), ResponseOrRef{
-		Response: &resp,
-	})
-
 	return nil
 }


Test patch:
diff --git a/openapi3/_testdata/openapi.json b/openapi3/_testdata/openapi.json
index 6fdb6e5..c1388c2 100644
--- a/openapi3/_testdata/openapi.json
+++ b/openapi3/_testdata/openapi.json
@@ -65,7 +65,7 @@
     ],
     "responses": {
      "200": {
-      "description": "OK",
+      "description": "This is a sample response.",
       "headers": {
        "X-Header-Field": {
         "style": "simple",
@@ -212,7 +212,7 @@
     },
     "responses": {
      "200": {
-      "description": "OK",
+      "description": "This is a sample response.",
       "headers": {
        "X-Header-Field": {
         "style": "simple",
@@ -333,7 +333,8 @@
        },
        "foo": {
         "pattern": "\\d+",
-        "type": "string"
+        "type": "string",
+        "default": "baz"
        }
       }
      },
diff --git a/openapi3/_testdata/openapi_req.json b/openapi3/_testdata/openapi_req.json
index 2a11c2a..bb9c86b 100644
--- a/openapi3/_testdata/openapi_req.json
+++ b/openapi3/_testdata/openapi_req.json
@@ -61,7 +61,11 @@
       }
      }
     ],
-    "responses": {}
+    "responses": {
+     "204": {
+      "description": "No Content"
+     }
+    }
    }
   }
  },
diff --git a/openapi3/_testdata/openapi_req_array.json b/openapi3/_testdata/openapi_req_array.json
index 147a637..bf646fa 100644
--- a/openapi3/_testdata/openapi_req_array.json
+++ b/openapi3/_testdata/openapi_req_array.json
@@ -20,7 +20,11 @@
       }
      }
     },
-    "responses": {}
+    "responses": {
+     "204": {
+      "description": "No Content"
+     }
+    }
    }
   }
  },
diff --git a/openapi3/_testdata/resp_schema.json b/openapi3/_testdata/resp_schema.json
index 674c076..ef7eb8d 100644
--- a/openapi3/_testdata/resp_schema.json
+++ b/openapi3/_testdata/resp_schema.json
@@ -37,6 +37,7 @@
                 "type": "number"
               },
               "foo": {
+                "default": "baz",
                 "pattern": "\\d+",
                 "type": "string"
               }
diff --git a/openapi3/reflect_test.go b/openapi3/reflect_test.go
index 9fa36bf..00e78bb 100644
--- a/openapi3/reflect_test.go
+++ b/openapi3/reflect_test.go
@@ -270,53 +270,27 @@ func TestReflector_SetRequest_pathParamAndBody(t *testing.T) {
 	s.Info.Version = apiVersion
 	assert.NoError(t, s.AddOperation(http.MethodPost, "/somewhere/{id}", op))
 
-	b, err := json.MarshalIndent(s, "", " ")
+	b, err := assertjson.MarshalIndentCompact(s, "", " ", 100)
 	assert.NoError(t, err)
 
 	expected := []byte(`{
-        	            	 "openapi": "3.0.2",
-        	            	 "info": {
-        	            	  "title": "SampleAPI",
-        	            	  "version": "1.2.3"
-        	            	 },
-        	            	 "paths": {
-        	            	  "/somewhere/{id}": {
-        	            	   "post": {
-        	            	    "parameters": [
-        	            	     {
-        	            	      "name": "id",
-        	            	      "in": "path",
-        	            	      "required": true,
-        	            	      "schema": {
-        	            	       "type": "string"
-        	            	      }
-        	            	     }
-        	            	    ],
-        	            	    "requestBody": {
-        	            	     "content": {
-        	            	      "application/json": {
-        	            	       "schema": {
-        	            	        "$ref": "#/components/schemas/Openapi3TestPathParamAndBody"
-        	            	       }
-        	            	      }
-        	            	     }
-        	            	    },
-        	            	    "responses": {}
-        	            	   }
-        	            	  }
-        	            	 },
-        	            	 "components": {
-        	            	  "schemas": {
-        	            	   "Openapi3TestPathParamAndBody": {
-        	            	    "type": "array",
-        	            	    "items": {
-        	            	     "type": "string"
-        	            	    },
-        	            	    "nullable": true
-        	            	   }
-        	            	  }
-        	            	 }
-        	            	}`)
+	 "openapi":"3.0.2",
+	 "info":{"title":"SampleAPI","version":"1.2.3"},
+	 "paths":{
+	  "/somewhere/{id}":{
+	   "post":{
+		"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],
+		"requestBody":{
+		 "content":{"application/json":{"schema":{"$ref":"#/components/schemas/Openapi3TestPathParamAndBody"}}}
+		},
+		"responses":{"204":{"description":"No Content"}}
+	   }
+	  }
+	 },
+	 "components":{
+	  "schemas":{"Openapi3TestPathParamAndBody":{"type":"array","items":{"type":"string"},"nullable":true}}
+	 }
+	}`)
 
 	assertjson.Equal(t, expected, b, string(b))
 }
@@ -337,53 +311,144 @@ func TestRequestBodyEnforcer(t *testing.T) {
 	s.Info.Version = apiVersion
 	assert.NoError(t, s.AddOperation(http.MethodGet, "/somewhere/{id}", op))
 
-	b, err := json.MarshalIndent(s, "", " ")
+	b, err := assertjson.MarshalIndentCompact(s, "", " ", 120)
+	assert.NoError(t, err)
+
+	expected := []byte(`{
+	 "openapi":"3.0.2",
+	 "info":{"title":"SampleAPI","version":"1.2.3"},
+	 "paths":{
+	  "/somewhere/{id}":{
+	   "get":{
+		"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],
+		"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Openapi3TestWithReqBody"}}}},
+		"responses":{"204":{"description":"No Content"}}
+	   }
+	  }
+	 },
+	 "components":{"schemas":{"Openapi3TestWithReqBody":{"type":"array","items":{"type":"string"},"nullable":true}}}
+	}`)
+
+	assertjson.Equal(t, expected, b, string(b))
+}
+
+func TestReflector_SetupResponse(t *testing.T) {
+	reflector := openapi3.Reflector{}
+	op := openapi3.Operation{}
+
+	s := reflector.SpecEns()
+	s.Info.Title = apiName
+	s.Info.Version = apiVersion
+
+	require.NoError(t, reflector.SetupResponse(openapi3.OperationContext{
+		Operation:  &op,
+		HTTPStatus: http.StatusNoContent,
+		Output: new(struct {
+			Val1 int
+			Val2 string
+		}),
+		RespHeaderMapping: map[string]string{
+			"Val1": "X-Value-1",
+			"Val2": "X-Value-2",
+		},
+	}))
+	require.NoError(t, s.AddOperation(http.MethodGet, "/somewhere", op))
+
+	b, err := assertjson.MarshalIndentCompact(s, "", " ", 120)
+	assert.NoError(t, err)
+
+	expected := []byte(`{
+	 "openapi":"3.0.2",
+	 "info":{"title":"SampleAPI","version":"1.2.3"},
+	 "paths":{
+	  "/somewhere":{
+	   "get":{
+		"responses":{
+		 "204":{
+		  "description":"No Content",
+		  "headers":{
+		   "X-Value-1":{"style":"simple","schema":{"type":"integer"}},
+		   "X-Value-2":{"style":"simple","schema":{"type":"string"}}
+		  }
+		 }
+		}
+	   }
+	  }
+	 }
+	}`)
+
+	assertjson.Equal(t, expected, b, string(b))
+}
+
+func TestReflector_SetupRequest(t *testing.T) {
+	reflector := openapi3.Reflector{}
+	op := openapi3.Operation{}
+
+	s := reflector.SpecEns()
+	s.Info.Title = apiName
+	s.Info.Version = apiVersion
+
+	require.NoError(t, reflector.SetupRequest(openapi3.OperationContext{
+		Operation:  &op,
+		HTTPMethod: http.MethodPost,
+		Input: new(struct {
+			Val1 int
+			Val2 string
+			Val3 float64
+			Val4 bool
+			Val5 string
+			Val6 multipart.File
+		}),
+		ReqHeaderMapping: map[string]string{
+			"Val1": "X-Value-1",
+		},
+		ReqQueryMapping: map[string]string{
+			"Val2": "value_2",
+		},
+		ReqFormDataMapping: map[string]string{
+			"Val3": "value3",
+			"Val6": "upload6",
+		},
+		ReqPathMapping: map[string]string{
+			"Val4": "value-4",
+		},
+		ReqCookieMapping: map[string]string{
+			"Val5": "value_5",
+		},
+	}))
+	require.NoError(t, s.AddOperation(http.MethodPost, "/somewhere/{value-4}", op))
+
+	b, err := assertjson.MarshalIndentCompact(s, "", " ", 120)
 	assert.NoError(t, err)
 
 	expected := []byte(`{
-        	            	 "openapi": "3.0.2",
-        	            	 "info": {
-        	            	  "title": "SampleAPI",
-        	            	  "version": "1.2.3"
-        	            	 },
-        	            	 "paths": {
-        	            	  "/somewhere/{id}": {
-        	            	   "get": {
-        	            	    "parameters": [
-        	            	     {
-        	            	      "name": "id",
-        	            	      "in": "path",
-        	            	      "required": true,
-        	            	      "schema": {
-        	            	       "type": "string"
-        	            	      }
-        	            	     }
-        	            	    ],
-        	            	    "requestBody": {
-        	            	     "content": {
-        	            	      "application/json": {
-        	            	       "schema": {
-        	            	        "$ref": "#/components/schemas/Openapi3TestWithReqBody"
-        	            	       }
-        	            	      }
-        	            	     }
-        	            	    },
-        	            	    "responses": {}
-        	            	   }
-        	            	  }
-        	            	 },
-        	            	 "components": {
-        	            	  "schemas": {
-        	            	   "Openapi3TestWithReqBody": {
-        	            	    "type": "array",
-        	            	    "items": {
-        	            	     "type": "string"
-        	            	    },
-        	            	    "nullable": true
-        	            	   }
-        	            	  }
-        	            	 }
-        	            	}`)
+	 "openapi":"3.0.2",
+	 "info":{"title":"SampleAPI","version":"1.2.3"},
+	 "paths":{
+	  "/somewhere/{value-4}":{
+	   "post":{
+		"parameters":[
+		 {"name":"value_2","in":"query","schema":{"type":"string"}},
+		 {"name":"value-4","in":"path","required":true,"schema":{"type":"boolean"}},
+		 {"name":"value_5","in":"cookie","schema":{"type":"string"}},
+		 {"name":"X-Value-1","in":"header","schema":{"type":"integer"}}
+		],
+		"requestBody":{
+		 "content":{
+		  "multipart/form-data":{
+		   "schema":{
+			"type":"object",
+			"properties":{"upload6":{"$ref":"#/components/schemas/FormDataMultipartFile"},"value3":{"type":"number"}}
+		   }
+		  }
+		 }
+		},
+		"responses":{"204":{"description":"No Content"}}
+	   }
+	  }
+	 },
+	 "components":{"schemas":{"FormDataMultipartFile":{"type":"string","format":"binary","nullable":true}}}
+	}`)
 
 	assertjson.Equal(t, expected, b, string(b))
 }