Repository: go-openapi/runtime
Pull request #194 (2020-12-18 18:25:42)

When response body is binary, DEBUG mode should not print it
When the response is a binary blob, printing it can mess up the
user's terminal. This change checks the response type and, if
the response is an octet-stream, omits the response body from
the logs.

Note that octet-stream is not the default response body type, so
it is unlikely that we will falsely omit a response body which
was not actually binary.

Signed-off-by: andrew2nelson <andrew2nelson@arista.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/go-openapi/runtime/pull/194?src=pr&el=h1) Report
> Merging [#194](https://codecov.io/gh/go-openapi/runtime/pull/194?src=pr&el=desc) (85f1075) into [master](https://codecov.io/gh/go-openapi/runtime/commit/1dcf9570be0e22d9cbed5bcdb0935990ad8be306?el=desc) (1dcf957) will **decrease** coverage by `0.10%`.
> The diff coverage is `42.85%`.

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

```diff
@@            Coverage Diff             @@
##           master     #194      +/-   ##
==========================================
- Coverage   79.88%   79.77%   -0.11%     
==========================================
  Files          43       43              
  Lines        2192     2195       +3     
==========================================
  Hits         1751     1751              
- Misses        339      342       +3     
  Partials      102      102              
```


| [Impacted Files](https://codecov.io/gh/go-openapi/runtime/pull/194?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [client/runtime.go](https://codecov.io/gh/go-openapi/runtime/pull/194/diff?src=pr&el=tree#diff-Y2xpZW50L3J1bnRpbWUuZ28=) | `63.86% <42.85%> (-0.97%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/go-openapi/runtime/pull/194?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/go-openapi/runtime/pull/194?src=pr&el=footer). Last update [1dcf957...85f1075](https://codecov.io/gh/go-openapi/runtime/pull/194?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.golangci.yml b/.golangci.yml
index f9381ae..71629d4 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -39,3 +39,4 @@ linters:
     - nestif
     - godot
     - errorlint
+    - noctx
diff --git a/client/runtime.go b/client/runtime.go
index f57c442..f6ea0fa 100644
--- a/client/runtime.go
+++ b/client/runtime.go
@@ -450,19 +450,23 @@ func (r *Runtime) Submit(operation *runtime.ClientOperation) (interface{}, error
 	}
 	defer res.Body.Close()
 
+	ct := res.Header.Get(runtime.HeaderContentType)
+	if ct == "" { // this should really really never occur
+		ct = r.DefaultMediaType
+	}
+
 	if r.Debug {
-		b, err2 := httputil.DumpResponse(res, true)
+		printBody := true
+		if ct == runtime.DefaultMime {
+			printBody = false // Spare the terminal from a binary blob.
+		}
+		b, err2 := httputil.DumpResponse(res, printBody)
 		if err2 != nil {
 			return nil, err2
 		}
 		r.logger.Debugf("%s\n", string(b))
 	}
 
-	ct := res.Header.Get(runtime.HeaderContentType)
-	if ct == "" { // this should really really never occur
-		ct = r.DefaultMediaType
-	}
-
 	mt, _, err := mime.ParseMediaType(ct)
 	if err != nil {
 		return nil, fmt.Errorf("parse content type: %s", err)
diff --git a/middleware/router.go b/middleware/router.go
index 02768ba..5052031 100644
--- a/middleware/router.go
+++ b/middleware/router.go
@@ -22,6 +22,7 @@ import (
 	"strings"
 
 	"github.com/go-openapi/runtime/security"
+	"github.com/go-openapi/swag"
 
 	"github.com/go-openapi/analysis"
 	"github.com/go-openapi/errors"
@@ -418,6 +419,15 @@ func (d *defaultRouteBuilder) AddRoute(method, path string, operation *spec.Oper
 		produces := d.analyzer.ProducesFor(operation)
 		parameters := d.analyzer.ParamsFor(method, strings.TrimPrefix(path, bp))
 
+		// add API defaults if not part of the spec
+		if defConsumes := d.api.DefaultConsumes(); defConsumes != "" && !swag.ContainsStringsCI(consumes, defConsumes) {
+			consumes = append(consumes, defConsumes)
+		}
+
+		if defProduces := d.api.DefaultProduces(); defProduces != "" && !swag.ContainsStringsCI(produces, defProduces) {
+			produces = append(produces, defProduces)
+		}
+
 		record := denco.NewRecord(pathConverter.ReplaceAllString(path, ":$1"), &routeEntry{
 			BasePath:       bp,
 			PathPattern:    path,


Test patch:
diff --git a/middleware/context_test.go b/middleware/context_test.go
index 8e8f9cd..d019345 100644
--- a/middleware/context_test.go
+++ b/middleware/context_test.go
@@ -93,10 +93,16 @@ func TestContentType_Issue172(t *testing.T) {
 
 		handler := Serve(swspec, api)
 		request, _ := http.NewRequest("GET", "/pets", nil)
-		request.Header.Add("Accept", "application/json")
+		request.Header.Add("Accept", "application/json+special")
 		recorder := httptest.NewRecorder()
 		handler.ServeHTTP(recorder, request)
 		assert.Equal(t, http.StatusNotAcceptable, recorder.Code)
+
+		// acceptable as defined as default by the API (not explicit in the spec)
+		request.Header.Add("Accept", "application/json")
+		recorder = httptest.NewRecorder()
+		handler.ServeHTTP(recorder, request)
+		assert.Equal(t, http.StatusOK, recorder.Code)
 	}
 }
 
diff --git a/middleware/router_test.go b/middleware/router_test.go
index eb78623..00e63d0 100644
--- a/middleware/router_test.go
+++ b/middleware/router_test.go
@@ -119,10 +119,15 @@ func TestRouterBuilder(t *testing.T) {
 	rec := postRecords[0]
 	assert.Equal(t, rec.Key, "/pets")
 	val := rec.Value.(*routeEntry)
-	assert.Len(t, val.Consumers, 1)
-	assert.Len(t, val.Producers, 1)
-	assert.Len(t, val.Consumes, 1)
-	assert.Len(t, val.Produces, 1)
+	assert.Len(t, val.Consumers, 2)
+	assert.Len(t, val.Producers, 2)
+	assert.Len(t, val.Consumes, 2)
+	assert.Len(t, val.Produces, 2)
+
+	assert.Contains(t, val.Consumers, "application/json")
+	assert.Contains(t, val.Producers, "application/x-yaml")
+	assert.Contains(t, val.Consumes, "application/json")
+	assert.Contains(t, val.Produces, "application/x-yaml")
 
 	assert.Len(t, val.Parameters, 1)
 
diff --git a/middleware/validation_test.go b/middleware/validation_test.go
index e7997b6..b0d1350 100644
--- a/middleware/validation_test.go
+++ b/middleware/validation_test.go
@@ -53,6 +53,7 @@ func TestContentTypeValidation(t *testing.T) {
 	spec, api := petstore.NewAPI(t)
 	context := NewContext(spec, api, nil)
 	context.router = DefaultRouter(spec, context.api)
+
 	mw := newTestValidation(context, http.HandlerFunc(terminator))
 
 	recorder := httptest.NewRecorder()
@@ -93,12 +94,33 @@ func TestContentTypeValidation(t *testing.T) {
 
 	recorder = httptest.NewRecorder()
 	request, _ = http.NewRequest("POST", "/api/pets", nil)
-	request.Header.Add("Accept", "application/json")
+	request.Header.Add("Accept", "application/json+special")
 	request.Header.Add("content-type", "text/html")
 
 	mw.ServeHTTP(recorder, request)
 	assert.Equal(t, 406, recorder.Code)
 	assert.Equal(t, "application/json", recorder.Header().Get("content-type"))
+
+	// client sends data with unsupported mime
+	recorder = httptest.NewRecorder()
+	request, _ = http.NewRequest("POST", "/api/pets", nil)
+	request.Header.Add("Accept", "application/json") // this content type is served by default by the API
+	request.Header.Add("content-type", "application/json+special")
+	request.ContentLength = 1
+
+	mw.ServeHTTP(recorder, request)
+	assert.Equal(t, 415, recorder.Code) // Unsupported media type
+	assert.Equal(t, "application/json", recorder.Header().Get("content-type"))
+
+	// client sends a body of data with no mime: breaks
+	recorder = httptest.NewRecorder()
+	request, _ = http.NewRequest("POST", "/api/pets", nil)
+	request.Header.Add("Accept", "application/json")
+	request.ContentLength = 1
+
+	mw.ServeHTTP(recorder, request)
+	assert.Equal(t, 415, recorder.Code)
+	assert.Equal(t, "application/json", recorder.Header().Get("content-type"))
 }
 
 func TestResponseFormatValidation(t *testing.T) {