diff --git a/modules/openapi-generator/src/main/resources/go-server/helpers.mustache b/modules/openapi-generator/src/main/resources/go-server/helpers.mustache index de8a203d84b..9a47f81c7da 100644 --- a/modules/openapi-generator/src/main/resources/go-server/helpers.mustache +++ b/modules/openapi-generator/src/main/resources/go-server/helpers.mustache @@ -2,7 +2,17 @@ package {{packageName}} import ( + "encoding/json" + "errors" + "io" + "mime/multipart" + "net/http" + "net/url" + "os" "reflect" + "strconv" + "strings" + "time" ) // Response return a ImplResponse struct filled @@ -64,3 +74,291 @@ func AssertRecurseValueRequired[T any](value reflect.Value, callback func(T) err } return nil } + +// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code +func EncodeJSONResponse(i interface{}, status *int,{{#addResponseHeaders}} headers map[string][]string,{{/addResponseHeaders}} w http.ResponseWriter) error { + wHeader := w.Header() + {{#addResponseHeaders}} + for key, values := range headers { + for _, value := range values { + wHeader.Add(key, value) + } + } + {{/addResponseHeaders}} + + f, ok := i.(*os.File) + if ok { + data, err := io.ReadAll(f) + if err != nil { + return err + } + wHeader.Set("Content-Type", http.DetectContentType(data)) + wHeader.Set("Content-Disposition", "attachment; filename="+f.Name()) + if status != nil { + w.WriteHeader(*status) + } else { + w.WriteHeader(http.StatusOK) + } + _, err = w.Write(data) + return err + } + wHeader.Set("Content-Type", "application/json; charset=UTF-8") + + if status != nil { + w.WriteHeader(*status) + } else { + w.WriteHeader(http.StatusOK) + } + + if i != nil { + return json.NewEncoder(w).Encode(i) + } + + return nil +} + +// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file +func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) { + _, fileHeader, err := r.FormFile(key) + if err != nil { + return nil, err + } + + return readFileHeaderToTempFile(fileHeader) +} + +// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files +func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) { + if err := r.ParseMultipartForm(32 << 20); err != nil { + return nil, err + } + + files := make([]*os.File, 0, len(r.MultipartForm.File[key])) + + for _, fileHeader := range r.MultipartForm.File[key] { + file, err := readFileHeaderToTempFile(fileHeader) + if err != nil { + return nil, err + } + + files = append(files, file) + } + + return files, nil +} + +// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file +func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) { + formFile, err := fileHeader.Open() + if err != nil { + return nil, err + } + + defer formFile.Close() + + // Use .* as suffix, because the asterisk is a placeholder for the random value, + // and the period allows consumers of this file to remove the suffix to obtain the original file name + file, err := os.CreateTemp("", fileHeader.Filename+".*") + if err != nil { + return nil, err + } + + defer file.Close() + + _, err = io.Copy(file, formFile) + if err != nil { + return nil, err + } + + 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 +} + +type ParseString[T Number | string | bool] func(v string) (T, error) + +// parseFloat64 parses a string parameter to an float64. +func parseFloat64(param string) (float64, error) { + if param == "" { + return 0, nil + } + + return strconv.ParseFloat(param, 64) +} + +// parseFloat32 parses a string parameter to an float32. +func parseFloat32(param string) (float32, error) { + if param == "" { + return 0, nil + } + + v, err := strconv.ParseFloat(param, 32) + return float32(v), err +} + +// parseInt64 parses a string parameter to an int64. +func parseInt64(param string) (int64, error) { + if param == "" { + return 0, nil + } + + return strconv.ParseInt(param, 10, 64) +} + +// parseInt32 parses a string parameter to an int32. +func parseInt32(param string) (int32, error) { + if param == "" { + return 0, nil + } + + val, err := strconv.ParseInt(param, 10, 32) + return int32(val), err +} + +// parseBool parses a string parameter to an bool. +func parseBool(param string) (bool, error) { + if param == "" { + return false, nil + } + + return strconv.ParseBool(param) +} + +type Operation[T Number | string | bool] func(actual string) (T, bool, error) + +func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] { + var empty T + return func(actual string) (T, bool, error) { + if actual == "" { + return empty, false, errors.New(errMsgRequiredMissing) + } + + v, err := parse(actual) + return v, false, err + } +} + +func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] { + return func(actual string) (T, bool, error) { + if actual == "" { + return def, true, nil + } + + v, err := parse(actual) + return v, false, err + } +} + +func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] { + return func(actual string) (T, bool, error) { + v, err := parse(actual) + return v, false, err + } +} + +type Constraint[T Number | string | bool] func(actual T) error + +func WithMinimum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual < expected { + return errors.New(errMsgMinValueConstraint) + } + + return nil + } +} + +func WithMaximum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual > expected { + return errors.New(errMsgMaxValueConstraint) + } + + return nil + } +} + +// parseNumericParameter parses a numeric parameter to its respective type. +func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) { + v, ok, err := fn(param) + if err != nil { + return 0, err + } + + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return 0, err + } + } + } + + return v, nil +} + +// parseBoolParameter parses a string parameter to a bool +func parseBoolParameter(param string, fn Operation[bool]) (bool, error) { + v, _, err := fn(param) + return v, err +} + +// parseNumericArrayParameter parses a string parameter containing array of values to its respective type. +func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) { + if param == "" { + if required { + return nil, errors.New(errMsgRequiredMissing) + } + + return nil, nil + } + + str := strings.Split(param, delim) + values := make([]T, len(str)) + + for i, s := range str { + v, ok, err := fn(s) + if err != nil { + return nil, err + } + + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return nil, err + } + } + } + + values[i] = v + } + + return values, nil +} + +// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered. +func parseQuery(rawQuery string) (url.Values, error) { + return url.ParseQuery(rawQuery) +} diff --git a/modules/openapi-generator/src/main/resources/go-server/logger.mustache b/modules/openapi-generator/src/main/resources/go-server/logger.mustache index e8464d6ad5f..dbc9faff4bc 100644 --- a/modules/openapi-generator/src/main/resources/go-server/logger.mustache +++ b/modules/openapi-generator/src/main/resources/go-server/logger.mustache @@ -2,11 +2,20 @@ package {{packageName}} import ( - "log" "net/http" +{{#routers}} + {{#mux}} + "log" "time" + {{/mux}} + {{#chi}} + "github.com/go-chi/chi/v5/middleware" + {{/chi}} +{{/routers}} ) +{{#routers}} +{{#mux}} func Logger(inner http.Handler, name string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() @@ -22,3 +31,10 @@ func Logger(inner http.Handler, name string) http.Handler { ) }) } +{{/mux}} +{{#chi}} +func Logger(inner http.Handler) http.Handler { + return middleware.Logger(inner) +} +{{/chi}} +{{/routers}} 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 207df91d9aa..0e10f7b97ca 100644 --- a/modules/openapi-generator/src/main/resources/go-server/routers.mustache +++ b/modules/openapi-generator/src/main/resources/go-server/routers.mustache @@ -2,9 +2,7 @@ package {{packageName}} import ( - "encoding/json" - "errors" - "time" + "net/http" {{#routers}} {{#mux}} "github.com/gorilla/mux" @@ -14,19 +12,11 @@ import ( {{/mux}} {{#chi}} "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" {{#featureCORS}} "github.com/go-chi/cors" {{/featureCORS}} {{/chi}} {{/routers}} - "io" - "mime/multipart" - "net/http" - "net/url" - "os" - "strconv" - "strings" ) // A Route defines the parameters for an api endpoint @@ -56,7 +46,7 @@ func NewRouter(routers ...Router) {{#routers}}{{#mux}}*mux.Router{{/mux}}{{#chi} {{/mux}} {{#chi}} router := chi.NewRouter() - router.Use(middleware.Logger) + router.Use(Logger) {{#featureCORS}} router.Use(cors.Handler(cors.Options{})) {{/featureCORS}} @@ -87,292 +77,3 @@ func NewRouter(routers ...Router) {{#routers}}{{#mux}}*mux.Router{{/mux}}{{#chi} return router } - -// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code -func EncodeJSONResponse(i interface{}, status *int,{{#addResponseHeaders}} headers map[string][]string,{{/addResponseHeaders}} w http.ResponseWriter) error { - wHeader := w.Header() - {{#addResponseHeaders}} - for key, values := range headers { - for _, value := range values { - wHeader.Add(key, value) - } - } - {{/addResponseHeaders}} - - f, ok := i.(*os.File) - if ok { - data, err := io.ReadAll(f) - if err != nil { - return err - } - wHeader.Set("Content-Type", http.DetectContentType(data)) - wHeader.Set("Content-Disposition", "attachment; filename="+f.Name()) - if status != nil { - w.WriteHeader(*status) - } else { - w.WriteHeader(http.StatusOK) - } - _, err = w.Write(data) - return err - } - wHeader.Set("Content-Type", "application/json; charset=UTF-8") - - if status != nil { - w.WriteHeader(*status) - } else { - w.WriteHeader(http.StatusOK) - } - - if i != nil { - return json.NewEncoder(w).Encode(i) - } - - return nil -} - -// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file -func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) { - _, fileHeader, err := r.FormFile(key) - if err != nil { - return nil, err - } - - return readFileHeaderToTempFile(fileHeader) -} - -// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files -func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) { - if err := r.ParseMultipartForm(32 << 20); err != nil { - return nil, err - } - - files := make([]*os.File, 0, len(r.MultipartForm.File[key])) - - for _, fileHeader := range r.MultipartForm.File[key] { - file, err := readFileHeaderToTempFile(fileHeader) - if err != nil { - return nil, err - } - - files = append(files, file) - } - - return files, nil -} - -// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file -func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) { - formFile, err := fileHeader.Open() - if err != nil { - return nil, err - } - - defer formFile.Close() - - // Use .* as suffix, because the asterisk is a placeholder for the random value, - // and the period allows consumers of this file to remove the suffix to obtain the original file name - file, err := os.CreateTemp("", fileHeader.Filename+".*") - if err != nil { - return nil, err - } - - defer file.Close() - - _, err = io.Copy(file, formFile) - if err != nil { - return nil, err - } - - 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 -} - -type ParseString[T Number | string | bool] func(v string) (T, error) - -// parseFloat64 parses a string parameter to an float64. -func parseFloat64(param string) (float64, error) { - if param == "" { - return 0, nil - } - - return strconv.ParseFloat(param, 64) -} - -// parseFloat32 parses a string parameter to an float32. -func parseFloat32(param string) (float32, error) { - if param == "" { - return 0, nil - } - - v, err := strconv.ParseFloat(param, 32) - return float32(v), err -} - -// parseInt64 parses a string parameter to an int64. -func parseInt64(param string) (int64, error) { - if param == "" { - return 0, nil - } - - return strconv.ParseInt(param, 10, 64) -} - -// parseInt32 parses a string parameter to an int32. -func parseInt32(param string) (int32, error) { - if param == "" { - return 0, nil - } - - val, err := strconv.ParseInt(param, 10, 32) - return int32(val), err -} - -// parseBool parses a string parameter to an bool. -func parseBool(param string) (bool, error) { - if param == "" { - return false, nil - } - - return strconv.ParseBool(param) -} - -type Operation[T Number | string | bool] func(actual string) (T, bool, error) - -func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] { - var empty T - return func(actual string) (T, bool, error) { - if actual == "" { - return empty, false, errors.New(errMsgRequiredMissing) - } - - v, err := parse(actual) - return v, false, err - } -} - -func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] { - return func(actual string) (T, bool, error) { - if actual == "" { - return def, true, nil - } - - v, err := parse(actual) - return v, false, err - } -} - -func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] { - return func(actual string) (T, bool, error) { - v, err := parse(actual) - return v, false, err - } -} - -type Constraint[T Number | string | bool] func(actual T) error - -func WithMinimum[T Number](expected T) Constraint[T] { - return func(actual T) error { - if actual < expected { - return errors.New(errMsgMinValueConstraint) - } - - return nil - } -} - -func WithMaximum[T Number](expected T) Constraint[T] { - return func(actual T) error { - if actual > expected { - return errors.New(errMsgMaxValueConstraint) - } - - return nil - } -} - -// parseNumericParameter parses a numeric parameter to its respective type. -func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) { - v, ok, err := fn(param) - if err != nil { - return 0, err - } - - if !ok { - for _, check := range checks { - if err := check(v); err != nil { - return 0, err - } - } - } - - return v, nil -} - -// parseBoolParameter parses a string parameter to a bool -func parseBoolParameter(param string, fn Operation[bool]) (bool, error) { - v, _, err := fn(param) - return v, err -} - -// parseNumericArrayParameter parses a string parameter containing array of values to its respective type. -func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) - } - - return nil, nil - } - - str := strings.Split(param, delim) - values := make([]T, len(str)) - - for i, s := range str { - v, ok, err := fn(s) - if err != nil { - return nil, err - } - - if !ok { - for _, check := range checks { - if err := check(v); err != nil { - return nil, err - } - } - } - - values[i] = v - } - - return values, nil -} - - -// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered. -func parseQuery(rawQuery string) (url.Values, error) { - return url.ParseQuery(rawQuery) -} \ No newline at end of file diff --git a/samples/openapi3/server/petstore/go/go-petstore/go/helpers.go b/samples/openapi3/server/petstore/go/go-petstore/go/helpers.go index 027954b0c9b..6f710348903 100644 --- a/samples/openapi3/server/petstore/go/go-petstore/go/helpers.go +++ b/samples/openapi3/server/petstore/go/go-petstore/go/helpers.go @@ -11,7 +11,17 @@ package petstoreserver import ( + "encoding/json" + "errors" + "io" + "mime/multipart" + "net/http" + "net/url" + "os" "reflect" + "strconv" + "strings" + "time" ) // Response return a ImplResponse struct filled @@ -69,3 +79,289 @@ func AssertRecurseValueRequired[T any](value reflect.Value, callback func(T) err } return nil } + +// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code +func EncodeJSONResponse(i interface{}, status *int, headers map[string][]string, w http.ResponseWriter) error { + wHeader := w.Header() + for key, values := range headers { + for _, value := range values { + wHeader.Add(key, value) + } + } + + f, ok := i.(*os.File) + if ok { + data, err := io.ReadAll(f) + if err != nil { + return err + } + wHeader.Set("Content-Type", http.DetectContentType(data)) + wHeader.Set("Content-Disposition", "attachment; filename="+f.Name()) + if status != nil { + w.WriteHeader(*status) + } else { + w.WriteHeader(http.StatusOK) + } + _, err = w.Write(data) + return err + } + wHeader.Set("Content-Type", "application/json; charset=UTF-8") + + if status != nil { + w.WriteHeader(*status) + } else { + w.WriteHeader(http.StatusOK) + } + + if i != nil { + return json.NewEncoder(w).Encode(i) + } + + return nil +} + +// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file +func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) { + _, fileHeader, err := r.FormFile(key) + if err != nil { + return nil, err + } + + return readFileHeaderToTempFile(fileHeader) +} + +// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files +func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) { + if err := r.ParseMultipartForm(32 << 20); err != nil { + return nil, err + } + + files := make([]*os.File, 0, len(r.MultipartForm.File[key])) + + for _, fileHeader := range r.MultipartForm.File[key] { + file, err := readFileHeaderToTempFile(fileHeader) + if err != nil { + return nil, err + } + + files = append(files, file) + } + + return files, nil +} + +// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file +func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) { + formFile, err := fileHeader.Open() + if err != nil { + return nil, err + } + + defer formFile.Close() + + // Use .* as suffix, because the asterisk is a placeholder for the random value, + // and the period allows consumers of this file to remove the suffix to obtain the original file name + file, err := os.CreateTemp("", fileHeader.Filename+".*") + if err != nil { + return nil, err + } + + defer file.Close() + + _, err = io.Copy(file, formFile) + if err != nil { + return nil, err + } + + 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 +} + +type ParseString[T Number | string | bool] func(v string) (T, error) + +// parseFloat64 parses a string parameter to an float64. +func parseFloat64(param string) (float64, error) { + if param == "" { + return 0, nil + } + + return strconv.ParseFloat(param, 64) +} + +// parseFloat32 parses a string parameter to an float32. +func parseFloat32(param string) (float32, error) { + if param == "" { + return 0, nil + } + + v, err := strconv.ParseFloat(param, 32) + return float32(v), err +} + +// parseInt64 parses a string parameter to an int64. +func parseInt64(param string) (int64, error) { + if param == "" { + return 0, nil + } + + return strconv.ParseInt(param, 10, 64) +} + +// parseInt32 parses a string parameter to an int32. +func parseInt32(param string) (int32, error) { + if param == "" { + return 0, nil + } + + val, err := strconv.ParseInt(param, 10, 32) + return int32(val), err +} + +// parseBool parses a string parameter to an bool. +func parseBool(param string) (bool, error) { + if param == "" { + return false, nil + } + + return strconv.ParseBool(param) +} + +type Operation[T Number | string | bool] func(actual string) (T, bool, error) + +func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] { + var empty T + return func(actual string) (T, bool, error) { + if actual == "" { + return empty, false, errors.New(errMsgRequiredMissing) + } + + v, err := parse(actual) + return v, false, err + } +} + +func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] { + return func(actual string) (T, bool, error) { + if actual == "" { + return def, true, nil + } + + v, err := parse(actual) + return v, false, err + } +} + +func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] { + return func(actual string) (T, bool, error) { + v, err := parse(actual) + return v, false, err + } +} + +type Constraint[T Number | string | bool] func(actual T) error + +func WithMinimum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual < expected { + return errors.New(errMsgMinValueConstraint) + } + + return nil + } +} + +func WithMaximum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual > expected { + return errors.New(errMsgMaxValueConstraint) + } + + return nil + } +} + +// parseNumericParameter parses a numeric parameter to its respective type. +func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) { + v, ok, err := fn(param) + if err != nil { + return 0, err + } + + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return 0, err + } + } + } + + return v, nil +} + +// parseBoolParameter parses a string parameter to a bool +func parseBoolParameter(param string, fn Operation[bool]) (bool, error) { + v, _, err := fn(param) + return v, err +} + +// parseNumericArrayParameter parses a string parameter containing array of values to its respective type. +func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) { + if param == "" { + if required { + return nil, errors.New(errMsgRequiredMissing) + } + + return nil, nil + } + + str := strings.Split(param, delim) + values := make([]T, len(str)) + + for i, s := range str { + v, ok, err := fn(s) + if err != nil { + return nil, err + } + + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return nil, err + } + } + } + + values[i] = v + } + + return values, nil +} + +// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered. +func parseQuery(rawQuery string) (url.Values, error) { + return url.ParseQuery(rawQuery) +} diff --git a/samples/openapi3/server/petstore/go/go-petstore/go/logger.go b/samples/openapi3/server/petstore/go/go-petstore/go/logger.go index b01819630b2..6f3cf7355f8 100644 --- a/samples/openapi3/server/petstore/go/go-petstore/go/logger.go +++ b/samples/openapi3/server/petstore/go/go-petstore/go/logger.go @@ -11,23 +11,10 @@ package petstoreserver import ( - "log" "net/http" - "time" + "github.com/go-chi/chi/v5/middleware" ) -func Logger(inner http.Handler, name string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - - inner.ServeHTTP(w, r) - - log.Printf( - "%s %s %s %s", - r.Method, - r.RequestURI, - name, - time.Since(start), - ) - }) +func Logger(inner http.Handler) http.Handler { + return middleware.Logger(inner) } 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 4d662e273c5..438895d1afe 100644 --- a/samples/openapi3/server/petstore/go/go-petstore/go/routers.go +++ b/samples/openapi3/server/petstore/go/go-petstore/go/routers.go @@ -11,18 +11,8 @@ package petstoreserver import ( - "encoding/json" - "errors" - "time" - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "io" - "mime/multipart" "net/http" - "net/url" - "os" - "strconv" - "strings" + "github.com/go-chi/chi/v5" ) // A Route defines the parameters for an api endpoint @@ -47,7 +37,7 @@ const errMsgMaxValueConstraint = "provided parameter is not respecting maximum v // NewRouter creates a new router for any number of api routers func NewRouter(routers ...Router) chi.Router { router := chi.NewRouter() - router.Use(middleware.Logger) + router.Use(Logger) for _, api := range routers { for _, route := range api.Routes() { var handler http.Handler = route.HandlerFunc @@ -57,290 +47,3 @@ func NewRouter(routers ...Router) chi.Router { return router } - -// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code -func EncodeJSONResponse(i interface{}, status *int, headers map[string][]string, w http.ResponseWriter) error { - wHeader := w.Header() - for key, values := range headers { - for _, value := range values { - wHeader.Add(key, value) - } - } - - f, ok := i.(*os.File) - if ok { - data, err := io.ReadAll(f) - if err != nil { - return err - } - wHeader.Set("Content-Type", http.DetectContentType(data)) - wHeader.Set("Content-Disposition", "attachment; filename="+f.Name()) - if status != nil { - w.WriteHeader(*status) - } else { - w.WriteHeader(http.StatusOK) - } - _, err = w.Write(data) - return err - } - wHeader.Set("Content-Type", "application/json; charset=UTF-8") - - if status != nil { - w.WriteHeader(*status) - } else { - w.WriteHeader(http.StatusOK) - } - - if i != nil { - return json.NewEncoder(w).Encode(i) - } - - return nil -} - -// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file -func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) { - _, fileHeader, err := r.FormFile(key) - if err != nil { - return nil, err - } - - return readFileHeaderToTempFile(fileHeader) -} - -// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files -func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) { - if err := r.ParseMultipartForm(32 << 20); err != nil { - return nil, err - } - - files := make([]*os.File, 0, len(r.MultipartForm.File[key])) - - for _, fileHeader := range r.MultipartForm.File[key] { - file, err := readFileHeaderToTempFile(fileHeader) - if err != nil { - return nil, err - } - - files = append(files, file) - } - - return files, nil -} - -// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file -func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) { - formFile, err := fileHeader.Open() - if err != nil { - return nil, err - } - - defer formFile.Close() - - // Use .* as suffix, because the asterisk is a placeholder for the random value, - // and the period allows consumers of this file to remove the suffix to obtain the original file name - file, err := os.CreateTemp("", fileHeader.Filename+".*") - if err != nil { - return nil, err - } - - defer file.Close() - - _, err = io.Copy(file, formFile) - if err != nil { - return nil, err - } - - 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 -} - -type ParseString[T Number | string | bool] func(v string) (T, error) - -// parseFloat64 parses a string parameter to an float64. -func parseFloat64(param string) (float64, error) { - if param == "" { - return 0, nil - } - - return strconv.ParseFloat(param, 64) -} - -// parseFloat32 parses a string parameter to an float32. -func parseFloat32(param string) (float32, error) { - if param == "" { - return 0, nil - } - - v, err := strconv.ParseFloat(param, 32) - return float32(v), err -} - -// parseInt64 parses a string parameter to an int64. -func parseInt64(param string) (int64, error) { - if param == "" { - return 0, nil - } - - return strconv.ParseInt(param, 10, 64) -} - -// parseInt32 parses a string parameter to an int32. -func parseInt32(param string) (int32, error) { - if param == "" { - return 0, nil - } - - val, err := strconv.ParseInt(param, 10, 32) - return int32(val), err -} - -// parseBool parses a string parameter to an bool. -func parseBool(param string) (bool, error) { - if param == "" { - return false, nil - } - - return strconv.ParseBool(param) -} - -type Operation[T Number | string | bool] func(actual string) (T, bool, error) - -func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] { - var empty T - return func(actual string) (T, bool, error) { - if actual == "" { - return empty, false, errors.New(errMsgRequiredMissing) - } - - v, err := parse(actual) - return v, false, err - } -} - -func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] { - return func(actual string) (T, bool, error) { - if actual == "" { - return def, true, nil - } - - v, err := parse(actual) - return v, false, err - } -} - -func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] { - return func(actual string) (T, bool, error) { - v, err := parse(actual) - return v, false, err - } -} - -type Constraint[T Number | string | bool] func(actual T) error - -func WithMinimum[T Number](expected T) Constraint[T] { - return func(actual T) error { - if actual < expected { - return errors.New(errMsgMinValueConstraint) - } - - return nil - } -} - -func WithMaximum[T Number](expected T) Constraint[T] { - return func(actual T) error { - if actual > expected { - return errors.New(errMsgMaxValueConstraint) - } - - return nil - } -} - -// parseNumericParameter parses a numeric parameter to its respective type. -func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) { - v, ok, err := fn(param) - if err != nil { - return 0, err - } - - if !ok { - for _, check := range checks { - if err := check(v); err != nil { - return 0, err - } - } - } - - return v, nil -} - -// parseBoolParameter parses a string parameter to a bool -func parseBoolParameter(param string, fn Operation[bool]) (bool, error) { - v, _, err := fn(param) - return v, err -} - -// parseNumericArrayParameter parses a string parameter containing array of values to its respective type. -func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) - } - - return nil, nil - } - - str := strings.Split(param, delim) - values := make([]T, len(str)) - - for i, s := range str { - v, ok, err := fn(s) - if err != nil { - return nil, err - } - - if !ok { - for _, check := range checks { - if err := check(v); err != nil { - return nil, err - } - } - } - - values[i] = v - } - - return values, nil -} - - -// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered. -func parseQuery(rawQuery string) (url.Values, error) { - return url.ParseQuery(rawQuery) -} \ No newline at end of file diff --git a/samples/server/others/go-server/no-body-path-params/go/helpers.go b/samples/server/others/go-server/no-body-path-params/go/helpers.go index 9c9664853e6..1306a51d3d6 100644 --- a/samples/server/others/go-server/no-body-path-params/go/helpers.go +++ b/samples/server/others/go-server/no-body-path-params/go/helpers.go @@ -11,7 +11,17 @@ package petstoreserver import ( + "encoding/json" + "errors" + "io" + "mime/multipart" + "net/http" + "net/url" + "os" "reflect" + "strconv" + "strings" + "time" ) // Response return a ImplResponse struct filled @@ -69,3 +79,289 @@ func AssertRecurseValueRequired[T any](value reflect.Value, callback func(T) err } return nil } + +// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code +func EncodeJSONResponse(i interface{}, status *int, headers map[string][]string, w http.ResponseWriter) error { + wHeader := w.Header() + for key, values := range headers { + for _, value := range values { + wHeader.Add(key, value) + } + } + + f, ok := i.(*os.File) + if ok { + data, err := io.ReadAll(f) + if err != nil { + return err + } + wHeader.Set("Content-Type", http.DetectContentType(data)) + wHeader.Set("Content-Disposition", "attachment; filename="+f.Name()) + if status != nil { + w.WriteHeader(*status) + } else { + w.WriteHeader(http.StatusOK) + } + _, err = w.Write(data) + return err + } + wHeader.Set("Content-Type", "application/json; charset=UTF-8") + + if status != nil { + w.WriteHeader(*status) + } else { + w.WriteHeader(http.StatusOK) + } + + if i != nil { + return json.NewEncoder(w).Encode(i) + } + + return nil +} + +// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file +func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) { + _, fileHeader, err := r.FormFile(key) + if err != nil { + return nil, err + } + + return readFileHeaderToTempFile(fileHeader) +} + +// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files +func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) { + if err := r.ParseMultipartForm(32 << 20); err != nil { + return nil, err + } + + files := make([]*os.File, 0, len(r.MultipartForm.File[key])) + + for _, fileHeader := range r.MultipartForm.File[key] { + file, err := readFileHeaderToTempFile(fileHeader) + if err != nil { + return nil, err + } + + files = append(files, file) + } + + return files, nil +} + +// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file +func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) { + formFile, err := fileHeader.Open() + if err != nil { + return nil, err + } + + defer formFile.Close() + + // Use .* as suffix, because the asterisk is a placeholder for the random value, + // and the period allows consumers of this file to remove the suffix to obtain the original file name + file, err := os.CreateTemp("", fileHeader.Filename+".*") + if err != nil { + return nil, err + } + + defer file.Close() + + _, err = io.Copy(file, formFile) + if err != nil { + return nil, err + } + + 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 +} + +type ParseString[T Number | string | bool] func(v string) (T, error) + +// parseFloat64 parses a string parameter to an float64. +func parseFloat64(param string) (float64, error) { + if param == "" { + return 0, nil + } + + return strconv.ParseFloat(param, 64) +} + +// parseFloat32 parses a string parameter to an float32. +func parseFloat32(param string) (float32, error) { + if param == "" { + return 0, nil + } + + v, err := strconv.ParseFloat(param, 32) + return float32(v), err +} + +// parseInt64 parses a string parameter to an int64. +func parseInt64(param string) (int64, error) { + if param == "" { + return 0, nil + } + + return strconv.ParseInt(param, 10, 64) +} + +// parseInt32 parses a string parameter to an int32. +func parseInt32(param string) (int32, error) { + if param == "" { + return 0, nil + } + + val, err := strconv.ParseInt(param, 10, 32) + return int32(val), err +} + +// parseBool parses a string parameter to an bool. +func parseBool(param string) (bool, error) { + if param == "" { + return false, nil + } + + return strconv.ParseBool(param) +} + +type Operation[T Number | string | bool] func(actual string) (T, bool, error) + +func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] { + var empty T + return func(actual string) (T, bool, error) { + if actual == "" { + return empty, false, errors.New(errMsgRequiredMissing) + } + + v, err := parse(actual) + return v, false, err + } +} + +func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] { + return func(actual string) (T, bool, error) { + if actual == "" { + return def, true, nil + } + + v, err := parse(actual) + return v, false, err + } +} + +func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] { + return func(actual string) (T, bool, error) { + v, err := parse(actual) + return v, false, err + } +} + +type Constraint[T Number | string | bool] func(actual T) error + +func WithMinimum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual < expected { + return errors.New(errMsgMinValueConstraint) + } + + return nil + } +} + +func WithMaximum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual > expected { + return errors.New(errMsgMaxValueConstraint) + } + + return nil + } +} + +// parseNumericParameter parses a numeric parameter to its respective type. +func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) { + v, ok, err := fn(param) + if err != nil { + return 0, err + } + + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return 0, err + } + } + } + + return v, nil +} + +// parseBoolParameter parses a string parameter to a bool +func parseBoolParameter(param string, fn Operation[bool]) (bool, error) { + v, _, err := fn(param) + return v, err +} + +// parseNumericArrayParameter parses a string parameter containing array of values to its respective type. +func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) { + if param == "" { + if required { + return nil, errors.New(errMsgRequiredMissing) + } + + return nil, nil + } + + str := strings.Split(param, delim) + values := make([]T, len(str)) + + for i, s := range str { + v, ok, err := fn(s) + if err != nil { + return nil, err + } + + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return nil, err + } + } + } + + values[i] = v + } + + return values, nil +} + +// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered. +func parseQuery(rawQuery string) (url.Values, error) { + return url.ParseQuery(rawQuery) +} diff --git a/samples/server/others/go-server/no-body-path-params/go/logger.go b/samples/server/others/go-server/no-body-path-params/go/logger.go index 09211a7dc10..77dab1906cf 100644 --- a/samples/server/others/go-server/no-body-path-params/go/logger.go +++ b/samples/server/others/go-server/no-body-path-params/go/logger.go @@ -11,8 +11,8 @@ package petstoreserver import ( - "log" "net/http" + "log" "time" ) diff --git a/samples/server/others/go-server/no-body-path-params/go/routers.go b/samples/server/others/go-server/no-body-path-params/go/routers.go index 249c28f9e3b..d89a1de60ce 100644 --- a/samples/server/others/go-server/no-body-path-params/go/routers.go +++ b/samples/server/others/go-server/no-body-path-params/go/routers.go @@ -11,17 +11,8 @@ package petstoreserver import ( - "encoding/json" - "errors" - "time" - "github.com/gorilla/mux" - "io" - "mime/multipart" "net/http" - "net/url" - "os" - "strconv" - "strings" + "github.com/gorilla/mux" ) // A Route defines the parameters for an api endpoint @@ -61,290 +52,3 @@ func NewRouter(routers ...Router) *mux.Router { return router } - -// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code -func EncodeJSONResponse(i interface{}, status *int, headers map[string][]string, w http.ResponseWriter) error { - wHeader := w.Header() - for key, values := range headers { - for _, value := range values { - wHeader.Add(key, value) - } - } - - f, ok := i.(*os.File) - if ok { - data, err := io.ReadAll(f) - if err != nil { - return err - } - wHeader.Set("Content-Type", http.DetectContentType(data)) - wHeader.Set("Content-Disposition", "attachment; filename="+f.Name()) - if status != nil { - w.WriteHeader(*status) - } else { - w.WriteHeader(http.StatusOK) - } - _, err = w.Write(data) - return err - } - wHeader.Set("Content-Type", "application/json; charset=UTF-8") - - if status != nil { - w.WriteHeader(*status) - } else { - w.WriteHeader(http.StatusOK) - } - - if i != nil { - return json.NewEncoder(w).Encode(i) - } - - return nil -} - -// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file -func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) { - _, fileHeader, err := r.FormFile(key) - if err != nil { - return nil, err - } - - return readFileHeaderToTempFile(fileHeader) -} - -// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files -func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) { - if err := r.ParseMultipartForm(32 << 20); err != nil { - return nil, err - } - - files := make([]*os.File, 0, len(r.MultipartForm.File[key])) - - for _, fileHeader := range r.MultipartForm.File[key] { - file, err := readFileHeaderToTempFile(fileHeader) - if err != nil { - return nil, err - } - - files = append(files, file) - } - - return files, nil -} - -// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file -func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) { - formFile, err := fileHeader.Open() - if err != nil { - return nil, err - } - - defer formFile.Close() - - // Use .* as suffix, because the asterisk is a placeholder for the random value, - // and the period allows consumers of this file to remove the suffix to obtain the original file name - file, err := os.CreateTemp("", fileHeader.Filename+".*") - if err != nil { - return nil, err - } - - defer file.Close() - - _, err = io.Copy(file, formFile) - if err != nil { - return nil, err - } - - 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 -} - -type ParseString[T Number | string | bool] func(v string) (T, error) - -// parseFloat64 parses a string parameter to an float64. -func parseFloat64(param string) (float64, error) { - if param == "" { - return 0, nil - } - - return strconv.ParseFloat(param, 64) -} - -// parseFloat32 parses a string parameter to an float32. -func parseFloat32(param string) (float32, error) { - if param == "" { - return 0, nil - } - - v, err := strconv.ParseFloat(param, 32) - return float32(v), err -} - -// parseInt64 parses a string parameter to an int64. -func parseInt64(param string) (int64, error) { - if param == "" { - return 0, nil - } - - return strconv.ParseInt(param, 10, 64) -} - -// parseInt32 parses a string parameter to an int32. -func parseInt32(param string) (int32, error) { - if param == "" { - return 0, nil - } - - val, err := strconv.ParseInt(param, 10, 32) - return int32(val), err -} - -// parseBool parses a string parameter to an bool. -func parseBool(param string) (bool, error) { - if param == "" { - return false, nil - } - - return strconv.ParseBool(param) -} - -type Operation[T Number | string | bool] func(actual string) (T, bool, error) - -func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] { - var empty T - return func(actual string) (T, bool, error) { - if actual == "" { - return empty, false, errors.New(errMsgRequiredMissing) - } - - v, err := parse(actual) - return v, false, err - } -} - -func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] { - return func(actual string) (T, bool, error) { - if actual == "" { - return def, true, nil - } - - v, err := parse(actual) - return v, false, err - } -} - -func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] { - return func(actual string) (T, bool, error) { - v, err := parse(actual) - return v, false, err - } -} - -type Constraint[T Number | string | bool] func(actual T) error - -func WithMinimum[T Number](expected T) Constraint[T] { - return func(actual T) error { - if actual < expected { - return errors.New(errMsgMinValueConstraint) - } - - return nil - } -} - -func WithMaximum[T Number](expected T) Constraint[T] { - return func(actual T) error { - if actual > expected { - return errors.New(errMsgMaxValueConstraint) - } - - return nil - } -} - -// parseNumericParameter parses a numeric parameter to its respective type. -func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) { - v, ok, err := fn(param) - if err != nil { - return 0, err - } - - if !ok { - for _, check := range checks { - if err := check(v); err != nil { - return 0, err - } - } - } - - return v, nil -} - -// parseBoolParameter parses a string parameter to a bool -func parseBoolParameter(param string, fn Operation[bool]) (bool, error) { - v, _, err := fn(param) - return v, err -} - -// parseNumericArrayParameter parses a string parameter containing array of values to its respective type. -func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) - } - - return nil, nil - } - - str := strings.Split(param, delim) - values := make([]T, len(str)) - - for i, s := range str { - v, ok, err := fn(s) - if err != nil { - return nil, err - } - - if !ok { - for _, check := range checks { - if err := check(v); err != nil { - return nil, err - } - } - } - - values[i] = v - } - - return values, nil -} - - -// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered. -func parseQuery(rawQuery string) (url.Values, error) { - return url.ParseQuery(rawQuery) -} \ No newline at end of file diff --git a/samples/server/petstore/go-api-server/go.sum b/samples/server/petstore/go-api-server/go.sum new file mode 100644 index 00000000000..535028803d2 --- /dev/null +++ b/samples/server/petstore/go-api-server/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/samples/server/petstore/go-api-server/go/helpers.go b/samples/server/petstore/go-api-server/go/helpers.go index 027954b0c9b..6f710348903 100644 --- a/samples/server/petstore/go-api-server/go/helpers.go +++ b/samples/server/petstore/go-api-server/go/helpers.go @@ -11,7 +11,17 @@ package petstoreserver import ( + "encoding/json" + "errors" + "io" + "mime/multipart" + "net/http" + "net/url" + "os" "reflect" + "strconv" + "strings" + "time" ) // Response return a ImplResponse struct filled @@ -69,3 +79,289 @@ func AssertRecurseValueRequired[T any](value reflect.Value, callback func(T) err } return nil } + +// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code +func EncodeJSONResponse(i interface{}, status *int, headers map[string][]string, w http.ResponseWriter) error { + wHeader := w.Header() + for key, values := range headers { + for _, value := range values { + wHeader.Add(key, value) + } + } + + f, ok := i.(*os.File) + if ok { + data, err := io.ReadAll(f) + if err != nil { + return err + } + wHeader.Set("Content-Type", http.DetectContentType(data)) + wHeader.Set("Content-Disposition", "attachment; filename="+f.Name()) + if status != nil { + w.WriteHeader(*status) + } else { + w.WriteHeader(http.StatusOK) + } + _, err = w.Write(data) + return err + } + wHeader.Set("Content-Type", "application/json; charset=UTF-8") + + if status != nil { + w.WriteHeader(*status) + } else { + w.WriteHeader(http.StatusOK) + } + + if i != nil { + return json.NewEncoder(w).Encode(i) + } + + return nil +} + +// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file +func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) { + _, fileHeader, err := r.FormFile(key) + if err != nil { + return nil, err + } + + return readFileHeaderToTempFile(fileHeader) +} + +// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files +func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) { + if err := r.ParseMultipartForm(32 << 20); err != nil { + return nil, err + } + + files := make([]*os.File, 0, len(r.MultipartForm.File[key])) + + for _, fileHeader := range r.MultipartForm.File[key] { + file, err := readFileHeaderToTempFile(fileHeader) + if err != nil { + return nil, err + } + + files = append(files, file) + } + + return files, nil +} + +// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file +func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) { + formFile, err := fileHeader.Open() + if err != nil { + return nil, err + } + + defer formFile.Close() + + // Use .* as suffix, because the asterisk is a placeholder for the random value, + // and the period allows consumers of this file to remove the suffix to obtain the original file name + file, err := os.CreateTemp("", fileHeader.Filename+".*") + if err != nil { + return nil, err + } + + defer file.Close() + + _, err = io.Copy(file, formFile) + if err != nil { + return nil, err + } + + 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 +} + +type ParseString[T Number | string | bool] func(v string) (T, error) + +// parseFloat64 parses a string parameter to an float64. +func parseFloat64(param string) (float64, error) { + if param == "" { + return 0, nil + } + + return strconv.ParseFloat(param, 64) +} + +// parseFloat32 parses a string parameter to an float32. +func parseFloat32(param string) (float32, error) { + if param == "" { + return 0, nil + } + + v, err := strconv.ParseFloat(param, 32) + return float32(v), err +} + +// parseInt64 parses a string parameter to an int64. +func parseInt64(param string) (int64, error) { + if param == "" { + return 0, nil + } + + return strconv.ParseInt(param, 10, 64) +} + +// parseInt32 parses a string parameter to an int32. +func parseInt32(param string) (int32, error) { + if param == "" { + return 0, nil + } + + val, err := strconv.ParseInt(param, 10, 32) + return int32(val), err +} + +// parseBool parses a string parameter to an bool. +func parseBool(param string) (bool, error) { + if param == "" { + return false, nil + } + + return strconv.ParseBool(param) +} + +type Operation[T Number | string | bool] func(actual string) (T, bool, error) + +func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] { + var empty T + return func(actual string) (T, bool, error) { + if actual == "" { + return empty, false, errors.New(errMsgRequiredMissing) + } + + v, err := parse(actual) + return v, false, err + } +} + +func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] { + return func(actual string) (T, bool, error) { + if actual == "" { + return def, true, nil + } + + v, err := parse(actual) + return v, false, err + } +} + +func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] { + return func(actual string) (T, bool, error) { + v, err := parse(actual) + return v, false, err + } +} + +type Constraint[T Number | string | bool] func(actual T) error + +func WithMinimum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual < expected { + return errors.New(errMsgMinValueConstraint) + } + + return nil + } +} + +func WithMaximum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual > expected { + return errors.New(errMsgMaxValueConstraint) + } + + return nil + } +} + +// parseNumericParameter parses a numeric parameter to its respective type. +func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) { + v, ok, err := fn(param) + if err != nil { + return 0, err + } + + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return 0, err + } + } + } + + return v, nil +} + +// parseBoolParameter parses a string parameter to a bool +func parseBoolParameter(param string, fn Operation[bool]) (bool, error) { + v, _, err := fn(param) + return v, err +} + +// parseNumericArrayParameter parses a string parameter containing array of values to its respective type. +func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) { + if param == "" { + if required { + return nil, errors.New(errMsgRequiredMissing) + } + + return nil, nil + } + + str := strings.Split(param, delim) + values := make([]T, len(str)) + + for i, s := range str { + v, ok, err := fn(s) + if err != nil { + return nil, err + } + + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return nil, err + } + } + } + + values[i] = v + } + + return values, nil +} + +// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered. +func parseQuery(rawQuery string) (url.Values, error) { + return url.ParseQuery(rawQuery) +} diff --git a/samples/server/petstore/go-api-server/go/logger.go b/samples/server/petstore/go-api-server/go/logger.go index b01819630b2..fc11db53049 100644 --- a/samples/server/petstore/go-api-server/go/logger.go +++ b/samples/server/petstore/go-api-server/go/logger.go @@ -11,8 +11,8 @@ package petstoreserver import ( - "log" "net/http" + "log" "time" ) diff --git a/samples/server/petstore/go-api-server/go/routers.go b/samples/server/petstore/go-api-server/go/routers.go index 183a2d8d10f..cbb3a397f4a 100644 --- a/samples/server/petstore/go-api-server/go/routers.go +++ b/samples/server/petstore/go-api-server/go/routers.go @@ -11,17 +11,8 @@ package petstoreserver import ( - "encoding/json" - "errors" - "time" - "github.com/gorilla/mux" - "io" - "mime/multipart" "net/http" - "net/url" - "os" - "strconv" - "strings" + "github.com/gorilla/mux" ) // A Route defines the parameters for an api endpoint @@ -61,290 +52,3 @@ func NewRouter(routers ...Router) *mux.Router { return router } - -// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code -func EncodeJSONResponse(i interface{}, status *int, headers map[string][]string, w http.ResponseWriter) error { - wHeader := w.Header() - for key, values := range headers { - for _, value := range values { - wHeader.Add(key, value) - } - } - - f, ok := i.(*os.File) - if ok { - data, err := io.ReadAll(f) - if err != nil { - return err - } - wHeader.Set("Content-Type", http.DetectContentType(data)) - wHeader.Set("Content-Disposition", "attachment; filename="+f.Name()) - if status != nil { - w.WriteHeader(*status) - } else { - w.WriteHeader(http.StatusOK) - } - _, err = w.Write(data) - return err - } - wHeader.Set("Content-Type", "application/json; charset=UTF-8") - - if status != nil { - w.WriteHeader(*status) - } else { - w.WriteHeader(http.StatusOK) - } - - if i != nil { - return json.NewEncoder(w).Encode(i) - } - - return nil -} - -// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file -func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) { - _, fileHeader, err := r.FormFile(key) - if err != nil { - return nil, err - } - - return readFileHeaderToTempFile(fileHeader) -} - -// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files -func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) { - if err := r.ParseMultipartForm(32 << 20); err != nil { - return nil, err - } - - files := make([]*os.File, 0, len(r.MultipartForm.File[key])) - - for _, fileHeader := range r.MultipartForm.File[key] { - file, err := readFileHeaderToTempFile(fileHeader) - if err != nil { - return nil, err - } - - files = append(files, file) - } - - return files, nil -} - -// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file -func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) { - formFile, err := fileHeader.Open() - if err != nil { - return nil, err - } - - defer formFile.Close() - - // Use .* as suffix, because the asterisk is a placeholder for the random value, - // and the period allows consumers of this file to remove the suffix to obtain the original file name - file, err := os.CreateTemp("", fileHeader.Filename+".*") - if err != nil { - return nil, err - } - - defer file.Close() - - _, err = io.Copy(file, formFile) - if err != nil { - return nil, err - } - - 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 -} - -type ParseString[T Number | string | bool] func(v string) (T, error) - -// parseFloat64 parses a string parameter to an float64. -func parseFloat64(param string) (float64, error) { - if param == "" { - return 0, nil - } - - return strconv.ParseFloat(param, 64) -} - -// parseFloat32 parses a string parameter to an float32. -func parseFloat32(param string) (float32, error) { - if param == "" { - return 0, nil - } - - v, err := strconv.ParseFloat(param, 32) - return float32(v), err -} - -// parseInt64 parses a string parameter to an int64. -func parseInt64(param string) (int64, error) { - if param == "" { - return 0, nil - } - - return strconv.ParseInt(param, 10, 64) -} - -// parseInt32 parses a string parameter to an int32. -func parseInt32(param string) (int32, error) { - if param == "" { - return 0, nil - } - - val, err := strconv.ParseInt(param, 10, 32) - return int32(val), err -} - -// parseBool parses a string parameter to an bool. -func parseBool(param string) (bool, error) { - if param == "" { - return false, nil - } - - return strconv.ParseBool(param) -} - -type Operation[T Number | string | bool] func(actual string) (T, bool, error) - -func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] { - var empty T - return func(actual string) (T, bool, error) { - if actual == "" { - return empty, false, errors.New(errMsgRequiredMissing) - } - - v, err := parse(actual) - return v, false, err - } -} - -func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] { - return func(actual string) (T, bool, error) { - if actual == "" { - return def, true, nil - } - - v, err := parse(actual) - return v, false, err - } -} - -func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] { - return func(actual string) (T, bool, error) { - v, err := parse(actual) - return v, false, err - } -} - -type Constraint[T Number | string | bool] func(actual T) error - -func WithMinimum[T Number](expected T) Constraint[T] { - return func(actual T) error { - if actual < expected { - return errors.New(errMsgMinValueConstraint) - } - - return nil - } -} - -func WithMaximum[T Number](expected T) Constraint[T] { - return func(actual T) error { - if actual > expected { - return errors.New(errMsgMaxValueConstraint) - } - - return nil - } -} - -// parseNumericParameter parses a numeric parameter to its respective type. -func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) { - v, ok, err := fn(param) - if err != nil { - return 0, err - } - - if !ok { - for _, check := range checks { - if err := check(v); err != nil { - return 0, err - } - } - } - - return v, nil -} - -// parseBoolParameter parses a string parameter to a bool -func parseBoolParameter(param string, fn Operation[bool]) (bool, error) { - v, _, err := fn(param) - return v, err -} - -// parseNumericArrayParameter parses a string parameter containing array of values to its respective type. -func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) - } - - return nil, nil - } - - str := strings.Split(param, delim) - values := make([]T, len(str)) - - for i, s := range str { - v, ok, err := fn(s) - if err != nil { - return nil, err - } - - if !ok { - for _, check := range checks { - if err := check(v); err != nil { - return nil, err - } - } - } - - values[i] = v - } - - return values, nil -} - - -// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered. -func parseQuery(rawQuery string) (url.Values, error) { - return url.ParseQuery(rawQuery) -} \ No newline at end of file diff --git a/samples/server/petstore/go-api-server/samples_tests/routers_test.go b/samples/server/petstore/go-api-server/samples_tests/routers_test.go index 6c2686fb86d..dcc0e491c86 100644 --- a/samples/server/petstore/go-api-server/samples_tests/routers_test.go +++ b/samples/server/petstore/go-api-server/samples_tests/routers_test.go @@ -31,8 +31,8 @@ func Test_Routers(t *testing.T) { lines := utils.ReadLines(filepath) expected := "type Routes map[string]Route" - if lines[34] != expected { - t.Errorf("Expected '%s', but got '%s'", expected, lines[34]) + if lines[25] != expected { + t.Errorf("Expected '%s', but got '%s'", expected, lines[25]) } }) } diff --git a/samples/server/petstore/go-chi-server/go/helpers.go b/samples/server/petstore/go-chi-server/go/helpers.go index 027954b0c9b..6f710348903 100644 --- a/samples/server/petstore/go-chi-server/go/helpers.go +++ b/samples/server/petstore/go-chi-server/go/helpers.go @@ -11,7 +11,17 @@ package petstoreserver import ( + "encoding/json" + "errors" + "io" + "mime/multipart" + "net/http" + "net/url" + "os" "reflect" + "strconv" + "strings" + "time" ) // Response return a ImplResponse struct filled @@ -69,3 +79,289 @@ func AssertRecurseValueRequired[T any](value reflect.Value, callback func(T) err } return nil } + +// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code +func EncodeJSONResponse(i interface{}, status *int, headers map[string][]string, w http.ResponseWriter) error { + wHeader := w.Header() + for key, values := range headers { + for _, value := range values { + wHeader.Add(key, value) + } + } + + f, ok := i.(*os.File) + if ok { + data, err := io.ReadAll(f) + if err != nil { + return err + } + wHeader.Set("Content-Type", http.DetectContentType(data)) + wHeader.Set("Content-Disposition", "attachment; filename="+f.Name()) + if status != nil { + w.WriteHeader(*status) + } else { + w.WriteHeader(http.StatusOK) + } + _, err = w.Write(data) + return err + } + wHeader.Set("Content-Type", "application/json; charset=UTF-8") + + if status != nil { + w.WriteHeader(*status) + } else { + w.WriteHeader(http.StatusOK) + } + + if i != nil { + return json.NewEncoder(w).Encode(i) + } + + return nil +} + +// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file +func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) { + _, fileHeader, err := r.FormFile(key) + if err != nil { + return nil, err + } + + return readFileHeaderToTempFile(fileHeader) +} + +// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files +func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) { + if err := r.ParseMultipartForm(32 << 20); err != nil { + return nil, err + } + + files := make([]*os.File, 0, len(r.MultipartForm.File[key])) + + for _, fileHeader := range r.MultipartForm.File[key] { + file, err := readFileHeaderToTempFile(fileHeader) + if err != nil { + return nil, err + } + + files = append(files, file) + } + + return files, nil +} + +// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file +func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) { + formFile, err := fileHeader.Open() + if err != nil { + return nil, err + } + + defer formFile.Close() + + // Use .* as suffix, because the asterisk is a placeholder for the random value, + // and the period allows consumers of this file to remove the suffix to obtain the original file name + file, err := os.CreateTemp("", fileHeader.Filename+".*") + if err != nil { + return nil, err + } + + defer file.Close() + + _, err = io.Copy(file, formFile) + if err != nil { + return nil, err + } + + 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 +} + +type ParseString[T Number | string | bool] func(v string) (T, error) + +// parseFloat64 parses a string parameter to an float64. +func parseFloat64(param string) (float64, error) { + if param == "" { + return 0, nil + } + + return strconv.ParseFloat(param, 64) +} + +// parseFloat32 parses a string parameter to an float32. +func parseFloat32(param string) (float32, error) { + if param == "" { + return 0, nil + } + + v, err := strconv.ParseFloat(param, 32) + return float32(v), err +} + +// parseInt64 parses a string parameter to an int64. +func parseInt64(param string) (int64, error) { + if param == "" { + return 0, nil + } + + return strconv.ParseInt(param, 10, 64) +} + +// parseInt32 parses a string parameter to an int32. +func parseInt32(param string) (int32, error) { + if param == "" { + return 0, nil + } + + val, err := strconv.ParseInt(param, 10, 32) + return int32(val), err +} + +// parseBool parses a string parameter to an bool. +func parseBool(param string) (bool, error) { + if param == "" { + return false, nil + } + + return strconv.ParseBool(param) +} + +type Operation[T Number | string | bool] func(actual string) (T, bool, error) + +func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] { + var empty T + return func(actual string) (T, bool, error) { + if actual == "" { + return empty, false, errors.New(errMsgRequiredMissing) + } + + v, err := parse(actual) + return v, false, err + } +} + +func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] { + return func(actual string) (T, bool, error) { + if actual == "" { + return def, true, nil + } + + v, err := parse(actual) + return v, false, err + } +} + +func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] { + return func(actual string) (T, bool, error) { + v, err := parse(actual) + return v, false, err + } +} + +type Constraint[T Number | string | bool] func(actual T) error + +func WithMinimum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual < expected { + return errors.New(errMsgMinValueConstraint) + } + + return nil + } +} + +func WithMaximum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual > expected { + return errors.New(errMsgMaxValueConstraint) + } + + return nil + } +} + +// parseNumericParameter parses a numeric parameter to its respective type. +func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) { + v, ok, err := fn(param) + if err != nil { + return 0, err + } + + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return 0, err + } + } + } + + return v, nil +} + +// parseBoolParameter parses a string parameter to a bool +func parseBoolParameter(param string, fn Operation[bool]) (bool, error) { + v, _, err := fn(param) + return v, err +} + +// parseNumericArrayParameter parses a string parameter containing array of values to its respective type. +func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) { + if param == "" { + if required { + return nil, errors.New(errMsgRequiredMissing) + } + + return nil, nil + } + + str := strings.Split(param, delim) + values := make([]T, len(str)) + + for i, s := range str { + v, ok, err := fn(s) + if err != nil { + return nil, err + } + + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return nil, err + } + } + } + + values[i] = v + } + + return values, nil +} + +// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered. +func parseQuery(rawQuery string) (url.Values, error) { + return url.ParseQuery(rawQuery) +} diff --git a/samples/server/petstore/go-chi-server/go/logger.go b/samples/server/petstore/go-chi-server/go/logger.go index b01819630b2..6f3cf7355f8 100644 --- a/samples/server/petstore/go-chi-server/go/logger.go +++ b/samples/server/petstore/go-chi-server/go/logger.go @@ -11,23 +11,10 @@ package petstoreserver import ( - "log" "net/http" - "time" + "github.com/go-chi/chi/v5/middleware" ) -func Logger(inner http.Handler, name string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - - inner.ServeHTTP(w, r) - - log.Printf( - "%s %s %s %s", - r.Method, - r.RequestURI, - name, - time.Since(start), - ) - }) +func Logger(inner http.Handler) http.Handler { + return middleware.Logger(inner) } diff --git a/samples/server/petstore/go-chi-server/go/routers.go b/samples/server/petstore/go-chi-server/go/routers.go index 4d662e273c5..438895d1afe 100644 --- a/samples/server/petstore/go-chi-server/go/routers.go +++ b/samples/server/petstore/go-chi-server/go/routers.go @@ -11,18 +11,8 @@ package petstoreserver import ( - "encoding/json" - "errors" - "time" - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "io" - "mime/multipart" "net/http" - "net/url" - "os" - "strconv" - "strings" + "github.com/go-chi/chi/v5" ) // A Route defines the parameters for an api endpoint @@ -47,7 +37,7 @@ const errMsgMaxValueConstraint = "provided parameter is not respecting maximum v // NewRouter creates a new router for any number of api routers func NewRouter(routers ...Router) chi.Router { router := chi.NewRouter() - router.Use(middleware.Logger) + router.Use(Logger) for _, api := range routers { for _, route := range api.Routes() { var handler http.Handler = route.HandlerFunc @@ -57,290 +47,3 @@ func NewRouter(routers ...Router) chi.Router { return router } - -// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code -func EncodeJSONResponse(i interface{}, status *int, headers map[string][]string, w http.ResponseWriter) error { - wHeader := w.Header() - for key, values := range headers { - for _, value := range values { - wHeader.Add(key, value) - } - } - - f, ok := i.(*os.File) - if ok { - data, err := io.ReadAll(f) - if err != nil { - return err - } - wHeader.Set("Content-Type", http.DetectContentType(data)) - wHeader.Set("Content-Disposition", "attachment; filename="+f.Name()) - if status != nil { - w.WriteHeader(*status) - } else { - w.WriteHeader(http.StatusOK) - } - _, err = w.Write(data) - return err - } - wHeader.Set("Content-Type", "application/json; charset=UTF-8") - - if status != nil { - w.WriteHeader(*status) - } else { - w.WriteHeader(http.StatusOK) - } - - if i != nil { - return json.NewEncoder(w).Encode(i) - } - - return nil -} - -// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file -func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) { - _, fileHeader, err := r.FormFile(key) - if err != nil { - return nil, err - } - - return readFileHeaderToTempFile(fileHeader) -} - -// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files -func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) { - if err := r.ParseMultipartForm(32 << 20); err != nil { - return nil, err - } - - files := make([]*os.File, 0, len(r.MultipartForm.File[key])) - - for _, fileHeader := range r.MultipartForm.File[key] { - file, err := readFileHeaderToTempFile(fileHeader) - if err != nil { - return nil, err - } - - files = append(files, file) - } - - return files, nil -} - -// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file -func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) { - formFile, err := fileHeader.Open() - if err != nil { - return nil, err - } - - defer formFile.Close() - - // Use .* as suffix, because the asterisk is a placeholder for the random value, - // and the period allows consumers of this file to remove the suffix to obtain the original file name - file, err := os.CreateTemp("", fileHeader.Filename+".*") - if err != nil { - return nil, err - } - - defer file.Close() - - _, err = io.Copy(file, formFile) - if err != nil { - return nil, err - } - - 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 -} - -type ParseString[T Number | string | bool] func(v string) (T, error) - -// parseFloat64 parses a string parameter to an float64. -func parseFloat64(param string) (float64, error) { - if param == "" { - return 0, nil - } - - return strconv.ParseFloat(param, 64) -} - -// parseFloat32 parses a string parameter to an float32. -func parseFloat32(param string) (float32, error) { - if param == "" { - return 0, nil - } - - v, err := strconv.ParseFloat(param, 32) - return float32(v), err -} - -// parseInt64 parses a string parameter to an int64. -func parseInt64(param string) (int64, error) { - if param == "" { - return 0, nil - } - - return strconv.ParseInt(param, 10, 64) -} - -// parseInt32 parses a string parameter to an int32. -func parseInt32(param string) (int32, error) { - if param == "" { - return 0, nil - } - - val, err := strconv.ParseInt(param, 10, 32) - return int32(val), err -} - -// parseBool parses a string parameter to an bool. -func parseBool(param string) (bool, error) { - if param == "" { - return false, nil - } - - return strconv.ParseBool(param) -} - -type Operation[T Number | string | bool] func(actual string) (T, bool, error) - -func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] { - var empty T - return func(actual string) (T, bool, error) { - if actual == "" { - return empty, false, errors.New(errMsgRequiredMissing) - } - - v, err := parse(actual) - return v, false, err - } -} - -func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] { - return func(actual string) (T, bool, error) { - if actual == "" { - return def, true, nil - } - - v, err := parse(actual) - return v, false, err - } -} - -func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] { - return func(actual string) (T, bool, error) { - v, err := parse(actual) - return v, false, err - } -} - -type Constraint[T Number | string | bool] func(actual T) error - -func WithMinimum[T Number](expected T) Constraint[T] { - return func(actual T) error { - if actual < expected { - return errors.New(errMsgMinValueConstraint) - } - - return nil - } -} - -func WithMaximum[T Number](expected T) Constraint[T] { - return func(actual T) error { - if actual > expected { - return errors.New(errMsgMaxValueConstraint) - } - - return nil - } -} - -// parseNumericParameter parses a numeric parameter to its respective type. -func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) { - v, ok, err := fn(param) - if err != nil { - return 0, err - } - - if !ok { - for _, check := range checks { - if err := check(v); err != nil { - return 0, err - } - } - } - - return v, nil -} - -// parseBoolParameter parses a string parameter to a bool -func parseBoolParameter(param string, fn Operation[bool]) (bool, error) { - v, _, err := fn(param) - return v, err -} - -// parseNumericArrayParameter parses a string parameter containing array of values to its respective type. -func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) - } - - return nil, nil - } - - str := strings.Split(param, delim) - values := make([]T, len(str)) - - for i, s := range str { - v, ok, err := fn(s) - if err != nil { - return nil, err - } - - if !ok { - for _, check := range checks { - if err := check(v); err != nil { - return nil, err - } - } - } - - values[i] = v - } - - return values, nil -} - - -// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered. -func parseQuery(rawQuery string) (url.Values, error) { - return url.ParseQuery(rawQuery) -} \ No newline at end of file