diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoServerCodegen.java index 2a8acd9c23ca..061a699898a0 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoServerCodegen.java @@ -17,19 +17,39 @@ package org.openapitools.codegen.languages; -import io.swagger.v3.oas.models.media.ComposedSchema; -import io.swagger.v3.oas.models.media.Schema; -import org.openapitools.codegen.*; -import org.openapitools.codegen.meta.features.*; +import java.io.File; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenParameter; +import org.openapitools.codegen.CodegenProperty; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.meta.features.DocumentationFeature; +import org.openapitools.codegen.meta.features.GlobalFeature; +import org.openapitools.codegen.meta.features.ParameterFeature; +import org.openapitools.codegen.meta.features.SchemaSupportFeature; +import org.openapitools.codegen.meta.features.SecurityFeature; +import org.openapitools.codegen.meta.features.WireFormatFeature; import org.openapitools.codegen.model.ModelMap; +import org.openapitools.codegen.model.ModelsMap; import org.openapitools.codegen.model.OperationMap; import org.openapitools.codegen.model.OperationsMap; import org.openapitools.codegen.utils.ModelUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.util.*; +import com.google.common.collect.Iterables; + +import io.swagger.v3.oas.models.media.ComposedSchema; +import io.swagger.v3.oas.models.media.Schema; public class GoServerCodegen extends AbstractGoCodegen { @@ -114,7 +134,7 @@ public GoServerCodegen() { optAddResponseHeaders.defaultValue(addResponseHeaders.toString()); cliOptions.add(optAddResponseHeaders); - + // option to exclude service factories; only interfaces are rendered CliOption optOnlyInterfaces = new CliOption("onlyInterfaces", "Exclude default service creators from output; only generate interfaces"); optOnlyInterfaces.setType("bool"); @@ -287,6 +307,38 @@ public void processOpts() { .doNotOverwrite()); } + @Override + public ModelsMap postProcessModels(ModelsMap objs) { + // The superclass determines the list of required golang imports. The actual list of imports + // depends on which types are used. So super.postProcessModels must be invoked at the beginning + // of this method. + objs = super.postProcessModels(objs); + + List> imports = objs.getImports(); + + for (ModelMap m : objs.getModels()) { + imports.add(createMapping("import", "encoding/json")); + + CodegenModel model = m.getModel(); + if (model.isEnum) { + continue; + } + + Boolean importErrors = false; + + for (CodegenProperty param : Iterables.concat(model.vars, model.allVars, model.requiredVars, model.optionalVars)) { + if (param.isNumeric && ((param.minimum != null && param.minimum != "") || (param.maximum != null && param.maximum != ""))) { + importErrors = true; + } + } + + if (importErrors) { + imports.add(createMapping("import", "errors")); + } + } + return objs; + } + @Override public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) { objs = super.postProcessOperationsWithModels(objs, allModels); 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 043410b13249..aa4ef1c3642f 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 @@ -93,35 +93,70 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re {{#allParams}} {{#isPathParam}} {{#isNumber}} - {{paramName}}Param, err := parseFloat32Parameter({{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}}, {{required}}) - if err != nil { + {{paramName}}Param, err := parseNumericParameter[float32]( + {{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}},{{#defaultValue}} + WithDefaultOrParse[float32]({{defaultValue}}, parseFloat32),{{/defaultValue}}{{^defaultValue}}{{#required}} + WithRequire[float32](parseFloat32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}} + WithParse[float32](parseFloat32),{{/required}}{{/defaultValue}}{{#minimum}} + WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[float32]({{maximum}}),{{/maximum}} + ) + if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/isNumber}} {{#isFloat}} - {{paramName}}Param, err := parseFloat32Parameter({{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}}, {{required}}) + {{paramName}}Param, err := parseNumericParameter[float32]( + {{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}},{{#defaultValue}} + WithDefaultOrParse[float32]({{defaultValue}}, parseFloat32),{{/defaultValue}}{{^defaultValue}}{{#required}} + WithRequire[float32](parseFloat32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}} + WithParse[float32](parseFloat32),{{/required}}{{/defaultValue}}{{#minimum}} + WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[float32]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/isFloat}} {{#isDouble}} - {{paramName}}Param, err := parseFloat64Parameter({{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}}, {{required}}) + {{paramName}}Param, err := parseNumericParameter[float64]( + {{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}},{{#defaultValue}} + WithDefaultOrParse[float64]({{defaultValue}}, parseFloat64),{{/defaultValue}}{{^defaultValue}}{{#required}} + WithRequire[float64](parseFloat64),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}} + WithParse[float64](parseFloat64),{{/required}}{{/defaultValue}}{{#minimum}} + WithMinimum[float64]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[float64]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/isDouble}} {{#isLong}} - {{paramName}}Param, err := parseInt64Parameter({{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}}, {{required}}) + {{paramName}}Param, err := parseNumericParameter[int64]( + {{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}},{{#defaultValue}} + WithDefaultOrParse[int64]({{defaultValue}}, parseInt64),{{/defaultValue}}{{^defaultValue}}{{#required}} + WithRequire[int64](parseInt64),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}} + WithParse[int64](parseInt64),{{/required}}{{/defaultValue}}{{#minimum}} + WithMinimum[int64]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[int64]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/isLong}} {{#isInteger}} - {{paramName}}Param, err := parseInt32Parameter({{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}}, {{required}}) + {{paramName}}Param, err := parseNumericParameter[int32]( + {{#routers}}{{#mux}}params["{{baseName}}"]{{/mux}}{{#chi}}chi.URLParam(r, "{{baseName}}"){{/chi}}{{/routers}},{{#defaultValue}} + WithDefaultOrParse[int32]({{defaultValue}}, parseInt32),{{/defaultValue}}{{^defaultValue}}{{#required}} + WithRequire[int32](parseInt32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}} + WithParse[int32](parseInt32),{{/required}}{{/defaultValue}}{{#minimum}} + WithMinimum[int32]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[int32]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return @@ -141,42 +176,82 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re {{/isPathParam}} {{#isQueryParam}} {{#isNumber}} - {{paramName}}Param, err := parseFloat32Parameter(query.Get("{{baseName}}"), {{required}}) + {{paramName}}Param, err := parseNumericParameter[float32]( + query.Get("{{baseName}}"),{{#defaultValue}} + WithDefaultOrParse[float32]({{defaultValue}}, parseFloat32),{{/defaultValue}}{{^defaultValue}}{{#required}} + WithRequire[float32](parseFloat32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}} + WithParse[float32](parseFloat32),{{/required}}{{/defaultValue}}{{#minimum}} + WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[float32]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/isNumber}} {{#isFloat}} - {{paramName}}Param, err := parseFloat32Parameter(query.Get("{{baseName}}"), {{required}}) + {{paramName}}Param, err := parseNumericParameter[float32]( + query.Get("{{baseName}}"),{{#defaultValue}} + WithDefaultOrParse[float32]({{defaultValue}}, parseFloat32),{{/defaultValue}}{{^defaultValue}}{{#required}} + WithRequire[float32](parseFloat32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}} + WithParse[float32](parseFloat32),{{/required}}{{/defaultValue}}{{#minimum}} + WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[float32]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/isFloat}} {{#isDouble}} - {{paramName}}Param, err := parseFloat64Parameter(query.Get("{{baseName}}"), {{required}}) + {{paramName}}Param, err := parseNumericParameter[float64]( + query.Get("{{baseName}}"),{{#defaultValue}} + WithDefaultOrParse[float64]({{defaultValue}}, parseFloat64),{{/defaultValue}}{{^defaultValue}}{{#required}} + WithRequire[float64](parseFloat64),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}} + WithParse[float64](parseFloat64),{{/required}}{{/defaultValue}}{{#minimum}} + WithMinimum[float64]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[float64]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/isDouble}} {{#isLong}} - {{paramName}}Param, err := parseInt64Parameter(query.Get("{{baseName}}"), {{required}}) + {{paramName}}Param, err := parseNumericParameter[int64]( + query.Get("{{baseName}}"),{{#defaultValue}} + WithDefaultOrParse[int64]({{defaultValue}}, parseInt64),{{/defaultValue}}{{^defaultValue}}{{#required}} + WithRequire[int64](parseInt64),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}} + WithParse[int64](parseInt64),{{/required}}{{/defaultValue}}{{#minimum}} + WithMinimum[int64]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[int64]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/isLong}} {{#isInteger}} - {{paramName}}Param, err := parseInt32Parameter(query.Get("{{baseName}}"), {{required}}) + {{paramName}}Param, err := parseNumericParameter[int32]( + query.Get("{{baseName}}"),{{#defaultValue}} + WithDefaultOrParse[int32]({{defaultValue}}, parseInt32),{{/defaultValue}}{{^defaultValue}}{{#required}} + WithRequire[int32](parseInt32),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}} + WithParse[int32](parseInt32),{{/required}}{{/defaultValue}}{{#minimum}} + WithMinimum[int32]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[int32]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/isInteger}} {{#isBoolean}} - {{paramName}}Param, err := parseBoolParameter(query.Get("{{baseName}}"), {{required}}) + {{paramName}}Param, err := parseBoolParameter( + query.Get("{{baseName}}"),{{#defaultValue}} + WithDefaultOrParse[bool]({{defaultValue}}, parseBool),{{/defaultValue}}{{^defaultValue}}{{#required}} + WithRequire[bool](parseBool),{{/required}}{{/defaultValue}}{{^defaultValue}}{{^required}} + WithParse[float32](parseBool),{{/required}}{{/defaultValue}} + ) if err != nil { w.WriteHeader(500) return @@ -184,35 +259,60 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re {{/isBoolean}} {{#isArray}} {{#items.isNumber}} - {{paramName}}Param, err := parseFloat32ArrayParameter(query.Get("{{baseName}}"), {{required}}) + {{paramName}}Param, err := parseNumericArrayParameter[float32]( + query.Get("{{baseName}}"), ",", {{required}}, + WithParse[float32](parseFloat32),{{#minimum}} + WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[float32]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/items.isNumber}} {{#items.isFloat}} - {{paramName}}Param, err := parseFloat32ArrayParameter(query.Get("{{baseName}}"), {{required}}) + {{paramName}}Param, err := parseNumericArrayParameter[float32]( + query.Get("{{baseName}}"), ",", {{required}}, + WithParse[float32](parseFloat32),{{#minimum}} + WithMinimum[float32]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[float32]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/items.isFloat}} {{#items.isDouble}} - {{paramName}}Param, err := parseFloat64ArrayParameter(query.Get("{{baseName}}"), {{required}}) + {{paramName}}Param, err := parseNumericArrayParameter[float64]( + query.Get("{{baseName}}"), ",", {{required}}, + WithParse[float64](parseFloat64),{{#minimum}} + WithMinimum[float64]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[float64]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/items.isDouble}} {{#items.isLong}} - {{paramName}}Param, err := parseInt64ArrayParameter(query.Get("{{baseName}}"), ",", {{required}}) + {{paramName}}Param, err := parseNumericArrayParameter[int64]( + query.Get("{{baseName}}"), ",", {{required}}, + WithParse[int64](parseInt64),{{#minimum}} + WithMinimum[int64]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[int64]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } {{/items.isLong}} {{#items.isInteger}} - {{paramName}}Param, err := parseInt32ArrayParameter(query.Get("{{baseName}}"), ",", {{required}}) + {{paramName}}Param, err := parseNumericArrayParameter[int32]( + query.Get("{{baseName}}"), ",", {{required}}, + WithParse[int32](parseInt32),{{#minimum}} + WithMinimum[int32]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[int32]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return @@ -237,7 +337,15 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re {{^isInteger}} {{^isBoolean}} {{^isArray}} + {{#defaultValue}} + {{paramName}}Param := "{{defaultValue}}" + if query.Has("{{baseName}}") { + {{paramName}}Param = query.Get("{{baseName}}") + } + {{/defaultValue}} + {{^defaultValue}} {{paramName}}Param := query.Get("{{baseName}}") + {{/defaultValue}} {{/isArray}} {{/isBoolean}} {{/isInteger}} @@ -257,21 +365,29 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re } {{/isFile}} {{#isLong}}{{#isArray}} - {{paramName}}Param, err := parseInt64ArrayParameter(r.FormValue("{{baseName}}"), ",", {{required}}){{/isArray}}{{^isArray}} - {{paramName}}Param, err := parseInt64Parameter(r.FormValue("{{baseName}}"), {{required}}){{/isArray}} + {{paramName}}Param, err := parseNumericArrayParameter[int64]( + r.FormValue("{{baseName}}"), ",", {{required}}, + WithParse[int64](parseInt64),{{#minimum}} + WithMinimum[int64]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[int64]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - {{/isLong}} + {{/isArray}}{{/isLong}} {{#isInteger}}{{#isArray}} - {{paramName}}Param, err := parseInt32ArrayParameter(r.FormValue("{{baseName}}"), ",", {{required}}){{/isArray}}{{^isArray}} - {{paramName}}Param, err := parseInt32Parameter(r.FormValue("{{baseName}}"), {{required}}){{/isArray}} + {{paramName}}Param, err := parseNumericArrayParameter[int32]( + r.FormValue("{{baseName}}"), ",", {{required}}, + WithParse[int32](parseInt32),{{#minimum}} + WithMinimum[int32]({{minimum}}),{{/minimum}}{{#maximum}} + WithMaximum[int32]({{maximum}}),{{/maximum}} + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - {{/isInteger}} + {{/isArray}}{{/isInteger}} {{^isFile}} {{^isLong}} {{paramName}}Param := r.FormValue("{{baseName}}") @@ -306,6 +422,10 @@ func (c *{{classname}}Controller) {{nickname}}(w http.ResponseWriter, r *http.Re if err := Assert{{baseType}}Required({{paramName}}Param); err != nil { c.errorHandler(w, r, err, nil) return + } + if err := Assert{{baseType}}Constraints({{paramName}}Param); err != nil { + c.errorHandler(w, r, err, nil) + return } {{/isModel}} {{/isArray}} diff --git a/modules/openapi-generator/src/main/resources/go-server/model.mustache b/modules/openapi-generator/src/main/resources/go-server/model.mustache index 73ee1c49fdb7..4e7f5ea0a57e 100644 --- a/modules/openapi-generator/src/main/resources/go-server/model.mustache +++ b/modules/openapi-generator/src/main/resources/go-server/model.mustache @@ -1,10 +1,13 @@ {{>partial_header}} package {{packageName}} + {{#models}}{{#imports}} {{#-first}}import ( {{/-first}} "{{import}}"{{#-last}} ) -{{/-last}}{{/imports}}{{#model}}{{#isEnum}}{{#description}}// {{{classname}}} : {{{description}}}{{/description}} +{{/-last}}{{/imports}} + +{{#model}}{{#isEnum}}{{#description}}// {{{classname}}} : {{{description}}}{{/description}} type {{{classname}}} {{^format}}{{dataType}}{{/format}}{{{format}}} // List of {{{classname}}} @@ -36,6 +39,30 @@ type {{classname}} struct { {{/vars}} }{{/isEnum}} +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *{{classname}}) UnmarshalJSON(data []byte) error { + {{#vars}} + {{#defaultValue}} + {{^isArray}} + {{#isBoolean}} + m.{{name}} = {{{.}}} + {{/isBoolean}} + {{#isNumeric}} + m.{{name}} = {{{.}}} + {{/isNumeric}} + {{^isBoolean}} + {{^isNumeric}} + m.{{name}} = "{{{.}}}" + {{/isNumeric}} + {{/isBoolean}} + {{/isArray}} + {{/defaultValue}} + {{/vars}} + + type Alias {{classname}} // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // Assert{{classname}}Required checks if the required fields are not zero-ed func Assert{{classname}}Required(obj {{classname}}) error { {{#hasRequired}} @@ -117,6 +144,23 @@ func Assert{{classname}}Required(obj {{classname}}) error { {{/items.isModel}} {{/isArray}} {{/isNullable}} +{{/Vars}} + return nil +} + +// Assert{{classname}}Constraints checks if the values respects the defined constraints +func Assert{{classname}}Constraints(obj {{classname}}) error { +{{#Vars}} +{{#minimum}} + if {{#isNullable}}obj.{{name}} != nil && *{{/isNullable}}obj.{{name}} < {{minimum}} { + return &ParsingError{Err: errors.New(errMsgMinValueConstraint)} + } +{{/minimum}} +{{#maximum}} + if {{#isNullable}}obj.{{name}} != nil && *{{/isNullable}}obj.{{name}} > {{maximum}} { + return &ParsingError{Err: errors.New(errMsgMaxValueConstraint)} + } +{{/maximum}} {{/Vars}} return nil }{{/model}}{{/models}} 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 58fd30827e7e..a86bb5933204 100644 --- a/modules/openapi-generator/src/main/resources/go-server/routers.mustache +++ b/modules/openapi-generator/src/main/resources/go-server/routers.mustache @@ -43,6 +43,8 @@ type Router interface { } const errMsgRequiredMissing = "required parameter is missing" +const errMsgMinValueConstraint = "provided parameter is not respecting minimum value constraint" +const errMsgMaxValueConstraint = "provided parameter is not respecting maximum value constraint" // NewRouter creates a new router for any number of api routers func NewRouter(routers ...Router) {{#routers}}{{#mux}}*mux.Router{{/mux}}{{#chi}}chi.Router{{/chi}}{{/routers}} { @@ -168,123 +170,119 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error return file, nil } -// parseFloatParameter parses a string parameter to an int64. -func parseFloatParameter(param string, bitSize int, required bool) (float64, error) { - if param == "" { - if required { - return 0, errors.New(errMsgRequiredMissing) - } +type Number interface { + ~int32 | ~int64 | ~float32 | ~float64 +} - return 0, nil - } +type ParseString[T Number | string | bool] func(v string) (T, error) - return strconv.ParseFloat(param, bitSize) +// parseFloat64 parses a string parameter to an float64. +func parseFloat64(param string) (float64, error) { + return strconv.ParseFloat(param, 64) } -// parseFloat64Parameter parses a string parameter to an float64. -func parseFloat64Parameter(param string, required bool) (float64, error) { - return parseFloatParameter(param, 64, required) +// parseFloat32 parses a string parameter to an float32. +func parseFloat32(param string) (float32, error) { + v, err := strconv.ParseFloat(param, 32) + return float32(v), err } -// parseFloat32Parameter parses a string parameter to an float32. -func parseFloat32Parameter(param string, required bool) (float32, error) { - val, err := parseFloatParameter(param, 32, required) - return float32(val), err +// parseInt64 parses a string parameter to an int64. +func parseInt64(param string) (int64, error) { + return strconv.ParseInt(param, 10, 64) } -// parseIntParameter parses a string parameter to an int64. -func parseIntParameter(param string, bitSize int, required bool) (int64, error) { - if param == "" { - if required { - return 0, errors.New(errMsgRequiredMissing) - } - - return 0, nil - } - - return strconv.ParseInt(param, 10, bitSize) +// parseInt32 parses a string parameter to an int32. +func parseInt32(param string) (int32, error) { + val, err := strconv.ParseInt(param, 10, 32) + return int32(val), err } -// parseInt64Parameter parses a string parameter to an int64. -func parseInt64Parameter(param string, required bool) (int64, error) { - return parseIntParameter(param, 64, required) +// parseBool parses a string parameter to an bool. +func parseBool(param string) (bool, error) { + return strconv.ParseBool(param) } -// parseInt32Parameter parses a string parameter to an int32. -func parseInt32Parameter(param string, required bool) (int32, error) { - val, err := parseIntParameter(param, 32, required) - return int32(val), err -} +type Operation[T Number | string | bool] func(actual string) (T, bool, error) -// parseBoolParameter parses a string parameter to a bool -func parseBoolParameter(param string, required bool) (bool, error) { - if param == "" { - if required { - return false, errors.New(errMsgRequiredMissing) +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) } - return false, nil - } - - val, err := strconv.ParseBool(param) - if err != nil { - return false, err + v, err := parse(actual) + return v, false, err } - - return bool(val), nil } -// parseFloat64ArrayParameter parses a string parameter containing array of values to []Float64. -func parseFloat64ArrayParameter(param, delim string, required bool) ([]float64, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) +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 } - return nil, nil + v, err := parse(actual) + return v, false, err } +} - str := strings.Split(param, delim) - floats := make([]float64, len(str)) +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 + } +} - for i, s := range str { - if v, err := strconv.ParseFloat(s, 64); err != nil { - return nil, err - } else { - floats[i] = v +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 floats, nil + return nil + } } -// parseFloat32ArrayParameter parses a string parameter containing array of values to []float32. -func parseFloat32ArrayParameter(param, delim string, required bool) ([]float32, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) +func WithMaximum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual > expected { + return errors.New(errMsgMaxValueConstraint) } - return nil, nil + return nil } +} - str := strings.Split(param, delim) - floats := make([]float32, len(str)) +// 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 + } - for i, s := range str { - if v, err := strconv.ParseFloat(s, 32); err != nil { - return nil, err - } else { - floats[i] = float32(v) + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return 0, err + } } } - return floats, nil + 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 +} -// parseInt64ArrayParameter parses a string parameter containing array of values to []int64. -func parseInt64ArrayParameter(param, delim string, required bool) ([]int64, error) { +// 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) @@ -294,39 +292,24 @@ func parseInt64ArrayParameter(param, delim string, required bool) ([]int64, erro } str := strings.Split(param, delim) - ints := make([]int64, len(str)) + values := make([]T, len(str)) for i, s := range str { - if v, err := strconv.ParseInt(s, 10, 64); err != nil { + v, ok, err := fn(s) + if err != nil { return nil, err - } else { - ints[i] = v } - } - - return ints, nil -} -// parseInt32ArrayParameter parses a string parameter containing array of values to []int32. -func parseInt32ArrayParameter(param, delim string, required bool) ([]int32, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return nil, err + } + } } - return nil, nil - } - - str := strings.Split(param, delim) - ints := make([]int32, len(str)) - - for i, s := range str { - if v, err := strconv.ParseInt(s, 10, 32); err != nil { - return nil, err - } else { - ints[i] = int32(v) - } + values[i] = v } - return ints, nil + return values, nil } diff --git a/samples/client/petstore/go/go.mod b/samples/client/petstore/go/go.mod index 38f545b41512..25bf96a0b591 100644 --- a/samples/client/petstore/go/go.mod +++ b/samples/client/petstore/go/go.mod @@ -6,6 +6,7 @@ go 1.13 require ( github.com/OpenAPITools/openapi-generator/samples/client/petstore/go/go-petstore v0.0.0-00010101000000-000000000000 + github.com/go-openapi/swag v0.22.3 github.com/stretchr/testify v1.8.1 golang.org/x/net v0.2.0 // indirect golang.org/x/oauth2 v0.2.0 diff --git a/samples/client/petstore/go/go.sum b/samples/client/petstore/go/go.sum index c69d361a60a6..b24083727941 100644 --- a/samples/client/petstore/go/go.sum +++ b/samples/client/petstore/go/go.sum @@ -40,6 +40,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -51,6 +52,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -106,14 +109,20 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -405,6 +414,7 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/samples/server/petstore/go-api-server/go.sum b/samples/server/petstore/go-api-server/go.sum index 2bf9262bdddd..535028803d22 100644 --- a/samples/server/petstore/go-api-server/go.sum +++ b/samples/server/petstore/go-api-server/go.sum @@ -1,2 +1,2 @@ -github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +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/api_pet.go b/samples/server/petstore/go-api-server/go/api_pet.go index ca5fe3e36237..5187c74b11e7 100644 --- a/samples/server/petstore/go-api-server/go/api_pet.go +++ b/samples/server/petstore/go-api-server/go/api_pet.go @@ -106,6 +106,10 @@ func (c *PetApiController) AddPet(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertPetConstraints(petParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.AddPet(r.Context(), petParam) // If an error occurred, encode the error with the status code if err != nil { @@ -119,7 +123,10 @@ func (c *PetApiController) AddPet(w http.ResponseWriter, r *http.Request) { // DeletePet - Deletes a pet func (c *PetApiController) DeletePet(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - petIdParam, err := parseInt64Parameter(params["petId"], true) + petIdParam, err := parseNumericParameter[int64]( + params["petId"], + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return @@ -167,7 +174,10 @@ func (c *PetApiController) FindPetsByTags(w http.ResponseWriter, r *http.Request // GetPetById - Find pet by ID func (c *PetApiController) GetPetById(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - petIdParam, err := parseInt64Parameter(params["petId"], true) + petIdParam, err := parseNumericParameter[int64]( + params["petId"], + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return @@ -195,6 +205,10 @@ func (c *PetApiController) UpdatePet(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertPetConstraints(petParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.UpdatePet(r.Context(), petParam) // If an error occurred, encode the error with the status code if err != nil { @@ -212,13 +226,20 @@ func (c *PetApiController) UpdatePetWithForm(w http.ResponseWriter, r *http.Requ return } params := mux.Vars(r) - petIdParam, err := parseInt64Parameter(params["petId"], true) + petIdParam, err := parseNumericParameter[int64]( + params["petId"], + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - nameParam := r.FormValue("name") - statusParam := r.FormValue("status") + + + nameParam := r.FormValue("name") + + + statusParam := r.FormValue("status") result, err := c.service.UpdatePetWithForm(r.Context(), petIdParam, nameParam, statusParam) // If an error occurred, encode the error with the status code if err != nil { @@ -236,19 +257,26 @@ func (c *PetApiController) UploadFile(w http.ResponseWriter, r *http.Request) { return } params := mux.Vars(r) - petIdParam, err := parseInt64Parameter(params["petId"], true) + petIdParam, err := parseNumericParameter[int64]( + params["petId"], + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - additionalMetadataParam := r.FormValue("additionalMetadata") + + + additionalMetadataParam := r.FormValue("additionalMetadata") fileParam, err := ReadFormFileToTempFile(r, "file") if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - result, err := c.service.UploadFile(r.Context(), petIdParam, additionalMetadataParam, fileParam) + + + result, err := c.service.UploadFile(r.Context(), petIdParam, additionalMetadataParam, fileParam) // 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_store.go b/samples/server/petstore/go-api-server/go/api_store.go index 737790ebcd3c..0687fc3a1584 100644 --- a/samples/server/petstore/go-api-server/go/api_store.go +++ b/samples/server/petstore/go-api-server/go/api_store.go @@ -102,7 +102,12 @@ func (c *StoreApiController) GetInventory(w http.ResponseWriter, r *http.Request // GetOrderById - Find purchase order by ID func (c *StoreApiController) GetOrderById(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - orderIdParam, err := parseInt64Parameter(params["orderId"], true) + orderIdParam, err := parseNumericParameter[int64]( + params["orderId"], + WithRequire[int64](parseInt64), + WithMinimum[int64](1), + WithMaximum[int64](5), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return @@ -130,6 +135,10 @@ func (c *StoreApiController) PlaceOrder(w http.ResponseWriter, r *http.Request) c.errorHandler(w, r, err, nil) return } + if err := AssertOrderConstraints(orderParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.PlaceOrder(r.Context(), orderParam) // If an error occurred, encode the error with the status code if err != nil { diff --git a/samples/server/petstore/go-api-server/go/api_user.go b/samples/server/petstore/go-api-server/go/api_user.go index 5eaf4297d362..19b5b2e5803e 100644 --- a/samples/server/petstore/go-api-server/go/api_user.go +++ b/samples/server/petstore/go-api-server/go/api_user.go @@ -106,6 +106,10 @@ func (c *UserApiController) CreateUser(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertUserConstraints(userParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.CreateUser(r.Context(), userParam) // If an error occurred, encode the error with the status code if err != nil { @@ -236,6 +240,10 @@ func (c *UserApiController) UpdateUser(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertUserConstraints(userParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.UpdateUser(r.Context(), usernameParam, userParam) // If an error occurred, encode the error with the status code if err != nil { diff --git a/samples/server/petstore/go-api-server/go/model_api_response.go b/samples/server/petstore/go-api-server/go/model_api_response.go index 3cb1dc2d3e27..0ef560396fd2 100644 --- a/samples/server/petstore/go-api-server/go/model_api_response.go +++ b/samples/server/petstore/go-api-server/go/model_api_response.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // ApiResponse - Describes the result of uploading an image resource type ApiResponse struct { @@ -19,7 +26,19 @@ type ApiResponse struct { Message string `json:"message,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *ApiResponse) UnmarshalJSON(data []byte) error { + + type Alias ApiResponse // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertApiResponseRequired checks if the required fields are not zero-ed func AssertApiResponseRequired(obj ApiResponse) error { return nil } + +// AssertApiResponseConstraints checks if the values respects the defined constraints +func AssertApiResponseConstraints(obj ApiResponse) error { + return nil +} diff --git a/samples/server/petstore/go-api-server/go/model_category.go b/samples/server/petstore/go-api-server/go/model_category.go index 9d73f38294ed..1581493112a2 100644 --- a/samples/server/petstore/go-api-server/go/model_category.go +++ b/samples/server/petstore/go-api-server/go/model_category.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // Category - A category for a pet type Category struct { @@ -17,7 +24,19 @@ type Category struct { Name string `json:"name,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Category) UnmarshalJSON(data []byte) error { + + type Alias Category // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertCategoryRequired checks if the required fields are not zero-ed func AssertCategoryRequired(obj Category) error { return nil } + +// AssertCategoryConstraints checks if the values respects the defined constraints +func AssertCategoryConstraints(obj Category) error { + return nil +} diff --git a/samples/server/petstore/go-api-server/go/model_order.go b/samples/server/petstore/go-api-server/go/model_order.go index c40a82bab091..1aa6b22e20f6 100644 --- a/samples/server/petstore/go-api-server/go/model_order.go +++ b/samples/server/petstore/go-api-server/go/model_order.go @@ -9,10 +9,14 @@ package petstoreserver + import ( "time" + "encoding/json" ) + + // Order - An order for a pets from the pet store type Order struct { @@ -30,7 +34,20 @@ type Order struct { Complete bool `json:"complete,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Order) UnmarshalJSON(data []byte) error { + m.Complete = false + + type Alias Order // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertOrderRequired checks if the required fields are not zero-ed func AssertOrderRequired(obj Order) error { return nil } + +// AssertOrderConstraints checks if the values respects the defined constraints +func AssertOrderConstraints(obj Order) error { + return nil +} diff --git a/samples/server/petstore/go-api-server/go/model_pet.go b/samples/server/petstore/go-api-server/go/model_pet.go index 5073463b3916..948533cd1a5b 100644 --- a/samples/server/petstore/go-api-server/go/model_pet.go +++ b/samples/server/petstore/go-api-server/go/model_pet.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // Pet - A pet for sale in the pet store type Pet struct { @@ -27,6 +34,13 @@ type Pet struct { Status string `json:"status,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Pet) UnmarshalJSON(data []byte) error { + + type Alias Pet // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertPetRequired checks if the required fields are not zero-ed func AssertPetRequired(obj Pet) error { elements := map[string]interface{}{ @@ -49,3 +63,8 @@ func AssertPetRequired(obj Pet) error { } return nil } + +// AssertPetConstraints checks if the values respects the defined constraints +func AssertPetConstraints(obj Pet) error { + return nil +} diff --git a/samples/server/petstore/go-api-server/go/model_tag.go b/samples/server/petstore/go-api-server/go/model_tag.go index c441e14e9969..47073503db00 100644 --- a/samples/server/petstore/go-api-server/go/model_tag.go +++ b/samples/server/petstore/go-api-server/go/model_tag.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // Tag - A tag for a pet type Tag struct { @@ -17,7 +24,19 @@ type Tag struct { Name string `json:"name,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Tag) UnmarshalJSON(data []byte) error { + + type Alias Tag // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertTagRequired checks if the required fields are not zero-ed func AssertTagRequired(obj Tag) error { return nil } + +// AssertTagConstraints checks if the values respects the defined constraints +func AssertTagConstraints(obj Tag) error { + return nil +} diff --git a/samples/server/petstore/go-api-server/go/model_user.go b/samples/server/petstore/go-api-server/go/model_user.go index 2fcaf883f4cb..0021fe30bd77 100644 --- a/samples/server/petstore/go-api-server/go/model_user.go +++ b/samples/server/petstore/go-api-server/go/model_user.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // User - A User who is purchasing from the pet store type User struct { @@ -30,7 +37,19 @@ type User struct { UserStatus int32 `json:"userStatus,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *User) UnmarshalJSON(data []byte) error { + + type Alias User // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertUserRequired checks if the required fields are not zero-ed func AssertUserRequired(obj User) error { return nil } + +// AssertUserConstraints checks if the values respects the defined constraints +func AssertUserConstraints(obj User) error { + return nil +} diff --git a/samples/server/petstore/go-api-server/go/routers.go b/samples/server/petstore/go-api-server/go/routers.go index d36e637e8ac7..8f2734893d03 100644 --- a/samples/server/petstore/go-api-server/go/routers.go +++ b/samples/server/petstore/go-api-server/go/routers.go @@ -37,6 +37,8 @@ type Router interface { } const errMsgRequiredMissing = "required parameter is missing" +const errMsgMinValueConstraint = "provided parameter is not respecting minimum value constraint" +const errMsgMaxValueConstraint = "provided parameter is not respecting maximum value constraint" // NewRouter creates a new router for any number of api routers func NewRouter(routers ...Router) *mux.Router { @@ -136,123 +138,119 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error return file, nil } -// parseFloatParameter parses a string parameter to an int64. -func parseFloatParameter(param string, bitSize int, required bool) (float64, error) { - if param == "" { - if required { - return 0, errors.New(errMsgRequiredMissing) - } +type Number interface { + ~int32 | ~int64 | ~float32 | ~float64 +} - return 0, nil - } +type ParseString[T Number | string | bool] func(v string) (T, error) - return strconv.ParseFloat(param, bitSize) +// parseFloat64 parses a string parameter to an float64. +func parseFloat64(param string) (float64, error) { + return strconv.ParseFloat(param, 64) } -// parseFloat64Parameter parses a string parameter to an float64. -func parseFloat64Parameter(param string, required bool) (float64, error) { - return parseFloatParameter(param, 64, required) +// parseFloat32 parses a string parameter to an float32. +func parseFloat32(param string) (float32, error) { + v, err := strconv.ParseFloat(param, 32) + return float32(v), err } -// parseFloat32Parameter parses a string parameter to an float32. -func parseFloat32Parameter(param string, required bool) (float32, error) { - val, err := parseFloatParameter(param, 32, required) - return float32(val), err +// parseInt64 parses a string parameter to an int64. +func parseInt64(param string) (int64, error) { + return strconv.ParseInt(param, 10, 64) } -// parseIntParameter parses a string parameter to an int64. -func parseIntParameter(param string, bitSize int, required bool) (int64, error) { - if param == "" { - if required { - return 0, errors.New(errMsgRequiredMissing) - } - - return 0, nil - } - - return strconv.ParseInt(param, 10, bitSize) +// parseInt32 parses a string parameter to an int32. +func parseInt32(param string) (int32, error) { + val, err := strconv.ParseInt(param, 10, 32) + return int32(val), err } -// parseInt64Parameter parses a string parameter to an int64. -func parseInt64Parameter(param string, required bool) (int64, error) { - return parseIntParameter(param, 64, required) +// parseBool parses a string parameter to an bool. +func parseBool(param string) (bool, error) { + return strconv.ParseBool(param) } -// parseInt32Parameter parses a string parameter to an int32. -func parseInt32Parameter(param string, required bool) (int32, error) { - val, err := parseIntParameter(param, 32, required) - return int32(val), err -} +type Operation[T Number | string | bool] func(actual string) (T, bool, error) -// parseBoolParameter parses a string parameter to a bool -func parseBoolParameter(param string, required bool) (bool, error) { - if param == "" { - if required { - return false, errors.New(errMsgRequiredMissing) +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) } - return false, nil - } - - val, err := strconv.ParseBool(param) - if err != nil { - return false, err + v, err := parse(actual) + return v, false, err } - - return bool(val), nil } -// parseFloat64ArrayParameter parses a string parameter containing array of values to []Float64. -func parseFloat64ArrayParameter(param, delim string, required bool) ([]float64, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) +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 } - return nil, nil + v, err := parse(actual) + return v, false, err } +} - str := strings.Split(param, delim) - floats := make([]float64, len(str)) +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 + } +} - for i, s := range str { - if v, err := strconv.ParseFloat(s, 64); err != nil { - return nil, err - } else { - floats[i] = v +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 floats, nil + return nil + } } -// parseFloat32ArrayParameter parses a string parameter containing array of values to []float32. -func parseFloat32ArrayParameter(param, delim string, required bool) ([]float32, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) +func WithMaximum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual > expected { + return errors.New(errMsgMaxValueConstraint) } - return nil, nil + return nil } +} - str := strings.Split(param, delim) - floats := make([]float32, len(str)) +// 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 + } - for i, s := range str { - if v, err := strconv.ParseFloat(s, 32); err != nil { - return nil, err - } else { - floats[i] = float32(v) + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return 0, err + } } } - return floats, nil + 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 +} -// parseInt64ArrayParameter parses a string parameter containing array of values to []int64. -func parseInt64ArrayParameter(param, delim string, required bool) ([]int64, error) { +// 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) @@ -262,39 +260,24 @@ func parseInt64ArrayParameter(param, delim string, required bool) ([]int64, erro } str := strings.Split(param, delim) - ints := make([]int64, len(str)) + values := make([]T, len(str)) for i, s := range str { - if v, err := strconv.ParseInt(s, 10, 64); err != nil { + v, ok, err := fn(s) + if err != nil { return nil, err - } else { - ints[i] = v } - } - - return ints, nil -} -// parseInt32ArrayParameter parses a string parameter containing array of values to []int32. -func parseInt32ArrayParameter(param, delim string, required bool) ([]int32, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return nil, err + } + } } - return nil, nil - } - - str := strings.Split(param, delim) - ints := make([]int32, len(str)) - - for i, s := range str { - if v, err := strconv.ParseInt(s, 10, 32); err != nil { - return nil, err - } else { - ints[i] = int32(v) - } + values[i] = v } - return ints, nil + return values, nil } 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 b6b9305d9df9..e0852f1398cc 100644 --- a/samples/server/petstore/go-chi-server/go/api_pet.go +++ b/samples/server/petstore/go-chi-server/go/api_pet.go @@ -106,6 +106,10 @@ func (c *PetApiController) AddPet(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertPetConstraints(petParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.AddPet(r.Context(), petParam) // If an error occurred, encode the error with the status code if err != nil { @@ -118,7 +122,10 @@ func (c *PetApiController) AddPet(w http.ResponseWriter, r *http.Request) { // DeletePet - Deletes a pet func (c *PetApiController) DeletePet(w http.ResponseWriter, r *http.Request) { - petIdParam, err := parseInt64Parameter(chi.URLParam(r, "petId"), true) + petIdParam, err := parseNumericParameter[int64]( + chi.URLParam(r, "petId"), + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return @@ -165,7 +172,10 @@ func (c *PetApiController) FindPetsByTags(w http.ResponseWriter, r *http.Request // GetPetById - Find pet by ID func (c *PetApiController) GetPetById(w http.ResponseWriter, r *http.Request) { - petIdParam, err := parseInt64Parameter(chi.URLParam(r, "petId"), true) + petIdParam, err := parseNumericParameter[int64]( + chi.URLParam(r, "petId"), + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return @@ -193,6 +203,10 @@ func (c *PetApiController) UpdatePet(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertPetConstraints(petParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.UpdatePet(r.Context(), petParam) // If an error occurred, encode the error with the status code if err != nil { @@ -209,13 +223,20 @@ func (c *PetApiController) UpdatePetWithForm(w http.ResponseWriter, r *http.Requ c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - petIdParam, err := parseInt64Parameter(chi.URLParam(r, "petId"), true) + petIdParam, err := parseNumericParameter[int64]( + chi.URLParam(r, "petId"), + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - nameParam := r.FormValue("name") - statusParam := r.FormValue("status") + + + nameParam := r.FormValue("name") + + + statusParam := r.FormValue("status") result, err := c.service.UpdatePetWithForm(r.Context(), petIdParam, nameParam, statusParam) // If an error occurred, encode the error with the status code if err != nil { @@ -232,19 +253,26 @@ func (c *PetApiController) UploadFile(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - petIdParam, err := parseInt64Parameter(chi.URLParam(r, "petId"), true) + petIdParam, err := parseNumericParameter[int64]( + chi.URLParam(r, "petId"), + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - additionalMetadataParam := r.FormValue("additionalMetadata") + + + additionalMetadataParam := r.FormValue("additionalMetadata") fileParam, err := ReadFormFileToTempFile(r, "file") if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - result, err := c.service.UploadFile(r.Context(), petIdParam, additionalMetadataParam, fileParam) + + + result, err := c.service.UploadFile(r.Context(), petIdParam, additionalMetadataParam, fileParam) // 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_store.go b/samples/server/petstore/go-chi-server/go/api_store.go index 905450a51ff4..bb6a5a3b2347 100644 --- a/samples/server/petstore/go-chi-server/go/api_store.go +++ b/samples/server/petstore/go-chi-server/go/api_store.go @@ -100,7 +100,12 @@ func (c *StoreApiController) GetInventory(w http.ResponseWriter, r *http.Request // GetOrderById - Find purchase order by ID func (c *StoreApiController) GetOrderById(w http.ResponseWriter, r *http.Request) { - orderIdParam, err := parseInt64Parameter(chi.URLParam(r, "orderId"), true) + orderIdParam, err := parseNumericParameter[int64]( + chi.URLParam(r, "orderId"), + WithRequire[int64](parseInt64), + WithMinimum[int64](1), + WithMaximum[int64](5), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return @@ -128,6 +133,10 @@ func (c *StoreApiController) PlaceOrder(w http.ResponseWriter, r *http.Request) c.errorHandler(w, r, err, nil) return } + if err := AssertOrderConstraints(orderParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.PlaceOrder(r.Context(), orderParam) // If an error occurred, encode the error with the status code if err != nil { diff --git a/samples/server/petstore/go-chi-server/go/api_user.go b/samples/server/petstore/go-chi-server/go/api_user.go index 8dde5c2457be..61ef7564d6c2 100644 --- a/samples/server/petstore/go-chi-server/go/api_user.go +++ b/samples/server/petstore/go-chi-server/go/api_user.go @@ -106,6 +106,10 @@ func (c *UserApiController) CreateUser(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertUserConstraints(userParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.CreateUser(r.Context(), userParam) // If an error occurred, encode the error with the status code if err != nil { @@ -233,6 +237,10 @@ func (c *UserApiController) UpdateUser(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertUserConstraints(userParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.UpdateUser(r.Context(), usernameParam, userParam) // If an error occurred, encode the error with the status code if err != nil { diff --git a/samples/server/petstore/go-chi-server/go/model_api_response.go b/samples/server/petstore/go-chi-server/go/model_api_response.go index 3cb1dc2d3e27..0ef560396fd2 100644 --- a/samples/server/petstore/go-chi-server/go/model_api_response.go +++ b/samples/server/petstore/go-chi-server/go/model_api_response.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // ApiResponse - Describes the result of uploading an image resource type ApiResponse struct { @@ -19,7 +26,19 @@ type ApiResponse struct { Message string `json:"message,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *ApiResponse) UnmarshalJSON(data []byte) error { + + type Alias ApiResponse // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertApiResponseRequired checks if the required fields are not zero-ed func AssertApiResponseRequired(obj ApiResponse) error { return nil } + +// AssertApiResponseConstraints checks if the values respects the defined constraints +func AssertApiResponseConstraints(obj ApiResponse) error { + return nil +} diff --git a/samples/server/petstore/go-chi-server/go/model_category.go b/samples/server/petstore/go-chi-server/go/model_category.go index 9d73f38294ed..1581493112a2 100644 --- a/samples/server/petstore/go-chi-server/go/model_category.go +++ b/samples/server/petstore/go-chi-server/go/model_category.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // Category - A category for a pet type Category struct { @@ -17,7 +24,19 @@ type Category struct { Name string `json:"name,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Category) UnmarshalJSON(data []byte) error { + + type Alias Category // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertCategoryRequired checks if the required fields are not zero-ed func AssertCategoryRequired(obj Category) error { return nil } + +// AssertCategoryConstraints checks if the values respects the defined constraints +func AssertCategoryConstraints(obj Category) error { + return nil +} diff --git a/samples/server/petstore/go-chi-server/go/model_order.go b/samples/server/petstore/go-chi-server/go/model_order.go index c40a82bab091..1aa6b22e20f6 100644 --- a/samples/server/petstore/go-chi-server/go/model_order.go +++ b/samples/server/petstore/go-chi-server/go/model_order.go @@ -9,10 +9,14 @@ package petstoreserver + import ( "time" + "encoding/json" ) + + // Order - An order for a pets from the pet store type Order struct { @@ -30,7 +34,20 @@ type Order struct { Complete bool `json:"complete,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Order) UnmarshalJSON(data []byte) error { + m.Complete = false + + type Alias Order // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertOrderRequired checks if the required fields are not zero-ed func AssertOrderRequired(obj Order) error { return nil } + +// AssertOrderConstraints checks if the values respects the defined constraints +func AssertOrderConstraints(obj Order) error { + return nil +} diff --git a/samples/server/petstore/go-chi-server/go/model_pet.go b/samples/server/petstore/go-chi-server/go/model_pet.go index 5073463b3916..948533cd1a5b 100644 --- a/samples/server/petstore/go-chi-server/go/model_pet.go +++ b/samples/server/petstore/go-chi-server/go/model_pet.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // Pet - A pet for sale in the pet store type Pet struct { @@ -27,6 +34,13 @@ type Pet struct { Status string `json:"status,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Pet) UnmarshalJSON(data []byte) error { + + type Alias Pet // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertPetRequired checks if the required fields are not zero-ed func AssertPetRequired(obj Pet) error { elements := map[string]interface{}{ @@ -49,3 +63,8 @@ func AssertPetRequired(obj Pet) error { } return nil } + +// AssertPetConstraints checks if the values respects the defined constraints +func AssertPetConstraints(obj Pet) error { + return nil +} diff --git a/samples/server/petstore/go-chi-server/go/model_tag.go b/samples/server/petstore/go-chi-server/go/model_tag.go index c441e14e9969..47073503db00 100644 --- a/samples/server/petstore/go-chi-server/go/model_tag.go +++ b/samples/server/petstore/go-chi-server/go/model_tag.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // Tag - A tag for a pet type Tag struct { @@ -17,7 +24,19 @@ type Tag struct { Name string `json:"name,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Tag) UnmarshalJSON(data []byte) error { + + type Alias Tag // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertTagRequired checks if the required fields are not zero-ed func AssertTagRequired(obj Tag) error { return nil } + +// AssertTagConstraints checks if the values respects the defined constraints +func AssertTagConstraints(obj Tag) error { + return nil +} diff --git a/samples/server/petstore/go-chi-server/go/model_user.go b/samples/server/petstore/go-chi-server/go/model_user.go index 2fcaf883f4cb..0021fe30bd77 100644 --- a/samples/server/petstore/go-chi-server/go/model_user.go +++ b/samples/server/petstore/go-chi-server/go/model_user.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // User - A User who is purchasing from the pet store type User struct { @@ -30,7 +37,19 @@ type User struct { UserStatus int32 `json:"userStatus,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *User) UnmarshalJSON(data []byte) error { + + type Alias User // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertUserRequired checks if the required fields are not zero-ed func AssertUserRequired(obj User) error { return nil } + +// AssertUserConstraints checks if the values respects the defined constraints +func AssertUserConstraints(obj User) error { + return nil +} diff --git a/samples/server/petstore/go-chi-server/go/routers.go b/samples/server/petstore/go-chi-server/go/routers.go index 3070d048e1b3..2d69e3e03b7d 100644 --- a/samples/server/petstore/go-chi-server/go/routers.go +++ b/samples/server/petstore/go-chi-server/go/routers.go @@ -38,6 +38,8 @@ type Router interface { } const errMsgRequiredMissing = "required parameter is missing" +const errMsgMinValueConstraint = "provided parameter is not respecting minimum value constraint" +const errMsgMaxValueConstraint = "provided parameter is not respecting maximum value constraint" // NewRouter creates a new router for any number of api routers func NewRouter(routers ...Router) chi.Router { @@ -132,123 +134,119 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error return file, nil } -// parseFloatParameter parses a string parameter to an int64. -func parseFloatParameter(param string, bitSize int, required bool) (float64, error) { - if param == "" { - if required { - return 0, errors.New(errMsgRequiredMissing) - } +type Number interface { + ~int32 | ~int64 | ~float32 | ~float64 +} - return 0, nil - } +type ParseString[T Number | string | bool] func(v string) (T, error) - return strconv.ParseFloat(param, bitSize) +// parseFloat64 parses a string parameter to an float64. +func parseFloat64(param string) (float64, error) { + return strconv.ParseFloat(param, 64) } -// parseFloat64Parameter parses a string parameter to an float64. -func parseFloat64Parameter(param string, required bool) (float64, error) { - return parseFloatParameter(param, 64, required) +// parseFloat32 parses a string parameter to an float32. +func parseFloat32(param string) (float32, error) { + v, err := strconv.ParseFloat(param, 32) + return float32(v), err } -// parseFloat32Parameter parses a string parameter to an float32. -func parseFloat32Parameter(param string, required bool) (float32, error) { - val, err := parseFloatParameter(param, 32, required) - return float32(val), err +// parseInt64 parses a string parameter to an int64. +func parseInt64(param string) (int64, error) { + return strconv.ParseInt(param, 10, 64) } -// parseIntParameter parses a string parameter to an int64. -func parseIntParameter(param string, bitSize int, required bool) (int64, error) { - if param == "" { - if required { - return 0, errors.New(errMsgRequiredMissing) - } - - return 0, nil - } - - return strconv.ParseInt(param, 10, bitSize) +// parseInt32 parses a string parameter to an int32. +func parseInt32(param string) (int32, error) { + val, err := strconv.ParseInt(param, 10, 32) + return int32(val), err } -// parseInt64Parameter parses a string parameter to an int64. -func parseInt64Parameter(param string, required bool) (int64, error) { - return parseIntParameter(param, 64, required) +// parseBool parses a string parameter to an bool. +func parseBool(param string) (bool, error) { + return strconv.ParseBool(param) } -// parseInt32Parameter parses a string parameter to an int32. -func parseInt32Parameter(param string, required bool) (int32, error) { - val, err := parseIntParameter(param, 32, required) - return int32(val), err -} +type Operation[T Number | string | bool] func(actual string) (T, bool, error) -// parseBoolParameter parses a string parameter to a bool -func parseBoolParameter(param string, required bool) (bool, error) { - if param == "" { - if required { - return false, errors.New(errMsgRequiredMissing) +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) } - return false, nil - } - - val, err := strconv.ParseBool(param) - if err != nil { - return false, err + v, err := parse(actual) + return v, false, err } - - return bool(val), nil } -// parseFloat64ArrayParameter parses a string parameter containing array of values to []Float64. -func parseFloat64ArrayParameter(param, delim string, required bool) ([]float64, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) +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 } - return nil, nil + v, err := parse(actual) + return v, false, err } +} - str := strings.Split(param, delim) - floats := make([]float64, len(str)) +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 + } +} - for i, s := range str { - if v, err := strconv.ParseFloat(s, 64); err != nil { - return nil, err - } else { - floats[i] = v +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 floats, nil + return nil + } } -// parseFloat32ArrayParameter parses a string parameter containing array of values to []float32. -func parseFloat32ArrayParameter(param, delim string, required bool) ([]float32, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) +func WithMaximum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual > expected { + return errors.New(errMsgMaxValueConstraint) } - return nil, nil + return nil } +} - str := strings.Split(param, delim) - floats := make([]float32, len(str)) +// 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 + } - for i, s := range str { - if v, err := strconv.ParseFloat(s, 32); err != nil { - return nil, err - } else { - floats[i] = float32(v) + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return 0, err + } } } - return floats, nil + 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 +} -// parseInt64ArrayParameter parses a string parameter containing array of values to []int64. -func parseInt64ArrayParameter(param, delim string, required bool) ([]int64, error) { +// 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) @@ -258,39 +256,24 @@ func parseInt64ArrayParameter(param, delim string, required bool) ([]int64, erro } str := strings.Split(param, delim) - ints := make([]int64, len(str)) + values := make([]T, len(str)) for i, s := range str { - if v, err := strconv.ParseInt(s, 10, 64); err != nil { + v, ok, err := fn(s) + if err != nil { return nil, err - } else { - ints[i] = v } - } - - return ints, nil -} -// parseInt32ArrayParameter parses a string parameter containing array of values to []int32. -func parseInt32ArrayParameter(param, delim string, required bool) ([]int32, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return nil, err + } + } } - return nil, nil - } - - str := strings.Split(param, delim) - ints := make([]int32, len(str)) - - for i, s := range str { - if v, err := strconv.ParseInt(s, 10, 32); err != nil { - return nil, err - } else { - ints[i] = int32(v) - } + values[i] = v } - return ints, nil + return values, nil } diff --git a/samples/server/petstore/go-server-required/go/api_pet.go b/samples/server/petstore/go-server-required/go/api_pet.go index b6b9305d9df9..e0852f1398cc 100644 --- a/samples/server/petstore/go-server-required/go/api_pet.go +++ b/samples/server/petstore/go-server-required/go/api_pet.go @@ -106,6 +106,10 @@ func (c *PetApiController) AddPet(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertPetConstraints(petParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.AddPet(r.Context(), petParam) // If an error occurred, encode the error with the status code if err != nil { @@ -118,7 +122,10 @@ func (c *PetApiController) AddPet(w http.ResponseWriter, r *http.Request) { // DeletePet - Deletes a pet func (c *PetApiController) DeletePet(w http.ResponseWriter, r *http.Request) { - petIdParam, err := parseInt64Parameter(chi.URLParam(r, "petId"), true) + petIdParam, err := parseNumericParameter[int64]( + chi.URLParam(r, "petId"), + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return @@ -165,7 +172,10 @@ func (c *PetApiController) FindPetsByTags(w http.ResponseWriter, r *http.Request // GetPetById - Find pet by ID func (c *PetApiController) GetPetById(w http.ResponseWriter, r *http.Request) { - petIdParam, err := parseInt64Parameter(chi.URLParam(r, "petId"), true) + petIdParam, err := parseNumericParameter[int64]( + chi.URLParam(r, "petId"), + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return @@ -193,6 +203,10 @@ func (c *PetApiController) UpdatePet(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertPetConstraints(petParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.UpdatePet(r.Context(), petParam) // If an error occurred, encode the error with the status code if err != nil { @@ -209,13 +223,20 @@ func (c *PetApiController) UpdatePetWithForm(w http.ResponseWriter, r *http.Requ c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - petIdParam, err := parseInt64Parameter(chi.URLParam(r, "petId"), true) + petIdParam, err := parseNumericParameter[int64]( + chi.URLParam(r, "petId"), + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - nameParam := r.FormValue("name") - statusParam := r.FormValue("status") + + + nameParam := r.FormValue("name") + + + statusParam := r.FormValue("status") result, err := c.service.UpdatePetWithForm(r.Context(), petIdParam, nameParam, statusParam) // If an error occurred, encode the error with the status code if err != nil { @@ -232,19 +253,26 @@ func (c *PetApiController) UploadFile(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - petIdParam, err := parseInt64Parameter(chi.URLParam(r, "petId"), true) + petIdParam, err := parseNumericParameter[int64]( + chi.URLParam(r, "petId"), + WithRequire[int64](parseInt64), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - additionalMetadataParam := r.FormValue("additionalMetadata") + + + additionalMetadataParam := r.FormValue("additionalMetadata") fileParam, err := ReadFormFileToTempFile(r, "file") if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return } - result, err := c.service.UploadFile(r.Context(), petIdParam, additionalMetadataParam, fileParam) + + + result, err := c.service.UploadFile(r.Context(), petIdParam, additionalMetadataParam, fileParam) // 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-server-required/go/api_store.go b/samples/server/petstore/go-server-required/go/api_store.go index 905450a51ff4..bb6a5a3b2347 100644 --- a/samples/server/petstore/go-server-required/go/api_store.go +++ b/samples/server/petstore/go-server-required/go/api_store.go @@ -100,7 +100,12 @@ func (c *StoreApiController) GetInventory(w http.ResponseWriter, r *http.Request // GetOrderById - Find purchase order by ID func (c *StoreApiController) GetOrderById(w http.ResponseWriter, r *http.Request) { - orderIdParam, err := parseInt64Parameter(chi.URLParam(r, "orderId"), true) + orderIdParam, err := parseNumericParameter[int64]( + chi.URLParam(r, "orderId"), + WithRequire[int64](parseInt64), + WithMinimum[int64](1), + WithMaximum[int64](5), + ) if err != nil { c.errorHandler(w, r, &ParsingError{Err: err}, nil) return @@ -128,6 +133,10 @@ func (c *StoreApiController) PlaceOrder(w http.ResponseWriter, r *http.Request) c.errorHandler(w, r, err, nil) return } + if err := AssertOrderConstraints(orderParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.PlaceOrder(r.Context(), orderParam) // If an error occurred, encode the error with the status code if err != nil { diff --git a/samples/server/petstore/go-server-required/go/api_user.go b/samples/server/petstore/go-server-required/go/api_user.go index 8dde5c2457be..61ef7564d6c2 100644 --- a/samples/server/petstore/go-server-required/go/api_user.go +++ b/samples/server/petstore/go-server-required/go/api_user.go @@ -106,6 +106,10 @@ func (c *UserApiController) CreateUser(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertUserConstraints(userParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.CreateUser(r.Context(), userParam) // If an error occurred, encode the error with the status code if err != nil { @@ -233,6 +237,10 @@ func (c *UserApiController) UpdateUser(w http.ResponseWriter, r *http.Request) { c.errorHandler(w, r, err, nil) return } + if err := AssertUserConstraints(userParam); err != nil { + c.errorHandler(w, r, err, nil) + return + } result, err := c.service.UpdateUser(r.Context(), usernameParam, userParam) // If an error occurred, encode the error with the status code if err != nil { diff --git a/samples/server/petstore/go-server-required/go/model_an_object.go b/samples/server/petstore/go-server-required/go/model_an_object.go index d10c34be958b..f420aa2c80a2 100644 --- a/samples/server/petstore/go-server-required/go/model_an_object.go +++ b/samples/server/petstore/go-server-required/go/model_an_object.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // AnObject - An array 3-deep. type AnObject struct { @@ -18,6 +25,13 @@ type AnObject struct { Pet []Pet `json:"Pet,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *AnObject) UnmarshalJSON(data []byte) error { + + type Alias AnObject // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertAnObjectRequired checks if the required fields are not zero-ed func AssertAnObjectRequired(obj AnObject) error { if err := AssertTagRequired(obj.Tag); err != nil { @@ -30,3 +44,8 @@ func AssertAnObjectRequired(obj AnObject) error { } return nil } + +// AssertAnObjectConstraints checks if the values respects the defined constraints +func AssertAnObjectConstraints(obj AnObject) error { + return nil +} diff --git a/samples/server/petstore/go-server-required/go/model_api_response.go b/samples/server/petstore/go-server-required/go/model_api_response.go index 3cb1dc2d3e27..0ef560396fd2 100644 --- a/samples/server/petstore/go-server-required/go/model_api_response.go +++ b/samples/server/petstore/go-server-required/go/model_api_response.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // ApiResponse - Describes the result of uploading an image resource type ApiResponse struct { @@ -19,7 +26,19 @@ type ApiResponse struct { Message string `json:"message,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *ApiResponse) UnmarshalJSON(data []byte) error { + + type Alias ApiResponse // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertApiResponseRequired checks if the required fields are not zero-ed func AssertApiResponseRequired(obj ApiResponse) error { return nil } + +// AssertApiResponseConstraints checks if the values respects the defined constraints +func AssertApiResponseConstraints(obj ApiResponse) error { + return nil +} diff --git a/samples/server/petstore/go-server-required/go/model_category.go b/samples/server/petstore/go-server-required/go/model_category.go index 9d73f38294ed..1581493112a2 100644 --- a/samples/server/petstore/go-server-required/go/model_category.go +++ b/samples/server/petstore/go-server-required/go/model_category.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // Category - A category for a pet type Category struct { @@ -17,7 +24,19 @@ type Category struct { Name string `json:"name,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Category) UnmarshalJSON(data []byte) error { + + type Alias Category // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertCategoryRequired checks if the required fields are not zero-ed func AssertCategoryRequired(obj Category) error { return nil } + +// AssertCategoryConstraints checks if the values respects the defined constraints +func AssertCategoryConstraints(obj Category) error { + return nil +} diff --git a/samples/server/petstore/go-server-required/go/model_order.go b/samples/server/petstore/go-server-required/go/model_order.go index 3dd97db3f244..ca53e1fec036 100644 --- a/samples/server/petstore/go-server-required/go/model_order.go +++ b/samples/server/petstore/go-server-required/go/model_order.go @@ -9,10 +9,14 @@ package petstoreserver + import ( "time" + "encoding/json" ) + + // Order - An order for a pets from the pet store type Order struct { SpecialInfo @@ -33,6 +37,14 @@ type Order struct { ShipDate time.Time `json:"shipDate,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Order) UnmarshalJSON(data []byte) error { + m.Complete = false + + type Alias Order // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertOrderRequired checks if the required fields are not zero-ed func AssertOrderRequired(obj Order) error { elements := map[string]interface{}{ @@ -50,3 +62,8 @@ func AssertOrderRequired(obj Order) error { return nil } + +// AssertOrderConstraints checks if the values respects the defined constraints +func AssertOrderConstraints(obj Order) error { + return nil +} diff --git a/samples/server/petstore/go-server-required/go/model_order_info.go b/samples/server/petstore/go-server-required/go/model_order_info.go index 47720a31bf3c..b5ec2eeaf0b7 100644 --- a/samples/server/petstore/go-server-required/go/model_order_info.go +++ b/samples/server/petstore/go-server-required/go/model_order_info.go @@ -9,10 +9,14 @@ package petstoreserver + import ( "time" + "encoding/json" ) + + // OrderInfo - An order info for a pets from the pet store type OrderInfo struct { @@ -23,7 +27,19 @@ type OrderInfo struct { ShipDate time.Time `json:"shipDate,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *OrderInfo) UnmarshalJSON(data []byte) error { + + type Alias OrderInfo // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertOrderInfoRequired checks if the required fields are not zero-ed func AssertOrderInfoRequired(obj OrderInfo) error { return nil } + +// AssertOrderInfoConstraints checks if the values respects the defined constraints +func AssertOrderInfoConstraints(obj OrderInfo) error { + return nil +} diff --git a/samples/server/petstore/go-server-required/go/model_pet.go b/samples/server/petstore/go-server-required/go/model_pet.go index 52049f459db8..9db44bec4e3a 100644 --- a/samples/server/petstore/go-server-required/go/model_pet.go +++ b/samples/server/petstore/go-server-required/go/model_pet.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // Pet - A pet for sale in the pet store type Pet struct { @@ -26,6 +33,13 @@ type Pet struct { Status string `json:"status,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Pet) UnmarshalJSON(data []byte) error { + + type Alias Pet // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertPetRequired checks if the required fields are not zero-ed func AssertPetRequired(obj Pet) error { elements := map[string]interface{}{ @@ -52,3 +66,8 @@ func AssertPetRequired(obj Pet) error { } return nil } + +// AssertPetConstraints checks if the values respects the defined constraints +func AssertPetConstraints(obj Pet) error { + return nil +} diff --git a/samples/server/petstore/go-server-required/go/model_special_info.go b/samples/server/petstore/go-server-required/go/model_special_info.go index e193d5c53fe2..9b03b1feb17c 100644 --- a/samples/server/petstore/go-server-required/go/model_special_info.go +++ b/samples/server/petstore/go-server-required/go/model_special_info.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // SpecialInfo - An order info for a pets from the pet store type SpecialInfo struct { @@ -17,7 +24,19 @@ type SpecialInfo struct { Type string `json:"type,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *SpecialInfo) UnmarshalJSON(data []byte) error { + + type Alias SpecialInfo // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertSpecialInfoRequired checks if the required fields are not zero-ed func AssertSpecialInfoRequired(obj SpecialInfo) error { return nil } + +// AssertSpecialInfoConstraints checks if the values respects the defined constraints +func AssertSpecialInfoConstraints(obj SpecialInfo) error { + return nil +} diff --git a/samples/server/petstore/go-server-required/go/model_tag.go b/samples/server/petstore/go-server-required/go/model_tag.go index c441e14e9969..47073503db00 100644 --- a/samples/server/petstore/go-server-required/go/model_tag.go +++ b/samples/server/petstore/go-server-required/go/model_tag.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // Tag - A tag for a pet type Tag struct { @@ -17,7 +24,19 @@ type Tag struct { Name string `json:"name,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *Tag) UnmarshalJSON(data []byte) error { + + type Alias Tag // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertTagRequired checks if the required fields are not zero-ed func AssertTagRequired(obj Tag) error { return nil } + +// AssertTagConstraints checks if the values respects the defined constraints +func AssertTagConstraints(obj Tag) error { + return nil +} diff --git a/samples/server/petstore/go-server-required/go/model_user.go b/samples/server/petstore/go-server-required/go/model_user.go index 80649d9f5141..95a33011498e 100644 --- a/samples/server/petstore/go-server-required/go/model_user.go +++ b/samples/server/petstore/go-server-required/go/model_user.go @@ -9,6 +9,13 @@ package petstoreserver + +import ( + "encoding/json" +) + + + // User - A User who is purchasing from the pet store type User struct { @@ -36,6 +43,13 @@ type User struct { DeepSliceMap [][]AnObject `json:"deepSliceMap,omitempty"` } +// UnmarshalJSON sets *m to a copy of data while respecting defaults if specified. +func (m *User) UnmarshalJSON(data []byte) error { + + type Alias User // To avoid infinite recursion + return json.Unmarshal(data, (*Alias)(m)) +} + // AssertUserRequired checks if the required fields are not zero-ed func AssertUserRequired(obj User) error { elements := map[string]interface{}{ @@ -57,3 +71,8 @@ func AssertUserRequired(obj User) error { } return nil } + +// AssertUserConstraints checks if the values respects the defined constraints +func AssertUserConstraints(obj User) error { + return nil +} diff --git a/samples/server/petstore/go-server-required/go/routers.go b/samples/server/petstore/go-server-required/go/routers.go index 3070d048e1b3..2d69e3e03b7d 100644 --- a/samples/server/petstore/go-server-required/go/routers.go +++ b/samples/server/petstore/go-server-required/go/routers.go @@ -38,6 +38,8 @@ type Router interface { } const errMsgRequiredMissing = "required parameter is missing" +const errMsgMinValueConstraint = "provided parameter is not respecting minimum value constraint" +const errMsgMaxValueConstraint = "provided parameter is not respecting maximum value constraint" // NewRouter creates a new router for any number of api routers func NewRouter(routers ...Router) chi.Router { @@ -132,123 +134,119 @@ func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error return file, nil } -// parseFloatParameter parses a string parameter to an int64. -func parseFloatParameter(param string, bitSize int, required bool) (float64, error) { - if param == "" { - if required { - return 0, errors.New(errMsgRequiredMissing) - } +type Number interface { + ~int32 | ~int64 | ~float32 | ~float64 +} - return 0, nil - } +type ParseString[T Number | string | bool] func(v string) (T, error) - return strconv.ParseFloat(param, bitSize) +// parseFloat64 parses a string parameter to an float64. +func parseFloat64(param string) (float64, error) { + return strconv.ParseFloat(param, 64) } -// parseFloat64Parameter parses a string parameter to an float64. -func parseFloat64Parameter(param string, required bool) (float64, error) { - return parseFloatParameter(param, 64, required) +// parseFloat32 parses a string parameter to an float32. +func parseFloat32(param string) (float32, error) { + v, err := strconv.ParseFloat(param, 32) + return float32(v), err } -// parseFloat32Parameter parses a string parameter to an float32. -func parseFloat32Parameter(param string, required bool) (float32, error) { - val, err := parseFloatParameter(param, 32, required) - return float32(val), err +// parseInt64 parses a string parameter to an int64. +func parseInt64(param string) (int64, error) { + return strconv.ParseInt(param, 10, 64) } -// parseIntParameter parses a string parameter to an int64. -func parseIntParameter(param string, bitSize int, required bool) (int64, error) { - if param == "" { - if required { - return 0, errors.New(errMsgRequiredMissing) - } - - return 0, nil - } - - return strconv.ParseInt(param, 10, bitSize) +// parseInt32 parses a string parameter to an int32. +func parseInt32(param string) (int32, error) { + val, err := strconv.ParseInt(param, 10, 32) + return int32(val), err } -// parseInt64Parameter parses a string parameter to an int64. -func parseInt64Parameter(param string, required bool) (int64, error) { - return parseIntParameter(param, 64, required) +// parseBool parses a string parameter to an bool. +func parseBool(param string) (bool, error) { + return strconv.ParseBool(param) } -// parseInt32Parameter parses a string parameter to an int32. -func parseInt32Parameter(param string, required bool) (int32, error) { - val, err := parseIntParameter(param, 32, required) - return int32(val), err -} +type Operation[T Number | string | bool] func(actual string) (T, bool, error) -// parseBoolParameter parses a string parameter to a bool -func parseBoolParameter(param string, required bool) (bool, error) { - if param == "" { - if required { - return false, errors.New(errMsgRequiredMissing) +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) } - return false, nil - } - - val, err := strconv.ParseBool(param) - if err != nil { - return false, err + v, err := parse(actual) + return v, false, err } - - return bool(val), nil } -// parseFloat64ArrayParameter parses a string parameter containing array of values to []Float64. -func parseFloat64ArrayParameter(param, delim string, required bool) ([]float64, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) +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 } - return nil, nil + v, err := parse(actual) + return v, false, err } +} - str := strings.Split(param, delim) - floats := make([]float64, len(str)) +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 + } +} - for i, s := range str { - if v, err := strconv.ParseFloat(s, 64); err != nil { - return nil, err - } else { - floats[i] = v +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 floats, nil + return nil + } } -// parseFloat32ArrayParameter parses a string parameter containing array of values to []float32. -func parseFloat32ArrayParameter(param, delim string, required bool) ([]float32, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) +func WithMaximum[T Number](expected T) Constraint[T] { + return func(actual T) error { + if actual > expected { + return errors.New(errMsgMaxValueConstraint) } - return nil, nil + return nil } +} - str := strings.Split(param, delim) - floats := make([]float32, len(str)) +// 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 + } - for i, s := range str { - if v, err := strconv.ParseFloat(s, 32); err != nil { - return nil, err - } else { - floats[i] = float32(v) + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return 0, err + } } } - return floats, nil + 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 +} -// parseInt64ArrayParameter parses a string parameter containing array of values to []int64. -func parseInt64ArrayParameter(param, delim string, required bool) ([]int64, error) { +// 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) @@ -258,39 +256,24 @@ func parseInt64ArrayParameter(param, delim string, required bool) ([]int64, erro } str := strings.Split(param, delim) - ints := make([]int64, len(str)) + values := make([]T, len(str)) for i, s := range str { - if v, err := strconv.ParseInt(s, 10, 64); err != nil { + v, ok, err := fn(s) + if err != nil { return nil, err - } else { - ints[i] = v } - } - - return ints, nil -} -// parseInt32ArrayParameter parses a string parameter containing array of values to []int32. -func parseInt32ArrayParameter(param, delim string, required bool) ([]int32, error) { - if param == "" { - if required { - return nil, errors.New(errMsgRequiredMissing) + if !ok { + for _, check := range checks { + if err := check(v); err != nil { + return nil, err + } + } } - return nil, nil - } - - str := strings.Split(param, delim) - ints := make([]int32, len(str)) - - for i, s := range str { - if v, err := strconv.ParseInt(s, 10, 32); err != nil { - return nil, err - } else { - ints[i] = int32(v) - } + values[i] = v } - return ints, nil + return values, nil }