[Go-Server] Add support for DateTime Query Parameters (#16749)

* Add support for DateTime objects in the Path, Query Params, and as a List

* Fix indentation

* Add an exaple that has dateTimes
Move the date parsing into a common util in the routers.go file.

* Fix compilation issue and regen

* Use the `RequiredError` to handle this case

* Only split on a "," and not an extra 'space' after the ",".
This commit is contained in:
Ian Cubbon 2023-10-22 06:07:13 -07:00 committed by GitHub
parent 5f71bb9afa
commit bf9fae641e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 216 additions and 6 deletions

View File

@ -168,6 +168,13 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re
return return
} }
{{/isInteger}} {{/isInteger}}
{{#isDateTime}}
{{paramName}}Param, err := time.Parse(time.RFC3339, {{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}})
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/isDateTime}}
{{^isNumber}} {{^isNumber}}
{{^isFloat}} {{^isFloat}}
{{^isDouble}} {{^isDouble}}
@ -190,6 +197,26 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re
{{/isNumber}} {{/isNumber}}
{{/isPathParam}} {{/isPathParam}}
{{#isQueryParam}} {{#isQueryParam}}
{{#isDateTime}}
{{#required}}
if !query.Has("{{baseName}}"){
c.errorHandler(w, r, &RequiredError{"{{baseName}}"}, nil)
return
}
{{paramName}}Param, err := parseTime(query.Get("{{baseName}}"))
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/required}}
{{^required}}
{{paramName}}Param, err := parseTime(query.Get("{{baseName}}"))
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/required}}
{{/isDateTime}}
{{#isNumber}} {{#isNumber}}
{{paramName}}Param, err := parseNumericParameter[float32]( {{paramName}}Param, err := parseNumericParameter[float32](
query.Get("{{baseName}}"),{{#defaultValue}} query.Get("{{baseName}}"),{{#defaultValue}}
@ -333,6 +360,13 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re
return return
} }
{{/items.isInteger}} {{/items.isInteger}}
{{#items.isDateTime}}
{{paramName}}Param, err := parseTimes(query.Get("{{baseName"}}))
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
{{/items.isDateTime}}
{{^items.isNumber}} {{^items.isNumber}}
{{^items.isFloat}} {{^items.isFloat}}
{{^items.isDouble}} {{^items.isDouble}}
@ -372,6 +406,7 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re
{{^isInteger}} {{^isInteger}}
{{^isBoolean}} {{^isBoolean}}
{{^isArray}} {{^isArray}}
{{^isDateTime}}
{{#defaultValue}} {{#defaultValue}}
{{paramName}}Param := "{{defaultValue}}" {{paramName}}Param := "{{defaultValue}}"
if query.Has("{{baseName}}") { if query.Has("{{baseName}}") {
@ -412,6 +447,7 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re
{{/isEnumOrRef}} {{/isEnumOrRef}}
{{/required}} {{/required}}
{{/defaultValue}} {{/defaultValue}}
{{/isDateTime}}
{{/isArray}} {{/isArray}}
{{/isBoolean}} {{/isBoolean}}
{{/isInteger}} {{/isInteger}}

View File

@ -4,6 +4,7 @@ package {{packageName}}
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"time"
{{#routers}} {{#routers}}
{{#mux}} {{#mux}}
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -183,6 +184,27 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error
return file, nil return file, nil
} }
func parseTimes(param string) ([]time.Time, error) {
splits := strings.Split(param, ",")
times := make([]time.Time, 0, len(splits))
for _, v := range splits {
t, err := parseTime(v)
if err != nil {
return nil, err
}
times = append(times, t)
}
return times, nil
}
// parseTime will parses a string parameter into a time.Time using the RFC3339 format
func parseTime(param string) (time.Time, error) {
if param == "" {
return time.Time{}, nil
}
return time.Parse(time.RFC3339, param)
}
type Number interface { type Number interface {
~int32 | ~int64 | ~float32 | ~float64 ~int32 | ~int64 | ~float32 | ~float64
} }

View File

@ -137,6 +137,24 @@ paths:
type: array type: array
items: items:
type: string type: string
- name: bornAfter
in: query
description: Find pets born after this date
required: true
style: form
explode: false
schema:
type: string
format: date-time
- name: bornBefore
in: query
description: Find pets born before this date
required: false
style: form
explode: false
schema:
type: string
format: date-time
responses: responses:
'200': '200':
description: successful operation description: successful operation

View File

@ -12,6 +12,7 @@ package petstoreserver
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"io" "io"
@ -150,6 +151,27 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error
return file, nil return file, nil
} }
func parseTimes(param string) ([]time.Time, error) {
splits := strings.Split(param, ",")
times := make([]time.Time, 0, len(splits))
for _, v := range splits {
t, err := parseTime(v)
if err != nil {
return nil, err
}
times = append(times, t)
}
return times, nil
}
// parseTime will parses a string parameter into a time.Time using the RFC3339 format
func parseTime(param string) (time.Time, error) {
if param == "" {
return time.Time{}, nil
}
return time.Parse(time.RFC3339, param)
}
type Number interface { type Number interface {
~int32 | ~int64 | ~float32 | ~float64 ~int32 | ~int64 | ~float32 | ~float64
} }

View File

@ -136,6 +136,24 @@ paths:
type: string type: string
type: array type: array
style: form style: form
- description: Find pets born after this date
explode: false
in: query
name: bornAfter
required: true
schema:
format: date-time
type: string
style: form
- description: Find pets born before this date
explode: false
in: query
name: bornBefore
required: false
schema:
format: date-time
type: string
style: form
responses: responses:
"200": "200":
content: content:

View File

@ -12,6 +12,7 @@ package petstoreserver
import ( import (
"context" "context"
"net/http" "net/http"
"time"
"os" "os"
) )
@ -68,7 +69,7 @@ type PetAPIServicer interface {
FilterPetsByCategory(context.Context, Gender, Species, []Species) (ImplResponse, error) FilterPetsByCategory(context.Context, Gender, Species, []Species) (ImplResponse, error)
FindPetsByStatus(context.Context, []string) (ImplResponse, error) FindPetsByStatus(context.Context, []string) (ImplResponse, error)
// Deprecated // Deprecated
FindPetsByTags(context.Context, []string) (ImplResponse, error) FindPetsByTags(context.Context, []string, time.Time, time.Time) (ImplResponse, error)
GetPetById(context.Context, int64) (ImplResponse, error) GetPetById(context.Context, int64) (ImplResponse, error)
GetPetImageById(context.Context, int64) (ImplResponse, error) GetPetImageById(context.Context, int64) (ImplResponse, error)
UpdatePet(context.Context, Pet) (ImplResponse, error) UpdatePet(context.Context, Pet) (ImplResponse, error)

View File

@ -223,7 +223,21 @@ func (c *PetAPIController) FindPetsByTags(w http.ResponseWriter, r *http.Request
if query.Has("tags") { if query.Has("tags") {
tagsParam = strings.Split(query.Get("tags"), ",") tagsParam = strings.Split(query.Get("tags"), ",")
} }
result, err := c.service.FindPetsByTags(r.Context(), tagsParam) if !query.Has("bornAfter"){
c.errorHandler(w, r, &RequiredError{"bornAfter"}, nil)
return
}
bornAfterParam, err := parseTime(query.Get("bornAfter"))
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
bornBeforeParam, err := parseTime(query.Get("bornBefore"))
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
result, err := c.service.FindPetsByTags(r.Context(), tagsParam, bornAfterParam, bornBeforeParam)
// If an error occurred, encode the error with the status code // If an error occurred, encode the error with the status code
if err != nil { if err != nil {
c.errorHandler(w, r, err, &result) c.errorHandler(w, r, err, &result)

View File

@ -13,6 +13,7 @@ import (
"context" "context"
"net/http" "net/http"
"errors" "errors"
"time"
"os" "os"
) )
@ -82,7 +83,7 @@ func (s *PetAPIService) FindPetsByStatus(ctx context.Context, status []string) (
// FindPetsByTags - Finds Pets by tags // FindPetsByTags - Finds Pets by tags
// Deprecated // Deprecated
func (s *PetAPIService) FindPetsByTags(ctx context.Context, tags []string) (ImplResponse, error) { func (s *PetAPIService) FindPetsByTags(ctx context.Context, tags []string, bornAfter time.Time, bornBefore time.Time) (ImplResponse, error) {
// TODO - update FindPetsByTags with the required logic for this service method. // TODO - update FindPetsByTags with the required logic for this service method.
// Add api_pet_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation. // Add api_pet_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.

View File

@ -12,6 +12,7 @@ package petstoreserver
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"io" "io"
"mime/multipart" "mime/multipart"
@ -154,6 +155,27 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error
return file, nil return file, nil
} }
func parseTimes(param string) ([]time.Time, error) {
splits := strings.Split(param, ",")
times := make([]time.Time, 0, len(splits))
for _, v := range splits {
t, err := parseTime(v)
if err != nil {
return nil, err
}
times = append(times, t)
}
return times, nil
}
// parseTime will parses a string parameter into a time.Time using the RFC3339 format
func parseTime(param string) (time.Time, error) {
if param == "" {
return time.Time{}, nil
}
return time.Parse(time.RFC3339, param)
}
type Number interface { type Number interface {
~int32 | ~int64 | ~float32 | ~float64 ~int32 | ~int64 | ~float32 | ~float64
} }

View File

@ -136,6 +136,24 @@ paths:
type: string type: string
type: array type: array
style: form style: form
- description: Find pets born after this date
explode: false
in: query
name: bornAfter
required: true
schema:
format: date-time
type: string
style: form
- description: Find pets born before this date
explode: false
in: query
name: bornBefore
required: false
schema:
format: date-time
type: string
style: form
responses: responses:
"200": "200":
content: content:

View File

@ -12,6 +12,7 @@ package petstoreserver
import ( import (
"context" "context"
"net/http" "net/http"
"time"
"os" "os"
) )
@ -68,7 +69,7 @@ type PetAPIServicer interface {
FilterPetsByCategory(context.Context, Gender, Species, []Species) (ImplResponse, error) FilterPetsByCategory(context.Context, Gender, Species, []Species) (ImplResponse, error)
FindPetsByStatus(context.Context, []string) (ImplResponse, error) FindPetsByStatus(context.Context, []string) (ImplResponse, error)
// Deprecated // Deprecated
FindPetsByTags(context.Context, []string) (ImplResponse, error) FindPetsByTags(context.Context, []string, time.Time, time.Time) (ImplResponse, error)
GetPetById(context.Context, int64) (ImplResponse, error) GetPetById(context.Context, int64) (ImplResponse, error)
GetPetImageById(context.Context, int64) (ImplResponse, error) GetPetImageById(context.Context, int64) (ImplResponse, error)
UpdatePet(context.Context, Pet) (ImplResponse, error) UpdatePet(context.Context, Pet) (ImplResponse, error)

View File

@ -221,7 +221,21 @@ func (c *PetAPIController) FindPetsByTags(w http.ResponseWriter, r *http.Request
if query.Has("tags") { if query.Has("tags") {
tagsParam = strings.Split(query.Get("tags"), ",") tagsParam = strings.Split(query.Get("tags"), ",")
} }
result, err := c.service.FindPetsByTags(r.Context(), tagsParam) if !query.Has("bornAfter"){
c.errorHandler(w, r, &RequiredError{"bornAfter"}, nil)
return
}
bornAfterParam, err := parseTime(query.Get("bornAfter"))
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
bornBeforeParam, err := parseTime(query.Get("bornBefore"))
if err != nil {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
result, err := c.service.FindPetsByTags(r.Context(), tagsParam, bornAfterParam, bornBeforeParam)
// If an error occurred, encode the error with the status code // If an error occurred, encode the error with the status code
if err != nil { if err != nil {
c.errorHandler(w, r, err, &result) c.errorHandler(w, r, err, &result)

View File

@ -13,6 +13,7 @@ import (
"context" "context"
"net/http" "net/http"
"errors" "errors"
"time"
"os" "os"
) )
@ -82,7 +83,7 @@ func (s *PetAPIService) FindPetsByStatus(ctx context.Context, status []string) (
// FindPetsByTags - Finds Pets by tags // FindPetsByTags - Finds Pets by tags
// Deprecated // Deprecated
func (s *PetAPIService) FindPetsByTags(ctx context.Context, tags []string) (ImplResponse, error) { func (s *PetAPIService) FindPetsByTags(ctx context.Context, tags []string, bornAfter time.Time, bornBefore time.Time) (ImplResponse, error) {
// TODO - update FindPetsByTags with the required logic for this service method. // TODO - update FindPetsByTags with the required logic for this service method.
// Add api_pet_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation. // Add api_pet_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.

View File

@ -12,6 +12,7 @@ package petstoreserver
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"io" "io"
@ -150,6 +151,27 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error
return file, nil return file, nil
} }
func parseTimes(param string) ([]time.Time, error) {
splits := strings.Split(param, ",")
times := make([]time.Time, 0, len(splits))
for _, v := range splits {
t, err := parseTime(v)
if err != nil {
return nil, err
}
times = append(times, t)
}
return times, nil
}
// parseTime will parses a string parameter into a time.Time using the RFC3339 format
func parseTime(param string) (time.Time, error) {
if param == "" {
return time.Time{}, nil
}
return time.Parse(time.RFC3339, param)
}
type Number interface { type Number interface {
~int32 | ~int64 | ~float32 | ~float64 ~int32 | ~int64 | ~float32 | ~float64
} }