Repository: deepmap/oapi-codegen (go)

Issue:
Duplicate Content-Type Header
I have a spec that explicitly names and provides for the `Content-Type` header, which, with the default templates, results in *two* values for that header.  ~~Some servers may~~ [Apigee](https://cloud.google.com/apigee) errors when receiving a request with two values for that header.

Here's a rough, simplified version of what the generated code looks like:

```go
func NewFooRequestWithBody(server string, params *FooParams, contentType string, body io.Reader) (*http.Request, error) {
	var err error

	queryUrl, err := url.Parse(server)
	if err != nil {
		return nil, err
	}

	basePath := fmt.Sprintf("/foo")
	if basePath[0] == '/' {
		basePath = basePath[1:]
	}

	queryUrl, err = queryUrl.Parse(basePath)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", queryUrl.String(), body)
	if err != nil {
		return nil, err
	}

	var headerParam0 string

	headerParam0, err = runtime.StyleParam("simple", false, "Content-Type", params.ContentType)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", headerParam0)

	req.Header.Add("Content-Type", contentType)
	return req, nil
}
```

Pull request description:
Allow Content-Type override
Changes the templates to Add the default Content-Type header, then Set custom headers to allow overwriting.

Fixes #273 

Patch:
diff --git a/examples/petstore-expanded/petstore-client.gen.go b/examples/petstore-expanded/petstore-client.gen.go
index 3822944..ddd5e2d 100644
--- a/examples/petstore-expanded/petstore-client.gen.go
+++ b/examples/petstore-expanded/petstore-client.gen.go
@@ -325,6 +325,7 @@ func NewAddPetRequestWithBody(server string, contentType string, body io.Reader)
 	}
 
 	req.Header.Add("Content-Type", contentType)
+
 	return req, nil
 }
 
diff --git a/pkg/codegen/templates/client.tmpl b/pkg/codegen/templates/client.tmpl
index 5d264a2..d725248 100644
--- a/pkg/codegen/templates/client.tmpl
+++ b/pkg/codegen/templates/client.tmpl
@@ -217,6 +217,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
         return nil, err
     }
 
+    {{if .HasBody}}req.Header.Add("Content-Type", contentType){{end}}
 {{range $paramIdx, $param := .HeaderParams}}
     {{if not .Required}} if params.{{.GoName}} != nil { {{end}}
     var headerParam{{$paramIdx}} string
@@ -237,7 +238,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
         return nil, err
     }
     {{end}}
-    req.Header.Add("{{.ParamName}}", headerParam{{$paramIdx}})
+    req.Header.Set("{{.ParamName}}", headerParam{{$paramIdx}})
     {{if not .Required}}}{{end}}
 {{end}}
 
@@ -268,7 +269,6 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
     req.AddCookie(cookie{{$paramIdx}})
     {{if not .Required}}}{{end}}
 {{end}}
-    {{if .HasBody}}req.Header.Add("Content-Type", contentType){{end}}
     return req, nil
 }
 
diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go
index d454970..3ac8087 100644
--- a/pkg/codegen/templates/templates.gen.go
+++ b/pkg/codegen/templates/templates.gen.go
@@ -642,6 +642,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
         return nil, err
     }
 
+    {{if .HasBody}}req.Header.Add("Content-Type", contentType){{end}}
 {{range $paramIdx, $param := .HeaderParams}}
     {{if not .Required}} if params.{{.GoName}} != nil { {{end}}
     var headerParam{{$paramIdx}} string
@@ -662,7 +663,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
         return nil, err
     }
     {{end}}
-    req.Header.Add("{{.ParamName}}", headerParam{{$paramIdx}})
+    req.Header.Set("{{.ParamName}}", headerParam{{$paramIdx}})
     {{if not .Required}}}{{end}}
 {{end}}
 
@@ -693,7 +694,6 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
     req.AddCookie(cookie{{$paramIdx}})
     {{if not .Required}}}{{end}}
 {{end}}
-    {{if .HasBody}}req.Header.Add("Content-Type", contentType){{end}}
     return req, nil
 }
 
diff --git a/pkg/runtime/styleparam.go b/pkg/runtime/styleparam.go
index 84f2f31..30c285a 100644
--- a/pkg/runtime/styleparam.go
+++ b/pkg/runtime/styleparam.go
@@ -23,6 +23,8 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
+
+	"github.com/deepmap/oapi-codegen/pkg/types"
 )
 
 // Given an input value, such as a primitive type, array or object, turn it
@@ -134,22 +136,29 @@ func sortedKeys(strMap map[string]string) []string {
 	return keys
 }
 
-// This is a special case. The struct may be a time, in which case, marshal
-// it in RFC3339 format.
-func marshalTimeValue(value interface{}) (string, bool) {
-	if timeVal, ok := value.(*time.Time); ok {
+// This is a special case. The struct may be a date or time, in
+// which case, marshal it in correct format.
+func marshalDateTimeValue(value interface{}) (string, bool) {
+	v := reflect.Indirect(reflect.ValueOf(value))
+	t := v.Type()
+
+	if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
+		tt := v.Convert(reflect.TypeOf(time.Time{}))
+		timeVal := tt.Interface().(time.Time)
 		return timeVal.Format(time.RFC3339Nano), true
 	}
 
-	if timeVal, ok := value.(time.Time); ok {
-		return timeVal.Format(time.RFC3339Nano), true
+	if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
+		d := v.Convert(reflect.TypeOf(types.Date{}))
+		dateVal := d.Interface().(types.Date)
+		return dateVal.Format(types.DateFormat), true
 	}
 
 	return "", false
 }
 
 func styleStruct(style string, explode bool, paramName string, value interface{}) (string, error) {
-	if timeVal, ok := marshalTimeValue(value); ok {
+	if timeVal, ok := marshalDateTimeValue(value); ok {
 		styledVal, err := stylePrimitive(style, explode, paramName, url.QueryEscape(timeVal))
 		if err != nil {
 			return "", errors.Wrap(err, "failed to style time")


Test patch:
diff --git a/internal/test/client/client.gen.go b/internal/test/client/client.gen.go
index 41cd7f0..4418e14 100644
--- a/internal/test/client/client.gen.go
+++ b/internal/test/client/client.gen.go
@@ -308,6 +308,7 @@ func NewPostBothRequestWithBody(server string, contentType string, body io.Reade
 	}
 
 	req.Header.Add("Content-Type", contentType)
+
 	return req, nil
 }
 
@@ -374,6 +375,7 @@ func NewPostJsonRequestWithBody(server string, contentType string, body io.Reade
 	}
 
 	req.Header.Add("Content-Type", contentType)
+
 	return req, nil
 }
 
@@ -429,6 +431,7 @@ func NewPostOtherRequestWithBody(server string, contentType string, body io.Read
 	}
 
 	req.Header.Add("Content-Type", contentType)
+
 	return req, nil
 }
 
diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go
index eda0b1a..38713ba 100644
--- a/internal/test/components/components.gen.go
+++ b/internal/test/components/components.gen.go
@@ -918,6 +918,7 @@ func NewEnsureEverythingIsReferencedRequestWithBody(server string, contentType s
 	}
 
 	req.Header.Add("Content-Type", contentType)
+
 	return req, nil
 }
 
@@ -1012,6 +1013,7 @@ func NewBodyWithAddPropsRequestWithBody(server string, contentType string, body
 	}
 
 	req.Header.Add("Content-Type", contentType)
+
 	return req, nil
 }
 
diff --git a/internal/test/issues/issue-grab_import_names/issue.gen.go b/internal/test/issues/issue-grab_import_names/issue.gen.go
index c8e1fc4..5e3d4ce 100644
--- a/internal/test/issues/issue-grab_import_names/issue.gen.go
+++ b/internal/test/issues/issue-grab_import_names/issue.gen.go
@@ -154,7 +154,7 @@ func NewGetFooRequest(server string, params *GetFooParams) (*http.Request, error
 			return nil, err
 		}
 
-		req.Header.Add("Foo", headerParam0)
+		req.Header.Set("Foo", headerParam0)
 	}
 
 	if params.Bar != nil {
@@ -165,7 +165,7 @@ func NewGetFooRequest(server string, params *GetFooParams) (*http.Request, error
 			return nil, err
 		}
 
-		req.Header.Add("Bar", headerParam1)
+		req.Header.Set("Bar", headerParam1)
 	}
 
 	return req, nil
diff --git a/internal/test/parameters/parameters.gen.go b/internal/test/parameters/parameters.gen.go
index f0dfe05..9f3f096 100644
--- a/internal/test/parameters/parameters.gen.go
+++ b/internal/test/parameters/parameters.gen.go
@@ -733,7 +733,7 @@ func NewGetHeaderRequest(server string, params *GetHeaderParams) (*http.Request,
 			return nil, err
 		}
 
-		req.Header.Add("X-Primitive", headerParam0)
+		req.Header.Set("X-Primitive", headerParam0)
 	}
 
 	if params.XPrimitiveExploded != nil {
@@ -744,7 +744,7 @@ func NewGetHeaderRequest(server string, params *GetHeaderParams) (*http.Request,
 			return nil, err
 		}
 
-		req.Header.Add("X-Primitive-Exploded", headerParam1)
+		req.Header.Set("X-Primitive-Exploded", headerParam1)
 	}
 
 	if params.XArrayExploded != nil {
@@ -755,7 +755,7 @@ func NewGetHeaderRequest(server string, params *GetHeaderParams) (*http.Request,
 			return nil, err
 		}
 
-		req.Header.Add("X-Array-Exploded", headerParam2)
+		req.Header.Set("X-Array-Exploded", headerParam2)
 	}
 
 	if params.XArray != nil {
@@ -766,7 +766,7 @@ func NewGetHeaderRequest(server string, params *GetHeaderParams) (*http.Request,
 			return nil, err
 		}
 
-		req.Header.Add("X-Array", headerParam3)
+		req.Header.Set("X-Array", headerParam3)
 	}
 
 	if params.XObjectExploded != nil {
@@ -777,7 +777,7 @@ func NewGetHeaderRequest(server string, params *GetHeaderParams) (*http.Request,
 			return nil, err
 		}
 
-		req.Header.Add("X-Object-Exploded", headerParam4)
+		req.Header.Set("X-Object-Exploded", headerParam4)
 	}
 
 	if params.XObject != nil {
@@ -788,7 +788,7 @@ func NewGetHeaderRequest(server string, params *GetHeaderParams) (*http.Request,
 			return nil, err
 		}
 
-		req.Header.Add("X-Object", headerParam5)
+		req.Header.Set("X-Object", headerParam5)
 	}
 
 	if params.XComplexObject != nil {
@@ -801,7 +801,7 @@ func NewGetHeaderRequest(server string, params *GetHeaderParams) (*http.Request,
 		}
 		headerParam6 = string(headerParamBuf6)
 
-		req.Header.Add("X-Complex-Object", headerParam6)
+		req.Header.Set("X-Complex-Object", headerParam6)
 	}
 
 	return req, nil
diff --git a/internal/test/schemas/schemas.gen.go b/internal/test/schemas/schemas.gen.go
index f628d7c..12186d4 100644
--- a/internal/test/schemas/schemas.gen.go
+++ b/internal/test/schemas/schemas.gen.go
@@ -391,6 +391,7 @@ func NewIssue185RequestWithBody(server string, contentType string, body io.Reade
 	}
 
 	req.Header.Add("Content-Type", contentType)
+
 	return req, nil
 }
 
@@ -548,6 +549,7 @@ func NewIssue9RequestWithBody(server string, params *Issue9Params, contentType s
 	}
 
 	req.Header.Add("Content-Type", contentType)
+
 	return req, nil
 }
 
diff --git a/pkg/runtime/styleparam_test.go b/pkg/runtime/styleparam_test.go
index fcf4eef..de3791a 100644
--- a/pkg/runtime/styleparam_test.go
+++ b/pkg/runtime/styleparam_test.go
@@ -18,6 +18,8 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
+
+	"github.com/deepmap/oapi-codegen/pkg/types"
 )
 
 func TestStyleParam(t *testing.T) {
@@ -34,7 +36,13 @@ func TestStyleParam(t *testing.T) {
 	dict := map[string]interface{}{}
 	dict["firstName"] = "Alex"
 	dict["role"] = "admin"
-	timestamp, _ := time.Parse(time.RFC3339, "2020-01-01T22:00:00+02:00")
+
+	type AliasedTime time.Time
+	ti, _ := time.Parse(time.RFC3339, "2020-01-01T22:00:00+02:00")
+	timestamp := AliasedTime(ti)
+
+	type AliasedDate types.Date
+	date := AliasedDate{time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)}
 
 	// ---------------------------- Simple Style -------------------------------
 
@@ -86,6 +94,22 @@ func TestStyleParam(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, "2020-01-01T22%3A00%3A00%2B02%3A00", result)
 
+	result, err = StyleParam("simple", false, "id", date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "2020-01-01", result)
+
+	result, err = StyleParam("simple", true, "id", date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "2020-01-01", result)
+
+	result, err = StyleParam("simple", false, "id", &date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "2020-01-01", result)
+
+	result, err = StyleParam("simple", true, "id", &date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "2020-01-01", result)
+
 	// ----------------------------- Label Style -------------------------------
 
 	result, err = StyleParam("label", false, "id", primitive)
@@ -136,6 +160,22 @@ func TestStyleParam(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, ".2020-01-01T22%3A00%3A00%2B02%3A00", result)
 
+	result, err = StyleParam("label", false, "id", date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ".2020-01-01", result)
+
+	result, err = StyleParam("label", true, "id", date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ".2020-01-01", result)
+
+	result, err = StyleParam("label", false, "id", &date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ".2020-01-01", result)
+
+	result, err = StyleParam("label", true, "id", &date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ".2020-01-01", result)
+
 	// ----------------------------- Matrix Style ------------------------------
 
 	result, err = StyleParam("matrix", false, "id", primitive)
@@ -186,6 +226,22 @@ func TestStyleParam(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, ";id=2020-01-01T22%3A00%3A00%2B02%3A00", result)
 
+	result, err = StyleParam("matrix", false, "id", date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ";id=2020-01-01", result)
+
+	result, err = StyleParam("matrix", true, "id", date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ";id=2020-01-01", result)
+
+	result, err = StyleParam("matrix", false, "id", &date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ";id=2020-01-01", result)
+
+	result, err = StyleParam("matrix", true, "id", &date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ";id=2020-01-01", result)
+
 	// ------------------------------ Form Style -------------------------------
 	result, err = StyleParam("form", false, "id", primitive)
 	assert.NoError(t, err)
@@ -235,6 +291,22 @@ func TestStyleParam(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, "id=2020-01-01T22%3A00%3A00%2B02%3A00", result)
 
+	result, err = StyleParam("form", false, "id", date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "id=2020-01-01", result)
+
+	result, err = StyleParam("form", true, "id", date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "id=2020-01-01", result)
+
+	result, err = StyleParam("form", false, "id", &date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "id=2020-01-01", result)
+
+	result, err = StyleParam("form", true, "id", &date)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "id=2020-01-01", result)
+
 	// ------------------------  spaceDelimited Style --------------------------
 
 	result, err = StyleParam("spaceDelimited", false, "id", primitive)
@@ -275,6 +347,18 @@ func TestStyleParam(t *testing.T) {
 	result, err = StyleParam("spaceDelimited", true, "id", &timestamp)
 	assert.Error(t, err)
 
+	result, err = StyleParam("spaceDelimited", false, "id", date)
+	assert.Error(t, err)
+
+	result, err = StyleParam("spaceDelimited", true, "id", date)
+	assert.Error(t, err)
+
+	result, err = StyleParam("spaceDelimited", false, "id", &date)
+	assert.Error(t, err)
+
+	result, err = StyleParam("spaceDelimited", true, "id", &date)
+	assert.Error(t, err)
+
 	// -------------------------  pipeDelimited Style --------------------------
 
 	result, err = StyleParam("pipeDelimited", false, "id", primitive)
@@ -315,6 +399,18 @@ func TestStyleParam(t *testing.T) {
 	result, err = StyleParam("pipeDelimited", true, "id", &timestamp)
 	assert.Error(t, err)
 
+	result, err = StyleParam("pipeDelimited", false, "id", date)
+	assert.Error(t, err)
+
+	result, err = StyleParam("pipeDelimited", true, "id", date)
+	assert.Error(t, err)
+
+	result, err = StyleParam("pipeDelimited", false, "id", &date)
+	assert.Error(t, err)
+
+	result, err = StyleParam("pipeDelimited", true, "id", &date)
+	assert.Error(t, err)
+
 	// ---------------------------  deepObject Style ---------------------------
 	result, err = StyleParam("deepObject", false, "id", primitive)
 	assert.Error(t, err)
@@ -352,6 +448,18 @@ func TestStyleParam(t *testing.T) {
 	result, err = StyleParam("deepObject", true, "id", &timestamp)
 	assert.Error(t, err)
 
+	result, err = StyleParam("deepObject", false, "id", date)
+	assert.Error(t, err)
+
+	result, err = StyleParam("deepObject", true, "id", date)
+	assert.Error(t, err)
+
+	result, err = StyleParam("deepObject", false, "id", &date)
+	assert.Error(t, err)
+
+	result, err = StyleParam("deepObject", true, "id", &date)
+	assert.Error(t, err)
+
 	// Misc tests
 	// Test type aliases
 	type StrType string