Repository: deepmap/oapi-codegen
Pull request #155 (2020-03-16 07:34:20)

fix: operations not appending to server url
If you provide a server of `https://my-url.com/path/v1` and you have a `GET /things` operation the correct full path for the operation is `https://my-url.com/path/v1/things`.

Current behavior is that if you don't append a trailing slash to your server string before you pass it to `NewClient(server string, opts ...ClientOption)` then for the above case you will end up with `https://my-url.com/path/things` because that's how go's `url.Parse` treats relative URLs.

## Notes

Fixes https://github.com/deepmap/oapi-codegen/issues/126

## Other

Somewhat related but the more I look into this the more I think that the `WithBaseURL` option doesn't actually make much sense and should probably be discouraged.

I have two main problems with it:

- Ultimately all it's actually doing is setting the  `client.Server` string, but that's already handled by passing it in as a string to `NewClient` so I don't understand why it's even needed.
- The name `WithBaseURL` doesn't align to the OpenAPI spec terminology, if an option like this had to exist I think it'd make more sense for it to be called `WithServer(serverURL string)`.
- I think [this](https://github.com/deepmap/oapi-codegen/pull/155/files#diff-04c6e90faac2675aa89e2176d2eec7d8) fix to the `README.md` docs shows how it's a bit clunky



Patch:
diff --git a/README.md b/README.md
index 40cc0c1..8be9ebb 100644
--- a/README.md
+++ b/README.md
@@ -369,7 +369,7 @@ which help you to use the various OpenAPI 3 Authentication mechanism.
         //
         // WithHTTPClient(httpClient *http.Client)
         //
-        client, clientErr := NewClient(context.Background(), []ClientOption{
+        client, clientErr := NewClient("https://api.deepmap.com", []ClientOption{
             WithBaseURL("https://api.deepmap.com"),
             WithRequestEditorFn(apiKeyProvider.Edit),
         }...,
diff --git a/examples/petstore-expanded/chi/api/petstore.gen.go b/examples/petstore-expanded/chi/api/petstore.gen.go
index 01b6d78..d3c25c0 100644
--- a/examples/petstore-expanded/chi/api/petstore.gen.go
+++ b/examples/petstore-expanded/chi/api/petstore.gen.go
@@ -219,14 +219,14 @@ var swaggerSpec = []string{
 	"bgypVNJK2hdRD0oF7qtAi27P5f5J+7Qak10DcrBFrNGCZC7SjqUNC1IPP9RiCUhaN3CVD1WgnaJY8pS5",
 	"wRn9uw8I6paKzTy2hoIRAq40ZfKTWj38tY6hIXnVbVSP6uidI5Tu0HwAq9UiGVdO9hzTnswxNZlDNapZ",
 	"VGDg2B2hTIUbufAecFEMlqU6VqilIFTZ+2wSctzpjLS2Xw93p8I05iaMQybhGk4612ia2p34W1tv/0WP",
-	"OB0Z2nG3cGZufuDo9Hxpx0ZWAiiXNoOcHxaCK+37sGQvlOFha3QUMHPzWClvj+e8rjOnw8MSfaFumiHb",
-	"mCIU2qF0OVSNFzBn3Or/Itt2Duq00uadc0gBv3LQvl7DA2UdcDKV6qXhzO1w+wZIz4HldZS/Oa7u7jW+",
-	"DNp8Wjpvbm72cxHFcZ4bBj+NFrNfi2J+vsbDa8PeOOm9YGZ3MSENJLAHM85PS6xefhee12CMY/+VjWuk",
-	"r4M2X+3S45rOlBoC5u2VEUOxDalcGUbeZ0JpQ12kJ127n9ba5KOn9Ihdl+jA5316Indh53dO3TxJS0W+",
-	"T277L2NhP3lf0nBHoqZD5/TrAPvMYpIr7f5Jz/ymVf57rHEheLvfJtbZM7vdaBFPcuUFbbyusYXjyre3",
-	"GnhAbcRpdM3iFkrVnK545LZFjzZ5tectbrWpDKO2E5apoeiIfewn7C6U/lYvuf62ddlLvrvMWoGMKNx/",
-	"kpC3BzGaCltY3Cq81185zhU76Li4/dYB9f124X6XXksSu/63yfU/W8YvFB3Vb0sob/Yynb3p71/a+5NX",
-	"X31/3d3v/hEAAP//81bN0nkSAAA=",
+	"OB0Z2nG3cGZufuDo9Hxpx0ZWAiiXNoOcHxaCK+37sGQvlOFha3QUMHPzWClvj+e8rjPdNDK2qUQotDPo",
+	"coYaL2DOuNX/Rbbt2NPhpI035wgCfuWgbbyGB8o6z2Qq1UuDldtZ9g1MngPLGajfHEZ39zoAlUFbS0P/",
+	"5uZmP/VQHKe1YfDT4DD7tSjE52tpvzbKjXPcCyJ2F/PPQAJ7MON0tMTq5XfheQ3GONRf2bhG+jpoa9Ue",
+	"PK7pTKkhYN5eGSAU25DKlVHjfSaUNrJFetK1+1mszTV6Bo/YdYmOc96nJ3IXZn3n1KtmnE2pyPfJbf9l",
+	"LOzn6ksa7kjUY+icfh1gm9MZWXKl3T/pmd+0yn+PNS4Eb/fbPDp7ZrcbLeJJrrx+jdc1tnBc+fbOAg+o",
+	"bTaNrlncQqma0xWP3Lbo0SavdrTFrfaQYdR2wjL1Dx2gj+2D3YXS3+ol19+lLnvJd5dZK5ARhftPEvL2",
+	"IEZTYQuLW4X3+gvFuWIHHRe33zp+vt8u3O/Sa0li1/82uf5ny/iFoqP6bQnlzV6ms/f4/St5f/Jiq2+n",
+	"u/vdPwIAAP//v4qmX1cSAAA=",
 }
 
 // GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/examples/petstore-expanded/echo/api/petstore-server.gen.go b/examples/petstore-expanded/echo/api/petstore-server.gen.go
index 04aa1d4..49a8a3d 100644
--- a/examples/petstore-expanded/echo/api/petstore-server.gen.go
+++ b/examples/petstore-expanded/echo/api/petstore-server.gen.go
@@ -102,8 +102,10 @@ func (w *ServerInterfaceWrapper) FindPetById(ctx echo.Context) error {
 	return err
 }
 
-// RegisterHandlers adds each server route to the EchoRouter.
-func RegisterHandlers(router interface {
+// This is a simple interface which specifies echo.Route addition functions which
+// are present on both echo.Echo and echo.Group, since we want to allow using
+// either of them for path registration
+type EchoRouter interface {
 	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
@@ -113,7 +115,10 @@ func RegisterHandlers(router interface {
 	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-}, si ServerInterface) {
+}
+
+// RegisterHandlers adds each server route to the EchoRouter.
+func RegisterHandlers(router EchoRouter, si ServerInterface) {
 
 	wrapper := ServerInterfaceWrapper{
 		Handler: si,
@@ -149,14 +154,14 @@ var swaggerSpec = []string{
 	"bgypVNJK2hdRD0oF7qtAi27P5f5J+7Qak10DcrBFrNGCZC7SjqUNC1IPP9RiCUhaN3CVD1WgnaJY8pS5",
 	"wRn9uw8I6paKzTy2hoIRAq40ZfKTWj38tY6hIXnVbVSP6uidI5Tu0HwAq9UiGVdO9hzTnswxNZlDNapZ",
 	"VGDg2B2hTIUbufAecFEMlqU6VqilIFTZ+2wSctzpjLS2Xw93p8I05iaMQybhGk4612ia2p34W1tv/0WP",
-	"OB0Z2nG3cGZufuDo9Hxpx0ZWAiiXNoOcHxaCK+37sGQvlOFha3QUMHPzWClvj+e8rjOnw8MSfaFumiHb",
-	"mCIU2qF0OVSNFzBn3Or/Itt2Duq00uadc0gBv3LQvl7DA2UdcDKV6qXhzO1w+wZIz4HldZS/Oa7u7jW+",
-	"DNp8Wjpvbm72cxHFcZ4bBj+NFrNfi2J+vsbDa8PeOOm9YGZ3MSENJLAHM85PS6xefhee12CMY/+VjWuk",
-	"r4M2X+3S45rOlBoC5u2VEUOxDalcGUbeZ0JpQ12kJ127n9ba5KOn9Ihdl+jA5316Indh53dO3TxJS0W+",
-	"T277L2NhP3lf0nBHoqZD5/TrAPvMYpIr7f5Jz/ymVf57rHEheLvfJtbZM7vdaBFPcuUFbbyusYXjyre3",
-	"GnhAbcRpdM3iFkrVnK545LZFjzZ5tectbrWpDKO2E5apoeiIfewn7C6U/lYvuf62ddlLvrvMWoGMKNx/",
-	"kpC3BzGaCltY3Cq81185zhU76Li4/dYB9f124X6XXksSu/63yfU/W8YvFB3Vb0sob/Yynb3p71/a+5NX",
-	"X31/3d3v/hEAAP//81bN0nkSAAA=",
+	"OB0Z2nG3cGZufuDo9Hxpx0ZWAiiXNoOcHxaCK+37sGQvlOFha3QUMHPzWClvj+e8rjPdNDK2qUQotDPo",
+	"coYaL2DOuNX/Rbbt2NPhpI035wgCfuWgbbyGB8o6z2Qq1UuDldtZ9g1MngPLGajfHEZ39zoAlUFbS0P/",
+	"5uZmP/VQHKe1YfDT4DD7tSjE52tpvzbKjXPcCyJ2F/PPQAJ7MON0tMTq5XfheQ3GONRf2bhG+jpoa9Ue",
+	"PK7pTKkhYN5eGSAU25DKlVHjfSaUNrJFetK1+1mszTV6Bo/YdYmOc96nJ3IXZn3n1KtmnE2pyPfJbf9l",
+	"LOzn6ksa7kjUY+icfh1gm9MZWXKl3T/pmd+0yn+PNS4Eb/fbPDp7ZrcbLeJJrrx+jdc1tnBc+fbOAg+o",
+	"bTaNrlncQqma0xWP3Lbo0SavdrTFrfaQYdR2wjL1Dx2gj+2D3YXS3+ol19+lLnvJd5dZK5ARhftPEvL2",
+	"IEZTYQuLW4X3+gvFuWIHHRe33zp+vt8u3O/Sa0li1/82uf5ny/iFoqP6bQnlzV6ms/f4/St5f/Jiq2+n",
+	"u/vdPwIAAP//v4qmX1cSAAA=",
 }
 
 // GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/examples/petstore-expanded/petstore-client.gen.go b/examples/petstore-expanded/petstore-client.gen.go
index 20264e6..ff7ea60 100644
--- a/examples/petstore-expanded/petstore-client.gen.go
+++ b/examples/petstore-expanded/petstore-client.gen.go
@@ -102,6 +102,10 @@ func NewClient(server string, opts ...ClientOption) (*Client, error) {
 			return nil, err
 		}
 	}
+	// ensure the server URL always has a trailing slash
+	if !strings.HasSuffix(client.Server, "/") {
+		client.Server += "/"
+	}
 	// create httpClient, if not already present
 	if client.Client == nil {
 		client.Client = http.DefaultClient
@@ -407,9 +411,6 @@ func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithRes
 // WithBaseURL overrides the baseURL.
 func WithBaseURL(baseURL string) ClientOption {
 	return func(c *Client) error {
-		if !strings.HasSuffix(baseURL, "/") {
-			baseURL += "/"
-		}
 		newBaseURL, err := url.Parse(baseURL)
 		if err != nil {
 			return err
diff --git a/go.mod b/go.mod
index 0fc8360..758e100 100644
--- a/go.mod
+++ b/go.mod
@@ -2,7 +2,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/getkin/kin-openapi v0.3.0
 	github.com/go-chi/chi v4.0.2+incompatible
 	github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219
 	github.com/labstack/echo/v4 v4.1.11
@@ -17,7 +17,7 @@ require (
 	golang.org/x/sys v0.0.0-20191115151921-52ab43148777 // indirect
 	golang.org/x/text v0.3.2 // indirect
 	golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f // indirect
-	gopkg.in/yaml.v2 v2.2.5
+	gopkg.in/yaml.v2 v2.2.8
 )
 
 go 1.13
diff --git a/go.sum b/go.sum
index eaf9711..cea98d0 100644
--- a/go.sum
+++ b/go.sum
@@ -6,8 +6,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/getkin/kin-openapi v0.2.0 h1:PbHHtYZpjKwZtGlIyELgA2DploRrsaXztoNNx9HjwNY=
-github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk=
+github.com/getkin/kin-openapi v0.3.0 h1:xsQ4mA20YJDMgIHdHqMKZ66QUe6/hi+x6yLsTTz8xyQ=
+github.com/getkin/kin-openapi v0.3.0/go.mod h1:W8dhxZgpE84ciM+VIItFqkmZ4eHtuomrdIHtASQIqi0=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
@@ -85,5 +85,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
-gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/pkg/codegen/templates/client-with-responses.tmpl b/pkg/codegen/templates/client-with-responses.tmpl
index e71f4be..4fc0a85 100644
--- a/pkg/codegen/templates/client-with-responses.tmpl
+++ b/pkg/codegen/templates/client-with-responses.tmpl
@@ -16,9 +16,6 @@ func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithRes
 // WithBaseURL overrides the baseURL.
 func WithBaseURL(baseURL string) ClientOption {
 	return func(c *Client) error {
-		if !strings.HasSuffix(baseURL, "/") {
-			baseURL += "/"
-		}
 		newBaseURL, err := url.Parse(baseURL)
 		if err != nil {
 			return err
diff --git a/pkg/codegen/templates/client.tmpl b/pkg/codegen/templates/client.tmpl
index 969b9c3..a215895 100644
--- a/pkg/codegen/templates/client.tmpl
+++ b/pkg/codegen/templates/client.tmpl
@@ -38,6 +38,10 @@ func NewClient(server string, opts ...ClientOption) (*Client, error) {
             return nil, err
         }
     }
+    // ensure the server URL always has a trailing slash
+    if !strings.HasSuffix(client.Server, "/") {
+        client.Server += "/"
+    }
     // create httpClient, if not already present
     if client.Client == nil {
         client.Client = http.DefaultClient
diff --git a/pkg/codegen/templates/register.tmpl b/pkg/codegen/templates/register.tmpl
index 4198b23..d7857f1 100644
--- a/pkg/codegen/templates/register.tmpl
+++ b/pkg/codegen/templates/register.tmpl
@@ -1,17 +1,22 @@
 
 
+// This is a simple interface which specifies echo.Route addition functions which
+// are present on both echo.Echo and echo.Group, since we want to allow using
+// either of them for path registration
+type EchoRouter interface {
+	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+}
+
 // RegisterHandlers adds each server route to the EchoRouter.
-func RegisterHandlers(router interface {
-                             	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             }, si ServerInterface) {
+func RegisterHandlers(router EchoRouter, si ServerInterface) {
 {{if .}}
     wrapper := ServerInterfaceWrapper{
         Handler: si,
diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go
index 259c1a3..1265290 100644
--- a/pkg/codegen/templates/templates.gen.go
+++ b/pkg/codegen/templates/templates.gen.go
@@ -288,9 +288,6 @@ func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithRes
 // WithBaseURL overrides the baseURL.
 func WithBaseURL(baseURL string) ClientOption {
 	return func(c *Client) error {
-		if !strings.HasSuffix(baseURL, "/") {
-			baseURL += "/"
-		}
 		newBaseURL, err := url.Parse(baseURL)
 		if err != nil {
 			return err
@@ -415,6 +412,10 @@ func NewClient(server string, opts ...ClientOption) (*Client, error) {
             return nil, err
         }
     }
+    // ensure the server URL always has a trailing slash
+    if !strings.HasSuffix(client.Server, "/") {
+        client.Server += "/"
+    }
     // create httpClient, if not already present
     if client.Client == nil {
         client.Client = http.DefaultClient
@@ -695,18 +696,23 @@ type {{.TypeName}} {{.Schema.TypeDecl}}
 `,
 	"register.tmpl": `
 
+// This is a simple interface which specifies echo.Route addition functions which
+// are present on both echo.Echo and echo.Group, since we want to allow using
+// either of them for path registration
+type EchoRouter interface {
+	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+}
+
 // RegisterHandlers adds each server route to the EchoRouter.
-func RegisterHandlers(router interface {
-                             	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-                             }, si ServerInterface) {
+func RegisterHandlers(router EchoRouter, si ServerInterface) {
 {{if .}}
     wrapper := ServerInterfaceWrapper{
         Handler: si,
diff --git a/pkg/runtime/router.go b/pkg/runtime/router.go
deleted file mode 100644
index b5c7ab3..0000000
--- a/pkg/runtime/router.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2019 DeepMap, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package runtime
-
-import (
-	"github.com/labstack/echo/v4"
-)
-
-// This is a simple interface which specifies echo.Route addition functions which
-// are present on both echo.Echo and echo.Group, since we want to allow using
-// either of them for path registration
-type EchoRouter interface {
-	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-	HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-	OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-	PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-}
diff --git a/pkg/runtime/styleparam.go b/pkg/runtime/styleparam.go
index a7f54c0..1d28131 100644
--- a/pkg/runtime/styleparam.go
+++ b/pkg/runtime/styleparam.go
@@ -132,7 +132,26 @@ 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 {
+		return timeVal.Format(time.RFC3339Nano), true
+	}
+
+	if timeVal, ok := value.(time.Time); ok {
+		return timeVal.Format(time.RFC3339Nano), true
+	}
+
+	return "", false
+}
+
 func styleStruct(style string, explode bool, paramName string, value interface{}) (string, error) {
+	if timeVal, ok := marshalTimeValue(value); ok {
+		return stylePrimitive(style, explode, paramName, timeVal)
+	}
+
 	if style == "deepObject" {
 		if !explode {
 			return "", errors.New("deepObjects must be exploded")
@@ -140,12 +159,6 @@ func styleStruct(style string, explode bool, paramName string, value interface{}
 		return MarshalDeepObject(value, paramName)
 	}
 
-	// This is a special case. The struct may be a time, in which case, marshal
-	// it in RFC3339 format.
-	if timeVal, ok := value.(*time.Time); ok {
-		return timeVal.Format(time.RFC3339Nano), nil
-	}
-
 	// Otherwise, we need to build a dictionary of the struct's fields. Each
 	// field may only be a primitive value.
 	v := reflect.ValueOf(value)


Test patch:
diff --git a/internal/test/client/client.gen.go b/internal/test/client/client.gen.go
index 0f8570a..49184df 100644
--- a/internal/test/client/client.gen.go
+++ b/internal/test/client/client.gen.go
@@ -77,6 +77,10 @@ func NewClient(server string, opts ...ClientOption) (*Client, error) {
 			return nil, err
 		}
 	}
+	// ensure the server URL always has a trailing slash
+	if !strings.HasSuffix(client.Server, "/") {
+		client.Server += "/"
+	}
 	// create httpClient, if not already present
 	if client.Client == nil {
 		client.Client = http.DefaultClient
@@ -497,9 +501,6 @@ func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithRes
 // WithBaseURL overrides the baseURL.
 func WithBaseURL(baseURL string) ClientOption {
 	return func(c *Client) error {
-		if !strings.HasSuffix(baseURL, "/") {
-			baseURL += "/"
-		}
 		newBaseURL, err := url.Parse(baseURL)
 		if err != nil {
 			return err
@@ -965,8 +966,10 @@ func (w *ServerInterfaceWrapper) GetJsonWithTrailingSlash(ctx echo.Context) erro
 	return err
 }
 
-// RegisterHandlers adds each server route to the EchoRouter.
-func RegisterHandlers(router interface {
+// This is a simple interface which specifies echo.Route addition functions which
+// are present on both echo.Echo and echo.Group, since we want to allow using
+// either of them for path registration
+type EchoRouter interface {
 	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
@@ -976,7 +979,10 @@ func RegisterHandlers(router interface {
 	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-}, si ServerInterface) {
+}
+
+// RegisterHandlers adds each server route to the EchoRouter.
+func RegisterHandlers(router EchoRouter, si ServerInterface) {
 
 	wrapper := ServerInterfaceWrapper{
 		Handler: si,
@@ -995,15 +1001,15 @@ func RegisterHandlers(router interface {
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"H4sIAAAAAAAC/8xUzY4SQRB+lUnpcWRYvc1RD2ZNFCMkHpBsmp6C7s1Md1tV7GZCeHdTDcgQN8jBNXsh",
-	"1fRXle+nerZgY5diwCAM9RbYOuxMLqe5nCzv0YqeE8WEJB7z7coTyxfToR6kTwg1sJAPa9iVQLF96kJv",
-	"8OfGEzZQz/eocjBqsVOID6uozQ2yJZ/ExwA1zJznQpCFi0eH4pAKcVh8aD0GKUxoDuV3L+4bcoqBkQtD",
-	"WKwxIBnBprCRCK20/Y8AJbTeYuDMM2Qh8Pl2puzFi9KHGbIUU6QHJCjhAYn3VG5G49FYgTFhMMlDDe9G",
-	"49ENlJCMuOxP9ejF3S1j/mkOpqXI2Uo10qiu2wZq+BpZ3kdxsHcH9dT0irMxCIbcYlJqvc1N1T0rjWNY",
-	"Wr0mXEENr6pTmtUhyuosR/V3OCpaQXnDQmi685GrSJ0RqGHpg6Eeyj/CPEtTaIP5r4FwOsag89b4hPSP",
-	"eFI+wL4dj1+q5oFGpaTh9pej/aTM/0u0lwLJZI8mX8rjN91nzENpMdoNeemhnm9hkjATmIPOHRGaBsp9",
-	"bZrOB1jsFictUV//Fc5PFHe19c/1FPZsr7H+xPey9/9qgYWMb31Y33Fr2FV/2wr9ss4OLVPteKFrstv9",
-	"CgAA//91LTf11gYAAA==",
+	"H4sIAAAAAAAC/8yUz24TMRDGX2U1cFyyKdz2CAdUJAgikTiEqHK8k9jVrm1mJq1W0b47GicliahCkGjV",
+	"SzTO/NE338/rLdjYpRgwCEO9BbYOO5PDaQ4ny1u0oudEMSGJx5xdeWL5YjrUg/QJoQYW8mENQwkU28cS",
+	"msGfG0/YQD3fVZVHoxaDlviwitrcIFvySXwMUMPMeS4EWbi4dygOqRCHxYfWY5DChGYffvfiviGnGBi5",
+	"MITFGgOSEWwKG4nQStv/CFBC6y0GzjpDXgQ+X89UvXhR+TBDlmKKdIcEJdwh8U7K1Wg8GmthTBhM8lDD",
+	"u9F4dAUlJCMu+1Pde3E3y5h/mr1pKXK2Uo00utd1AzV8jSzvozjYuYN6anqtszEIhtxiUmq9zU3VLauM",
+	"B1gavSZcQQ2vqgPNao+yOuGo/h6PilZQ3rAQmu505CpSZwRqWPpgqIfyD5gnNIU2mP/YOw912LSt1hw5",
+	"cZTdwhof8eIjHqw4qn07Hr9UE4bDjipJaffnWX9S5c/C+p8IZfUP2XOAfut/QkAqi9FuyEsP9XwLk4RZ",
+	"wBx07ojQNFDuYtN0PsBiWBx2ifo+XIBionUXs3i2j2Un/xIWhwXOw/hfV1zI+NaH9Q23hl31t2uij/Fs",
+	"3zLVjhd6b4bhVwAAAP//2pHiCAkHAAA=",
 }
 
 // GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/internal/test/client/client_test.go b/internal/test/client/client_test.go
new file mode 100644
index 0000000..62e132a
--- /dev/null
+++ b/internal/test/client/client_test.go
@@ -0,0 +1,44 @@
+package client
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestTemp(t *testing.T) {
+
+	var (
+		withTrailingSlash    string = "https://my-api.com/some-base-url/v1/"
+		withoutTrailingSlash string = "https://my-api.com/some-base-url/v1"
+	)
+
+	client1, err := NewClient(
+		withTrailingSlash,
+	)
+	assert.NoError(t, err)
+
+	client2, err := NewClient(
+		withoutTrailingSlash,
+	)
+	assert.NoError(t, err)
+
+	client3, err := NewClient(
+		"",
+		WithBaseURL(withTrailingSlash),
+	)
+	assert.NoError(t, err)
+
+	client4, err := NewClient(
+		"",
+		WithBaseURL(withoutTrailingSlash),
+	)
+	assert.NoError(t, err)
+
+	expectedURL := withTrailingSlash
+
+	assert.Equal(t, expectedURL, client1.Server)
+	assert.Equal(t, expectedURL, client2.Server)
+	assert.Equal(t, expectedURL, client3.Server)
+	assert.Equal(t, expectedURL, client4.Server)
+}
diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go
index cf00683..3cf0059 100644
--- a/internal/test/components/components.gen.go
+++ b/internal/test/components/components.gen.go
@@ -759,6 +759,10 @@ func NewClient(server string, opts ...ClientOption) (*Client, error) {
 			return nil, err
 		}
 	}
+	// ensure the server URL always has a trailing slash
+	if !strings.HasSuffix(client.Server, "/") {
+		client.Server += "/"
+	}
 	// create httpClient, if not already present
 	if client.Client == nil {
 		client.Client = http.DefaultClient
@@ -952,9 +956,6 @@ func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithRes
 // WithBaseURL overrides the baseURL.
 func WithBaseURL(baseURL string) ClientOption {
 	return func(c *Client) error {
-		if !strings.HasSuffix(baseURL, "/") {
-			baseURL += "/"
-		}
 		newBaseURL, err := url.Parse(baseURL)
 		if err != nil {
 			return err
@@ -1119,8 +1120,10 @@ func (w *ServerInterfaceWrapper) BodyWithAddProps(ctx echo.Context) error {
 	return err
 }
 
-// RegisterHandlers adds each server route to the EchoRouter.
-func RegisterHandlers(router interface {
+// This is a simple interface which specifies echo.Route addition functions which
+// are present on both echo.Echo and echo.Group, since we want to allow using
+// either of them for path registration
+type EchoRouter interface {
 	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
@@ -1130,7 +1133,10 @@ func RegisterHandlers(router interface {
 	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-}, si ServerInterface) {
+}
+
+// RegisterHandlers adds each server route to the EchoRouter.
+func RegisterHandlers(router EchoRouter, si ServerInterface) {
 
 	wrapper := ServerInterfaceWrapper{
 		Handler: si,
@@ -1144,20 +1150,20 @@ func RegisterHandlers(router interface {
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"H4sIAAAAAAAC/9xWy27bOhD9FWLuXQp2Hu1GOxdF0RRoGzQBukiMgBFHEVOZVEg6rhDo34shJcuRaVdO",
-	"s2lXtiTO4xyeeTxBpheVVqichfQJKm74Ah0a/3TePX29vcfM0atMK4fK/+VVVcqMO6nV9N5qRe9sVuCC",
-	"e09GV2icRO/pg8RS0J//DeaQwn/TPu40GNnphf9tYzVNAgYfltKggPSq9TCn1w5/umlVcjkI6eoKIQXr",
-	"jFR30NBRgTYzsqIcIQXO1vggATKHhyWaeh0MrXunRZvzt/WL+q9DHnzYSivbgQkP/8hNzpiVi6pE1oFk",
-	"ug/WZkGOZkJIMuHl+RpFSOvYA4983ogvlcM7NLAV/iO3rLdlPUNM54yMmVQOkgF1UsR9K77ACOoEdBUC",
-	"xCh5zql3kVCEedId7RhJ9rBwspuFnJcWh8Dfa7RMacd4WepVnIM/xf1K0E53Q3NmuYVsRoAs46qOoKq3",
-	"MB2Q+2Fpvzksba9EpVW90EvLciottipkVrBil0a370cpNL8L+6rwx5mHvJKXsPh2X3WP71zj634lXcGC",
-	"E5Zrw4TM/CETCN9KPUT4Ll3xyWq1bqqjWE7gkZdL9B0s12bBHaTg+3ay4+jJiKPxsmsjxdh/RtVW7rk0",
-	"1n3ZBcDocoQA/Klkw9XcjwKpck3GpcxQWeyZgs9nl+TdSUfu4RKtYxdoHr2MHtHYcI3Hk6PJUWiwqHgl",
-	"IYXTydHkmCqDu8LnP/Wrgr2hi73hQtwQPP/lDj3c4UAiyyCDfoliXAnG2a0WdVuVLby4iq7pWujBj+Iz",
-	"AWlYwSzpZCbEuU8hebalXQ0zuSyk7VPY3QZ8sI0dqKtKqIiH/hpC+fezeV+T2NKIdbW/iTCtoUnGZKs2",
-	"OprvAes23JKoEIVlTrNbvFZuaRQKGria8fZkmMFUh7FsyXKlzY/dDJzsZeCg7hkR/4ClaNebN828SaDS",
-	"NiI234ZYu7Buqot2Oi4VfRXSYOai+BOS5bXayzPpOGYbkSgtyAOBmheuzuMH0FjWN9aNF06hbv/orqUZ",
-	"SsOv278CAAD//0Y7czRJDQAA",
+	"H4sIAAAAAAAC/9xWS2/bOBD+KwR3j4Sdx+5FNy8WRVOgbdAE6CExAkYcRUxlUiHpuEKg/14MKVm2RLly",
+	"mkt7sklxHt83zxea6lWpFShnafJCS274ChwYf7psT5/vHyF1eJVq5UD5v7wsC5lyJ7WaP1qt8M6mOay4",
+	"12R0CcZJ8JreSSgE/vnbQEYT+te8szsPQnZ+5X8bW3XNqIGntTQgaHLTaFjitYPvbl4WXPZMuqoEmlDr",
+	"jFQPtManAmxqZIk+0oRyssVHGUVx+rQGU22NgXX/adH4/GV7Uf12yIMOW2plWzDh8IdEckGsXJUFkBYk",
+	"0Z2xxgtUtBBCoggvLrcoglunHnjk8459qRw8gKED8++5JZ0s6RgiOiMoTKRylPWokyKuW/EVRFAzqstg",
+	"IEbJPqdeBUMLS9Y+bRlhB1g4G2ch44WFPvD/NViitCO8KPQmzsGv4n4jaOfj0JxZD5AtEJAlXFURVNUA",
+	"0xG+H+f2P8e57TNRaVWt9NqSDEuLbHKZ5iQfy9FhfJQC8zOzbwp/mnjwi72GxX8PVff0zjW97jfS5SQo",
+	"IZk2RMjUPzKB8IHrwcJX6fIPVqttU53EMqPPvFiD72CZNivuaEJ932YjT88mPI2XXWMpxv4eVQPfM2ms",
+	"+zQGwOhiQgL4V2xH1dKPAqkyjcKFTEFZ6JiiHy+uUbuTDtXTa7COXIF59mn0DMaGMJ7OTmYnocGC4qWk",
+	"CT2fncxOsTK4y73/c78q2DsM7B0X4g7h+S8P4OH2BxJKhjTolijClSCc3GtRNVXZwItn0S2GBQ9+FF8I",
+	"moQVzGKeLIS49C6wvS3tpu/JdS5t58J4G/DGdnagtippiTx0YQjl383mQ01ikCPWVT4SYVrTmk3xVu10",
+	"NN8Dtm24IVEBCEucJvdwq9zaKBA4cDXhzcswg7EOY96i5Eabb+MMnB1k4KjuGUn+HkvRrres6+XeCqfW",
+	"RVEzWmobyT7fl0izwe6mGy55XCr8KqSB1EUJYZint+og8ZjYMdlIzuLG3MtY88pdevpEmhqGnf3jlWOp",
+	"XUjaONX9XKmHgavr+kcAAAD//xxDQTJrDQAA",
 }
 
 // GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/internal/test/issues/issue-52/issue.gen.go b/internal/test/issues/issue-52/issue.gen.go
index 9ffd972..6e0155f 100644
--- a/internal/test/issues/issue-52/issue.gen.go
+++ b/internal/test/issues/issue-52/issue.gen.go
@@ -131,6 +131,10 @@ func NewClient(server string, opts ...ClientOption) (*Client, error) {
 			return nil, err
 		}
 	}
+	// ensure the server URL always has a trailing slash
+	if !strings.HasSuffix(client.Server, "/") {
+		client.Server += "/"
+	}
 	// create httpClient, if not already present
 	if client.Client == nil {
 		client.Client = http.DefaultClient
@@ -222,9 +226,6 @@ func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithRes
 // WithBaseURL overrides the baseURL.
 func WithBaseURL(baseURL string) ClientOption {
 	return func(c *Client) error {
-		if !strings.HasSuffix(baseURL, "/") {
-			baseURL += "/"
-		}
 		newBaseURL, err := url.Parse(baseURL)
 		if err != nil {
 			return err
@@ -312,8 +313,10 @@ func (w *ServerInterfaceWrapper) ExampleGet(ctx echo.Context) error {
 	return err
 }
 
-// RegisterHandlers adds each server route to the EchoRouter.
-func RegisterHandlers(router interface {
+// This is a simple interface which specifies echo.Route addition functions which
+// are present on both echo.Echo and echo.Group, since we want to allow using
+// either of them for path registration
+type EchoRouter interface {
 	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
@@ -323,7 +326,10 @@ func RegisterHandlers(router interface {
 	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-}, si ServerInterface) {
+}
+
+// RegisterHandlers adds each server route to the EchoRouter.
+func RegisterHandlers(router EchoRouter, si ServerInterface) {
 
 	wrapper := ServerInterfaceWrapper{
 		Handler: si,
diff --git a/internal/test/parameters/parameters.gen.go b/internal/test/parameters/parameters.gen.go
index e2efb1e..fc6a003 100644
--- a/internal/test/parameters/parameters.gen.go
+++ b/internal/test/parameters/parameters.gen.go
@@ -154,6 +154,10 @@ func NewClient(server string, opts ...ClientOption) (*Client, error) {
 			return nil, err
 		}
 	}
+	// ensure the server URL always has a trailing slash
+	if !strings.HasSuffix(client.Server, "/") {
+		client.Server += "/"
+	}
 	// create httpClient, if not already present
 	if client.Client == nil {
 		client.Client = http.DefaultClient
@@ -1471,9 +1475,6 @@ func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithRes
 // WithBaseURL overrides the baseURL.
 func WithBaseURL(baseURL string) ClientOption {
 	return func(c *Client) error {
-		if !strings.HasSuffix(baseURL, "/") {
-			baseURL += "/"
-		}
 		newBaseURL, err := url.Parse(baseURL)
 		if err != nil {
 			return err
@@ -3014,8 +3015,10 @@ func (w *ServerInterfaceWrapper) GetSimplePrimitive(ctx echo.Context) error {
 	return err
 }
 
-// RegisterHandlers adds each server route to the EchoRouter.
-func RegisterHandlers(router interface {
+// This is a simple interface which specifies echo.Route addition functions which
+// are present on both echo.Echo and echo.Group, since we want to allow using
+// either of them for path registration
+type EchoRouter interface {
 	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
@@ -3025,7 +3028,10 @@ func RegisterHandlers(router interface {
 	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-}, si ServerInterface) {
+}
+
+// RegisterHandlers adds each server route to the EchoRouter.
+func RegisterHandlers(router EchoRouter, si ServerInterface) {
 
 	wrapper := ServerInterfaceWrapper{
 		Handler: si,
@@ -3056,24 +3062,23 @@ func RegisterHandlers(router interface {
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"H4sIAAAAAAAC/9xaS2/bOBD+K8bsnhaq5bY33YLuK8D2sescFihyYKRxzK4ksiQdxDD03xd82LIelilH",
-	"lt3cGmk4883Hj9PhyBuIWcZZjrmSEG1AoOQsl2j+mNOMp/iPe6SfxCxXmCv9T4XPKuQpobn+S8ZLzIh5",
-	"vuYIEUglaP4IRVEEkKCMBeWKshwiuJlI43eyjTVhD98wVqBNrR8T/QPTVs+f7ctoA1wwjkJRC+422YtG",
-	"c4WPKKAI4FbeJJkF5V4+MJYiyfXL0tnPAhcQwU9hmX/ogoefSzwCv6+owASir9vFgQ5dxrmvuK1iXFAh",
-	"1SeSYQsxAQiWtr2oRTVWwZ6re8MpzRdML05pjG5zchMIPt7eae+KKu0e7lCqyRzFEwoI4AmFtNvwdjqb",
-	"zrQh45gTTiGC99PZ9C0EwIlaGvyh22+bX7jhRJCs0G8e0aSrkyV6X/VuwB+oPuwvMK4EyVChkBB9reiH",
-	"cJ7S2CwOv0lWU1HX9lSF4diAyMCGYEuDiQz7XCqxwuI+qGr83Wx2KN7OLqwdhMLEDGPG/qPYzYaxaNBQ",
-	"PRBc0Iwq+qQN8ZmnLEGIFiSV6BKLt262qVXScpYldwsmMqLsqXj/DoLGISkCLwiarwMIcHgILmwyIUKQ",
-	"tS8O0o2DKsykF6DdExu+BWADV9cWjYhrRxzbHjov5lg3Qr/6WMfShNBF0kAQzlVUqqnF1qBkuTWluCWn",
-	"4t6UiyWSBEVXufjTWry0XCy3bhymf9982VsybuHowPLmNyfcy5SSJrIbbe2JarTCcgDmtZWXJkx7kjzp",
-	"PEe1OQTpxy86zcyco4MZuhKUkgdM3Y4YGYWbqak1v3Q2dX/VlzVLVJsmfPqxYVQbgFRr0+2aDGGgLi8A",
-	"ucoyItYQ5as0bVC4bYv7cnioOx6CRB/9jk3XJ9amueN0Vdd18LV/7F+v6nZ0VHXXg8ejwnsJkVemvIwo",
-	"QZ9rwqNJ9yn92Fh0yimlydkVZ7Mbjb+d4noReHqdO8JgP6mNxVWjzNHEg6sBitwr0luzxvUj8QUV7gfT",
-	"HCdS3i0FWz0ufQZzX0rzzrFcj7HuRYZu31co1r8i8nLmeijlPasjV+oEkXffb0zYMs/Euj5ZMLXGv9RN",
-	"UmJ2PbuJ/DsTWVeqf++MjmTqdTWuJXu+IVuZt14KPa/GNZiXQ+l3Ra6zeoEBXA3CWRDsyDg2GqrzMdJU",
-	"u4OPIRBcci5QS6d9FjlcKbaf8ao9j8dNeN5Ydr3jBJviOf8br5BY+c7Wg8XrGSiMRVi91z7eAM1b1l3x",
-	"SGF0Iv2/8c7bFl7FUGEs0nbfMPzp2v8EUyPqJGI8pHUeVkztl+b3BBb+SqQQwVIpHoWh+zGBQqmmupfN",
-	"CJ8SqnvZ/wMAAP//xqb11moiAAA=",
+	"H4sIAAAAAAAC/9xZS2/jNhD+K8a0p0JreXdvugXbV4Duo3UOBRY5MNI45lYSuSQdJDD03wuSelIPS7aV",
+	"OHuzpeF8832cGQypPYQs4SzFVEkI9iBQcpZKNH/WNOEx/pM/0k9ClipMlf6p8FH5PCY01f9kuMWEmOdP",
+	"HCEAqQRN7yHLMg8ilKGgXFGWQgBXC2n8LgqsBbv7hqECbWr9GPQPTFs9frYvgz1wwTgKRW1w11ENjaYK",
+	"71FA5sG1vIoSG1T+8o6xGEmqX1bOfha4gQB+8iv+fg7uf67iEfh9RwVGEHwtFnsausK5bbhtxrihQqpP",
+	"JMEOYTwQLO564aAaK6/m6tZoStMN04tjGmK+OakBgo/XN9q7okq7hxuUarFG8YACPHhAIe02vF2ulitt",
+	"yDimhFMI4P1ytXwLHnCitiZ+P99vy8/fcyJIkuk392joarJE76veDfgD1Yf6AuNKkAQVCgnB10b+EM5j",
+	"GprF/jfJnCwa2p5mYuRqQGDCBq+QwSBDXUsldpjdes0cf7da9eGVdr5TCJnB9EPG/qM4rIaxaMnQLAgu",
+	"aEIVfdCG+MhjFiEEGxJLzImFhZuCGng1qTZMJETZInj/DrxWTWTeKEQtTw8gnoyYo0QLIgR5GgtLGrBU",
+	"YSJH4ZdPLFpHPK0whvSeL4xSFlYUzChdWCOgca3MhW4jDklwHOJc5d5kElqDSsNOBiEDp/jTXRybQt4i",
+	"iVAMFfKf1uLUQt4WbvKY/n3zpbZk1pIegH7zW56Fz1Lk7UCutHV3EM9W8j1RvXDht6OyVdAt1hx9oC+C",
+	"V9cO2kRyRwWhvuYQkzuMc71NTvj7pekCvwwOQn+5y9rNo2vHx8ww58lJD6R6MhOiYQjnnIzqmhWz41TR",
+	"+kbIc6g2JmFn1+cT68qqw/o01w0IVK/jHyivSv7NzJog3MHUOkW5l86thChBH53UotFw4X1sLTqm8Gg0",
+	"e05ZdvMJVubUJMWO71UHJJuWTLOJ02pVNBohzhka1WvOqHafmqbaCV3q0rOKEylvtoLt7rdjLpW+VOaD",
+	"V0oTriRf5MLo+w7F06+IvLov7KNcszpw6IwQ+fApwsBWPCPr+ugMcQbwKlGiKua+YdqE8jsTyRD3v0uj",
+	"A9RHnTcd9me7U6p466Uw8bzpRPVsQY07d7qazX/f5CCeA7CkeuhqxGU7z/XqANsjAF/yKO1E375YO6lJ",
+	"2o9DzXljxNFx3Vp2uQduS3E+1RqfaybIdjlH7tkUcifZw8PHumPdBR+651du/MfAddfCizh2z6ZSeak+",
+	"Xp/6JwBHmaOUGJE8c8mgXZsvzTb8nYghgK1SPPD9/DOzQqmWelJMCF8SCtlt9n8AAAD//xeCHE2EIAAA",
 }
 
 // GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/internal/test/schemas/schemas.gen.go b/internal/test/schemas/schemas.gen.go
index 3d7fdf2..bf7cadc 100644
--- a/internal/test/schemas/schemas.gen.go
+++ b/internal/test/schemas/schemas.gen.go
@@ -88,6 +88,10 @@ func NewClient(server string, opts ...ClientOption) (*Client, error) {
 			return nil, err
 		}
 	}
+	// ensure the server URL always has a trailing slash
+	if !strings.HasSuffix(client.Server, "/") {
+		client.Server += "/"
+	}
 	// create httpClient, if not already present
 	if client.Client == nil {
 		client.Client = http.DefaultClient
@@ -373,9 +377,6 @@ func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithRes
 // WithBaseURL overrides the baseURL.
 func WithBaseURL(baseURL string) ClientOption {
 	return func(c *Client) error {
-		if !strings.HasSuffix(baseURL, "/") {
-			baseURL += "/"
-		}
 		newBaseURL, err := url.Parse(baseURL)
 		if err != nil {
 			return err
@@ -707,8 +708,10 @@ func (w *ServerInterfaceWrapper) Issue9(ctx echo.Context) error {
 	return err
 }
 
-// RegisterHandlers adds each server route to the EchoRouter.
-func RegisterHandlers(router interface {
+// This is a simple interface which specifies echo.Route addition functions which
+// are present on both echo.Echo and echo.Group, since we want to allow using
+// either of them for path registration
+type EchoRouter interface {
 	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
@@ -718,7 +721,10 @@ func RegisterHandlers(router interface {
 	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
 	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
-}, si ServerInterface) {
+}
+
+// RegisterHandlers adds each server route to the EchoRouter.
+func RegisterHandlers(router EchoRouter, si ServerInterface) {
 
 	wrapper := ServerInterfaceWrapper{
 		Handler: si,
@@ -734,18 +740,18 @@ func RegisterHandlers(router interface {
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"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",
+	"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",
 }
 
 // GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/pkg/runtime/styleparam_test.go b/pkg/runtime/styleparam_test.go
index 33c6c8a..48ad36e 100644
--- a/pkg/runtime/styleparam_test.go
+++ b/pkg/runtime/styleparam_test.go
@@ -15,6 +15,7 @@ package runtime
 
 import (
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -33,6 +34,7 @@ func TestStyleParam(t *testing.T) {
 	dict := map[string]interface{}{}
 	dict["firstName"] = "Alex"
 	dict["role"] = "admin"
+	timestamp, _ := time.Parse(time.RFC3339, "2020-01-01T22:00:00Z")
 
 	// ---------------------------- Simple Style -------------------------------
 
@@ -68,6 +70,22 @@ func TestStyleParam(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, "firstName=Alex,role=admin", result)
 
+	result, err = StyleParam("simple", false, "id", timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("simple", true, "id", timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("simple", false, "id", &timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("simple", true, "id", &timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "2020-01-01T22:00:00Z", result)
+
 	// ----------------------------- Label Style -------------------------------
 
 	result, err = StyleParam("label", false, "id", primitive)
@@ -102,6 +120,22 @@ func TestStyleParam(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, ".firstName=Alex.role=admin", result)
 
+	result, err = StyleParam("label", false, "id", timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ".2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("label", true, "id", timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ".2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("label", false, "id", &timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ".2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("label", true, "id", &timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ".2020-01-01T22:00:00Z", result)
+
 	// ----------------------------- Matrix Style ------------------------------
 
 	result, err = StyleParam("matrix", false, "id", primitive)
@@ -136,6 +170,22 @@ func TestStyleParam(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, ";firstName=Alex;role=admin", result)
 
+	result, err = StyleParam("matrix", false, "id", timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ";id=2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("matrix", true, "id", timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ";id=2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("matrix", false, "id", &timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ";id=2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("matrix", true, "id", &timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, ";id=2020-01-01T22:00:00Z", result)
+
 	// ------------------------------ Form Style -------------------------------
 	result, err = StyleParam("form", false, "id", primitive)
 	assert.NoError(t, err)
@@ -169,6 +219,22 @@ func TestStyleParam(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, "firstName=Alex&role=admin", result)
 
+	result, err = StyleParam("form", false, "id", timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "id=2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("form", true, "id", timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "id=2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("form", false, "id", &timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "id=2020-01-01T22:00:00Z", result)
+
+	result, err = StyleParam("form", true, "id", &timestamp)
+	assert.NoError(t, err)
+	assert.EqualValues(t, "id=2020-01-01T22:00:00Z", result)
+
 	// ------------------------  spaceDelimited Style --------------------------
 
 	result, err = StyleParam("spaceDelimited", false, "id", primitive)
@@ -196,6 +262,19 @@ func TestStyleParam(t *testing.T) {
 
 	result, err = StyleParam("spaceDelimited", true, "id", dict)
 	assert.Error(t, err)
+
+	result, err = StyleParam("spaceDelimited", false, "id", timestamp)
+	assert.Error(t, err)
+
+	result, err = StyleParam("spaceDelimited", true, "id", timestamp)
+	assert.Error(t, err)
+
+	result, err = StyleParam("spaceDelimited", false, "id", &timestamp)
+	assert.Error(t, err)
+
+	result, err = StyleParam("spaceDelimited", true, "id", &timestamp)
+	assert.Error(t, err)
+
 	// -------------------------  pipeDelimited Style --------------------------
 
 	result, err = StyleParam("pipeDelimited", false, "id", primitive)
@@ -224,6 +303,18 @@ func TestStyleParam(t *testing.T) {
 	result, err = StyleParam("pipeDelimited", true, "id", dict)
 	assert.Error(t, err)
 
+	result, err = StyleParam("pipeDelimited", false, "id", timestamp)
+	assert.Error(t, err)
+
+	result, err = StyleParam("pipeDelimited", true, "id", timestamp)
+	assert.Error(t, err)
+
+	result, err = StyleParam("pipeDelimited", false, "id", &timestamp)
+	assert.Error(t, err)
+
+	result, err = StyleParam("pipeDelimited", true, "id", &timestamp)
+	assert.Error(t, err)
+
 	// ---------------------------  deepObject Style ---------------------------
 	result, err = StyleParam("deepObject", false, "id", primitive)
 	assert.Error(t, err)
@@ -249,6 +340,19 @@ func TestStyleParam(t *testing.T) {
 	assert.NoError(t, err)
 	assert.EqualValues(t, "id[firstName]=Alex&id[role]=admin", result)
 
+	result, err = StyleParam("deepObject", false, "id", timestamp)
+	assert.Error(t, err)
+
+	result, err = StyleParam("deepObject", true, "id", timestamp)
+	assert.Error(t, err)
+
+	result, err = StyleParam("deepObject", false, "id", &timestamp)
+	assert.Error(t, err)
+
+	result, err = StyleParam("deepObject", true, "id", &timestamp)
+	assert.Error(t, err)
+
+
 	// Misc tests
 	// Test type aliases
 	type StrType string