From bf9fae641e178391d0539b7c9232cef61b2ece6a Mon Sep 17 00:00:00 2001 From: Ian Cubbon Date: Sun, 22 Oct 2023 06:07:13 -0700 Subject: [PATCH] [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 ",". --- .../go-server/controller-api.mustache | 36 +++++++++++++++++++ .../main/resources/go-server/routers.mustache | 22 ++++++++++++ .../resources/3_0/go-server/petstore.yaml | 18 ++++++++++ .../petstore/go/go-petstore/go/routers.go | 22 ++++++++++++ .../petstore/go-api-server/api/openapi.yaml | 18 ++++++++++ .../server/petstore/go-api-server/go/api.go | 3 +- .../petstore/go-api-server/go/api_pet.go | 16 ++++++++- .../go-api-server/go/api_pet_service.go | 3 +- .../petstore/go-api-server/go/routers.go | 22 ++++++++++++ .../petstore/go-chi-server/api/openapi.yaml | 18 ++++++++++ .../server/petstore/go-chi-server/go/api.go | 3 +- .../petstore/go-chi-server/go/api_pet.go | 16 ++++++++- .../go-chi-server/go/api_pet_service.go | 3 +- .../petstore/go-chi-server/go/routers.go | 22 ++++++++++++ 14 files changed, 216 insertions(+), 6 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/go-server/controller-api.mustache b/modules/openapi-generator/src/main/resources/go-server/controller-api.mustache index 5769d40a18c..34abbf2941f 100644 --- a/modules/openapi-generator/src/main/resources/go-server/controller-api.mustache +++ b/modules/openapi-generator/src/main/resources/go-server/controller-api.mustache @@ -168,6 +168,13 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re return } {{/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}} {{^isFloat}} {{^isDouble}} @@ -190,6 +197,26 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re {{/isNumber}} {{/isPathParam}} {{#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}} {{paramName}}Param, err := parseNumericParameter[float32]( query.Get("{{baseName}}"),{{#defaultValue}} @@ -333,6 +360,13 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re return } {{/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.isFloat}} {{^items.isDouble}} @@ -372,6 +406,7 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re {{^isInteger}} {{^isBoolean}} {{^isArray}} + {{^isDateTime}} {{#defaultValue}} {{paramName}}Param := "{{defaultValue}}" if query.Has("{{baseName}}") { @@ -412,6 +447,7 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re {{/isEnumOrRef}} {{/required}} {{/defaultValue}} + {{/isDateTime}} {{/isArray}} {{/isBoolean}} {{/isInteger}} diff --git a/modules/openapi-generator/src/main/resources/go-server/routers.mustache b/modules/openapi-generator/src/main/resources/go-server/routers.mustache index ca06cca97ac..2dfdc8c01d1 100644 --- a/modules/openapi-generator/src/main/resources/go-server/routers.mustache +++ b/modules/openapi-generator/src/main/resources/go-server/routers.mustache @@ -4,6 +4,7 @@ package {{packageName}} import ( "encoding/json" "errors" + "time" {{#routers}} {{#mux}} "github.com/gorilla/mux" @@ -183,6 +184,27 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error 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 { ~int32 | ~int64 | ~float32 | ~float64 } diff --git a/modules/openapi-generator/src/test/resources/3_0/go-server/petstore.yaml b/modules/openapi-generator/src/test/resources/3_0/go-server/petstore.yaml index 26a1f081695..62bade51543 100644 --- a/modules/openapi-generator/src/test/resources/3_0/go-server/petstore.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/go-server/petstore.yaml @@ -137,6 +137,24 @@ paths: type: array items: 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: '200': description: successful operation diff --git a/samples/openapi3/server/petstore/go/go-petstore/go/routers.go b/samples/openapi3/server/petstore/go/go-petstore/go/routers.go index 4e6f1007f44..358a6281c80 100644 --- a/samples/openapi3/server/petstore/go/go-petstore/go/routers.go +++ b/samples/openapi3/server/petstore/go/go-petstore/go/routers.go @@ -12,6 +12,7 @@ package petstoreserver import ( "encoding/json" "errors" + "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "io" @@ -150,6 +151,27 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error 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 { ~int32 | ~int64 | ~float32 | ~float64 } diff --git a/samples/server/petstore/go-api-server/api/openapi.yaml b/samples/server/petstore/go-api-server/api/openapi.yaml index 701dca72075..c835943823d 100644 --- a/samples/server/petstore/go-api-server/api/openapi.yaml +++ b/samples/server/petstore/go-api-server/api/openapi.yaml @@ -136,6 +136,24 @@ paths: type: string type: array 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: "200": content: diff --git a/samples/server/petstore/go-api-server/go/api.go b/samples/server/petstore/go-api-server/go/api.go index 4e99b1fe232..9f7cac7f6ac 100644 --- a/samples/server/petstore/go-api-server/go/api.go +++ b/samples/server/petstore/go-api-server/go/api.go @@ -12,6 +12,7 @@ package petstoreserver import ( "context" "net/http" + "time" "os" ) @@ -68,7 +69,7 @@ type PetAPIServicer interface { FilterPetsByCategory(context.Context, Gender, Species, []Species) (ImplResponse, error) FindPetsByStatus(context.Context, []string) (ImplResponse, error) // Deprecated - FindPetsByTags(context.Context, []string) (ImplResponse, error) + FindPetsByTags(context.Context, []string, time.Time, time.Time) (ImplResponse, error) GetPetById(context.Context, int64) (ImplResponse, error) GetPetImageById(context.Context, int64) (ImplResponse, error) UpdatePet(context.Context, Pet) (ImplResponse, error) diff --git a/samples/server/petstore/go-api-server/go/api_pet.go b/samples/server/petstore/go-api-server/go/api_pet.go index f283133bc65..7818abb84d7 100644 --- a/samples/server/petstore/go-api-server/go/api_pet.go +++ b/samples/server/petstore/go-api-server/go/api_pet.go @@ -223,7 +223,21 @@ func (c *PetAPIController) FindPetsByTags(w http.ResponseWriter, r *http.Request if query.Has("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 err != nil { c.errorHandler(w, r, err, &result) diff --git a/samples/server/petstore/go-api-server/go/api_pet_service.go b/samples/server/petstore/go-api-server/go/api_pet_service.go index 5987c4bed62..710a4576cd8 100644 --- a/samples/server/petstore/go-api-server/go/api_pet_service.go +++ b/samples/server/petstore/go-api-server/go/api_pet_service.go @@ -13,6 +13,7 @@ import ( "context" "net/http" "errors" + "time" "os" ) @@ -82,7 +83,7 @@ func (s *PetAPIService) FindPetsByStatus(ctx context.Context, status []string) ( // FindPetsByTags - Finds Pets by tags // 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. // Add api_pet_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation. diff --git a/samples/server/petstore/go-api-server/go/routers.go b/samples/server/petstore/go-api-server/go/routers.go index ec25be4df67..6ad214ba4b6 100644 --- a/samples/server/petstore/go-api-server/go/routers.go +++ b/samples/server/petstore/go-api-server/go/routers.go @@ -12,6 +12,7 @@ package petstoreserver import ( "encoding/json" "errors" + "time" "github.com/gorilla/mux" "io" "mime/multipart" @@ -154,6 +155,27 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error 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 { ~int32 | ~int64 | ~float32 | ~float64 } diff --git a/samples/server/petstore/go-chi-server/api/openapi.yaml b/samples/server/petstore/go-chi-server/api/openapi.yaml index 701dca72075..c835943823d 100644 --- a/samples/server/petstore/go-chi-server/api/openapi.yaml +++ b/samples/server/petstore/go-chi-server/api/openapi.yaml @@ -136,6 +136,24 @@ paths: type: string type: array 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: "200": content: diff --git a/samples/server/petstore/go-chi-server/go/api.go b/samples/server/petstore/go-chi-server/go/api.go index 4e99b1fe232..9f7cac7f6ac 100644 --- a/samples/server/petstore/go-chi-server/go/api.go +++ b/samples/server/petstore/go-chi-server/go/api.go @@ -12,6 +12,7 @@ package petstoreserver import ( "context" "net/http" + "time" "os" ) @@ -68,7 +69,7 @@ type PetAPIServicer interface { FilterPetsByCategory(context.Context, Gender, Species, []Species) (ImplResponse, error) FindPetsByStatus(context.Context, []string) (ImplResponse, error) // Deprecated - FindPetsByTags(context.Context, []string) (ImplResponse, error) + FindPetsByTags(context.Context, []string, time.Time, time.Time) (ImplResponse, error) GetPetById(context.Context, int64) (ImplResponse, error) GetPetImageById(context.Context, int64) (ImplResponse, error) UpdatePet(context.Context, Pet) (ImplResponse, error) diff --git a/samples/server/petstore/go-chi-server/go/api_pet.go b/samples/server/petstore/go-chi-server/go/api_pet.go index d01736abbf3..7da84107257 100644 --- a/samples/server/petstore/go-chi-server/go/api_pet.go +++ b/samples/server/petstore/go-chi-server/go/api_pet.go @@ -221,7 +221,21 @@ func (c *PetAPIController) FindPetsByTags(w http.ResponseWriter, r *http.Request if query.Has("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 err != nil { c.errorHandler(w, r, err, &result) diff --git a/samples/server/petstore/go-chi-server/go/api_pet_service.go b/samples/server/petstore/go-chi-server/go/api_pet_service.go index 5987c4bed62..710a4576cd8 100644 --- a/samples/server/petstore/go-chi-server/go/api_pet_service.go +++ b/samples/server/petstore/go-chi-server/go/api_pet_service.go @@ -13,6 +13,7 @@ import ( "context" "net/http" "errors" + "time" "os" ) @@ -82,7 +83,7 @@ func (s *PetAPIService) FindPetsByStatus(ctx context.Context, status []string) ( // FindPetsByTags - Finds Pets by tags // 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. // Add api_pet_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation. diff --git a/samples/server/petstore/go-chi-server/go/routers.go b/samples/server/petstore/go-chi-server/go/routers.go index 4e6f1007f44..358a6281c80 100644 --- a/samples/server/petstore/go-chi-server/go/routers.go +++ b/samples/server/petstore/go-chi-server/go/routers.go @@ -12,6 +12,7 @@ package petstoreserver import ( "encoding/json" "errors" + "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "io" @@ -150,6 +151,27 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error 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 { ~int32 | ~int64 | ~float32 | ~float64 }