Repository: deepmap/oapi-codegen
Pull request #26 (2019-06-07 11:58:34)

Responses with unmarshalled JSON payloads.
* Response payloads generated for each operation
* Response bodies are unmarshalled into the response payloads (according to their content type)
* Currently only JSON / YAML / XML are supported, but others can easily be added
* The default is to generate the original client code
* A new option "richclient" enables generation of the new client code

chrusty: Here is a sample from the generated "petstore" client:

```golang

// AddPetResponse is returned by Client.AddPet()
type AddPetResponse struct {
	Body         []byte
	HTTPResponse *http.Response
	JSON200      struct {
		// Embedded struct due to allOf(#/components/schemas/NewPet)
		NewPet
		// Embedded fields due to inline allOf schema
		Id int64 `json:"id"`
	} // 'pet response' (#/components/schemas/Pet)
	JSONDefault Error // 'unexpected error' (#/components/schemas/Error)
}

// Status returns HTTPResponse.Status
func (r *AddPetResponse) Status() string {
	if r.HTTPResponse != nil {
		return r.HTTPResponse.Status
	}
	return http.StatusText(0)
}

// StatusCode returns HTTPResponse.StatusCode
func (r *AddPetResponse) StatusCode() int {
	if r.HTTPResponse != nil {
		return r.HTTPResponse.StatusCode
	}
	return 0
}

// AddPet request with JSON body
func (c *Client) AddPet(ctx context.Context, body NewPet) (*AddPetResponse, error) {
	req, err := NewAddPetRequest(c.Server, body)
	if err != nil {
		return nil, err
	}
	req = req.WithContext(ctx)
	if c.RequestEditor != nil {
		err = c.RequestEditor(req, ctx)
		if err != nil {
			return nil, err
		}
	}

	rsp, err := c.Client.Do(req)
	if err != nil {
		return nil, err
	}

	bodyBytes, err := ioutil.ReadAll(rsp.Body)
	if err != nil {
		return nil, err
	}

	response := &AddPetResponse{
		Body:         bodyBytes,
		HTTPResponse: rsp,
	}

	switch {
	case strings.Contains(rsp.Header.Get("Content-type"), "json") && rsp.StatusCode == 200:
		response.JSON200 = struct {
			// Embedded struct due to allOf(#/components/schemas/NewPet)
			NewPet
			// Embedded fields due to inline allOf schema
			Id int64 `json:"id"`
		}{}
		if err := json.Unmarshal(bodyBytes, &response.JSON200); err != nil {
			return nil, err
		}
	case strings.Contains(rsp.Header.Get("Content-type"), "json"):
		response.JSONDefault = Error{}
		if err := json.Unmarshal(bodyBytes, &response.JSONDefault); err != nil {
			return nil, err
		}
	}

	return response, nil
}```
deepmap-marcinr: This is a big change, thank you for putting in so much work, but I think it needs to be restructured a little bit.

If you look at the existing client, I've designed it so that you can use any layer of it - you can use the Client object itself, and let it manage http requests, but if you want to do something that I didn't foresee, you can use the request generator yourself.

I was thinking about auto-unmarshaling of responses, since I would have eventually gotten there myself, but it would have worked along those lines.

Could you change your code so that it creates a helper function to unmarshal a given response, which is optional to use? This way, you don't need a "client" and a "richclient", but simply a client.

You could even make the helper function really smart, in that it can wrap each request with type safety.

If you have 

```
func (c *Client) DoSomething(ctx context.Context, param string) (*http.Response, error)
```

You could also generate:
`ParseResponseForDoSomething(*http.Response)(*DoSomethingResponse, error)`

Then, in the client:

```
func (c *Client) DoSomethingWithResponse(ctx context.Context, param string) (*DoSomethingResponse, error) {
   resp, err := c.DoSomething(ctx, param)
  ...
   return PArseResponseForDoSomething(resp)
}
```




chrusty: It looks like these test failures are related to the diff. The generated code seems OK, not sure how to debug the diff.
deepmap-marcinr: I think you need to run go generate after rebasing to fix the Travis error.

deepmap-marcinr: Actually, one more thought ...

The client currently ends up having `X (*http.Response, error)` and `XWithResponse (*XResponse, error). It means if I'm mocking the client (which is why there's a client interface), I now have twice the functions I need to mock. It's one extra line of code in the handler if the client only has one version of the function, since you call the ParseResponse explicitly. I'm not asking you to change anything, just wondering what you think.



chrusty: > Actually, one more thought ...
> 
> The client currently ends up having `X (*http.Response, error)` and `XWithResponse (*XResponse, error). It means if I'm mocking the client (which is why there's a client interface), I now have twice the functions I need to mock. It's one extra line of code in the handler if the client only has one version of the function, since you call the ParseResponse explicitly. I'm not asking you to change anything, just wondering what you think.

The interfaces (and relative simplicity) are what makes this project stand apart from the others! Yes, this change is going to mean mocking twice as many methods (this is what I was considering when I came up with the `richclient` option previously). Calling the `Parse....()` functions is going to mean a second step of boilerplate each time someone calls an API method. There are ups and downs to either side of this, I'm happy to go with whatever style you're pushing for the project (it is important to keep it clean and consistent).

So the options are:
1. Use the `richclient` option to determine what the client methods return
2. Generate two sets of client methods (one with *http.Response, the other with *SomeThingResponse)
3. Don't add new methods, but have the option to run an additional `Parse` step on the *http.Responses


At least we have options!
deepmap-marcinr: My issue with adding the XXXWithResponse functions automatically now is
that it breaks existing code. To do it right, I would have to make
oapi-codegen/v2 to break API compatibility. In my own code, here, all my
mocks would break, so I'm trying to avoid that for now.

I think I have a good answer, though!

Make a second client - ClientWithResponses, and embed ClientInterface

type ClientInterface {
   DoX() (http.response, error)
}

type ClientWithResponses struct {
   ClientInterface
   DoXWithResponse() (XResponse, error)
}

func (c *ClientWithResponses) DoXWithResponse() (XResponse(), error) {
   httpResponse, err := c.DoX()
   <insert your magic here>
}

func NewClientWithResponses() ClientWithResponses {
   return &ClientWithResponses{ClientInterface:&Client{}}
}

All your WithResponse functions go on ClientWithResponses. It calls into
the embedded interface. I can put a mock into the embedded interface and
still have all your response unmarshaling code work, and current API's are
not broken. What do you think? The change should be pretty minor -
refactoring that .tmpl file mostly.

On Mon, Jun 10, 2019 at 11:14 AM Prawn <notifications@github.com> wrote:

> Actually, one more thought ...
>
> The client currently ends up having X (*http.Response, error) and
> `XWithResponse (*XResponse, error). It means if I'm mocking the client
> (which is why there's a client interface), I now have twice the functions I
> need to mock. It's one extra line of code in the handler if the client only
> has one version of the function, since you call the ParseResponse
> explicitly. I'm not asking you to change anything, just wondering what you
> think.
>
> The interfaces (and relative simplicity) are what makes this project stand
> apart from the others! Yes, this change is going to mean mocking twice as
> many methods (this is what I was considering when I came up with the
> richclient option previously). Calling the Parse....() functions is going
> to mean a second step of boilerplate each time someone calls an API method.
> There are ups and downs to either side of this, I'm happy to go with
> whatever style you're pushing for the project (it is important to keep it
> clean and consistent).
>
> So the options are:
>
>    1. Use the richclient option to determine what the client methods
>    return
>    2. Generate two sets of client methods (one with *http.Response, the
>    other with *SomeThingResponse)
>    3. Don't add new methods, but have the option to run an additional
>    Parse step on the *http.Responses
>
> At least we have options!
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/deepmap/oapi-codegen/pull/26?email_source=notifications&email_token=ALKC5DEARPNYT5WKNCRBRPLPZ2KZRA5CNFSM4HVWNLVKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXKXDCI#issuecomment-500527497>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ALKC5DGMQMGL36VUOAZXBW3PZ2KZRANCNFSM4HVWNLVA>
> .
>

chrusty: > My issue with adding the XXXWithResponse functions automatically now is that it breaks existing code. To do it right, I would have to make oapi-codegen/v2 to break API compatibility. In my own code, here, all my mocks would break, so I'm trying to avoid that for now. I think I have a good answer, though! Make a second client - ClientWithResponses, and embed ClientInterface type ClientInterface { DoX() (http.response, error) } type ClientWithResponses struct { ClientInterface DoXWithResponse() (XResponse, error) } func (c *ClientWithResponses) DoXWithResponse() (XResponse(), error) { httpResponse, err := c.DoX() <insert your magic here> } func NewClientWithResponses() ClientWithResponses { return &ClientWithResponses{ClientInterface:&Client{}} } All your WithResponse functions go on ClientWithResponses. It calls into the embedded interface. I can put a mock into the embedded interface and still have all your response unmarshaling code work, and current API's are not broken. What do you think? The change should be pretty minor - refactoring that .tmpl file mostly.
> […](#)
> On Mon, Jun 10, 2019 at 11:14 AM Prawn ***@***.***> wrote: Actually, one more thought ... The client currently ends up having X (*http.Response, error) and `XWithResponse (*XResponse, error). It means if I'm mocking the client (which is why there's a client interface), I now have twice the functions I need to mock. It's one extra line of code in the handler if the client only has one version of the function, since you call the ParseResponse explicitly. I'm not asking you to change anything, just wondering what you think. The interfaces (and relative simplicity) are what makes this project stand apart from the others! Yes, this change is going to mean mocking twice as many methods (this is what I was considering when I came up with the richclient option previously). Calling the Parse....() functions is going to mean a second step of boilerplate each time someone calls an API method. There are ups and downs to either side of this, I'm happy to go with whatever style you're pushing for the project (it is important to keep it clean and consistent). So the options are: 1. Use the richclient option to determine what the client methods return 2. Generate two sets of client methods (one with *http.Response, the other with *SomeThingResponse) 3. Don't add new methods, but have the option to run an additional Parse step on the *http.Responses At least we have options! — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#26?email_source=notifications&email_token=ALKC5DEARPNYT5WKNCRBRPLPZ2KZRA5CNFSM4HVWNLVKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXKXDCI#issuecomment-500527497>, or mute the thread <https://github.com/notifications/unsubscribe-auth/ALKC5DGMQMGL36VUOAZXBW3PZ2KZRANCNFSM4HVWNLVA> .

That works. I'll sort it out now. Good solution!
chrusty: @deepmap-marcinr this seems to work quite nicely. Also the tests are passing now.

Patch:
diff --git a/examples/petstore-expanded/api/petstore-client.gen.go b/examples/petstore-expanded/api/petstore-client.gen.go
index 294eb2e..ae9e263 100644
--- a/examples/petstore-expanded/api/petstore-client.gen.go
+++ b/examples/petstore-expanded/api/petstore-client.gen.go
@@ -10,6 +10,7 @@ import (
 	"fmt"
 	"github.com/deepmap/oapi-codegen/pkg/runtime"
 	"io"
+	"io/ioutil"
 	"net/http"
 	"strings"
 )
@@ -108,6 +109,295 @@ func (c *Client) FindPetById(ctx context.Context, id int64) (*http.Response, err
 	return c.Client.Do(req)
 }
 
+// ClientWithResponses builds on ClientInterface to offer response payloads
+type ClientWithResponses struct {
+	ClientInterface
+}
+
+// NewClientWithResponses returns a ClientWithResponses with a default Client:
+func NewClientWithResponses(server string) *ClientWithResponses {
+	return &ClientWithResponses{
+		ClientInterface: &Client{
+			Client: http.Client{},
+			Server: server,
+		},
+	}
+}
+
+// FindPetsResponse is returned by Client.FindPets()
+type FindPetsResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+	JSON200      *[]struct {
+		// Embedded struct due to allOf(#/components/schemas/NewPet)
+		NewPet
+		// Embedded fields due to inline allOf schema
+		Id int64 `json:"id"`
+	} // 'pet response' ()
+	JSONDefault *Error // 'unexpected error' (#/components/schemas/Error)
+}
+
+// Status returns HTTPResponse.Status
+func (r *FindPetsResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *FindPetsResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseFindPetsResponse parses an HTTP response from a FindPetsWithResponse call
+func ParseFindPetsResponse(rsp *http.Response) (*FindPetsResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &FindPetsResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+		response.JSON200 = &[]struct {
+			// Embedded struct due to allOf(#/components/schemas/NewPet)
+			NewPet
+			// Embedded fields due to inline allOf schema
+			Id int64 `json:"id"`
+		}{}
+		if err := json.Unmarshal(bodyBytes, response.JSON200); err != nil {
+			return nil, err
+		}
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json"):
+		response.JSONDefault = &Error{}
+		if err := json.Unmarshal(bodyBytes, response.JSONDefault); err != nil {
+			return nil, err
+		}
+	}
+
+	return response, nil
+}
+
+// FindPets request returning *FindPetsResponse
+func (c *ClientWithResponses) FindPetsWithResponse(ctx context.Context, params *FindPetsParams) (*FindPetsResponse, error) {
+	rsp, err := c.FindPets(ctx, params)
+	if err != nil {
+		return nil, err
+	}
+	return ParseFindPetsResponse(rsp)
+}
+
+// AddPetResponse is returned by Client.AddPet()
+type AddPetResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+	JSON200      *struct {
+		// Embedded struct due to allOf(#/components/schemas/NewPet)
+		NewPet
+		// Embedded fields due to inline allOf schema
+		Id int64 `json:"id"`
+	} // 'pet response' (#/components/schemas/Pet)
+	JSONDefault *Error // 'unexpected error' (#/components/schemas/Error)
+}
+
+// Status returns HTTPResponse.Status
+func (r *AddPetResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *AddPetResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseAddPetResponse parses an HTTP response from a AddPetWithResponse call
+func ParseAddPetResponse(rsp *http.Response) (*AddPetResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &AddPetResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+		response.JSON200 = &struct {
+			// Embedded struct due to allOf(#/components/schemas/NewPet)
+			NewPet
+			// Embedded fields due to inline allOf schema
+			Id int64 `json:"id"`
+		}{}
+		if err := json.Unmarshal(bodyBytes, response.JSON200); err != nil {
+			return nil, err
+		}
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json"):
+		response.JSONDefault = &Error{}
+		if err := json.Unmarshal(bodyBytes, response.JSONDefault); err != nil {
+			return nil, err
+		}
+	}
+
+	return response, nil
+}
+
+// AddPet request with JSON body returning *AddPetResponse
+func (c *ClientWithResponses) AddPetWithResponse(ctx context.Context, body NewPet) (*AddPetResponse, error) {
+	rsp, err := c.AddPet(ctx, body)
+	if err != nil {
+		return nil, err
+	}
+	return ParseAddPetResponse(rsp)
+}
+
+// DeletePetResponse is returned by Client.DeletePet()
+type DeletePetResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+	JSONDefault  *Error // 'unexpected error' (#/components/schemas/Error)
+}
+
+// Status returns HTTPResponse.Status
+func (r *DeletePetResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *DeletePetResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseDeletePetResponse parses an HTTP response from a DeletePetWithResponse call
+func ParseDeletePetResponse(rsp *http.Response) (*DeletePetResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &DeletePetResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 204:
+		break // No content-type
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json"):
+		response.JSONDefault = &Error{}
+		if err := json.Unmarshal(bodyBytes, response.JSONDefault); err != nil {
+			return nil, err
+		}
+	}
+
+	return response, nil
+}
+
+// DeletePet request returning *DeletePetResponse
+func (c *ClientWithResponses) DeletePetWithResponse(ctx context.Context, id int64) (*DeletePetResponse, error) {
+	rsp, err := c.DeletePet(ctx, id)
+	if err != nil {
+		return nil, err
+	}
+	return ParseDeletePetResponse(rsp)
+}
+
+// FindPetByIdResponse is returned by Client.FindPetById()
+type FindPetByIdResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+	JSON200      *struct {
+		// Embedded struct due to allOf(#/components/schemas/NewPet)
+		NewPet
+		// Embedded fields due to inline allOf schema
+		Id int64 `json:"id"`
+	} // 'pet response' (#/components/schemas/Pet)
+	JSONDefault *Error // 'unexpected error' (#/components/schemas/Error)
+}
+
+// Status returns HTTPResponse.Status
+func (r *FindPetByIdResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *FindPetByIdResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseFindPetByIdResponse parses an HTTP response from a FindPetByIdWithResponse call
+func ParseFindPetByIdResponse(rsp *http.Response) (*FindPetByIdResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &FindPetByIdResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+		response.JSON200 = &struct {
+			// Embedded struct due to allOf(#/components/schemas/NewPet)
+			NewPet
+			// Embedded fields due to inline allOf schema
+			Id int64 `json:"id"`
+		}{}
+		if err := json.Unmarshal(bodyBytes, response.JSON200); err != nil {
+			return nil, err
+		}
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json"):
+		response.JSONDefault = &Error{}
+		if err := json.Unmarshal(bodyBytes, response.JSONDefault); err != nil {
+			return nil, err
+		}
+	}
+
+	return response, nil
+}
+
+// FindPetById request returning *FindPetByIdResponse
+func (c *ClientWithResponses) FindPetByIdWithResponse(ctx context.Context, id int64) (*FindPetByIdResponse, error) {
+	rsp, err := c.FindPetById(ctx, id)
+	if err != nil {
+		return nil, err
+	}
+	return ParseFindPetByIdResponse(rsp)
+}
+
 // NewFindPetsRequest generates requests for FindPets
 func NewFindPetsRequest(server string, params *FindPetsParams) (*http.Request, error) {
 	var err error
diff --git a/go.mod b/go.mod
index 2a657af..0c9f21b 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/deepmap/oapi-codegen
 require (
 	github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c
 	github.com/getkin/kin-openapi v0.2.0
+	github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219
 	github.com/labstack/echo/v4 v4.1.5
 	github.com/mattn/go-colorable v0.1.2 // indirect
 	github.com/pkg/errors v0.8.1
@@ -12,3 +13,5 @@ require (
 	golang.org/x/sys v0.0.0-20190524152521-dbbf3f1254d4 // indirect
 	golang.org/x/text v0.3.2 // indirect
 )
+
+replace github.com/deepmap/oapi-codegen => ./
diff --git a/go.sum b/go.sum
index 760171c..b5ccd3c 100644
--- a/go.sum
+++ b/go.sum
@@ -10,6 +10,8 @@ github.com/getkin/kin-openapi v0.2.0 h1:PbHHtYZpjKwZtGlIyELgA2DploRrsaXztoNNx9Hj
 github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC5IXdEA547bcoU5dozgQAfc8Onsg4=
+github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -54,6 +56,7 @@ golang.org/x/sys v0.0.0-20190524152521-dbbf3f1254d4/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go
index b5e9523..80eddf7 100644
--- a/pkg/codegen/codegen.go
+++ b/pkg/codegen/codegen.go
@@ -114,6 +114,9 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri
 		if strings.Contains(str, "io.") {
 			imports = append(imports, "io")
 		}
+		if strings.Contains(str, "ioutil.") {
+			imports = append(imports, "io/ioutil")
+		}
 		if strings.Contains(str, "url.") {
 			imports = append(imports, "net/url")
 		}
@@ -141,6 +144,12 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri
 		if strings.Contains(str, "fmt.") {
 			imports = append(imports, "fmt")
 		}
+		if strings.Contains(str, "yaml.") {
+			imports = append(imports, "gopkg.in/yaml.v2")
+		}
+		if strings.Contains(str, "xml.") {
+			imports = append(imports, "encoding/xml")
+		}
 	}
 
 	importsOut, err := GenerateImports(t, imports, packageName)
diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go
index 9abf4f9..51cd6fb 100644
--- a/pkg/codegen/operations.go
+++ b/pkg/codegen/operations.go
@@ -453,3 +453,21 @@ func GenerateClient(t *template.Template, ops []OperationDefinition) (string, er
 	}
 	return buf.String(), nil
 }
+
+// Uses the template engine to generate the function which registers our wrappers
+// as Echo path handlers.
+func GenerateClientWithResponses(t *template.Template, ops []OperationDefinition) (string, error) {
+	var buf bytes.Buffer
+	w := bufio.NewWriter(&buf)
+
+	err := t.ExecuteTemplate(w, "client-with-responses.tmpl", ops)
+
+	if err != nil {
+		return "", fmt.Errorf("error generating client bindings: %s", err)
+	}
+	err = w.Flush()
+	if err != nil {
+		return "", fmt.Errorf("error flushing output buffer for client: %s", err)
+	}
+	return buf.String(), nil
+}
diff --git a/pkg/codegen/template_helpers.go b/pkg/codegen/template_helpers.go
index 23410a7..767b7f3 100644
--- a/pkg/codegen/template_helpers.go
+++ b/pkg/codegen/template_helpers.go
@@ -14,9 +14,21 @@
 package codegen
 
 import (
+	"bytes"
 	"fmt"
+	"os"
 	"strings"
 	"text/template"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/labstack/echo/v4"
+)
+
+var (
+	payloadPrefix    = "payload"
+	contentTypesJSON = []string{echo.MIMEApplicationJSON, "text/x-json"}
+	contentTypesYAML = []string{"application/yaml", "application/x-yaml", "text/yaml", "text/x-yaml"}
+	contentTypesXML  = []string{echo.MIMEApplicationXML, echo.MIMETextXML}
 )
 
 // This function takes an array of Parameter definition, and generates a valid
@@ -69,14 +81,246 @@ func genParamFmtString(path string) string {
 	return ReplacePathParamsWithStr(path)
 }
 
+// genResponsePayload generates the payload returned at the end of each client request function
+func genResponsePayload(operationID string) string {
+	var buffer = bytes.NewBufferString("")
+
+	// Here is where we build up a response:
+	fmt.Fprintf(buffer, "&%sResponse{\n", operationID)
+	fmt.Fprintf(buffer, "Body: bodyBytes,\n")
+	fmt.Fprintf(buffer, "HTTPResponse: rsp,\n")
+	fmt.Fprintf(buffer, "}")
+
+	return buffer.String()
+}
+
+// genResponseType generates type definitions for those that we can read
+func genResponseType(operationID string, responses openapi3.Responses) string {
+	var buffer = bytes.NewBufferString("")
+
+	// The header and standard struct attributes:
+	fmt.Fprintf(buffer, "// %sResponse is returned by Client.%s()\n", operationID, operationID)
+	fmt.Fprintf(buffer, "type %sResponse struct {\n", operationID)
+	fmt.Fprintf(buffer, "Body []byte\n")
+	fmt.Fprintf(buffer, "HTTPResponse *http.Response\n")
+
+	// Add an attribute for each possible response:
+	sortedResponsesKeys := SortedResponsesKeys(responses)
+	for _, responseName := range sortedResponsesKeys {
+		responseRef, ok := responses[responseName]
+		if !ok {
+			continue
+		}
+
+		// We can only generate a type if we have a value:
+		if responseRef.Value != nil {
+			sortedContentKeys := SortedContentKeys(responseRef.Value.Content)
+			for _, contentTypeName := range sortedContentKeys {
+				contentType, ok := responseRef.Value.Content[contentTypeName]
+				if !ok {
+					continue
+				}
+
+				// We can only generate a type if we have a schema:
+				if contentType.Schema != nil {
+
+					// Make sure that we actually have a go-type for this response:
+					goType, err := schemaToGoType(contentType.Schema, true)
+					if err != nil {
+						fmt.Fprintf(os.Stderr, "Unable to determine Go type for %s.%s: %v\n", operationID, contentTypeName, err)
+						continue
+					}
+
+					// Generate different attribute names for different content-types:
+					switch {
+
+					// JSON:
+					case contains(contentTypesJSON, contentTypeName):
+						attributeName := fmt.Sprintf("JSON%s", ToCamelCase(responseName))
+						fmt.Fprintf(buffer, "%s *%s // '%s' (%s)\n", attributeName, goType, responseRef.Value.Description, contentType.Schema.Ref)
+
+					// YAML:
+					case contains(contentTypesYAML, contentTypeName):
+						attributeName := fmt.Sprintf("YAML%s", ToCamelCase(responseName))
+						fmt.Fprintf(buffer, "%s *%s // '%s' (%s)\n", attributeName, goType, responseRef.Value.Description, contentType.Schema.Ref)
+
+					// XML:
+					case contains(contentTypesXML, contentTypeName):
+						attributeName := fmt.Sprintf("XML%s", ToCamelCase(responseName))
+						fmt.Fprintf(buffer, "%s *%s // '%s' (%s)\n", attributeName, goType, responseRef.Value.Description, contentType.Schema.Ref)
+					}
+				}
+			}
+		}
+	}
+	fmt.Fprintf(buffer, "}\n")
+
+	// Status() provides an easy way to get the Status:
+	fmt.Fprintf(buffer, "// Status returns HTTPResponse.Status\n")
+	fmt.Fprintf(buffer, "func (r *%sResponse) Status() string {\n", operationID)
+	fmt.Fprintf(buffer, "	if r.HTTPResponse != nil {\n")
+	fmt.Fprintf(buffer, "		return r.HTTPResponse.Status\n")
+	fmt.Fprintf(buffer, "	}\n")
+	fmt.Fprintf(buffer, "	return http.StatusText(0)\n")
+	fmt.Fprintf(buffer, "}\n")
+
+	// StatusCode() provides an easy way to get the StatusCode:
+	fmt.Fprintf(buffer, "// StatusCode returns HTTPResponse.StatusCode\n")
+	fmt.Fprintf(buffer, "func (r *%sResponse) StatusCode() int {\n", operationID)
+	fmt.Fprintf(buffer, "	if r.HTTPResponse != nil {\n")
+	fmt.Fprintf(buffer, "		return r.HTTPResponse.StatusCode\n")
+	fmt.Fprintf(buffer, "	}\n")
+	fmt.Fprintf(buffer, "	return 0\n")
+	fmt.Fprintf(buffer, "}\n")
+
+	return buffer.String()
+}
+
+// genResponseUnmarshal generates unmarshaling steps for structured response payloads
+func genResponseUnmarshal(operationID string, responses openapi3.Responses) string {
+	var buffer = bytes.NewBufferString("")
+	var mostSpecific = make(map[string]string)  // content-type and status-code
+	var lessSpecific = make(map[string]string)  // status-code only
+	var leastSpecific = make(map[string]string) // content-type only (default responses)
+
+	// Add a case for each possible response:
+	sortedResponsesKeys := SortedResponsesKeys(responses)
+	for _, responseName := range sortedResponsesKeys {
+		responseRef, ok := responses[responseName]
+		if !ok {
+			continue
+		}
+
+		// We can't do much without a value:
+		if responseRef.Value == nil {
+			fmt.Fprintf(os.Stderr, "Response %s.%s has nil value\n", operationID, responseName)
+			continue
+		}
+
+		// If there is no content-type then we have no unmarshaling to do:
+		if len(responseRef.Value.Content) == 0 {
+			caseAction := "break // No content-type"
+			if responseName == "default" {
+				caseClause := "default:"
+				leastSpecific[caseClause] = caseAction
+			} else {
+				caseClause := fmt.Sprintf("case rsp.StatusCode == %s:", responseName)
+				lessSpecific[caseClause] = caseAction
+			}
+			continue
+		}
+
+		// If we made it this far then we need to handle unmarshaling for each content-type:
+		sortedContentKeys := SortedContentKeys(responseRef.Value.Content)
+		for _, contentTypeName := range sortedContentKeys {
+			contentType, ok := responseRef.Value.Content[contentTypeName]
+			if !ok {
+				continue
+			}
+
+			// But we can only do this if we actually have a schema (otherwise there will be no struct to unmarshal into):
+			if contentType.Schema == nil {
+				fmt.Fprintf(os.Stderr, "Response %s.%s has nil schema\n", operationID, responseName)
+				continue
+			}
+
+			// Make sure that we actually have a go-type for this response:
+			goType, err := schemaToGoType(contentType.Schema, true)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "Unable to determine Go type for %s.%s: %v\n", operationID, contentTypeName, err)
+				continue
+			}
+
+			// Add content-types here (json / yaml / xml etc):
+			switch {
+
+			// JSON:
+			case contains(contentTypesJSON, contentTypeName):
+				attributeName := fmt.Sprintf("JSON%s", ToCamelCase(responseName))
+				caseAction := fmt.Sprintf("response.%s = &%s{} \n if err := json.Unmarshal(bodyBytes, response.%s); err != nil { \n return nil, err \n}", attributeName, goType, attributeName)
+				if responseName == "default" {
+					caseClause := fmt.Sprintf("case strings.Contains(rsp.Header.Get(\"%s\"), \"json\"):", echo.HeaderContentType)
+					leastSpecific[caseClause] = caseAction
+				} else {
+					caseClause := fmt.Sprintf("case strings.Contains(rsp.Header.Get(\"%s\"), \"json\") && rsp.StatusCode == %s:", echo.HeaderContentType, responseName)
+					mostSpecific[caseClause] = caseAction
+				}
+
+			// YAML:
+			case contains(contentTypesYAML, contentTypeName):
+				attributeName := fmt.Sprintf("YAML%s", ToCamelCase(responseName))
+				caseAction := fmt.Sprintf("response.%s = &%s{} \n if err := yaml.Unmarshal(bodyBytes, response.%s); err != nil { \n return nil, err \n}", attributeName, goType, attributeName)
+				if responseName == "default" {
+					caseClause := fmt.Sprintf("case strings.Contains(rsp.Header.Get(\"%s\"), \"yaml\"):", echo.HeaderContentType)
+					leastSpecific[caseClause] = caseAction
+				} else {
+					caseClause := fmt.Sprintf("case strings.Contains(rsp.Header.Get(\"%s\"), \"yaml\") && rsp.StatusCode == %s:", echo.HeaderContentType, responseName)
+					mostSpecific[caseClause] = caseAction
+				}
+
+			// XML:
+			case contains(contentTypesXML, contentTypeName):
+				attributeName := fmt.Sprintf("XML%s", ToCamelCase(responseName))
+				caseAction := fmt.Sprintf("response.%s = &%s{} \n if err := xml.Unmarshal(bodyBytes, response.%s); err != nil { \n return nil, err \n}", attributeName, goType, attributeName)
+				if responseName == "default" {
+					caseClause := fmt.Sprintf("case strings.Contains(rsp.Header.Get(\"%s\"), \"xml\"):", echo.HeaderContentType)
+					leastSpecific[caseClause] = caseAction
+				} else {
+					caseClause := fmt.Sprintf("case strings.Contains(rsp.Header.Get(\"%s\"), \"xml\") && rsp.StatusCode == %s:", echo.HeaderContentType, responseName)
+					mostSpecific[caseClause] = caseAction
+				}
+
+			// Everything else:
+			default:
+				caseAction := fmt.Sprintf("// Content-type (%s) unsupported", contentTypeName)
+				if responseName == "default" {
+					caseClause := "default:"
+					leastSpecific[caseClause] = caseAction
+				} else {
+					caseClause := fmt.Sprintf("case rsp.StatusCode == %s:", responseName)
+					lessSpecific[caseClause] = caseAction
+				}
+			}
+		}
+	}
+
+	// Now build the switch statement in order of most-to-least specific:
+	fmt.Fprintf(buffer, "switch {\n")
+	for caseClause, caseAction := range mostSpecific {
+		fmt.Fprintf(buffer, "%s\n%s\n", caseClause, caseAction)
+	}
+	for caseClause, caseAction := range lessSpecific {
+		fmt.Fprintf(buffer, "%s\n%s\n", caseClause, caseAction)
+	}
+	for caseClause, caseAction := range leastSpecific {
+		fmt.Fprintf(buffer, "%s\n%s\n", caseClause, caseAction)
+	}
+	fmt.Fprintf(buffer, "}\n")
+
+	return buffer.String()
+}
+
+// contains tells us if a string is found in a slice of strings:
+func contains(strings []string, s string) bool {
+	for _, stringInSlice := range strings {
+		if s == stringInSlice {
+			return true
+		}
+	}
+	return false
+}
+
 // This function map is passed to the template engine, and we can call each
 // function here by keyName from the template code.
 var TemplateFunctions = template.FuncMap{
-	"genParamArgs":        genParamArgs,
-	"genParamTypes":       genParamTypes,
-	"genParamNames":       genParamNames,
-	"genParamFmtString":   genParamFmtString,
-	"swaggerUriToEchoUri": SwaggerUriToEchoUri,
-	"lcFirst":             LowercaseFirstCharacter,
-	"camelCase":           ToCamelCase,
+	"genParamArgs":         genParamArgs,
+	"genParamTypes":        genParamTypes,
+	"genParamNames":        genParamNames,
+	"genParamFmtString":    genParamFmtString,
+	"swaggerUriToEchoUri":  SwaggerUriToEchoUri,
+	"lcFirst":              LowercaseFirstCharacter,
+	"camelCase":            ToCamelCase,
+	"genResponsePayload":   genResponsePayload,
+	"genResponseType":      genResponseType,
+	"genResponseUnmarshal": genResponseUnmarshal,
 }
diff --git a/pkg/codegen/templates/client.tmpl b/pkg/codegen/templates/client.tmpl
index dc40ba1..8c04bf6 100644
--- a/pkg/codegen/templates/client.tmpl
+++ b/pkg/codegen/templates/client.tmpl
@@ -17,14 +17,15 @@ type ClientInterface interface {
 {{range .}}{{$opid := .OperationId -}}
 {{- if .HasBody}}// {{$opid}} request with JSON body
     {{$opid}}(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}, body {{if not .GetBodyDefinition.Required}}*{{end}}{{if .GetBodyDefinition.CustomType}}{{$opid}}RequestBody{{else}}{{.GetBodyDefinition.TypeDef}}{{end}}) (*http.Response, error)
-{{- end}}
+{{- end}}{{/* if .HasBody */}}
 {{if .GenerateGenericForm}}
     // {{$opid}}{{if .HasAnyBody}}WithBody{{end}} request{{if .HasAnyBody}} with arbitrary body{{end}}
     {{$opid}}{{if .HasAnyBody}}WithBody{{end}}(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasAnyBody}}, contentType string, body io.Reader{{end}}) (*http.Response, error)
-{{end}}
-{{end}}{{/* range .OperationId */}}
+{{end}}{{/* if .GenerateGenericForm */}}
+{{end}}{{/* range . $opid := .OperationId */}}
 }
 
+{{/* Generate client methods */}}
 {{range .}}{{$opid := .OperationId}}
 {{if .HasBody}}
 // {{$opid}} request with JSON body
@@ -42,7 +43,7 @@ func (c *Client) {{$opid}}(ctx context.Context{{genParamArgs .PathParams}}{{if .
     }
     return c.Client.Do(req)
 }
-{{end}}{{/* end of .HasBody */}}
+{{end}}{{/* if .HasBody */}}
 
 {{if .GenerateGenericForm}}
 // {{$opid}}{{if .HasAnyBody}}WithBody{{end}} request{{if .HasAnyBody}} with arbitrary body{{end}}
@@ -60,9 +61,69 @@ func (c *Client) {{$opid}}{{if .HasAnyBody}}WithBody{{end}}(ctx context.Context{
         }
     return c.Client.Do(req)
 }
-{{end}}
-{{end}}
+{{end}}{{/* if .GenerateGenericForm */}}
+{{end}}{{/* range . $opid := .OperationId */}}
+
+// ClientWithResponses builds on ClientInterface to offer response payloads
+type ClientWithResponses struct {
+    ClientInterface
+}
+
+// NewClientWithResponses returns a ClientWithResponses with a default Client:
+func NewClientWithResponses(server string) *ClientWithResponses {
+    return &ClientWithResponses{
+        ClientInterface: &Client{
+            Client: http.Client{},
+            Server: server,
+        },
+    }
+}
+
+{{/* Generate parse functions for responses*/}}
+{{range .}}{{$opid := .OperationId}}
+{{genResponseType $opid .Spec.Responses}}
+
+// Parse{{$opid}}Response parses an HTTP response from a {{$opid}}WithResponse call
+func Parse{{$opid}}Response(rsp *http.Response) (*{{$opid}}Response, error) {
+    bodyBytes, err := ioutil.ReadAll(rsp.Body)
+    defer rsp.Body.Close()
+    if err != nil {
+        return nil, err
+    }
+    
+    response := {{genResponsePayload $opid}}
+
+    {{genResponseUnmarshal $opid .Spec.Responses}}
+
+    return response, nil
+}
+
+{{/* Generate client methods (with responses)*/}}
+{{if .HasBody}}
+// {{$opid}} request with JSON body returning *{{$opid}}Response
+func (c *ClientWithResponses) {{$opid}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}, body {{if not .GetBodyDefinition.Required}}*{{end}}{{if .GetBodyDefinition.CustomType}}{{$opid}}RequestBody{{else}}{{.GetBodyDefinition.TypeDef}}{{end}}) (*{{$opid}}Response, error){
+    rsp, err := c.{{$opid}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}, body)
+	if err != nil {
+		return nil, err
+	}
+    return Parse{{$opid}}Response(rsp)
+}
+{{end}}{{/* if .HasBody */}}
+
+{{if .GenerateGenericForm}}
+// {{$opid}}{{if .HasAnyBody}}WithBody{{end}} request{{if .HasAnyBody}} with arbitrary body{{end}} returning *{{$opid}}Response
+func (c *ClientWithResponses) {{$opid}}{{if .HasAnyBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasAnyBody}}, contentType string, body io.Reader{{end}}) (*{{$opid}}Response, error){
+    rsp, err := c.{{$opid}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}{{if .HasAnyBody}}, contentType, body{{end}})
+    if err != nil {
+        return nil, err
+    }
+    return Parse{{$opid}}Response(rsp)
+}
+{{end}}{{/* if .GenerateGenericForm */}}
+{{end}}{{/* range . $opid := .OperationId */}}
+
 
+{{/* Generate server */}}
 {{range .}}{{$opid := .OperationId -}}
 {{if .HasBody}}
 // New{{$opid}}Request generates requests for {{$opid}} with JSON body
diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go
index a8952fc..61ce4c3 100644
--- a/pkg/codegen/templates/templates.gen.go
+++ b/pkg/codegen/templates/templates.gen.go
@@ -21,14 +21,15 @@ type ClientInterface interface {
 {{range .}}{{$opid := .OperationId -}}
 {{- if .HasBody}}// {{$opid}} request with JSON body
     {{$opid}}(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}, body {{if not .GetBodyDefinition.Required}}*{{end}}{{if .GetBodyDefinition.CustomType}}{{$opid}}RequestBody{{else}}{{.GetBodyDefinition.TypeDef}}{{end}}) (*http.Response, error)
-{{- end}}
+{{- end}}{{/* if .HasBody */}}
 {{if .GenerateGenericForm}}
     // {{$opid}}{{if .HasAnyBody}}WithBody{{end}} request{{if .HasAnyBody}} with arbitrary body{{end}}
     {{$opid}}{{if .HasAnyBody}}WithBody{{end}}(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasAnyBody}}, contentType string, body io.Reader{{end}}) (*http.Response, error)
-{{end}}
-{{end}}{{/* range .OperationId */}}
+{{end}}{{/* if .GenerateGenericForm */}}
+{{end}}{{/* range . $opid := .OperationId */}}
 }
 
+{{/* Generate client methods */}}
 {{range .}}{{$opid := .OperationId}}
 {{if .HasBody}}
 // {{$opid}} request with JSON body
@@ -46,7 +47,7 @@ func (c *Client) {{$opid}}(ctx context.Context{{genParamArgs .PathParams}}{{if .
     }
     return c.Client.Do(req)
 }
-{{end}}{{/* end of .HasBody */}}
+{{end}}{{/* if .HasBody */}}
 
 {{if .GenerateGenericForm}}
 // {{$opid}}{{if .HasAnyBody}}WithBody{{end}} request{{if .HasAnyBody}} with arbitrary body{{end}}
@@ -64,9 +65,69 @@ func (c *Client) {{$opid}}{{if .HasAnyBody}}WithBody{{end}}(ctx context.Context{
         }
     return c.Client.Do(req)
 }
-{{end}}
-{{end}}
+{{end}}{{/* if .GenerateGenericForm */}}
+{{end}}{{/* range . $opid := .OperationId */}}
+
+// ClientWithResponses builds on ClientInterface to offer response payloads
+type ClientWithResponses struct {
+    ClientInterface
+}
+
+// NewClientWithResponses returns a ClientWithResponses with a default Client:
+func NewClientWithResponses(server string) *ClientWithResponses {
+    return &ClientWithResponses{
+        ClientInterface: &Client{
+            Client: http.Client{},
+            Server: server,
+        },
+    }
+}
+
+{{/* Generate parse functions for responses*/}}
+{{range .}}{{$opid := .OperationId}}
+{{genResponseType $opid .Spec.Responses}}
+
+// Parse{{$opid}}Response parses an HTTP response from a {{$opid}}WithResponse call
+func Parse{{$opid}}Response(rsp *http.Response) (*{{$opid}}Response, error) {
+    bodyBytes, err := ioutil.ReadAll(rsp.Body)
+    defer rsp.Body.Close()
+    if err != nil {
+        return nil, err
+    }
+    
+    response := {{genResponsePayload $opid}}
+
+    {{genResponseUnmarshal $opid .Spec.Responses}}
+
+    return response, nil
+}
+
+{{/* Generate client methods (with responses)*/}}
+{{if .HasBody}}
+// {{$opid}} request with JSON body returning *{{$opid}}Response
+func (c *ClientWithResponses) {{$opid}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}, body {{if not .GetBodyDefinition.Required}}*{{end}}{{if .GetBodyDefinition.CustomType}}{{$opid}}RequestBody{{else}}{{.GetBodyDefinition.TypeDef}}{{end}}) (*{{$opid}}Response, error){
+    rsp, err := c.{{$opid}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}, body)
+	if err != nil {
+		return nil, err
+	}
+    return Parse{{$opid}}Response(rsp)
+}
+{{end}}{{/* if .HasBody */}}
+
+{{if .GenerateGenericForm}}
+// {{$opid}}{{if .HasAnyBody}}WithBody{{end}} request{{if .HasAnyBody}} with arbitrary body{{end}} returning *{{$opid}}Response
+func (c *ClientWithResponses) {{$opid}}{{if .HasAnyBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasAnyBody}}, contentType string, body io.Reader{{end}}) (*{{$opid}}Response, error){
+    rsp, err := c.{{$opid}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}{{if .HasAnyBody}}, contentType, body{{end}})
+    if err != nil {
+        return nil, err
+    }
+    return Parse{{$opid}}Response(rsp)
+}
+{{end}}{{/* if .GenerateGenericForm */}}
+{{end}}{{/* range . $opid := .OperationId */}}
+
 
+{{/* Generate server */}}
 {{range .}}{{$opid := .OperationId -}}
 {{if .HasBody}}
 // New{{$opid}}Request generates requests for {{$opid}} with JSON body


Test patch:
diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go
index 5959506..0fd6f01 100644
--- a/internal/test/components/components.gen.go
+++ b/internal/test/components/components.gen.go
@@ -52,6 +52,21 @@ type Client struct {
 type ClientInterface interface {
 }
 
+// ClientWithResponses builds on ClientInterface to offer response payloads
+type ClientWithResponses struct {
+	ClientInterface
+}
+
+// NewClientWithResponses returns a ClientWithResponses with a default Client:
+func NewClientWithResponses(server string) *ClientWithResponses {
+	return &ClientWithResponses{
+		ClientInterface: &Client{
+			Client: http.Client{},
+			Server: server,
+		},
+	}
+}
+
 // ServerInterface represents all server handlers.
 type ServerInterface interface {
 }
diff --git a/internal/test/parameters/parameters.gen.go b/internal/test/parameters/parameters.gen.go
index f05d224..cb393c4 100644
--- a/internal/test/parameters/parameters.gen.go
+++ b/internal/test/parameters/parameters.gen.go
@@ -13,6 +13,7 @@ import (
 	"github.com/deepmap/oapi-codegen/pkg/runtime"
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/labstack/echo/v4"
+	"io/ioutil"
 	"net/http"
 	"net/url"
 	"strings"
@@ -423,6 +424,953 @@ func (c *Client) GetSimplePrimitive(ctx context.Context, param int32) (*http.Res
 	return c.Client.Do(req)
 }
 
+// ClientWithResponses builds on ClientInterface to offer response payloads
+type ClientWithResponses struct {
+	ClientInterface
+}
+
+// NewClientWithResponses returns a ClientWithResponses with a default Client:
+func NewClientWithResponses(server string) *ClientWithResponses {
+	return &ClientWithResponses{
+		ClientInterface: &Client{
+			Client: http.Client{},
+			Server: server,
+		},
+	}
+}
+
+// GetContentObjectResponse is returned by Client.GetContentObject()
+type GetContentObjectResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetContentObjectResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetContentObjectResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetContentObjectResponse parses an HTTP response from a GetContentObjectWithResponse call
+func ParseGetContentObjectResponse(rsp *http.Response) (*GetContentObjectResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetContentObjectResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetContentObject request returning *GetContentObjectResponse
+func (c *ClientWithResponses) GetContentObjectWithResponse(ctx context.Context, param ComplexObject) (*GetContentObjectResponse, error) {
+	rsp, err := c.GetContentObject(ctx, param)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetContentObjectResponse(rsp)
+}
+
+// GetCookieResponse is returned by Client.GetCookie()
+type GetCookieResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetCookieResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetCookieResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetCookieResponse parses an HTTP response from a GetCookieWithResponse call
+func ParseGetCookieResponse(rsp *http.Response) (*GetCookieResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetCookieResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	}
+
+	return response, nil
+}
+
+// GetCookie request returning *GetCookieResponse
+func (c *ClientWithResponses) GetCookieWithResponse(ctx context.Context, params *GetCookieParams) (*GetCookieResponse, error) {
+	rsp, err := c.GetCookie(ctx, params)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetCookieResponse(rsp)
+}
+
+// GetHeaderResponse is returned by Client.GetHeader()
+type GetHeaderResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetHeaderResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetHeaderResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetHeaderResponse parses an HTTP response from a GetHeaderWithResponse call
+func ParseGetHeaderResponse(rsp *http.Response) (*GetHeaderResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetHeaderResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	}
+
+	return response, nil
+}
+
+// GetHeader request returning *GetHeaderResponse
+func (c *ClientWithResponses) GetHeaderWithResponse(ctx context.Context, params *GetHeaderParams) (*GetHeaderResponse, error) {
+	rsp, err := c.GetHeader(ctx, params)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetHeaderResponse(rsp)
+}
+
+// GetLabelExplodeArrayResponse is returned by Client.GetLabelExplodeArray()
+type GetLabelExplodeArrayResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetLabelExplodeArrayResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetLabelExplodeArrayResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetLabelExplodeArrayResponse parses an HTTP response from a GetLabelExplodeArrayWithResponse call
+func ParseGetLabelExplodeArrayResponse(rsp *http.Response) (*GetLabelExplodeArrayResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetLabelExplodeArrayResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetLabelExplodeArray request returning *GetLabelExplodeArrayResponse
+func (c *ClientWithResponses) GetLabelExplodeArrayWithResponse(ctx context.Context, param []int32) (*GetLabelExplodeArrayResponse, error) {
+	rsp, err := c.GetLabelExplodeArray(ctx, param)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetLabelExplodeArrayResponse(rsp)
+}
+
+// GetLabelExplodeObjectResponse is returned by Client.GetLabelExplodeObject()
+type GetLabelExplodeObjectResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetLabelExplodeObjectResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetLabelExplodeObjectResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetLabelExplodeObjectResponse parses an HTTP response from a GetLabelExplodeObjectWithResponse call
+func ParseGetLabelExplodeObjectResponse(rsp *http.Response) (*GetLabelExplodeObjectResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetLabelExplodeObjectResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetLabelExplodeObject request returning *GetLabelExplodeObjectResponse
+func (c *ClientWithResponses) GetLabelExplodeObjectWithResponse(ctx context.Context, param Object) (*GetLabelExplodeObjectResponse, error) {
+	rsp, err := c.GetLabelExplodeObject(ctx, param)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetLabelExplodeObjectResponse(rsp)
+}
+
+// GetLabelNoExplodeArrayResponse is returned by Client.GetLabelNoExplodeArray()
+type GetLabelNoExplodeArrayResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetLabelNoExplodeArrayResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetLabelNoExplodeArrayResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetLabelNoExplodeArrayResponse parses an HTTP response from a GetLabelNoExplodeArrayWithResponse call
+func ParseGetLabelNoExplodeArrayResponse(rsp *http.Response) (*GetLabelNoExplodeArrayResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetLabelNoExplodeArrayResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetLabelNoExplodeArray request returning *GetLabelNoExplodeArrayResponse
+func (c *ClientWithResponses) GetLabelNoExplodeArrayWithResponse(ctx context.Context, param []int32) (*GetLabelNoExplodeArrayResponse, error) {
+	rsp, err := c.GetLabelNoExplodeArray(ctx, param)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetLabelNoExplodeArrayResponse(rsp)
+}
+
+// GetLabelNoExplodeObjectResponse is returned by Client.GetLabelNoExplodeObject()
+type GetLabelNoExplodeObjectResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetLabelNoExplodeObjectResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetLabelNoExplodeObjectResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetLabelNoExplodeObjectResponse parses an HTTP response from a GetLabelNoExplodeObjectWithResponse call
+func ParseGetLabelNoExplodeObjectResponse(rsp *http.Response) (*GetLabelNoExplodeObjectResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetLabelNoExplodeObjectResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetLabelNoExplodeObject request returning *GetLabelNoExplodeObjectResponse
+func (c *ClientWithResponses) GetLabelNoExplodeObjectWithResponse(ctx context.Context, param Object) (*GetLabelNoExplodeObjectResponse, error) {
+	rsp, err := c.GetLabelNoExplodeObject(ctx, param)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetLabelNoExplodeObjectResponse(rsp)
+}
+
+// GetMatrixExplodeArrayResponse is returned by Client.GetMatrixExplodeArray()
+type GetMatrixExplodeArrayResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetMatrixExplodeArrayResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetMatrixExplodeArrayResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetMatrixExplodeArrayResponse parses an HTTP response from a GetMatrixExplodeArrayWithResponse call
+func ParseGetMatrixExplodeArrayResponse(rsp *http.Response) (*GetMatrixExplodeArrayResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetMatrixExplodeArrayResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetMatrixExplodeArray request returning *GetMatrixExplodeArrayResponse
+func (c *ClientWithResponses) GetMatrixExplodeArrayWithResponse(ctx context.Context, id []int32) (*GetMatrixExplodeArrayResponse, error) {
+	rsp, err := c.GetMatrixExplodeArray(ctx, id)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetMatrixExplodeArrayResponse(rsp)
+}
+
+// GetMatrixExplodeObjectResponse is returned by Client.GetMatrixExplodeObject()
+type GetMatrixExplodeObjectResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetMatrixExplodeObjectResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetMatrixExplodeObjectResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetMatrixExplodeObjectResponse parses an HTTP response from a GetMatrixExplodeObjectWithResponse call
+func ParseGetMatrixExplodeObjectResponse(rsp *http.Response) (*GetMatrixExplodeObjectResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetMatrixExplodeObjectResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetMatrixExplodeObject request returning *GetMatrixExplodeObjectResponse
+func (c *ClientWithResponses) GetMatrixExplodeObjectWithResponse(ctx context.Context, id Object) (*GetMatrixExplodeObjectResponse, error) {
+	rsp, err := c.GetMatrixExplodeObject(ctx, id)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetMatrixExplodeObjectResponse(rsp)
+}
+
+// GetMatrixNoExplodeArrayResponse is returned by Client.GetMatrixNoExplodeArray()
+type GetMatrixNoExplodeArrayResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetMatrixNoExplodeArrayResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetMatrixNoExplodeArrayResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetMatrixNoExplodeArrayResponse parses an HTTP response from a GetMatrixNoExplodeArrayWithResponse call
+func ParseGetMatrixNoExplodeArrayResponse(rsp *http.Response) (*GetMatrixNoExplodeArrayResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetMatrixNoExplodeArrayResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetMatrixNoExplodeArray request returning *GetMatrixNoExplodeArrayResponse
+func (c *ClientWithResponses) GetMatrixNoExplodeArrayWithResponse(ctx context.Context, id []int32) (*GetMatrixNoExplodeArrayResponse, error) {
+	rsp, err := c.GetMatrixNoExplodeArray(ctx, id)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetMatrixNoExplodeArrayResponse(rsp)
+}
+
+// GetMatrixNoExplodeObjectResponse is returned by Client.GetMatrixNoExplodeObject()
+type GetMatrixNoExplodeObjectResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetMatrixNoExplodeObjectResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetMatrixNoExplodeObjectResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetMatrixNoExplodeObjectResponse parses an HTTP response from a GetMatrixNoExplodeObjectWithResponse call
+func ParseGetMatrixNoExplodeObjectResponse(rsp *http.Response) (*GetMatrixNoExplodeObjectResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetMatrixNoExplodeObjectResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetMatrixNoExplodeObject request returning *GetMatrixNoExplodeObjectResponse
+func (c *ClientWithResponses) GetMatrixNoExplodeObjectWithResponse(ctx context.Context, id Object) (*GetMatrixNoExplodeObjectResponse, error) {
+	rsp, err := c.GetMatrixNoExplodeObject(ctx, id)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetMatrixNoExplodeObjectResponse(rsp)
+}
+
+// GetPassThroughResponse is returned by Client.GetPassThrough()
+type GetPassThroughResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetPassThroughResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetPassThroughResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetPassThroughResponse parses an HTTP response from a GetPassThroughWithResponse call
+func ParseGetPassThroughResponse(rsp *http.Response) (*GetPassThroughResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetPassThroughResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetPassThrough request returning *GetPassThroughResponse
+func (c *ClientWithResponses) GetPassThroughWithResponse(ctx context.Context, param string) (*GetPassThroughResponse, error) {
+	rsp, err := c.GetPassThrough(ctx, param)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetPassThroughResponse(rsp)
+}
+
+// GetQueryFormResponse is returned by Client.GetQueryForm()
+type GetQueryFormResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetQueryFormResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetQueryFormResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetQueryFormResponse parses an HTTP response from a GetQueryFormWithResponse call
+func ParseGetQueryFormResponse(rsp *http.Response) (*GetQueryFormResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetQueryFormResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetQueryForm request returning *GetQueryFormResponse
+func (c *ClientWithResponses) GetQueryFormWithResponse(ctx context.Context, params *GetQueryFormParams) (*GetQueryFormResponse, error) {
+	rsp, err := c.GetQueryForm(ctx, params)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetQueryFormResponse(rsp)
+}
+
+// GetSimpleExplodeArrayResponse is returned by Client.GetSimpleExplodeArray()
+type GetSimpleExplodeArrayResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetSimpleExplodeArrayResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetSimpleExplodeArrayResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetSimpleExplodeArrayResponse parses an HTTP response from a GetSimpleExplodeArrayWithResponse call
+func ParseGetSimpleExplodeArrayResponse(rsp *http.Response) (*GetSimpleExplodeArrayResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetSimpleExplodeArrayResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetSimpleExplodeArray request returning *GetSimpleExplodeArrayResponse
+func (c *ClientWithResponses) GetSimpleExplodeArrayWithResponse(ctx context.Context, param []int32) (*GetSimpleExplodeArrayResponse, error) {
+	rsp, err := c.GetSimpleExplodeArray(ctx, param)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetSimpleExplodeArrayResponse(rsp)
+}
+
+// GetSimpleExplodeObjectResponse is returned by Client.GetSimpleExplodeObject()
+type GetSimpleExplodeObjectResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetSimpleExplodeObjectResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetSimpleExplodeObjectResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetSimpleExplodeObjectResponse parses an HTTP response from a GetSimpleExplodeObjectWithResponse call
+func ParseGetSimpleExplodeObjectResponse(rsp *http.Response) (*GetSimpleExplodeObjectResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetSimpleExplodeObjectResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetSimpleExplodeObject request returning *GetSimpleExplodeObjectResponse
+func (c *ClientWithResponses) GetSimpleExplodeObjectWithResponse(ctx context.Context, param Object) (*GetSimpleExplodeObjectResponse, error) {
+	rsp, err := c.GetSimpleExplodeObject(ctx, param)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetSimpleExplodeObjectResponse(rsp)
+}
+
+// GetSimpleNoExplodeArrayResponse is returned by Client.GetSimpleNoExplodeArray()
+type GetSimpleNoExplodeArrayResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetSimpleNoExplodeArrayResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetSimpleNoExplodeArrayResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetSimpleNoExplodeArrayResponse parses an HTTP response from a GetSimpleNoExplodeArrayWithResponse call
+func ParseGetSimpleNoExplodeArrayResponse(rsp *http.Response) (*GetSimpleNoExplodeArrayResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetSimpleNoExplodeArrayResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetSimpleNoExplodeArray request returning *GetSimpleNoExplodeArrayResponse
+func (c *ClientWithResponses) GetSimpleNoExplodeArrayWithResponse(ctx context.Context, param []int32) (*GetSimpleNoExplodeArrayResponse, error) {
+	rsp, err := c.GetSimpleNoExplodeArray(ctx, param)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetSimpleNoExplodeArrayResponse(rsp)
+}
+
+// GetSimpleNoExplodeObjectResponse is returned by Client.GetSimpleNoExplodeObject()
+type GetSimpleNoExplodeObjectResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetSimpleNoExplodeObjectResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetSimpleNoExplodeObjectResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetSimpleNoExplodeObjectResponse parses an HTTP response from a GetSimpleNoExplodeObjectWithResponse call
+func ParseGetSimpleNoExplodeObjectResponse(rsp *http.Response) (*GetSimpleNoExplodeObjectResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetSimpleNoExplodeObjectResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetSimpleNoExplodeObject request returning *GetSimpleNoExplodeObjectResponse
+func (c *ClientWithResponses) GetSimpleNoExplodeObjectWithResponse(ctx context.Context, param Object) (*GetSimpleNoExplodeObjectResponse, error) {
+	rsp, err := c.GetSimpleNoExplodeObject(ctx, param)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetSimpleNoExplodeObjectResponse(rsp)
+}
+
+// GetSimplePrimitiveResponse is returned by Client.GetSimplePrimitive()
+type GetSimplePrimitiveResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *GetSimplePrimitiveResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *GetSimplePrimitiveResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseGetSimplePrimitiveResponse parses an HTTP response from a GetSimplePrimitiveWithResponse call
+func ParseGetSimplePrimitiveResponse(rsp *http.Response) (*GetSimplePrimitiveResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetSimplePrimitiveResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case rsp.StatusCode == 200:
+		// Content-type (text/plain) unsupported
+	}
+
+	return response, nil
+}
+
+// GetSimplePrimitive request returning *GetSimplePrimitiveResponse
+func (c *ClientWithResponses) GetSimplePrimitiveWithResponse(ctx context.Context, param int32) (*GetSimplePrimitiveResponse, error) {
+	rsp, err := c.GetSimplePrimitive(ctx, param)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetSimplePrimitiveResponse(rsp)
+}
+
 // NewGetContentObjectRequest generates requests for GetContentObject
 func NewGetContentObjectRequest(server string, param ComplexObject) (*http.Request, error) {
 	var err error
diff --git a/internal/test/schemas/schemas.gen.go b/internal/test/schemas/schemas.gen.go
index c58d508..ab6016a 100644
--- a/internal/test/schemas/schemas.gen.go
+++ b/internal/test/schemas/schemas.gen.go
@@ -14,6 +14,7 @@ import (
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/labstack/echo/v4"
 	"io"
+	"io/ioutil"
 	"net/http"
 	"strings"
 )
@@ -74,6 +75,71 @@ func (c *Client) Issue9(ctx context.Context, params *Issue9Params, body *Issue9R
 	return c.Client.Do(req)
 }
 
+// ClientWithResponses builds on ClientInterface to offer response payloads
+type ClientWithResponses struct {
+	ClientInterface
+}
+
+// NewClientWithResponses returns a ClientWithResponses with a default Client:
+func NewClientWithResponses(server string) *ClientWithResponses {
+	return &ClientWithResponses{
+		ClientInterface: &Client{
+			Client: http.Client{},
+			Server: server,
+		},
+	}
+}
+
+// Issue9Response is returned by Client.Issue9()
+type Issue9Response struct {
+	Body         []byte
+	HTTPResponse *http.Response
+}
+
+// Status returns HTTPResponse.Status
+func (r *Issue9Response) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r *Issue9Response) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// ParseIssue9Response parses an HTTP response from a Issue9WithResponse call
+func ParseIssue9Response(rsp *http.Response) (*Issue9Response, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &Issue9Response{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	}
+
+	return response, nil
+}
+
+// Issue9 request with JSON body returning *Issue9Response
+func (c *ClientWithResponses) Issue9WithResponse(ctx context.Context, params *Issue9Params, body *Issue9RequestBody) (*Issue9Response, error) {
+	rsp, err := c.Issue9(ctx, params, body)
+	if err != nil {
+		return nil, err
+	}
+	return ParseIssue9Response(rsp)
+}
+
 // NewIssue9Request generates requests for Issue9 with JSON body
 func NewIssue9Request(server string, params *Issue9Params, body *Issue9RequestBody) (*http.Request, error) {
 	var bodyReader io.Reader
diff --git a/pkg/codegen/codegen_test.go b/pkg/codegen/codegen_test.go
new file mode 100644
index 0000000..31060ba
--- /dev/null
+++ b/pkg/codegen/codegen_test.go
@@ -0,0 +1,194 @@
+package codegen
+
+import (
+	"bytes"
+	"go/format"
+	"io/ioutil"
+	"net/http"
+	"testing"
+
+	examplePetstore "github.com/deepmap/oapi-codegen/examples/petstore-expanded/api"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/golangci/lint-1"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestExamplePetStoreCodeGeneration(t *testing.T) {
+
+	// Input vars for code generation:
+	packageName := "api"
+	opts := Options{
+		GenerateClient: true,
+		GenerateServer: true,
+		GenerateTypes:  true,
+		EmbedSpec:      true,
+	}
+
+	// Get a spec from the example PetStore definition:
+	swagger, err := examplePetstore.GetSwagger()
+	assert.NoError(t, err)
+
+	// Run our code generation:
+	code, err := Generate(swagger, packageName, opts)
+	assert.NoError(t, err)
+	assert.NotEmpty(t, code)
+
+	// Check that we have valid (formattable) code:
+	_, err = format.Source([]byte(code))
+	assert.NoError(t, err)
+
+	// Check that we have a package:
+	assert.Contains(t, code, "package api")
+
+	// Check that the client method signatures return response structs:
+	assert.Contains(t, code, "func (c *Client) FindPetById(ctx context.Context, id int64) (*http.Response, error) {")
+
+	// Make sure the generated code is valid:
+	linter := new(lint.Linter)
+	problems, err := linter.Lint("test.gen.go", []byte(code))
+	assert.NoError(t, err)
+	assert.Len(t, problems, 0)
+}
+
+func TestExamplePetStoreParseFunction(t *testing.T) {
+
+	bodyBytes := []byte(`{"id": 5, "name": "testpet", "tag": "cat"}`)
+
+	cannedResponse := &http.Response{
+		StatusCode: 200,
+		Body:       ioutil.NopCloser(bytes.NewReader(bodyBytes)),
+		Header:     http.Header{},
+	}
+	cannedResponse.Header.Add("Content-type", "application/json")
+
+	findPetByIDResponse, err := examplePetstore.ParseFindPetByIdResponse(cannedResponse)
+	assert.NoError(t, err)
+	assert.NotNil(t, findPetByIDResponse.JSON200)
+	assert.Equal(t, int64(5), findPetByIDResponse.JSON200.Id)
+	assert.Equal(t, "testpet", findPetByIDResponse.JSON200.Name)
+	assert.NotNil(t, findPetByIDResponse.JSON200.Tag)
+	assert.Equal(t, "cat", *findPetByIDResponse.JSON200.Tag)
+}
+
+func TestExampleOpenAPICodeGeneration(t *testing.T) {
+
+	// Input vars for code generation:
+	packageName := "testswagger"
+	opts := Options{
+		GenerateClient: true,
+		GenerateServer: true,
+		GenerateTypes:  true,
+		EmbedSpec:      true,
+	}
+
+	// Get a spec from the test definition in this file:
+	swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(testOpenAPIDefinition))
+	assert.NoError(t, err)
+
+	// Run our code generation:
+	code, err := Generate(swagger, packageName, opts)
+	assert.NoError(t, err)
+	assert.NotEmpty(t, code)
+
+	// Check that we have valid (formattable) code:
+	_, err = format.Source([]byte(code))
+	assert.NoError(t, err)
+
+	// Check that we have a package:
+	assert.Contains(t, code, "package testswagger")
+
+	// Check that response structs are generated correctly:
+	assert.Contains(t, code, "type GetTestByNameResponse struct {")
+
+	// Check that the helper methods are generated correctly:
+	assert.Contains(t, code, "func (r *GetTestByNameResponse) Status() string {")
+	assert.Contains(t, code, "func (r *GetTestByNameResponse) StatusCode() int {")
+	assert.Contains(t, code, "func ParseGetTestByNameResponse(rsp *http.Response) (*GetTestByNameResponse, error) {")
+
+	// Check the client method signatures:
+	assert.Contains(t, code, "func (c *Client) GetTestByName(ctx context.Context, name string) (*http.Response, error) {")
+	assert.Contains(t, code, "func (c *ClientWithResponses) GetTestByNameWithResponse(ctx context.Context, name string) (*GetTestByNameResponse, error) {")
+
+	// Make sure the generated code is valid:
+	linter := new(lint.Linter)
+	problems, err := linter.Lint("test.gen.go", []byte(code))
+	assert.NoError(t, err)
+	assert.Len(t, problems, 0)
+}
+
+const testOpenAPIDefinition = `
+openapi: 3.0.1
+
+info:
+  title: OpenAPI-CodeGen Test
+  description: 'This is a test OpenAPI Spec'
+  version: 1.0.0
+
+servers:
+- url: https://test.oapi-codegen.com/v2
+- url: http://test.oapi-codegen.com/v2
+
+paths:
+  /test/{name}:
+    get:
+      tags:
+      - pet
+      summary: Get test
+      operationId: getTestByName
+      parameters:
+      - name: name
+        in: path
+        required: true
+        schema:
+          type: string
+      responses:
+        200:
+          description: Success
+          content:
+            application/xml:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Test'
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Test'
+        default:
+          description: Error
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Error'
+
+components:
+  schemas:
+  
+    Test:
+      properties:
+        name:
+          type: string
+        cases:
+          type: array
+          items:
+            $ref: '#/components/schemas/TestCase'
+
+    TestCase:
+      properties:
+        name:
+          type: string
+        command:
+          type: string
+  
+    Error:
+      properties:
+        code:
+          type: integer
+          format: int32
+        message:
+          type: string
+`