From bf4ecb6070ba0cfdc9a486f3b6e54ea5ef90cc82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 05:34:10 +0000 Subject: [PATCH] build(deps): bump github.com/oapi-codegen/runtime from 1.1.2 to 1.2.0 Bumps [github.com/oapi-codegen/runtime](https://github.com/oapi-codegen/runtime) from 1.1.2 to 1.2.0. - [Release notes](https://github.com/oapi-codegen/runtime/releases) - [Commits](https://github.com/oapi-codegen/runtime/compare/v1.1.2...v1.2.0) --- updated-dependencies: - dependency-name: github.com/oapi-codegen/runtime dependency-version: 1.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 8 +- .../oapi-codegen/runtime/bindform.go | 7 +- .../oapi-codegen/runtime/bindparam.go | 285 +++++++++++++++++- .../oapi-codegen/runtime/bindstring.go | 27 ++ .../oapi-codegen/runtime/deepobject.go | 40 +-- .../oapi-codegen/runtime/paramformat.go | 52 ++++ .../oapi-codegen/runtime/styleparam.go | 42 ++- .../oapi-codegen/runtime/types/date.go | 19 +- .../oapi-codegen/runtime/types/email.go | 11 +- .../oapi-codegen/runtime/types/regexes.go | 11 - vendor/modules.txt | 2 +- 12 files changed, 452 insertions(+), 54 deletions(-) create mode 100644 vendor/github.com/oapi-codegen/runtime/paramformat.go delete mode 100644 vendor/github.com/oapi-codegen/runtime/types/regexes.go diff --git a/go.mod b/go.mod index d4dc0a3..4ca3ea9 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/aquasecurity/table v1.11.0 github.com/liamg/tml v0.7.0 github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 - github.com/oapi-codegen/runtime v1.1.2 + github.com/oapi-codegen/runtime v1.2.0 github.com/pterm/pterm v0.12.82 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 diff --git a/go.sum b/go.sum index 4057dea..9be3ee4 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,8 @@ github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPci github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 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/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -69,8 +69,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 h1:5vHNY1uuPBRBWqB2Dp0G7YB03phxLQZupZTIZaeorjc= github.com/oapi-codegen/oapi-codegen/v2 v2.5.1/go.mod h1:ro0npU1BWkcGpCgGD9QwPp44l5OIZ94tB3eabnT7DjQ= -github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= -github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4= +github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/vendor/github.com/oapi-codegen/runtime/bindform.go b/vendor/github.com/oapi-codegen/runtime/bindform.go index 31b19d9..121f5d6 100644 --- a/vendor/github.com/oapi-codegen/runtime/bindform.go +++ b/vendor/github.com/oapi-codegen/runtime/bindform.go @@ -288,7 +288,12 @@ func bindAdditionalProperties(additionalProperties reflect.Value, parentStruct r func marshalFormImpl(v reflect.Value, result url.Values, name string) { switch v.Kind() { - case reflect.Interface, reflect.Ptr: + case reflect.Ptr: + if v.IsNil() { + break + } + fallthrough + case reflect.Interface: marshalFormImpl(v.Elem(), result, name) case reflect.Slice: for i := 0; i < v.Len(); i++ { diff --git a/vendor/github.com/oapi-codegen/runtime/bindparam.go b/vendor/github.com/oapi-codegen/runtime/bindparam.go index df069c9..7906f53 100644 --- a/vendor/github.com/oapi-codegen/runtime/bindparam.go +++ b/vendor/github.com/oapi-codegen/runtime/bindparam.go @@ -63,6 +63,12 @@ type BindStyledParameterOptions struct { Explode bool // Whether the parameter is required in the query Required bool + // Type is the OpenAPI type of the parameter (e.g. "string", "integer"). + Type string + // Format is the OpenAPI format of the parameter (e.g. "byte", "date-time"). + // When set to "byte" and the destination is []byte, the value is + // base64-decoded rather than treated as a generic slice. + Format string } // BindStyledParameterWithOptions binds a parameter as described in the Path Parameters @@ -109,7 +115,7 @@ func BindStyledParameterWithOptions(style string, paramName string, value string // This is the basic type of the destination object. t := v.Type() - if t.Kind() == reflect.Struct { + if t.Kind() == reflect.Struct || t.Kind() == reflect.Map { // We've got a destination object, we'll create a JSON representation // of the input value, and let the json library deal with the unmarshaling parts, err := splitStyledParameter(style, opts.Explode, true, paramName, value) @@ -121,6 +127,22 @@ func BindStyledParameterWithOptions(style string, paramName string, value string } if t.Kind() == reflect.Slice { + if opts.Format == "byte" && isByteSlice(t) { + parts, err := splitStyledParameter(style, opts.Explode, false, paramName, value) + if err != nil { + return fmt.Errorf("error splitting input '%s' into parts: %w", value, err) + } + if len(parts) != 1 { + return fmt.Errorf("expected single base64 value for byte slice parameter '%s', got %d parts", paramName, len(parts)) + } + decoded, err := base64Decode(parts[0]) + if err != nil { + return fmt.Errorf("error decoding base64 parameter '%s': %w", paramName, err) + } + v.SetBytes(decoded) + return nil + } + // Chop up the parameter into parts based on its style parts, err := splitStyledParameter(style, opts.Explode, false, paramName, value) if err != nil { @@ -306,8 +328,29 @@ func bindSplitPartsToDestinationStruct(paramName string, parts []string, explode // tell them apart. This code tries to fail, but the moral of the story is that // you shouldn't pass objects via form styled query arguments, just use // the Content parameter form. +// +// Deprecated: BindQueryParameter pre-decodes the query string via url.Values, +// which makes it impossible to distinguish literal commas from delimiter commas +// in form/explode=false parameters. Use BindRawQueryParameter instead, which +// operates on the raw query string and handles encoded delimiters correctly. func BindQueryParameter(style string, explode bool, required bool, paramName string, queryParams url.Values, dest interface{}) error { + return BindQueryParameterWithOptions(style, explode, required, paramName, queryParams, dest, BindQueryParameterOptions{}) +} + +// BindQueryParameterOptions defines optional arguments for BindQueryParameterWithOptions. +type BindQueryParameterOptions struct { + // Type is the OpenAPI type of the parameter (e.g. "string", "integer"). + Type string + // Format is the OpenAPI format of the parameter (e.g. "byte", "date-time"). + // When set to "byte" and the destination is []byte, the value is + // base64-decoded rather than treated as a generic slice. + Format string +} + +// BindQueryParameterWithOptions works like BindQueryParameter with additional options. +func BindQueryParameterWithOptions(style string, explode bool, required bool, paramName string, + queryParams url.Values, dest interface{}, opts BindQueryParameterOptions) error { // dv = destination value. dv := reflect.Indirect(reflect.ValueOf(dest)) @@ -378,7 +421,18 @@ func BindQueryParameter(style string, explode bool, required bool, paramName str return nil } } - err = bindSplitPartsToDestinationArray(values, output) + if opts.Format == "byte" && isByteSlice(t) { + if len(values) != 1 { + return fmt.Errorf("expected single base64 value for byte slice parameter '%s', got %d values", paramName, len(values)) + } + decoded, decErr := base64Decode(values[0]) + if decErr != nil { + return fmt.Errorf("error decoding base64 parameter '%s': %w", paramName, decErr) + } + v.SetBytes(decoded) + } else { + err = bindSplitPartsToDestinationArray(values, output) + } case reflect.Struct: // This case is really annoying, and error prone, but the // form style object binding doesn't tell us which arguments @@ -442,9 +496,37 @@ func BindQueryParameter(style string, explode bool, required bool, paramName str var err error switch k { case reflect.Slice: - err = bindSplitPartsToDestinationArray(parts, output) + if opts.Format == "byte" && isByteSlice(t) { + // For non-exploded form, the value was split on commas above. + // Rejoin to get the original base64 string. + raw := strings.Join(parts, ",") + decoded, decErr := base64Decode(raw) + if decErr != nil { + return fmt.Errorf("error decoding base64 parameter '%s': %w", paramName, decErr) + } + v.SetBytes(decoded) + } else { + err = bindSplitPartsToDestinationArray(parts, output) + } case reflect.Struct: - err = bindSplitPartsToDestinationStruct(paramName, parts, explode, output) + // Some struct types (e.g. types.Date, time.Time) are scalar values + // that should be bound from a single string, not decomposed as + // key-value objects. Detect these via the Binder and + // TextUnmarshaler interfaces. + switch v := output.(type) { + case Binder: + if len(parts) != 1 { + return fmt.Errorf("multiple values for single value parameter '%s'", paramName) + } + err = v.Bind(parts[0]) + case encoding.TextUnmarshaler: + if len(parts) != 1 { + return fmt.Errorf("multiple values for single value parameter '%s'", paramName) + } + err = v.UnmarshalText([]byte(parts[0])) + default: + err = bindSplitPartsToDestinationStruct(paramName, parts, explode, output) + } default: if len(parts) == 0 { if required { @@ -478,6 +560,201 @@ func BindQueryParameter(style string, explode bool, required bool, paramName str } } +// findRawQueryParam extracts the raw (still-percent-encoded) values for a given +// parameter name from a raw query string, without URL-decoding the values. +// The parameter key is decoded for comparison purposes, but the returned values +// remain in their original encoded form. +func findRawQueryParam(rawQuery, paramName string) (values []string, found bool) { + for rawQuery != "" { + var part string + if i := strings.IndexByte(rawQuery, '&'); i >= 0 { + part = rawQuery[:i] + rawQuery = rawQuery[i+1:] + } else { + part = rawQuery + rawQuery = "" + } + if part == "" { + continue + } + key := part + var val string + if i := strings.IndexByte(part, '='); i >= 0 { + key = part[:i] + val = part[i+1:] + } + decodedKey, err := url.QueryUnescape(key) + if err != nil { + // Skip malformed keys. + continue + } + if decodedKey == paramName { + values = append(values, val) + found = true + } + } + return values, found +} + +// BindRawQueryParameter works like BindQueryParameter but operates on the raw +// (undecoded) query string instead of pre-parsed url.Values. This correctly +// handles form/explode=false parameters whose values contain literal commas +// encoded as %2C — something that BindQueryParameter cannot do because +// url.Values has already decoded %2C to ',' before we can split on the +// delimiter comma. +func BindRawQueryParameter(style string, explode bool, required bool, paramName string, + rawQuery string, dest any) error { + + // dv = destination value. + dv := reflect.Indirect(reflect.ValueOf(dest)) + + // intermediate value form which is either dv or dv dereferenced. + v := dv + + // inner code will bind the string's value to this interface. + var output any + + // required params are never pointers, but it may happen that optional param + // is not pointer as well if user decides to annotate it with + // x-go-type-skip-optional-pointer + var extraIndirect = !required && v.Kind() == reflect.Pointer + if !extraIndirect { + output = dest + } else { + if v.IsNil() { + t := v.Type() + newValue := reflect.New(t.Elem()) + output = newValue.Interface() + } else { + output = v.Interface() + } + v = reflect.Indirect(reflect.ValueOf(output)) + } + + // This is the basic type of the destination object. + t := v.Type() + k := t.Kind() + + switch style { + case "form": + if explode { + // For the explode case, url.ParseQuery is fine — there are no + // delimiter commas to confuse with literal commas. + queryParams, err := url.ParseQuery(rawQuery) + if err != nil { + return fmt.Errorf("error parsing query string: %w", err) + } + values, found := queryParams[paramName] + + switch k { + case reflect.Slice: + if !found { + if required { + return fmt.Errorf("query parameter '%s' is required", paramName) + } + return nil + } + err = bindSplitPartsToDestinationArray(values, output) + case reflect.Struct: + var fieldsPresent bool + fieldsPresent, err = bindParamsToExplodedObject(paramName, queryParams, output) + if !fieldsPresent { + return nil + } + default: + if len(values) == 0 { + if required { + return fmt.Errorf("query parameter '%s' is required", paramName) + } + return nil + } + if len(values) != 1 { + return fmt.Errorf("multiple values for single value parameter '%s'", paramName) + } + if !found { + if required { + return fmt.Errorf("query parameter '%s' is required", paramName) + } + return nil + } + err = BindStringToObject(values[0], output) + } + if err != nil { + return err + } + if extraIndirect { + dv.Set(reflect.ValueOf(output)) + } + return nil + } + + // form, explode=false — the core fix. + // Use findRawQueryParam to get the still-encoded value, split on + // literal ',' (which is the OpenAPI delimiter), then URL-decode + // each resulting part individually. + rawValues, found := findRawQueryParam(rawQuery, paramName) + if !found { + if required { + return fmt.Errorf("query parameter '%s' is required", paramName) + } + return nil + } + if len(rawValues) != 1 { + return fmt.Errorf("parameter '%s' is not exploded, but is specified multiple times", paramName) + } + + rawParts := strings.Split(rawValues[0], ",") + parts := make([]string, len(rawParts)) + for i, rp := range rawParts { + decoded, err := url.QueryUnescape(rp) + if err != nil { + return fmt.Errorf("error decoding query parameter '%s' part %q: %w", paramName, rp, err) + } + parts[i] = decoded + } + + var err error + switch k { + case reflect.Slice: + err = bindSplitPartsToDestinationArray(parts, output) + case reflect.Struct: + err = bindSplitPartsToDestinationStruct(paramName, parts, explode, output) + default: + if len(parts) == 0 { + if required { + return fmt.Errorf("query parameter '%s' is required", paramName) + } + return nil + } + if len(parts) != 1 { + return fmt.Errorf("multiple values for single value parameter '%s'", paramName) + } + err = BindStringToObject(parts[0], output) + } + if err != nil { + return err + } + if extraIndirect { + dv.Set(reflect.ValueOf(output)) + } + return nil + + case "deepObject": + if !explode { + return errors.New("deepObjects must be exploded") + } + queryParams, err := url.ParseQuery(rawQuery) + if err != nil { + return fmt.Errorf("error parsing query string: %w", err) + } + return UnmarshalDeepObject(dest, paramName, queryParams) + case "spaceDelimited", "pipeDelimited": + return fmt.Errorf("query arguments of style '%s' aren't yet supported", style) + default: + return fmt.Errorf("style '%s' on parameter '%s' is invalid", style, paramName) + } +} + // bindParamsToExplodedObject reflects the destination structure, and pulls the value for // each settable field from the given parameters map. This is to deal with the // exploded form styled object which may occupy any number of parameter names. diff --git a/vendor/github.com/oapi-codegen/runtime/bindstring.go b/vendor/github.com/oapi-codegen/runtime/bindstring.go index 764036d..f38cf84 100644 --- a/vendor/github.com/oapi-codegen/runtime/bindstring.go +++ b/vendor/github.com/oapi-codegen/runtime/bindstring.go @@ -31,6 +31,22 @@ import ( // know the destination type each place that we use this, is to generate code // to read each specific type. func BindStringToObject(src string, dst interface{}) error { + return BindStringToObjectWithOptions(src, dst, BindStringToObjectOptions{}) +} + +// BindStringToObjectOptions defines optional arguments for BindStringToObjectWithOptions. +type BindStringToObjectOptions struct { + // Type is the OpenAPI type of the parameter (e.g. "string", "integer"). + Type string + // Format is the OpenAPI format of the parameter (e.g. "byte", "date-time"). + // When set to "byte" and the destination is []byte, the source string is + // base64-decoded rather than treated as a generic slice. + Format string +} + +// BindStringToObjectWithOptions takes a string, and attempts to assign it to the destination +// interface via whatever type conversion is necessary, with additional options. +func BindStringToObjectWithOptions(src string, dst interface{}, opts BindStringToObjectOptions) error { var err error v := reflect.ValueOf(dst) @@ -59,6 +75,17 @@ func BindStringToObject(src string, dst interface{}) error { } switch t.Kind() { + case reflect.Slice: + if opts.Format == "byte" && isByteSlice(t) { + decoded, decErr := base64Decode(src) + if decErr != nil { + return fmt.Errorf("error binding string parameter: %w", decErr) + } + v.SetBytes(decoded) + return nil + } + // Non-binary slices fall through to the default error case. + fallthrough case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: var val int64 val, err = strconv.ParseInt(src, 10, 64) diff --git a/vendor/github.com/oapi-codegen/runtime/deepobject.go b/vendor/github.com/oapi-codegen/runtime/deepobject.go index 588465c..d1b4830 100644 --- a/vendor/github.com/oapi-codegen/runtime/deepobject.go +++ b/vendor/github.com/oapi-codegen/runtime/deepobject.go @@ -1,6 +1,7 @@ package runtime import ( + "bytes" "encoding/json" "errors" "fmt" @@ -71,8 +72,10 @@ func MarshalDeepObject(i interface{}, paramName string) (string, error) { if err != nil { return "", fmt.Errorf("failed to marshal input to JSON: %w", err) } + e := json.NewDecoder(bytes.NewReader(buf)) + e.UseNumber() var i2 interface{} - err = json.Unmarshal(buf, &i2) + err = e.Decode(&i2) if err != nil { return "", fmt.Errorf("failed to unmarshal JSON: %w", err) } @@ -133,11 +136,16 @@ func UnmarshalDeepObject(dst interface{}, paramName string, params url.Values) e if strings.HasPrefix(pName, searchStr) { // trim the parameter name from the full name. pName = pName[len(paramName):] - fieldNames = append(fieldNames, pName) - if len(pValues) != 1 { - return fmt.Errorf("%s has multiple values", pName) + if len(pValues) == 1 { + fieldNames = append(fieldNames, pName) + fieldValues = append(fieldValues, pValues[0]) + } else { + // Non-indexed array format: expand repeated keys into indexed entries + for i, value := range pValues { + fieldNames = append(fieldNames, pName+"["+strconv.Itoa(i)+"]") + fieldValues = append(fieldValues, value) + } } - fieldValues = append(fieldValues, pValues[0]) } } @@ -255,12 +263,10 @@ func assignPathValues(dst interface{}, pathValues fieldOrValue) error { tm, err = time.Parse(time.RFC3339Nano, pathValues.value) if err != nil { // Fall back to parsing it as a date. - // TODO: why is this marked as an ineffassign? - tm, err = time.Parse(types.DateFormat, pathValues.value) //nolint:ineffassign,staticcheck + tm, err = time.Parse(types.DateFormat, pathValues.value) if err != nil { return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %w", pathValues.value, err) } - return fmt.Errorf("invalid date format: %w", err) } dst := iv if it != reflect.TypeOf(time.Time{}) { @@ -335,26 +341,24 @@ func assignPathValues(dst interface{}, pathValues fieldOrValue) error { } func assignSlice(dst reflect.Value, pathValues fieldOrValue) error { - // Gather up the values nValues := len(pathValues.fields) - values := make([]string, nValues) - // We expect to have consecutive array indices in the map + + // Process each array element by index for i := 0; i < nValues; i++ { indexStr := strconv.Itoa(i) fv, found := pathValues.fields[indexStr] if !found { return errors.New("array deepObjects must have consecutive indices") } - values[i] = fv.value - } - // This could be cleaner, but we can call into assignPathValues to - // avoid recreating this logic. - for i := 0; i < nValues; i++ { + // Get the destination element dstElem := dst.Index(i).Addr() - err := assignPathValues(dstElem.Interface(), fieldOrValue{value: values[i]}) + + // assignPathValues handles both simple values (via fv.value) and + // nested objects (via fv.fields) automatically + err := assignPathValues(dstElem.Interface(), fv) if err != nil { - return fmt.Errorf("error binding array: %w", err) + return fmt.Errorf("error binding array element %d: %w", i, err) } } diff --git a/vendor/github.com/oapi-codegen/runtime/paramformat.go b/vendor/github.com/oapi-codegen/runtime/paramformat.go new file mode 100644 index 0000000..0450593 --- /dev/null +++ b/vendor/github.com/oapi-codegen/runtime/paramformat.go @@ -0,0 +1,52 @@ +package runtime + +import ( + "encoding/base64" + "fmt" + "reflect" + "strings" +) + +// isByteSlice reports whether t is []byte (or equivalently []uint8). +func isByteSlice(t reflect.Type) bool { + return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 +} + +// base64Decode decodes s as base64. +// +// Per OpenAPI 3.0, format: byte uses RFC 4648 Section 4 (standard alphabet, +// padded). We use padding presence to select the right decoder, rather than +// blindly cascading (which can produce corrupt output when RawStdEncoding +// silently accepts padded input and treats '=' as data). +// +// The logic: +// 1. If s contains '=' padding → standard padded decoder (Std or URL based on alphabet). +// 2. If s contains URL-safe characters ('_' or '-') → RawURLEncoding. +// 3. Otherwise → RawStdEncoding (unpadded, standard alphabet). +func base64Decode(s string) ([]byte, error) { + if s == "" { + return []byte{}, nil + } + + if strings.ContainsRune(s, '=') { + // Padded input. Pick alphabet based on whether URL-safe chars are present. + if strings.ContainsAny(s, "-_") { + return base64Decode1(base64.URLEncoding, s) + } + return base64Decode1(base64.StdEncoding, s) + } + + // Unpadded input. Pick alphabet based on whether URL-safe chars are present. + if strings.ContainsAny(s, "-_") { + return base64Decode1(base64.RawURLEncoding, s) + } + return base64Decode1(base64.RawStdEncoding, s) +} + +func base64Decode1(enc *base64.Encoding, s string) ([]byte, error) { + b, err := enc.DecodeString(s) + if err != nil { + return nil, fmt.Errorf("failed to base64-decode string %q: %w", s, err) + } + return b, nil +} diff --git a/vendor/github.com/oapi-codegen/runtime/styleparam.go b/vendor/github.com/oapi-codegen/runtime/styleparam.go index 8491c85..389aa2a 100644 --- a/vendor/github.com/oapi-codegen/runtime/styleparam.go +++ b/vendor/github.com/oapi-codegen/runtime/styleparam.go @@ -16,6 +16,7 @@ package runtime import ( "bytes" "encoding" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -51,10 +52,31 @@ func StyleParam(style string, explode bool, paramName string, value interface{}) return StyleParamWithLocation(style, explode, paramName, ParamLocationUndefined, value) } -// Given an input value, such as a primitive type, array or object, turn it -// into a parameter based on style/explode definition, performing whatever -// escaping is necessary based on parameter location +// StyleParamWithLocation serializes a Go value into an OpenAPI-styled parameter +// string, performing escaping based on parameter location. func StyleParamWithLocation(style string, explode bool, paramName string, paramLocation ParamLocation, value interface{}) (string, error) { + return StyleParamWithOptions(style, explode, paramName, value, StyleParamOptions{ + ParamLocation: paramLocation, + }) +} + +// StyleParamOptions defines optional arguments for StyleParamWithOptions. +type StyleParamOptions struct { + // ParamLocation controls URL escaping behavior. + ParamLocation ParamLocation + // Type is the OpenAPI type of the parameter (e.g. "string", "integer"). + Type string + // Format is the OpenAPI format of the parameter (e.g. "byte", "date-time"). + // When set to "byte" and the value is []byte, it is base64-encoded as a + // single string rather than treated as a generic slice of uint8. + Format string + // Required indicates whether the parameter is required. + Required bool +} + +// StyleParamWithOptions serializes a Go value into an OpenAPI-styled parameter +// string with additional options. +func StyleParamWithOptions(style string, explode bool, paramName string, value interface{}, opts StyleParamOptions) (string, error) { t := reflect.TypeOf(value) v := reflect.ValueOf(value) @@ -83,24 +105,28 @@ func StyleParamWithLocation(style string, explode bool, paramName string, paramL return "", fmt.Errorf("error marshaling '%s' as text: %w", value, err) } - return stylePrimitive(style, explode, paramName, paramLocation, string(b)) + return stylePrimitive(style, explode, paramName, opts.ParamLocation, string(b)) } } switch t.Kind() { case reflect.Slice: + if opts.Format == "byte" && isByteSlice(t) { + encoded := base64.StdEncoding.EncodeToString(v.Bytes()) + return stylePrimitive(style, explode, paramName, opts.ParamLocation, encoded) + } n := v.Len() sliceVal := make([]interface{}, n) for i := 0; i < n; i++ { sliceVal[i] = v.Index(i).Interface() } - return styleSlice(style, explode, paramName, paramLocation, sliceVal) + return styleSlice(style, explode, paramName, opts.ParamLocation, sliceVal) case reflect.Struct: - return styleStruct(style, explode, paramName, paramLocation, value) + return styleStruct(style, explode, paramName, opts.ParamLocation, value) case reflect.Map: - return styleMap(style, explode, paramName, paramLocation, value) + return styleMap(style, explode, paramName, opts.ParamLocation, value) default: - return stylePrimitive(style, explode, paramName, paramLocation, value) + return stylePrimitive(style, explode, paramName, opts.ParamLocation, value) } } diff --git a/vendor/github.com/oapi-codegen/runtime/types/date.go b/vendor/github.com/oapi-codegen/runtime/types/date.go index 6ad852f..155751a 100644 --- a/vendor/github.com/oapi-codegen/runtime/types/date.go +++ b/vendor/github.com/oapi-codegen/runtime/types/date.go @@ -12,7 +12,7 @@ type Date struct { } func (d Date) MarshalJSON() ([]byte, error) { - return json.Marshal(d.Time.Format(DateFormat)) + return json.Marshal(d.Format(DateFormat)) } func (d *Date) UnmarshalJSON(data []byte) error { @@ -30,7 +30,7 @@ func (d *Date) UnmarshalJSON(data []byte) error { } func (d Date) String() string { - return d.Time.Format(DateFormat) + return d.Format(DateFormat) } func (d *Date) UnmarshalText(data []byte) error { @@ -41,3 +41,18 @@ func (d *Date) UnmarshalText(data []byte) error { d.Time = parsed return nil } + +// Bind implements the runtime.Binder interface so that Date is treated as a +// scalar value when binding query parameters rather than being decomposed as +// a struct with key-value pairs. +func (d *Date) Bind(src string) error { + if src == "" { + return nil + } + parsed, err := time.Parse(DateFormat, src) + if err != nil { + return err + } + d.Time = parsed + return nil +} diff --git a/vendor/github.com/oapi-codegen/runtime/types/email.go b/vendor/github.com/oapi-codegen/runtime/types/email.go index e4a1cbd..234e7cc 100644 --- a/vendor/github.com/oapi-codegen/runtime/types/email.go +++ b/vendor/github.com/oapi-codegen/runtime/types/email.go @@ -3,6 +3,7 @@ package types import ( "encoding/json" "errors" + "net/mail" ) // ErrValidationEmail is the sentinel error returned when an email fails validation @@ -14,11 +15,12 @@ var ErrValidationEmail = errors.New("email: failed to pass regex validation") type Email string func (e Email) MarshalJSON() ([]byte, error) { - if !emailRegex.MatchString(string(e)) { + m, err := mail.ParseAddress(string(e)) + if err != nil { return nil, ErrValidationEmail } - return json.Marshal(string(e)) + return json.Marshal(m.Address) } func (e *Email) UnmarshalJSON(data []byte) error { @@ -31,10 +33,11 @@ func (e *Email) UnmarshalJSON(data []byte) error { return err } - *e = Email(s) - if !emailRegex.MatchString(s) { + m, err := mail.ParseAddress(s) + if err != nil { return ErrValidationEmail } + *e = Email(m.Address) return nil } diff --git a/vendor/github.com/oapi-codegen/runtime/types/regexes.go b/vendor/github.com/oapi-codegen/runtime/types/regexes.go deleted file mode 100644 index 94f17df..0000000 --- a/vendor/github.com/oapi-codegen/runtime/types/regexes.go +++ /dev/null @@ -1,11 +0,0 @@ -package types - -import "regexp" - -const ( - emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" -) - -var ( - emailRegex = regexp.MustCompile(emailRegexString) -) diff --git a/vendor/modules.txt b/vendor/modules.txt index 8c83617..6190b63 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -50,7 +50,7 @@ github.com/mattn/go-runewidth # github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 ## explicit; go 1.22.5 github.com/oapi-codegen/oapi-codegen/v2/pkg/securityprovider -# github.com/oapi-codegen/runtime v1.1.2 +# github.com/oapi-codegen/runtime v1.2.0 ## explicit; go 1.20 github.com/oapi-codegen/runtime github.com/oapi-codegen/runtime/types