Repository: oapi-codegen/oapi-codegen (Go)

Issue:
Multiple json content types in a single requestBody or response lead to duplicate types and client interface functions
```yaml
openapi: 3.0.1
info:
  title: api
  version: "0.1.0"
paths:
  /whatever:
    post:
      operationId: CreateWhatever
      requestBody:
        content:
          application/json-patch+json:
            schema:
              $ref: "#/components/schemas/Whatever"
          application/json:
            schema:
              $ref: "#/components/schemas/Whatever"
          text/json:
            schema:
              $ref: "#/components/schemas/Whatever"
          application/*+json:
            schema:
              $ref: "#/components/schemas/Whatever"
      responses:
        201:
          description: Whatever created
          content:
            text/plain:
              schema:
                $ref: "#/components/schemas/Whatever"
            application/json:
              schema:
                $ref: "#/components/schemas/Whatever"
            text/json:
              schema:
                $ref: "#/components/schemas/Whatever"
components:
  schemas:
    Whatever:
      type: object
      properties:
        someProperty:
          type: string
```

Generated Client Boilerplate:
```go
// CreateWhateverJSONRequestBody defines body for CreateWhatever for application/*+json ContentType.
type CreateWhateverJSONRequestBody = Whatever

// CreateWhateverJSONRequestBody defines body for CreateWhatever for application/json ContentType.
type CreateWhateverJSONRequestBody = Whatever

// CreateWhateverJSONRequestBody defines body for CreateWhatever for application/json-patch+json ContentType.
type CreateWhateverJSONRequestBody = Whatever

...

// The interface specification for the client above.
type ClientInterface interface {
	// CreateWhatever request with any body
	CreateWhateverWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)

	CreateWhatever(ctx context.Context, body CreateWhateverJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)

	CreateWhatever(ctx context.Context, body CreateWhateverJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)

	CreateWhatever(ctx context.Context, body CreateWhateverJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)

...

// ClientWithResponsesInterface is the interface specification for the client with responses above.
type ClientWithResponsesInterface interface {
	// CreateWhatever request with any body
	CreateWhateverWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateWhateverResponse, error)

	CreateWhateverWithResponse(ctx context.Context, body CreateWhateverJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateWhateverResponse, error)

	CreateWhateverWithResponse(ctx context.Context, body CreateWhateverJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateWhateverResponse, error)

	CreateWhateverWithResponse(ctx context.Context, body CreateWhateverJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateWhateverResponse, error)
}
}
```


Discussion: Happens to me, too.

v1.11.0 did not have the problem, v1.12.0 did.
To confirm, does this create duplicate names, such that we'd see a compilation error, or is this "just" duplicated code?
any suggestions how to go about it?
> To confirm, does this create duplicate names, such that we'd see a compilation error, or is this "just" duplicated code?

It creates duplicate names and compilation errors.
Hey, sorry yeah so we'll need to get a patch to deal with this - hopefully will be in next couple of weeks, thanks for bearing with!
@jamietanna I don't if you had the time to look into this issue, but if we can agree to a concept, I can try to contribute a fix.

I have two ideas so far:

- Use `any` and check types runtime so that the same function can accept different bodies (basically drop build time type safety)
- Create an interface with a private function (so that outside of the package noone can implement the interface) and implement it on supported types

I realize that none of those are perfect solutions, so if anyone has any better ideas, please don't keep them to yourself. :)
Sorry for the delay with this - I meant to get it shipped in the v1. 13.1 patch release just now, but slipped my mind - am working on a fix now which should be out today
https://github.com/deepmap/oapi-codegen/pull/1159 should resolve this - will see what Marcin thinks and if this works for y'all too :smile: 
@jamietanna does this PR cover the case when different media types do not actually use the same schema? I mean, that's kind of the point, but from what I can tell in the example, each media types use the same schema.



Fix (patch):
diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go
index 7c63b0e17..6d42b2194 100644
--- a/pkg/codegen/operations.go
+++ b/pkg/codegen/operations.go
@@ -398,7 +398,15 @@ func (r RequestBodyDefinition) Suffix() string {
 
 // IsSupportedByClient returns true if we support this content type for client. Otherwise only generic method will ge generated
 func (r RequestBodyDefinition) IsSupportedByClient() bool {
-	return r.NameTag == "JSON" || r.NameTag == "Formdata" || r.NameTag == "Text"
+	return r.IsJSON() || r.NameTag == "Formdata" || r.NameTag == "Text"
+}
+
+// IsJSON returns whether this is a JSON media type, for instance:
+// - application/json
+// - application/vnd.api+json
+// - application/*+json
+func (r RequestBodyDefinition) IsJSON() bool {
+	return util.IsMediaTypeJson(r.ContentType)
 }
 
 // IsSupported returns true if we support this content type for server. Otherwise io.Reader will be generated
@@ -642,9 +650,11 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody
 		var defaultBody bool
 
 		switch {
-		case util.IsMediaTypeJson(contentType):
+		case contentType == "application/json":
 			tag = "JSON"
 			defaultBody = true
+		case util.IsMediaTypeJson(contentType):
+			tag = mediaTypeToCamelCase(contentType)
 		case strings.HasPrefix(contentType, "multipart/"):
 			tag = "Multipart"
 		case contentType == "application/x-www-form-urlencoded":
@@ -742,8 +752,10 @@ func GenerateResponseDefinitions(operationID string, responses openapi3.Response
 			content := response.Content[contentType]
 			var tag string
 			switch {
-			case util.IsMediaTypeJson(contentType):
+			case contentType == "application/json":
 				tag = "JSON"
+			case util.IsMediaTypeJson(contentType):
+				tag = mediaTypeToCamelCase(contentType)
 			case contentType == "application/x-www-form-urlencoded":
 				tag = "Formdata"
 			case strings.HasPrefix(contentType, "multipart/"):
diff --git a/pkg/codegen/templates/client.tmpl b/pkg/codegen/templates/client.tmpl
index 5d4a48cec..10ee5644d 100644
--- a/pkg/codegen/templates/client.tmpl
+++ b/pkg/codegen/templates/client.tmpl
@@ -135,7 +135,7 @@ func (c *{{ $clientTypeName }}) {{$opid}}{{.Suffix}}(ctx context.Context{{genPar
 // New{{$opid}}Request{{.Suffix}} calls the generic {{$opid}} builder with {{.ContentType}} body
 func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Request, error) {
     var bodyReader io.Reader
-    {{if eq .NameTag "JSON" -}}
+    {{if .IsJSON -}}
         buf, err := json.Marshal(body)
         if err != nil {
             return nil, err
diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go
index f8de46cf1..79aee7368 100644
--- a/pkg/codegen/utils.go
+++ b/pkg/codegen/utils.go
@@ -179,6 +179,17 @@ func replaceInitialism(s string) string {
 	})
 }
 
+// mediaTypeToCamelCase converts a media type to a PascalCase representation
+func mediaTypeToCamelCase(s string) string {
+	// ToCamelCase doesn't - and won't - add `/` to the characters it'll allow word boundary
+	s = strings.Replace(s, "/", "_", 1)
+	// including a _ to make sure that these are treated as word boundaries by `ToCamelCase`
+	s = strings.Replace(s, "*", "Wildcard_", 1)
+	s = strings.Replace(s, "+", "Plus_", 1)
+
+	return ToCamelCaseWithInitialism(s)
+}
+
 // SortedSchemaKeys returns the keys of the given SchemaRef dictionary in sorted
 // order, since Golang scrambles dictionary keys
 func SortedSchemaKeys(dict map[string]*openapi3.SchemaRef) []string {