Repository: go-openapi/runtime
Pull request #195 (2021-01-18 16:49:31)

route creation: append API default consumer and producer if not explicitly part of the analyzed spec


Whenever the spec is not explicit about some consumers or producers, the API publishes some internal default (e.g. application/json).
However, the lists registered for the route are not updated accordingly.

* contributes go-swagger/go-swagger#1430

Signed-off-by: Frederic BIDON <fredbi@yahoo.com>


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/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) {