fix: add involves edge from task to agent:nomos at creation
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

Plus sync vendor directory for Docker build compatibility.
This commit is contained in:
2026-08-11 22:03:12 +02:00
parent 7d6a3320d4
commit febc153b7f
3384 changed files with 945212 additions and 2 deletions

1
vendor/github.com/oapi-codegen/runtime/.gitignore generated vendored Normal file
View File

@@ -0,0 +1 @@
bin/

201
vendor/github.com/oapi-codegen/runtime/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

44
vendor/github.com/oapi-codegen/runtime/Makefile generated vendored Normal file
View File

@@ -0,0 +1,44 @@
GOBASE=$(shell pwd)
GOBIN=$(GOBASE)/bin
$(GOBIN)/golangci-lint:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v2.10.1
.PHONY: tools
tools: $(GOBIN)/golangci-lint
lint: tools
# run the root module explicitly, to prevent recursive calls by re-invoking `make ...` top-level
$(GOBIN)/golangci-lint run ./...
# then, for all child modules, use a module-managed `Makefile`
git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && env GOBIN=$(GOBIN) make lint'
lint-ci: tools
# for the root module, explicitly run the step, to prevent recursive calls
$(GOBIN)/golangci-lint run ./... --output.text.path=stdout --timeout=5m
# then, for all child modules, use a module-managed `Makefile`
git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && env GOBIN=$(GOBIN) make lint-ci'
generate:
# for the root module, explicitly run the step, to prevent recursive calls
go generate ./...
# then, for all child modules, use a module-managed `Makefile`
git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && make generate'
test:
# for the root module, explicitly run the step, to prevent recursive calls
go test -cover ./...
# then, for all child modules, use a module-managed `Makefile`
git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && make test'
tidy:
# for the root module, explicitly run the step, to prevent recursive calls
go mod tidy
# then, for all child modules, use a module-managed `Makefile`
git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && make tidy'
tidy-ci:
# for the root module, explicitly run the step, to prevent recursive calls
tidied -verbose
# then, for all child modules, use a module-managed `Makefile`
git ls-files '**/*go.mod' -z | xargs -0 -I{} bash -xc 'cd $$(dirname {}) && make tidy-ci'

190
vendor/github.com/oapi-codegen/runtime/README.md generated vendored Normal file
View File

@@ -0,0 +1,190 @@
# oapi-codegen/runtime
⚠️ This README may be for the latest development version, which may
contain unreleased changes. Please ensure you're looking at the README for the latest release version.
This package provides helper functions for marshaling and unmarshalling HTTP
parameters in headers, cookies, and query arguments in various formats, as well
as functions for reading and writing form encoded data representing models.
You won't need to use this package directly, since it's imported be the boilerplate
from `oapi-codegen`, however, you do need to use the correct version needed by
the code generator, since it makes assumptions about runtime behavior.
## Parameter Handling
OpenAPI 3.x parameters are characterized by three orthogonal attributes:
**style**, **location**, and **explode**. The serialized form on the wire is
determined by the combination of all three.
### Styles
Parameters come in the following styles (all defined by the OpenAPI 3.x spec):
- **`simple`** — comma-separated values. The default for path and header
parameters.
- **`label`** — values prefixed with `.`, separated by `.` (explode) or `,`
(no explode). Path parameters only.
- **`matrix`** — values prefixed with `;name=`, repeated (explode) or
comma-separated (no explode). Path parameters only.
- **`form`** — `name=value` pairs joined with `&`. The default for query and
cookie parameters.
- **`spaceDelimited`** — array elements joined by literal spaces (no
explode); behaves identically to `form` when exploded. Query parameters,
arrays only.
- **`pipeDelimited`** — array elements joined by literal `|` (no explode);
behaves identically to `form` when exploded. Query parameters, arrays
only.
- **`deepObject`** — nested bracket notation, e.g. `name[field]=value`.
Query parameters, objects only, must be exploded.
### Locations
Each style is only valid in specific parameter locations:
| Location | Allowed styles |
|----------|---------------|
| `path` | `simple`, `label`, `matrix` |
| `query` | `form`, `spaceDelimited`, `pipeDelimited`, `deepObject` |
| `header` | `simple` |
| `cookie` | `form` |
### Explode
Each style can be `explode: true` or `explode: false`, which changes how
multi-value parameters (arrays and objects) are packed.
<table>
<thead>
<tr>
<th>Style</th><th>Type</th><th><code>explode: false</code></th><th><code>explode: true</code></th>
</tr>
</thead>
<tbody>
<tr><td rowspan="3"><code>simple</code></td>
<td>primitive <code>5</code></td><td><code>5</code></td><td><code>5</code></td></tr>
<tr><td>array <code>[3,4,5]</code></td><td><code>3,4,5</code></td><td><code>3,4,5</code></td></tr>
<tr><td>object <code>{role:"admin", firstName:"Alex"}</code></td><td><code>firstName,Alex,role,admin</code></td><td><code>firstName=Alex,role=admin</code></td></tr>
<tr><td rowspan="3"><code>label</code></td>
<td>primitive <code>5</code></td><td><code>.5</code></td><td><code>.5</code></td></tr>
<tr><td>array <code>[3,4,5]</code></td><td><code>.3,4,5</code></td><td><code>.3.4.5</code></td></tr>
<tr><td>object <code>{role:"admin", firstName:"Alex"}</code></td><td><code>.firstName,Alex,role,admin</code></td><td><code>.firstName=Alex.role=admin</code></td></tr>
<tr><td rowspan="3"><code>matrix</code></td>
<td>primitive <code>5</code></td><td><code>;id=5</code></td><td><code>;id=5</code></td></tr>
<tr><td>array <code>[3,4,5]</code></td><td><code>;id=3,4,5</code></td><td><code>;id=3;id=4;id=5</code></td></tr>
<tr><td>object <code>{role:"admin", firstName:"Alex"}</code></td><td><code>;id=firstName,Alex,role,admin</code></td><td><code>;firstName=Alex;role=admin</code></td></tr>
<tr><td rowspan="3"><code>form</code></td>
<td>primitive <code>5</code></td><td><code>id=5</code></td><td><code>id=5</code></td></tr>
<tr><td>array <code>[3,4,5]</code></td><td><code>id=3,4,5</code></td><td><code>id=3&amp;id=4&amp;id=5</code></td></tr>
<tr><td>object <code>{role:"admin", firstName:"Alex"}</code></td><td><code>id=firstName,Alex,role,admin</code></td><td><code>firstName=Alex&amp;role=admin</code></td></tr>
<tr><td rowspan="3"><code>spaceDelimited</code></td>
<td>primitive</td><td colspan="2"><em>not supported</em></td></tr>
<tr><td>array <code>[3,4,5]</code></td><td><code>id=3 4 5</code></td><td><code>id=3&amp;id=4&amp;id=5</code></td></tr>
<tr><td>object</td><td colspan="2"><em>not supported</em></td></tr>
<tr><td rowspan="3"><code>pipeDelimited</code></td>
<td>primitive</td><td colspan="2"><em>not supported</em></td></tr>
<tr><td>array <code>[3,4,5]</code></td><td><code>id=3|4|5</code></td><td><code>id=3&amp;id=4&amp;id=5</code></td></tr>
<tr><td>object</td><td colspan="2"><em>not supported</em></td></tr>
<tr><td rowspan="3"><code>deepObject</code></td>
<td>primitive</td><td colspan="2"><em>not supported</em></td></tr>
<tr><td>array <code>[3,4,5]</code></td><td colspan="2"><code>id[0]=3&amp;id[1]=4&amp;id[2]=5</code> (explode required)</td></tr>
<tr><td>object <code>{role:"admin", firstName:"Alex"}</code></td><td colspan="2"><code>id[firstName]=Alex&amp;id[role]=admin</code> (explode required)</td></tr>
</tbody>
</table>
> The above outputs are shown unescaped for readability. In real use, values
> destined for query parameters are passed through `url.QueryEscape` (or
> `url.PathEscape` for path parameters), so reserved characters and
> non-ASCII bytes are percent-encoded on the wire.
### Parameter Limitations
The OpenAPI 3.x parameter styles are convenient but each has at least one
sharp edge. The list below documents behaviors that surprise users and the
cases where round-tripping is not possible in principle.
#### Encoding
- **Query and path values are percent-encoded.** Reserved characters
(`&`, `=`, `#`, `?`, etc.) and non-ASCII bytes are escaped via
`url.QueryEscape` / `url.PathEscape`. Spaces in query values are encoded
as `+` (form-urlencoded convention), matching `url.Values.Encode()`.
- **Header values are passed through raw.** Per RFC 7230 §3.2.6, header
field values may contain visible ASCII plus space/tab; bytes ≥ `0x80` are
`obs-text` and explicitly marked obsolete in RFC 9110. There is no
generally-agreed mechanism for transporting non-ASCII text in arbitrary
header values (RFC 8187 covers only header *parameters* like
`Content-Disposition` `filename*=`). If your spec puts non-ASCII or
control characters into a header parameter, the wire format is
RFC-noncompliant and proxies may strip or reject the request.
- **Cookie values are passed through raw.** Per RFC 6265 §4.1.1, cookie
values may not contain `CTL`, whitespace, `"`, `,`, `;`, `\`, or any byte
`0x80`. Most cookie libraries URL-encode by convention, but this
package does not — if your spec puts spaces or non-ASCII into a cookie
parameter, the value will not be RFC 6265-conformant.
- **Map keys are percent-encoded for `deepObject` only.** For `simple`,
`label`, `matrix`, and `form` styles, map keys are emitted raw. If your
map keys are non-ASCII or contain URL-reserved characters, the wire
representation will not be encoded.
- **`allowReserved`** (`StyleParamOptions.AllowReserved`) is a query-only
option per the OpenAPI 3.x spec, and only applies to *values*, not
parameter names or map keys.
#### `deepObject`
- **Bracket notation is structural, not data.** `MarshalDeepObject`
percent-encodes literal `[` and `]` inside values and map keys as `%5B`
/ `%5D` on the wire. However, once a server calls `url.ParseQuery`, those
escapes are decoded back to `[` and `]` — at which point a key like
`p[a[b]]` is ambiguous between `{p: {a: {b: ...}}}` and
`{p: {"a[b]": ...}}`. `UnmarshalDeepObject` cannot distinguish these
cases and adopts the same greedy left-to-right parse used by qs (Node),
Rails `Rack::Utils.parse_nested_query`, and similar libraries: every
unescaped `[` opens a new nesting level. **Literal `[` and `]` inside
map keys cannot be round-tripped.** Use a different separator in
user-supplied keys if this matters to you.
- **OpenAPI 3.x defines `deepObject` only for object schemas.** This
package extends it to maps and arrays for convenience (arrays are
numerically indexed: `p[0]`, `p[1]`, …), but other tooling may not
accept that wire form.
- **`deepObject` requires `explode: true`.** Non-exploded `deepObject` has
no well-defined wire format; an error is returned.
#### `spaceDelimited` / `pipeDelimited`
- **Array-only.** Per the OpenAPI spec, these styles only apply to arrays
of primitives. Passing a primitive or object returns an error.
- **Exploded form degenerates to `form`.** When `explode: true`, the
separator becomes `&` (not space or pipe), so the output is
byte-identical to `form` exploded. The space/pipe separator only takes
effect when `explode: false`. This is per the OpenAPI spec, but it
surprises many users.
- **Unexploded `spaceDelimited` is RFC-fragile.** Literal spaces in a
query string are tolerated by most parsers but are not RFC 3986-
conformant; `+` would be the form-urlencoded canonical form for space,
but `spaceDelimited` is defined to use literal `%20`-equivalent space
(the value bytes themselves are then encoded normally).
#### Type-conversion edge cases
- **`null`** in a struct field marshals to the literal string `"null"` in
`deepObject` output. There is no distinct OpenAPI representation for
absent vs. JSON-null in query parameters.
- **`time.Time` and `types.Date`** are formatted via RFC 3339 and
`2006-01-02` respectively when used as primitives in any style. If you
want a different format, wrap the field in a type that implements
`encoding.TextMarshaler`.
- **`types.UUID`** stringifies to the canonical hyphenated 36-character
form; query/path location escaping is a no-op (UUIDs are in the
unreserved set).
- **`json.Marshaler` is consulted for structs**, then the result is
re-decoded with `UseNumber()` and re-styled. Numbers therefore retain
their original precision, but the round-trip through JSON means struct
field tags are honored (not raw Go field names).

24
vendor/github.com/oapi-codegen/runtime/bind.go generated vendored Normal file
View File

@@ -0,0 +1,24 @@
// Copyright 2021 DeepMap, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runtime
// Binder is the interface implemented by types that can be bound to a query string or a parameter string
// The input can be assumed to be a valid string. If you define a Bind method you are responsible for all
// data being completely bound to the type.
//
// By convention, to approximate the behavior of Bind functions themselves,
// Binder implements Bind("") as a no-op.
type Binder interface {
Bind(src string) error
}

403
vendor/github.com/oapi-codegen/runtime/bindform.go generated vendored Normal file
View File

@@ -0,0 +1,403 @@
package runtime
import (
"encoding/json"
"errors"
"fmt"
"mime/multipart"
"net/url"
"reflect"
"strconv"
"strings"
"github.com/oapi-codegen/runtime/types"
)
const tagName = "json"
const jsonContentType = "application/json"
type RequestBodyEncoding struct {
ContentType string
Style string
Explode *bool
Required *bool
}
func BindMultipart(ptr interface{}, reader multipart.Reader) error {
const defaultMemory = 32 << 20
form, err := reader.ReadForm(defaultMemory)
if err != nil {
return err
}
return BindForm(ptr, form.Value, form.File, nil)
}
func BindForm(ptr interface{}, form map[string][]string, files map[string][]*multipart.FileHeader, encodings map[string]RequestBodyEncoding) error {
ptrVal := reflect.Indirect(reflect.ValueOf(ptr))
if ptrVal.Kind() != reflect.Struct {
return errors.New("form data body should be a struct")
}
tValue := ptrVal.Type()
for i := 0; i < tValue.NumField(); i++ {
field := ptrVal.Field(i)
tag := tValue.Field(i).Tag.Get(tagName)
if !field.CanInterface() || tag == "-" {
continue
}
tag = strings.Split(tag, ",")[0] // extract the name of the tag
if encoding, ok := encodings[tag]; ok {
// custom encoding
values := form[tag]
if len(values) == 0 {
continue
}
value := values[0]
if encoding.ContentType != "" {
if strings.HasPrefix(encoding.ContentType, jsonContentType) {
if err := json.Unmarshal([]byte(value), ptr); err != nil {
return err
}
}
return errors.New("unsupported encoding, only application/json is supported")
} else {
var explode bool
if encoding.Explode != nil {
explode = *encoding.Explode
}
var required bool
if encoding.Required != nil {
required = *encoding.Required
}
if err := BindStyledParameterWithOptions(encoding.Style, tag, value, field.Addr().Interface(), BindStyledParameterOptions{
ParamLocation: ParamLocationUndefined,
Explode: explode,
Required: required,
}); err != nil {
return err
}
}
} else {
// regular form data
if _, err := bindFormImpl(field, form, files, tag); err != nil {
return err
}
}
}
return nil
}
func MarshalForm(ptr interface{}, encodings map[string]RequestBodyEncoding) (url.Values, error) {
ptrVal := reflect.Indirect(reflect.ValueOf(ptr))
if ptrVal.Kind() != reflect.Struct {
return nil, errors.New("form data body should be a struct")
}
tValue := ptrVal.Type()
result := make(url.Values)
for i := 0; i < tValue.NumField(); i++ {
field := ptrVal.Field(i)
tag := tValue.Field(i).Tag.Get(tagName)
if !field.CanInterface() || tag == "-" {
continue
}
omitEmpty := strings.HasSuffix(tag, ",omitempty")
if omitEmpty && field.IsZero() {
continue
}
tag = strings.Split(tag, ",")[0] // extract the name of the tag
if encoding, ok := encodings[tag]; ok && encoding.ContentType != "" {
if strings.HasPrefix(encoding.ContentType, jsonContentType) {
if data, err := json.Marshal(field); err != nil { //nolint:staticcheck
return nil, err
} else {
result[tag] = append(result[tag], string(data))
}
}
return nil, errors.New("unsupported encoding, only application/json is supported")
} else {
marshalFormImpl(field, result, tag)
}
}
return result, nil
}
func bindFormImpl(v reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) {
var hasData bool
switch v.Kind() {
case reflect.Interface:
return bindFormImpl(v.Elem(), form, files, name)
case reflect.Ptr:
ptrData := v.Elem()
if !ptrData.IsValid() {
ptrData = reflect.New(v.Type().Elem())
}
ptrHasData, err := bindFormImpl(ptrData, form, files, name)
if err == nil && ptrHasData && !v.Elem().IsValid() {
v.Set(ptrData)
}
return ptrHasData, err
case reflect.Slice:
if files := append(files[name], files[name+"[]"]...); len(files) != 0 {
if _, ok := v.Interface().([]types.File); ok {
result := make([]types.File, len(files))
for i, file := range files {
result[i].InitFromMultipart(file)
}
v.Set(reflect.ValueOf(result))
hasData = true
}
}
indexedElementsCount := indexedElementsCount(form, files, name)
items := append(form[name], form[name+"[]"]...)
if indexedElementsCount+len(items) != 0 {
result := reflect.MakeSlice(v.Type(), indexedElementsCount+len(items), indexedElementsCount+len(items))
for i := 0; i < indexedElementsCount; i++ {
if _, err := bindFormImpl(result.Index(i), form, files, fmt.Sprintf("%s[%v]", name, i)); err != nil {
return false, err
}
}
for i, item := range items {
if err := BindStringToObject(item, result.Index(indexedElementsCount+i).Addr().Interface()); err != nil {
return false, err
}
}
v.Set(result)
hasData = true
}
case reflect.Struct:
if files := files[name]; len(files) != 0 {
if file, ok := v.Interface().(types.File); ok {
file.InitFromMultipart(files[0])
v.Set(reflect.ValueOf(file))
return true, nil
}
}
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i)
tag := field.Tag.Get(tagName)
if field.Name == "AdditionalProperties" && field.Type.Kind() == reflect.Map && tag == "-" {
additionalPropertiesHasData, err := bindAdditionalProperties(v.Field(i), v, form, files, name)
if err != nil {
return false, err
}
hasData = hasData || additionalPropertiesHasData
}
if !v.Field(i).CanInterface() || tag == "-" {
continue
}
tag = strings.Split(tag, ",")[0] // extract the name of the tag
fieldHasData, err := bindFormImpl(v.Field(i), form, files, fmt.Sprintf("%s[%s]", name, tag))
if err != nil {
return false, err
}
hasData = hasData || fieldHasData
}
return hasData, nil
case reflect.Map:
// A bool-keyed map (such as nullable.Nullable[T], which is
// map[bool]T) is treated as a nullable wrapper: bind the inner type
// and store the result under map[true]. An absent field stays as
// the zero (unspecified) map.
if v.Type().Key().Kind() == reflect.Bool {
valuePtr := reflect.New(v.Type().Elem())
valueHasData, err := bindFormImpl(valuePtr.Elem(), form, files, name)
if err != nil {
return false, err
}
if valueHasData {
newMap := reflect.MakeMap(v.Type())
newMap.SetMapIndex(reflect.ValueOf(true), valuePtr.Elem())
v.Set(newMap)
return true, nil
}
return false, nil
}
return bindFormMap(v, form, files, name)
default:
value := form[name]
if len(value) != 0 {
return true, BindStringToObject(value[0], v.Addr().Interface())
}
}
return hasData, nil
}
func indexedElementsCount(form map[string][]string, files map[string][]*multipart.FileHeader, name string) int {
name += "["
maxIndex := -1
for k := range form {
if strings.HasPrefix(k, name) {
str := strings.TrimPrefix(k, name)
str = str[:strings.Index(str, "]")]
if idx, err := strconv.Atoi(str); err == nil {
if idx > maxIndex {
maxIndex = idx
}
}
}
}
for k := range files {
if strings.HasPrefix(k, name) {
str := strings.TrimPrefix(k, name)
str = str[:strings.Index(str, "]")]
if idx, err := strconv.Atoi(str); err == nil {
if idx > maxIndex {
maxIndex = idx
}
}
}
}
return maxIndex + 1
}
func bindAdditionalProperties(additionalProperties reflect.Value, parentStruct reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) {
hasData := false
valueType := additionalProperties.Type().Elem()
// store all fixed properties in a set
fieldsSet := make(map[string]struct{})
for i := 0; i < parentStruct.NumField(); i++ {
tag := parentStruct.Type().Field(i).Tag.Get(tagName)
if !parentStruct.Field(i).CanInterface() || tag == "-" {
continue
}
tag = strings.Split(tag, ",")[0]
fieldsSet[tag] = struct{}{}
}
result := reflect.MakeMap(additionalProperties.Type())
for k := range form {
if strings.HasPrefix(k, name+"[") {
key := strings.TrimPrefix(k, name+"[")
key = key[:strings.Index(key, "]")]
if _, ok := fieldsSet[key]; ok {
continue
}
value := reflect.New(valueType)
ptrHasData, err := bindFormImpl(value, form, files, fmt.Sprintf("%s[%s]", name, key))
if err != nil {
return false, err
}
result.SetMapIndex(reflect.ValueOf(key), value.Elem())
hasData = hasData || ptrHasData
}
}
for k := range files {
if strings.HasPrefix(k, name+"[") {
key := strings.TrimPrefix(k, name+"[")
key = key[:strings.Index(key, "]")]
if _, ok := fieldsSet[key]; ok {
continue
}
value := reflect.New(valueType)
result.SetMapIndex(reflect.ValueOf(key), value)
ptrHasData, err := bindFormImpl(value, form, files, fmt.Sprintf("%s[%s]", name, key))
if err != nil {
return false, err
}
result.SetMapIndex(reflect.ValueOf(key), value.Elem())
hasData = hasData || ptrHasData
}
}
if hasData {
additionalProperties.Set(result)
}
return hasData, nil
}
// bindFormMap binds form (and file) entries of the form `name[key]=value`
// into a generic map[K]V destination. It is reached from bindFormImpl for
// non-bool-keyed maps; the bool-keyed case is handled separately as a
// nullable wrapper.
func bindFormMap(v reflect.Value, form map[string][]string, files map[string][]*multipart.FileHeader, name string) (bool, error) {
keyType := v.Type().Key()
valueType := v.Type().Elem()
result := reflect.MakeMap(v.Type())
hasData := false
prefix := name + "["
seen := map[string]struct{}{}
process := func(formKey string) error {
if !strings.HasPrefix(formKey, prefix) {
return nil
}
inner := strings.TrimPrefix(formKey, prefix)
end := strings.Index(inner, "]")
if end < 0 {
return nil
}
innerKey := inner[:end]
if _, ok := seen[innerKey]; ok {
return nil
}
seen[innerKey] = struct{}{}
keyPtr := reflect.New(keyType)
if err := BindStringToObject(innerKey, keyPtr.Interface()); err != nil {
return err
}
valuePtr := reflect.New(valueType)
innerHasData, err := bindFormImpl(valuePtr.Elem(), form, files, fmt.Sprintf("%s[%s]", name, innerKey))
if err != nil {
return err
}
if innerHasData {
result.SetMapIndex(keyPtr.Elem(), valuePtr.Elem())
hasData = true
}
return nil
}
for k := range form {
if err := process(k); err != nil {
return false, err
}
}
for k := range files {
if err := process(k); err != nil {
return false, err
}
}
if hasData {
v.Set(result)
}
return hasData, nil
}
func marshalFormImpl(v reflect.Value, result url.Values, name string) {
switch v.Kind() {
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++ {
elem := v.Index(i)
marshalFormImpl(elem, result, fmt.Sprintf("%s[%v]", name, i))
}
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i)
tag := field.Tag.Get(tagName)
if field.Name == "AdditionalProperties" && tag == "-" {
iter := v.MapRange()
for iter.Next() {
marshalFormImpl(iter.Value(), result, fmt.Sprintf("%s[%s]", name, iter.Key().String()))
}
continue
}
if !v.Field(i).CanInterface() || tag == "-" {
continue
}
tag = strings.Split(tag, ",")[0] // extract the name of the tag
marshalFormImpl(v.Field(i), result, fmt.Sprintf("%s[%s]", name, tag))
}
default:
result[name] = append(result[name], fmt.Sprint(v.Interface()))
}
}

915
vendor/github.com/oapi-codegen/runtime/bindparam.go generated vendored Normal file
View File

@@ -0,0 +1,915 @@
// Copyright 2019 DeepMap, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runtime
import (
"encoding"
"encoding/json"
"errors"
"fmt"
"net/url"
"reflect"
"strings"
"time"
"github.com/oapi-codegen/runtime/types"
)
// RequiredParameterError is returned when a required query parameter is missing.
// Generated server code can use errors.As to detect this and produce a
// framework-specific typed error for the application's error handler.
type RequiredParameterError struct {
ParamName string
}
func (e *RequiredParameterError) Error() string {
return fmt.Sprintf("query parameter '%s' is required", e.ParamName)
}
// BindStyledParameter binds a parameter as described in the Path Parameters
// section here to a Go object:
// https://swagger.io/docs/specification/serialization/
// It is a backward compatible function to clients generated with codegen
// up to version v1.5.5. v1.5.6+ calls the function below.
// Deprecated: BindStyledParameter is deprecated.
func BindStyledParameter(style string, explode bool, paramName string,
value string, dest interface{}) error {
return BindStyledParameterWithOptions(style, paramName, value, dest, BindStyledParameterOptions{
ParamLocation: ParamLocationUndefined,
Explode: explode,
Required: true,
})
}
// BindStyledParameterWithLocation binds a parameter as described in the Path Parameters
// section here to a Go object:
// https://swagger.io/docs/specification/serialization/
// This is a compatibility function which is used by oapi-codegen v2.0.0 and earlier.
// Deprecated: BindStyledParameterWithLocation is deprecated.
func BindStyledParameterWithLocation(style string, explode bool, paramName string,
paramLocation ParamLocation, value string, dest interface{}) error {
return BindStyledParameterWithOptions(style, paramName, value, dest, BindStyledParameterOptions{
ParamLocation: paramLocation,
Explode: explode,
Required: true, // This emulates behavior before the required parameter was optional.
})
}
// BindStyledParameterOptions defines optional arguments for BindStyledParameterWithOptions
type BindStyledParameterOptions struct {
// ParamLocation tells us where the parameter is located in the request.
ParamLocation ParamLocation
// Whether the parameter should use exploded structure
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
// AllowReserved, when true, indicates that the parameter value may
// contain RFC 3986 reserved characters without percent-encoding.
AllowReserved bool
}
// BindStyledParameterWithOptions binds a parameter as described in the Path Parameters
// section here to a Go object:
// https://swagger.io/docs/specification/serialization/
func BindStyledParameterWithOptions(style string, paramName string, value string, dest any, opts BindStyledParameterOptions) error {
if opts.Required {
if value == "" {
return fmt.Errorf("parameter '%s' is empty, can't bind its value", paramName)
}
}
// Based on the location of the parameter, we need to unescape it properly.
var err error
switch opts.ParamLocation {
case ParamLocationQuery, ParamLocationUndefined:
// We unescape undefined parameter locations here for older generated code,
// since prior to this refactoring, they always query unescaped.
value, err = url.QueryUnescape(value)
if err != nil {
return fmt.Errorf("error unescaping query parameter '%s': %w", paramName, err)
}
case ParamLocationPath:
value, err = url.PathUnescape(value)
if err != nil {
return fmt.Errorf("error unescaping path parameter '%s': %w", paramName, err)
}
default:
// Headers and cookies aren't escaped.
}
// If the destination implements encoding.TextUnmarshaler we use it for binding
if tu, ok := dest.(encoding.TextUnmarshaler); ok {
if err := tu.UnmarshalText([]byte(value)); err != nil {
return fmt.Errorf("error unmarshaling '%s' text as %T: %w", value, dest, err)
}
return nil
}
// Everything comes in by pointer, dereference it
v := reflect.Indirect(reflect.ValueOf(dest))
// This is the basic type of the destination object.
t := v.Type()
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)
if err != nil {
return err
}
return bindSplitPartsToDestinationStruct(paramName, parts, opts.Explode, dest)
}
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 {
return fmt.Errorf("error splitting input '%s' into parts: %w", value, err)
}
return bindSplitPartsToDestinationArray(parts, dest)
}
// For primitive types, only label and matrix styles need prefix stripping
// via splitStyledParameter. Simple and form styles can bind the raw value
// directly — splitting on commas would incorrectly reject primitive values
// that contain commas (see https://github.com/oapi-codegen/runtime/issues/114).
if style == "label" || style == "matrix" {
parts, err := splitStyledParameter(style, opts.Explode, false, paramName, value)
if err != nil {
return fmt.Errorf("error splitting parameter '%s': %w", paramName, err)
}
if len(parts) != 1 {
return fmt.Errorf("parameter '%s': expected single value, got %d parts", paramName, len(parts))
}
value = parts[0]
}
return BindStringToObject(value, dest)
}
// This is a complex set of operations, but each given parameter style can be
// packed together in multiple ways, using different styles of separators, and
// different packing strategies based on the explode flag. This function takes
// as input any parameter format, and unpacks it to a simple list of strings
// or key-values which we can then treat generically.
// Why, oh why, great Swagger gods, did you have to make this so complicated?
func splitStyledParameter(style string, explode bool, object bool, paramName string, value string) ([]string, error) {
switch style {
case "simple":
// In the simple case, we always split on comma
parts := strings.Split(value, ",")
return parts, nil
case "label":
// In the label case, it's more tricky. In the no explode case, we have
// /users/.3,4,5 for arrays
// /users/.role,admin,firstName,Alex for objects
// in the explode case, we have:
// /users/.3.4.5
// /users/.role=admin.firstName=Alex
if explode {
// In the exploded case, split everything on periods.
parts := strings.Split(value, ".")
// The first part should be an empty string because we have a
// leading period.
if parts[0] != "" {
return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName)
}
return parts[1:], nil
} else {
// In the unexploded case, we strip off the leading period.
if value[0] != '.' {
return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName)
}
// The rest is comma separated.
return strings.Split(value[1:], ","), nil
}
case "matrix":
if explode {
// In the exploded case, we break everything up on semicolon
parts := strings.Split(value, ";")
// The first part should always be empty string, since we started
// with ;something
if parts[0] != "" {
return nil, fmt.Errorf("invalid format for matrix parameter '%s', should start with ';'", paramName)
}
parts = parts[1:]
// Now, if we have an object, we just have a list of x=y statements.
// for a non-object, like an array, we have id=x, id=y. id=z, etc,
// so we need to strip the prefix from each of them.
if !object {
prefix := paramName + "="
for i := range parts {
parts[i] = strings.TrimPrefix(parts[i], prefix)
}
}
return parts, nil
} else {
// In the unexploded case, parameters will start with ;paramName=
prefix := ";" + paramName + "="
if !strings.HasPrefix(value, prefix) {
return nil, fmt.Errorf("expected parameter '%s' to start with %s", paramName, prefix)
}
str := strings.TrimPrefix(value, prefix)
return strings.Split(str, ","), nil
}
case "form":
var parts []string
if explode {
parts = strings.Split(value, "&")
if !object {
prefix := paramName + "="
for i := range parts {
parts[i] = strings.TrimPrefix(parts[i], prefix)
}
}
return parts, nil
} else {
parts = strings.Split(value, ",")
prefix := paramName + "="
for i := range parts {
parts[i] = strings.TrimPrefix(parts[i], prefix)
}
}
return parts, nil
}
return nil, fmt.Errorf("unhandled parameter style: %s", style)
}
// Given a set of values as a slice, create a slice to hold them all, and
// assign to each one by one.
func bindSplitPartsToDestinationArray(parts []string, dest interface{}) error {
// Everything comes in by pointer, dereference it
v := reflect.Indirect(reflect.ValueOf(dest))
// This is the basic type of the destination object.
t := v.Type()
// We've got a destination array, bind each object one by one.
// This generates a slice of the correct element type and length to
// hold all the parts.
newArray := reflect.MakeSlice(t, len(parts), len(parts))
for i, p := range parts {
err := BindStringToObject(p, newArray.Index(i).Addr().Interface())
if err != nil {
return fmt.Errorf("error setting array element: %w", err)
}
}
v.Set(newArray)
return nil
}
// Given a set of chopped up parameter parts, bind them to a destination
// struct. The exploded parameter controls whether we send key value pairs
// in the exploded case, or a sequence of values which are interpreted as
// tuples.
// Given the struct Id { firstName string, role string }, as in the canonical
// swagger examples, in the exploded case, we would pass
// ["firstName=Alex", "role=admin"], where in the non-exploded case, we would
// pass "firstName", "Alex", "role", "admin"]
//
// We punt the hard work of binding these values to the object to the json
// library. We'll turn those arrays into JSON strings, and unmarshal
// into the struct.
func bindSplitPartsToDestinationStruct(paramName string, parts []string, explode bool, dest interface{}) error {
// 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
var fields []string
if explode {
fields = make([]string, len(parts))
for i, property := range parts {
propertyParts := strings.Split(property, "=")
if len(propertyParts) != 2 {
return fmt.Errorf("parameter '%s' has invalid exploded format", paramName)
}
fields[i] = "\"" + propertyParts[0] + "\":\"" + propertyParts[1] + "\""
}
} else {
if len(parts)%2 != 0 {
return fmt.Errorf("parameter '%s' has invalid format, property/values need to be pairs", paramName)
}
fields = make([]string, len(parts)/2)
for i := 0; i < len(parts); i += 2 {
key := parts[i]
value := parts[i+1]
fields[i/2] = "\"" + key + "\":\"" + value + "\""
}
}
jsonParam := "{" + strings.Join(fields, ",") + "}"
err := json.Unmarshal([]byte(jsonParam), dest)
if err != nil {
return fmt.Errorf("error binding parameter %s fields: %w", paramName, err)
}
return nil
}
// BindQueryParameter works much like BindStyledParameter, however it takes a query argument
// input array from the url package, since query arguments come through a
// different path than the styled arguments. They're also exceptionally fussy.
// For example, consider the exploded and unexploded form parameter examples:
// (exploded) /users?role=admin&firstName=Alex
// (unexploded) /users?id=role,admin,firstName,Alex
//
// In the first case, we can pull the "id" parameter off the context,
// and unmarshal via json as an intermediate. Easy. In the second case, we
// don't have the id QueryParam present, but must find "role", and "firstName".
// what if there is another parameter similar to "ID" named "role"? We can't
// 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
// AllowReserved, when true, indicates that the parameter value may
// contain RFC 3986 reserved characters without percent-encoding.
AllowReserved bool
}
// 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))
// 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 interface{}
// 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 {
// If the parameter is required, then the generated code will pass us
// a pointer to it: &int, &object, and so forth. We can directly set
// them.
output = dest
} else {
// For optional parameters, we have an extra indirect. An optional
// parameter of type "int" will be *int on the struct. We pass that
// in by pointer, and have **int.
// If the destination, is a nil pointer, we need to allocate it.
if v.IsNil() {
t := v.Type()
newValue := reflect.New(t.Elem())
// for now, hang onto the output buffer separately from destination,
// as we don't want to write anything to destination until we can
// unmarshal successfully, and check whether a field is required.
output = newValue.Interface()
} else {
// If the destination isn't nil, just use that.
output = v.Interface()
}
// Get rid of that extra indirect as compared to the required case,
// so the code below doesn't have to care.
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", "spaceDelimited", "pipeDelimited":
var parts []string
if explode {
// ok, the explode case in query arguments is very, very annoying,
// because an exploded object, such as /users?role=admin&firstName=Alex
// isn't actually present in the parameter array. We have to do
// different things based on destination type.
//
// Note: spaceDelimited and pipeDelimited with explode=true are
// serialized identically to form explode=true (each value is a
// separate key=value pair), so we share this code path.
values, found := queryParams[paramName]
var err error
switch k {
case reflect.Slice:
// In the slice case, we simply use the arguments provided by
// http library.
if !found {
if required {
return &RequiredParameterError{ParamName: paramName}
} else {
// If an optional parameter is not found, we do nothing,
return nil
}
}
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
// in the query string correspond to the object's fields. We'll
// try to bind field by field.
var fieldsPresent bool
fieldsPresent, err = bindParamsToExplodedObject(paramName, queryParams, output)
// If no fields were set, and there is no error, we will not fall
// through to assign the destination. An absent required
// parameter is an error, matching the slice and primitive cases
// above; this also covers scalar struct types such as
// types.Date and time.Time.
if !fieldsPresent {
if required {
return &RequiredParameterError{ParamName: paramName}
}
return nil
}
default:
// Primitive object case. We expect to have 1 value to
// unmarshal.
if len(values) == 0 {
if required {
return &RequiredParameterError{ParamName: paramName}
} else {
return nil
}
}
if len(values) != 1 {
return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
}
if !found {
if required {
return &RequiredParameterError{ParamName: paramName}
} else {
// If an optional parameter is not found, we do nothing,
return nil
}
}
err = BindStringToObject(values[0], output)
}
if err != nil {
return err
}
// If the parameter is required (or relies on x-go-type-skip-optional-pointer),
// and we've successfully unmarshaled it, this assigns the new object to the
// pointer pointer.
if extraIndirect {
dv.Set(reflect.ValueOf(output))
}
return nil
} else {
values, found := queryParams[paramName]
if !found {
if required {
return &RequiredParameterError{ParamName: paramName}
} else {
return nil
}
}
if len(values) != 1 {
return fmt.Errorf("parameter '%s' is not exploded, but is specified multiple times", paramName)
}
// For primitive types, the raw value should be used as-is
// without splitting on commas. Per the OpenAPI specification,
// explode has no effect on primitive types — the serialization
// is the same regardless of the explode value. Comma splitting
// is only meaningful for array and object types.
// See: https://swagger.io/docs/specification/serialization/
if k != reflect.Slice && k != reflect.Struct && k != reflect.Map {
err := BindStringToObject(values[0], output)
if err != nil {
return err
}
if extraIndirect {
dv.Set(reflect.ValueOf(output))
}
return nil
}
switch style {
case "spaceDelimited":
parts = strings.Split(values[0], " ")
case "pipeDelimited":
parts = strings.Split(values[0], "|")
default:
parts = strings.Split(values[0], ",")
}
}
var err error
switch k {
case reflect.Slice:
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, reflect.Map:
// 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)
}
}
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")
}
return unmarshalDeepObject(dest, paramName, queryParams, required)
default:
return fmt.Errorf("style '%s' on parameter '%s' is invalid", style, paramName)
}
}
// 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", "spaceDelimited", "pipeDelimited":
if explode {
// For the explode case, url.ParseQuery is fine — there are no
// delimiter commas to confuse with literal commas.
//
// Note: spaceDelimited and pipeDelimited with explode=true are
// serialized identically to form explode=true (each value is a
// separate key=value pair), so we share this code path.
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 &RequiredParameterError{ParamName: paramName}
}
return nil
}
err = bindSplitPartsToDestinationArray(values, output)
case reflect.Struct:
var fieldsPresent bool
fieldsPresent, err = bindParamsToExplodedObject(paramName, queryParams, output)
// An absent required parameter is an error, matching the slice
// and primitive cases above; this also covers scalar struct
// types such as types.Date and time.Time.
if !fieldsPresent {
if required {
return &RequiredParameterError{ParamName: paramName}
}
return nil
}
default:
if len(values) == 0 {
if required {
return &RequiredParameterError{ParamName: paramName}
}
return nil
}
if len(values) != 1 {
return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
}
if !found {
if required {
return &RequiredParameterError{ParamName: paramName}
}
return nil
}
err = BindStringToObject(values[0], output)
}
if err != nil {
return err
}
if extraIndirect {
dv.Set(reflect.ValueOf(output))
}
return nil
}
// explode=false — use findRawQueryParam to get the still-encoded
// value, split on the style-specific delimiter, then URL-decode
// each resulting part individually.
rawValues, found := findRawQueryParam(rawQuery, paramName)
if !found {
if required {
return &RequiredParameterError{ParamName: paramName}
}
return nil
}
if len(rawValues) != 1 {
return fmt.Errorf("parameter '%s' is not exploded, but is specified multiple times", paramName)
}
// For primitive types, decode the raw value as-is without splitting
// on commas. Per the OpenAPI specification, explode has no effect on
// primitive types. Comma splitting is only meaningful for array and
// object types.
if k != reflect.Slice && k != reflect.Struct && k != reflect.Map {
decoded, err := url.QueryUnescape(rawValues[0])
if err != nil {
return fmt.Errorf("error decoding query parameter '%s' value %q: %w", paramName, rawValues[0], err)
}
err = BindStringToObject(decoded, output)
if err != nil {
return err
}
if extraIndirect {
dv.Set(reflect.ValueOf(output))
}
return nil
}
var rawParts []string
switch style {
case "spaceDelimited":
// Normalise all space representations to %20, then split.
normalized := strings.ReplaceAll(rawValues[0], "+", "%20")
normalized = strings.ReplaceAll(normalized, " ", "%20")
rawParts = strings.Split(normalized, "%20")
case "pipeDelimited":
rawParts = strings.Split(rawValues[0], "|")
default:
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)
}
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)
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.
// We don't try to be smart here, if the field exists as a query argument,
// set its value. This function returns a boolean, telling us whether there was
// anything to bind. There will be nothing to bind if a parameter isn't found by name,
// or none of an exploded object's fields are present.
func bindParamsToExplodedObject(paramName string, values url.Values, dest interface{}) (bool, error) {
// Dereference pointers to their destination values
binder, v, t := indirect(dest)
if binder != nil {
_, found := values[paramName]
if !found {
return false, nil
}
return true, BindStringToObject(values.Get(paramName), dest)
}
if t.Kind() != reflect.Struct {
return false, fmt.Errorf("unmarshaling query arg '%s' into wrong type", paramName)
}
fieldsPresent := false
for i := 0; i < t.NumField(); i++ {
fieldT := t.Field(i)
// Skip unsettable fields, such as internal ones.
if !v.Field(i).CanSet() {
continue
}
// Find the json annotation on the field, and use the json specified
// name if available, otherwise, just the field name.
tag := fieldT.Tag.Get("json")
fieldName := fieldT.Name
if tag != "" {
tagParts := strings.Split(tag, ",")
name := tagParts[0]
if name != "" {
fieldName = name
}
}
// At this point, we look up field name in the parameter list.
fieldVal, found := values[fieldName]
if found {
if len(fieldVal) != 1 {
return false, fmt.Errorf("field '%s' specified multiple times for param '%s'", fieldName, paramName)
}
err := BindStringToObject(fieldVal[0], v.Field(i).Addr().Interface())
if err != nil {
return false, fmt.Errorf("could not bind query arg '%s' to request object: %s'", paramName, err)
}
fieldsPresent = true
}
}
return fieldsPresent, nil
}
// indirect
func indirect(dest interface{}) (interface{}, reflect.Value, reflect.Type) {
v := reflect.ValueOf(dest)
if v.Type().NumMethod() > 0 && v.CanInterface() {
if u, ok := v.Interface().(Binder); ok {
return u, reflect.Value{}, nil
}
}
v = reflect.Indirect(v)
t := v.Type()
// special handling for custom types which might look like an object. We
// don't want to use object binding on them, but rather treat them as
// primitive types. time.Time{} is a unique case since we can't add a Binder
// to it without changing the underlying generated code.
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
return dest, reflect.Value{}, nil
}
if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
return dest, reflect.Value{}, nil
}
return nil, v, t
}

216
vendor/github.com/oapi-codegen/runtime/bindstring.go generated vendored Normal file
View File

@@ -0,0 +1,216 @@
// Copyright 2019 DeepMap, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runtime
import (
"encoding"
"errors"
"fmt"
"reflect"
"strconv"
"time"
"github.com/oapi-codegen/runtime/types"
)
// BindStringToObject takes a string, and attempts to assign it to the destination
// interface via whatever type conversion is necessary. We have to do this
// via reflection instead of a much simpler type switch so that we can handle
// type aliases. This function was the easy way out, the better way, since we
// 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
// Check if the destination implements Binder interface before any reflection
if binder, ok := dst.(Binder); ok {
return binder.Bind(src)
}
v := reflect.ValueOf(dst)
t := reflect.TypeOf(dst)
// We need to dereference pointers
if t.Kind() == reflect.Ptr {
v = reflect.Indirect(v)
t = v.Type()
}
// For some optional args
if t.Kind() == reflect.Ptr {
if v.IsNil() {
v.Set(reflect.New(t.Elem()))
}
v = reflect.Indirect(v)
t = v.Type()
}
// The resulting type must be settable. reflect will catch issues like
// passing the destination by value.
if !v.CanSet() {
return errors.New("destination is not settable")
}
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)
if err == nil {
if v.OverflowInt(val) {
err = fmt.Errorf("value '%s' overflows destination of type: %s", src, t.Kind())
}
if err == nil {
v.SetInt(val)
}
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
var val uint64
val, err = strconv.ParseUint(src, 10, 64)
if err == nil {
if v.OverflowUint(val) {
err = fmt.Errorf("value '%s' overflows destination of type: %s", src, t.Kind())
}
v.SetUint(val)
}
case reflect.String:
v.SetString(src)
err = nil
case reflect.Float64, reflect.Float32:
var val float64
val, err = strconv.ParseFloat(src, 64)
if err == nil {
if v.OverflowFloat(val) {
err = fmt.Errorf("value '%s' overflows destination of type: %s", src, t.Kind())
}
v.SetFloat(val)
}
case reflect.Bool:
var val bool
val, err = strconv.ParseBool(src)
if err == nil {
v.SetBool(val)
}
case reflect.Array:
if tu, ok := dst.(encoding.TextUnmarshaler); ok {
if err := tu.UnmarshalText([]byte(src)); err != nil {
return fmt.Errorf("error unmarshaling '%s' text as %T: %w", src, dst, err)
}
return nil
}
fallthrough
case reflect.Struct:
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
// Don't fail on empty string.
if src == "" {
return nil
}
// Time is a special case of a struct that we handle
parsedTime, err := time.Parse(time.RFC3339Nano, src)
if err != nil {
parsedTime, err = time.Parse(types.DateFormat, src)
if err != nil {
return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %w", src, err)
}
}
// So, assigning this gets a little fun. We have a value to the
// dereference destination. We can't do a conversion to
// time.Time because the result isn't assignable, so we need to
// convert pointers.
if t != reflect.TypeOf(time.Time{}) {
vPtr := v.Addr()
vtPtr := vPtr.Convert(reflect.TypeOf(&time.Time{}))
v = reflect.Indirect(vtPtr)
}
v.Set(reflect.ValueOf(parsedTime))
return nil
}
if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
// Don't fail on empty string.
if src == "" {
return nil
}
parsedTime, err := time.Parse(types.DateFormat, src)
if err != nil {
return fmt.Errorf("error parsing '%s' as date: %w", src, err)
}
parsedDate := types.Date{Time: parsedTime}
// We have to do the same dance here to assign, just like with times
// above.
if t != reflect.TypeOf(types.Date{}) {
vPtr := v.Addr()
vtPtr := vPtr.Convert(reflect.TypeOf(&types.Date{}))
v = reflect.Indirect(vtPtr)
}
v.Set(reflect.ValueOf(parsedDate))
return nil
}
// We fall through to the error case below if we haven't handled the
// destination type above.
fallthrough
case reflect.Map:
// A bool-keyed map (such as nullable.Nullable[T], which is
// map[bool]T) is treated as a nullable wrapper: bind src into a
// fresh value of the inner type and store it under map[true].
if t.Kind() == reflect.Map && t.Key().Kind() == reflect.Bool {
elemPtr := reflect.New(t.Elem())
if bindErr := BindStringToObjectWithOptions(src, elemPtr.Interface(), opts); bindErr != nil {
return bindErr
}
newMap := reflect.MakeMap(t)
newMap.SetMapIndex(reflect.ValueOf(true), elemPtr.Elem())
v.Set(newMap)
return nil
}
fallthrough
default:
// We've got a bunch of types unimplemented, don't fail silently.
err = fmt.Errorf("can not bind to destination of type: %s", t.Kind())
}
if err != nil {
return fmt.Errorf("error binding string parameter: %w", err)
}
return nil
}

431
vendor/github.com/oapi-codegen/runtime/deepobject.go generated vendored Normal file
View File

@@ -0,0 +1,431 @@
package runtime
import (
"bytes"
"encoding"
"encoding/json"
"errors"
"fmt"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/oapi-codegen/runtime/types"
)
func marshalDeepObject(in interface{}, path []string) ([]string, error) {
var result []string
switch t := in.(type) {
case []interface{}:
// For the array, we will use numerical subscripts of the form [x],
// in the same order as the array.
for i, iface := range t {
newPath := append(path, strconv.Itoa(i))
fields, err := marshalDeepObject(iface, newPath)
if err != nil {
return nil, fmt.Errorf("error traversing array: %w", err)
}
result = append(result, fields...)
}
case map[string]interface{}:
// For a map, each key (field name) becomes a member of the path, and
// we recurse. First, sort the keys.
keys := make([]string, len(t))
i := 0
for k := range t {
keys[i] = k
i++
}
sort.Strings(keys)
// Now, for each key, we recursively marshal it.
for _, k := range keys {
newPath := append(path, k)
fields, err := marshalDeepObject(t[k], newPath)
if err != nil {
return nil, fmt.Errorf("error traversing map: %w", err)
}
result = append(result, fields...)
}
default:
// Now, for a concrete value, we will turn the path elements
// into a deepObject style set of subscripts. [a, b, c] turns into
// [a][b][c]. Path segments may originate from user-controlled map
// keys, so each segment is percent-encoded; the literal '[' and ']'
// remain as structural delimiters.
encoded := make([]string, len(path))
for i, p := range path {
encoded[i] = url.QueryEscape(p)
}
prefix := "[" + strings.Join(encoded, "][") + "]"
var value string
if t == nil {
value = "null"
} else {
value = url.QueryEscape(fmt.Sprintf("%v", t))
}
result = []string{
prefix + "=" + value,
}
}
return result, nil
}
func MarshalDeepObject(i interface{}, paramName string) (string, error) {
// We're going to marshal to JSON and unmarshal into an interface{},
// which will use the json pkg to deal with all the field annotations. We
// can then walk the generic object structure to produce a deepObject. This
// isn't efficient and it would be more efficient to reflect on our own,
// but it's complicated, error-prone code.
buf, err := json.Marshal(i)
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 = e.Decode(&i2)
if err != nil {
return "", fmt.Errorf("failed to unmarshal JSON: %w", err)
}
fields, err := marshalDeepObject(i2, nil)
if err != nil {
return "", fmt.Errorf("error traversing JSON structure: %w", err)
}
// Prefix the param name to each subscripted field. The param name is
// percent-encoded to keep the wire output ASCII-clean even if the spec
// declares a non-identifier parameter name.
encodedParamName := url.QueryEscape(paramName)
for i := range fields {
fields[i] = encodedParamName + fields[i]
}
return strings.Join(fields, "&"), nil
}
type fieldOrValue struct {
fields map[string]fieldOrValue
value string
}
func (f *fieldOrValue) appendPathValue(path []string, value string) {
fieldName := path[0]
if len(path) == 1 {
f.fields[fieldName] = fieldOrValue{value: value}
return
}
pv, found := f.fields[fieldName]
if !found {
pv = fieldOrValue{
fields: make(map[string]fieldOrValue),
}
f.fields[fieldName] = pv
}
pv.appendPathValue(path[1:], value)
}
func makeFieldOrValue(paths [][]string, values []string) fieldOrValue {
f := fieldOrValue{
fields: make(map[string]fieldOrValue),
}
for i := range paths {
path := paths[i]
value := values[i]
f.appendPathValue(path, value)
}
return f
}
// UnmarshalDeepObject decodes deepObject-style query parameters (e.g.
// `filter[name]=alice&filter[role]=admin`) into dst, using paramName as the
// outer prefix.
//
// Encoding: MarshalDeepObject percent-encodes values, map keys, and the
// param name itself, so any byte sequence — including non-ASCII text and URL
// reserved characters in values or in map keys — round-trips correctly. The
// '[' and ']' characters that appear at the top level of each fragment are
// always structural; literal brackets inside data are encoded as %5B/%5D on
// the wire.
//
// Known limitation: literal '[' or ']' inside map keys cannot be round-
// tripped. After url.ParseQuery decodes %5B/%5D back to '['/']', the parser
// cannot distinguish a structural bracket from a literal bracket that was
// part of a key (e.g. `p[a[b]]` is ambiguous between `{p: {a: {b: ...}}}`
// and `{p: {"a[b]": ...}}`). This matches the behavior of every other
// deepObject implementation (qs, Rails, PHP); the deepObject style itself
// does not define an escape mechanism for brackets inside keys.
func UnmarshalDeepObject(dst interface{}, paramName string, params url.Values) error {
return unmarshalDeepObject(dst, paramName, params, false)
}
func unmarshalDeepObject(dst interface{}, paramName string, params url.Values, required bool) error {
// Params are all the query args, so we need those that look like
// "paramName["...
var fieldNames []string
var fieldValues []string
searchStr := paramName + "["
for pName, pValues := range params {
if strings.HasPrefix(pName, searchStr) {
// trim the parameter name from the full name.
pName = pName[len(paramName):]
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)
}
}
}
}
if len(fieldNames) == 0 {
if required {
return fmt.Errorf("query parameter '%s' is required", paramName)
} else {
return nil
}
}
// Now, for each field, reconstruct its subscript path and value
paths := make([][]string, len(fieldNames))
for i, path := range fieldNames {
path = strings.TrimLeft(path, "[")
path = strings.TrimRight(path, "]")
paths[i] = strings.Split(path, "][")
}
fieldPaths := makeFieldOrValue(paths, fieldValues)
err := assignPathValues(dst, fieldPaths)
if err != nil {
return fmt.Errorf("error assigning value to destination: %w", err)
}
return nil
}
// This returns a field name, either using the variable name, or the json
// annotation if that exists.
func getFieldName(f reflect.StructField) string {
n := f.Name
tag, found := f.Tag.Lookup("json")
if found {
// If we have a json field, and the first part of it before the
// first comma is non-empty, that's our field name.
parts := strings.Split(tag, ",")
if parts[0] != "" {
n = parts[0]
}
}
return n
}
// Create a map of field names that we'll see in the deepObject to reflect
// field indices on the given type.
func fieldIndicesByJSONTag(i interface{}) (map[string]int, error) {
t := reflect.TypeOf(i)
if t.Kind() != reflect.Struct {
return nil, errors.New("expected a struct as input")
}
n := t.NumField()
fieldMap := make(map[string]int)
for i := 0; i < n; i++ {
field := t.Field(i)
fieldName := getFieldName(field)
fieldMap[fieldName] = i
}
return fieldMap, nil
}
func assignPathValues(dst interface{}, pathValues fieldOrValue) error {
//t := reflect.TypeOf(dst)
v := reflect.ValueOf(dst)
iv := reflect.Indirect(v)
it := iv.Type()
switch it.Kind() {
case reflect.Map:
dstMap := reflect.MakeMap(iv.Type())
for key, value := range pathValues.fields {
dstKey := reflect.ValueOf(key)
dstVal := reflect.New(iv.Type().Elem())
err := assignPathValues(dstVal.Interface(), value)
if err != nil {
return fmt.Errorf("error binding map: %w", err)
}
dstMap.SetMapIndex(dstKey, dstVal.Elem())
}
iv.Set(dstMap)
return nil
case reflect.Slice:
sliceLength := len(pathValues.fields)
dstSlice := reflect.MakeSlice(it, sliceLength, sliceLength)
err := assignSlice(dstSlice, pathValues)
if err != nil {
return fmt.Errorf("error assigning slice: %w", err)
}
iv.Set(dstSlice)
return nil
case reflect.Struct:
// Some special types we care about are structs. Handle them
// here. They may be redefined, so we need to do some hoop
// jumping. If the types are aliased, we need to type convert
// the pointer, then set the value of the dereference pointer.
// We check to see if the object implements the Binder interface first.
if dst, isBinder := v.Interface().(Binder); isBinder {
return dst.Bind(pathValues.value)
}
// Then check the legacy types
if it.ConvertibleTo(reflect.TypeOf(types.Date{})) {
var date types.Date
var err error
date.Time, err = time.Parse(types.DateFormat, pathValues.value)
if err != nil {
return fmt.Errorf("invalid date format: %w", err)
}
dst := iv
if it != reflect.TypeOf(types.Date{}) {
// Types are aliased, convert the pointers.
ivPtr := iv.Addr()
aPtr := ivPtr.Convert(reflect.TypeOf(&types.Date{}))
dst = reflect.Indirect(aPtr)
}
dst.Set(reflect.ValueOf(date))
return nil
}
if it.ConvertibleTo(reflect.TypeOf(time.Time{})) {
var tm time.Time
var err error
tm, err = time.Parse(time.RFC3339Nano, pathValues.value)
if err != nil {
// Fall back to parsing it as a date.
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)
}
}
dst := iv
if it != reflect.TypeOf(time.Time{}) {
// Types are aliased, convert the pointers.
ivPtr := iv.Addr()
aPtr := ivPtr.Convert(reflect.TypeOf(&time.Time{}))
dst = reflect.Indirect(aPtr)
}
dst.Set(reflect.ValueOf(tm))
return nil
}
// For other struct types that implement TextUnmarshaler (e.g. uuid.UUID),
// use that for binding. This comes after the legacy Date/time.Time checks
// above which have special fallback format handling.
if tu, ok := v.Interface().(encoding.TextUnmarshaler); ok {
return tu.UnmarshalText([]byte(pathValues.value))
}
fieldMap, err := fieldIndicesByJSONTag(iv.Interface())
if err != nil {
return fmt.Errorf("failed enumerating fields: %w", err)
}
for _, fieldName := range sortedFieldOrValueKeys(pathValues.fields) {
fieldValue := pathValues.fields[fieldName]
fieldIndex, found := fieldMap[fieldName]
if !found {
return fmt.Errorf("field [%s] is not present in destination object", fieldName)
}
field := iv.Field(fieldIndex)
err = assignPathValues(field.Addr().Interface(), fieldValue)
if err != nil {
return fmt.Errorf("error assigning field [%s]: %w", fieldName, err)
}
}
return nil
case reflect.Ptr:
// If we have a pointer after redirecting, it means we're dealing with
// an optional field, such as *string, which was passed in as &foo. We
// will allocate it if necessary, and call ourselves with a different
// interface.
dstVal := reflect.New(it.Elem())
dstPtr := dstVal.Interface()
err := assignPathValues(dstPtr, pathValues)
iv.Set(dstVal)
return err
case reflect.Bool:
val, err := strconv.ParseBool(pathValues.value)
if err != nil {
return fmt.Errorf("expected a valid bool, got %s", pathValues.value)
}
iv.SetBool(val)
return nil
case reflect.Float32:
val, err := strconv.ParseFloat(pathValues.value, 32)
if err != nil {
return fmt.Errorf("expected a valid float, got %s", pathValues.value)
}
iv.SetFloat(val)
return nil
case reflect.Float64:
val, err := strconv.ParseFloat(pathValues.value, 64)
if err != nil {
return fmt.Errorf("expected a valid float, got %s", pathValues.value)
}
iv.SetFloat(val)
return nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val, err := strconv.ParseInt(pathValues.value, 10, 64)
if err != nil {
return fmt.Errorf("expected a valid int, got %s", pathValues.value)
}
iv.SetInt(val)
return nil
case reflect.String:
iv.SetString(pathValues.value)
return nil
default:
return errors.New("unhandled type: " + it.String())
}
}
func assignSlice(dst reflect.Value, pathValues fieldOrValue) error {
nValues := len(pathValues.fields)
// 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")
}
// Get the destination element
dstElem := dst.Index(i).Addr()
// 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 element %d: %w", i, err)
}
}
return nil
}
func sortedFieldOrValueKeys(m map[string]fieldOrValue) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}

34
vendor/github.com/oapi-codegen/runtime/jsonmerge.go generated vendored Normal file
View File

@@ -0,0 +1,34 @@
package runtime
import (
"encoding/json"
"github.com/apapsch/go-jsonmerge/v2"
)
// JsonMerge merges two JSON representation into a single object. `data` is the
// existing representation and `patch` is the new data to be merged in
//
// Deprecated: Use JSONMerge instead.
func JsonMerge(data, patch json.RawMessage) (json.RawMessage, error) {
return JSONMerge(data, patch)
}
// JSONMerge merges two JSON representation into a single object. `data` is the
// existing representation and `patch` is the new data to be merged in
func JSONMerge(data, patch json.RawMessage) (json.RawMessage, error) {
merger := jsonmerge.Merger{
CopyNonexistent: true,
}
if data == nil {
data = []byte(`{}`)
}
if patch == nil {
patch = []byte(`{}`)
}
merged, err := merger.MergeBytes(data, patch)
if err != nil {
return nil, err
}
return merged, nil
}

52
vendor/github.com/oapi-codegen/runtime/paramformat.go generated vendored Normal file
View File

@@ -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
}

6
vendor/github.com/oapi-codegen/runtime/renovate.json generated vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"local>oapi-codegen/renovate-config"
]
}

View File

@@ -0,0 +1,16 @@
package nethttp
import (
"context"
"net/http"
)
type StrictHTTPHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (response interface{}, err error)
type StrictHTTPMiddlewareFunc func(f StrictHTTPHandlerFunc, operationID string) StrictHTTPHandlerFunc
// Deprecated: Use StrictHTTPHandlerFunc instead.
type StrictHttpHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (response interface{}, err error)
// Deprecated: Use StrictHTTPMiddlewareFunc instead.
type StrictHttpMiddlewareFunc func(f StrictHttpHandlerFunc, operationID string) StrictHttpHandlerFunc

558
vendor/github.com/oapi-codegen/runtime/styleparam.go generated vendored Normal file
View File

@@ -0,0 +1,558 @@
// Copyright 2019 DeepMap, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runtime
import (
"bytes"
"encoding"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/google/uuid"
"github.com/oapi-codegen/runtime/types"
)
// Parameter escaping works differently based on where a header is found
type ParamLocation int
const (
ParamLocationUndefined ParamLocation = iota
ParamLocationQuery
ParamLocationPath
ParamLocationHeader
ParamLocationCookie
)
// StyleParam is used by older generated code, and must remain compatible
// with that code. It is not to be used in new templates. Please see the
// function below, which can specialize its output based on the location of
// the parameter.
func StyleParam(style string, explode bool, paramName string, value interface{}) (string, error) {
return StyleParamWithLocation(style, explode, paramName, ParamLocationUndefined, value)
}
// 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
// AllowReserved, when true, prevents percent-encoding of RFC 3986
// reserved characters in query parameter values. Per the OpenAPI 3.x
// spec, this only applies to query parameters.
AllowReserved 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)
// Things may be passed in by pointer, we need to dereference, so return
// error on nil.
if t.Kind() == reflect.Ptr {
if v.IsNil() {
return "", fmt.Errorf("value is a nil pointer")
}
v = reflect.Indirect(v)
t = v.Type()
}
// If the value implements encoding.TextMarshaler we use it for marshaling
// https://github.com/deepmap/oapi-codegen/issues/504
if tu, ok := value.(encoding.TextMarshaler); ok {
t := reflect.Indirect(reflect.ValueOf(value)).Type()
convertableToTime := t.ConvertibleTo(reflect.TypeOf(time.Time{}))
convertableToDate := t.ConvertibleTo(reflect.TypeOf(types.Date{}))
// Since both time.Time and types.Date implement encoding.TextMarshaler
// we should avoid calling theirs MarshalText()
if !convertableToTime && !convertableToDate {
b, err := tu.MarshalText()
if err != nil {
return "", fmt.Errorf("error marshaling '%s' as text: %w", value, err)
}
return stylePrimitive(style, explode, paramName, opts.ParamLocation, opts.AllowReserved, 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, opts.AllowReserved, 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, opts.ParamLocation, opts.AllowReserved, sliceVal)
case reflect.Struct:
return styleStruct(style, explode, paramName, opts.ParamLocation, opts.AllowReserved, value)
case reflect.Map:
return styleMap(style, explode, paramName, opts.ParamLocation, opts.AllowReserved, value)
default:
return stylePrimitive(style, explode, paramName, opts.ParamLocation, opts.AllowReserved, value)
}
}
func styleSlice(style string, explode bool, paramName string, paramLocation ParamLocation, allowReserved bool, values []interface{}) (string, error) {
if style == "deepObject" {
if !explode {
return "", errors.New("deepObjects must be exploded")
}
return MarshalDeepObject(values, paramName)
}
var prefix string
var separator string
escapedName := escapeParameterName(paramName, paramLocation)
switch style {
case "simple":
separator = ","
case "label":
prefix = "."
if explode {
separator = "."
} else {
separator = ","
}
case "matrix":
prefix = fmt.Sprintf(";%s=", escapedName)
if explode {
separator = prefix
} else {
separator = ","
}
case "form":
prefix = fmt.Sprintf("%s=", escapedName)
if explode {
separator = "&" + prefix
} else {
separator = ","
}
case "spaceDelimited":
prefix = fmt.Sprintf("%s=", escapedName)
if explode {
separator = "&" + prefix
} else {
separator = " "
}
case "pipeDelimited":
prefix = fmt.Sprintf("%s=", escapedName)
if explode {
separator = "&" + prefix
} else {
separator = "|"
}
default:
return "", fmt.Errorf("unsupported style '%s'", style)
}
// We're going to assume here that the array is one of simple types.
var err error
var part string
parts := make([]string, len(values))
for i, v := range values {
part, err = primitiveToString(v)
part = escapeParameterString(part, paramLocation, allowReserved)
parts[i] = part
if err != nil {
return "", fmt.Errorf("error formatting '%s': %w", paramName, err)
}
}
return prefix + strings.Join(parts, separator), nil
}
func sortedKeys(strMap map[string]string) []string {
keys := make([]string, len(strMap))
i := 0
for k := range strMap {
keys[i] = k
i++
}
sort.Strings(keys)
return keys
}
// These are special cases. The value may be a date, time, or uuid,
// in which case, marshal it into the correct format.
func marshalKnownTypes(value interface{}) (string, bool) {
v := reflect.Indirect(reflect.ValueOf(value))
t := v.Type()
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
tt := v.Convert(reflect.TypeOf(time.Time{}))
timeVal := tt.Interface().(time.Time)
return timeVal.Format(time.RFC3339Nano), true
}
if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
d := v.Convert(reflect.TypeOf(types.Date{}))
dateVal := d.Interface().(types.Date)
return dateVal.Format(types.DateFormat), true
}
if t.ConvertibleTo(reflect.TypeOf(types.UUID{})) {
u := v.Convert(reflect.TypeOf(types.UUID{}))
uuidVal := u.Interface().(types.UUID)
return uuidVal.String(), true
}
return "", false
}
func styleStruct(style string, explode bool, paramName string, paramLocation ParamLocation, allowReserved bool, value interface{}) (string, error) {
if timeVal, ok := marshalKnownTypes(value); ok {
styledVal, err := stylePrimitive(style, explode, paramName, paramLocation, allowReserved, timeVal)
if err != nil {
return "", fmt.Errorf("failed to style time: %w", err)
}
return styledVal, nil
}
if style == "deepObject" {
if !explode {
return "", errors.New("deepObjects must be exploded")
}
return MarshalDeepObject(value, paramName)
}
// If input has Marshaler, such as object has Additional Property or AnyOf,
// We use this Marshaler and convert into interface{} before styling.
if m, ok := value.(json.Marshaler); ok {
buf, err := m.MarshalJSON()
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 = e.Decode(&i2)
if err != nil {
return "", fmt.Errorf("failed to unmarshal JSON: %w", err)
}
s, err := StyleParamWithOptions(style, explode, paramName, i2, StyleParamOptions{
ParamLocation: paramLocation,
AllowReserved: allowReserved,
})
if err != nil {
return "", fmt.Errorf("error style JSON structure: %w", err)
}
return s, nil
}
// Otherwise, we need to build a dictionary of the struct's fields. Each
// field may only be a primitive value.
v := reflect.ValueOf(value)
t := reflect.TypeOf(value)
fieldDict := make(map[string]string)
for i := 0; i < t.NumField(); i++ {
fieldT := t.Field(i)
// Find the json annotation on the field, and use the json specified
// name if available, otherwise, just the field name.
tag := fieldT.Tag.Get("json")
fieldName := fieldT.Name
if tag != "" {
tagParts := strings.Split(tag, ",")
name := tagParts[0]
if name != "" {
fieldName = name
}
}
f := v.Field(i)
// Unset optional fields will be nil pointers, skip over those.
if f.Type().Kind() == reflect.Ptr && f.IsNil() {
continue
}
str, err := primitiveToString(f.Interface())
if err != nil {
return "", fmt.Errorf("error formatting '%s': %w", paramName, err)
}
fieldDict[fieldName] = str
}
return processFieldDict(style, explode, paramName, paramLocation, allowReserved, fieldDict)
}
func styleMap(style string, explode bool, paramName string, paramLocation ParamLocation, allowReserved bool, value interface{}) (string, error) {
if style == "deepObject" {
if !explode {
return "", errors.New("deepObjects must be exploded")
}
return MarshalDeepObject(value, paramName)
}
v := reflect.ValueOf(value)
fieldDict := make(map[string]string)
for _, fieldName := range v.MapKeys() {
str, err := primitiveToString(v.MapIndex(fieldName).Interface())
if err != nil {
return "", fmt.Errorf("error formatting '%s': %w", paramName, err)
}
fieldDict[fieldName.String()] = str
}
return processFieldDict(style, explode, paramName, paramLocation, allowReserved, fieldDict)
}
func processFieldDict(style string, explode bool, paramName string, paramLocation ParamLocation, allowReserved bool, fieldDict map[string]string) (string, error) {
var parts []string
// This works for everything except deepObject. We'll handle that one
// separately.
if style != "deepObject" {
if explode {
for _, k := range sortedKeys(fieldDict) {
v := escapeParameterString(fieldDict[k], paramLocation, allowReserved)
parts = append(parts, k+"="+v)
}
} else {
for _, k := range sortedKeys(fieldDict) {
v := escapeParameterString(fieldDict[k], paramLocation, allowReserved)
parts = append(parts, k)
parts = append(parts, v)
}
}
}
escapedName := escapeParameterName(paramName, paramLocation)
var prefix string
var separator string
switch style {
case "simple":
separator = ","
case "label":
prefix = "."
if explode {
separator = prefix
} else {
separator = ","
}
case "matrix":
if explode {
separator = ";"
prefix = ";"
} else {
separator = ","
prefix = fmt.Sprintf(";%s=", escapedName)
}
case "form":
if explode {
separator = "&"
} else {
prefix = fmt.Sprintf("%s=", escapedName)
separator = ","
}
case "deepObject":
{
if !explode {
return "", fmt.Errorf("deepObject parameters must be exploded")
}
for _, k := range sortedKeys(fieldDict) {
v := fieldDict[k]
part := fmt.Sprintf("%s[%s]=%s", escapedName, k, v)
parts = append(parts, part)
}
separator = "&"
}
default:
return "", fmt.Errorf("unsupported style '%s'", style)
}
return prefix + strings.Join(parts, separator), nil
}
func stylePrimitive(style string, explode bool, paramName string, paramLocation ParamLocation, allowReserved bool, value interface{}) (string, error) {
strVal, err := primitiveToString(value)
if err != nil {
return "", err
}
escapedName := escapeParameterName(paramName, paramLocation)
var prefix string
switch style {
case "simple":
case "label":
prefix = "."
case "matrix":
prefix = fmt.Sprintf(";%s=", escapedName)
case "form":
prefix = fmt.Sprintf("%s=", escapedName)
default:
return "", fmt.Errorf("unsupported style '%s'", style)
}
return prefix + escapeParameterString(strVal, paramLocation, allowReserved), nil
}
// Converts a primitive value to a string. We need to do this based on the
// Kind of an interface, not the Type to work with aliased types.
func primitiveToString(value interface{}) (string, error) {
var output string
// sometimes time and date used like primitive types
// it can happen if paramether is object and has time or date as field
if res, ok := marshalKnownTypes(value); ok {
return res, nil
}
// Values may come in by pointer for optionals, so make sure to dereferene.
v := reflect.Indirect(reflect.ValueOf(value))
t := v.Type()
kind := t.Kind()
switch kind {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
output = strconv.FormatInt(v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
output = strconv.FormatUint(v.Uint(), 10)
case reflect.Float64:
output = strconv.FormatFloat(v.Float(), 'f', -1, 64)
case reflect.Float32:
output = strconv.FormatFloat(v.Float(), 'f', -1, 32)
case reflect.Bool:
if v.Bool() {
output = "true"
} else {
output = "false"
}
case reflect.String:
output = v.String()
case reflect.Struct:
// If input has Marshaler, such as object has Additional Property or AnyOf,
// We use this Marshaler and convert into interface{} before styling.
if v, ok := value.(uuid.UUID); ok {
output = v.String()
break
}
if m, ok := value.(json.Marshaler); ok {
buf, err := m.MarshalJSON()
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 = e.Decode(&i2)
if err != nil {
return "", fmt.Errorf("failed to unmarshal JSON: %w", err)
}
output, err = primitiveToString(i2)
if err != nil {
return "", fmt.Errorf("error convert JSON structure: %w", err)
}
break
}
fallthrough
default:
v, ok := value.(fmt.Stringer)
if !ok {
return "", fmt.Errorf("unsupported type %s", reflect.TypeOf(value).String())
}
output = v.String()
}
return output, nil
}
// escapeParameterName escapes a parameter name for use in query strings and
// paths. This ensures characters like [] in parameter names (e.g. user_ids[])
// are properly percent-encoded per RFC 3986.
func escapeParameterName(name string, paramLocation ParamLocation) string {
// Parameter names should always be encoded regardless of allowReserved,
// which only applies to values per the OpenAPI spec.
return escapeParameterString(name, paramLocation, false)
}
// escapeParameterString escapes a parameter value based on the location of
// that parameter. Query params and path params need different kinds of
// escaping, while header and cookie params seem not to need escaping.
// When allowReserved is true and the location is query, RFC 3986 reserved
// characters are left unencoded per the OpenAPI allowReserved specification.
func escapeParameterString(value string, paramLocation ParamLocation, allowReserved bool) string {
switch paramLocation {
case ParamLocationQuery:
if allowReserved {
return escapeQueryAllowReserved(value)
}
return url.QueryEscape(value)
case ParamLocationPath:
return url.PathEscape(value)
default:
return value
}
}
// escapeQueryAllowReserved percent-encodes a query parameter value while
// leaving RFC 3986 reserved characters (:/?#[]@!$&'()*+,;=) unencoded, as
// specified by OpenAPI's allowReserved parameter option. Only characters that
// are neither unreserved nor reserved are encoded (e.g., spaces, control
// characters, non-ASCII).
func escapeQueryAllowReserved(value string) string {
// RFC 3986 reserved characters that should NOT be encoded when
// allowReserved is true.
const reserved = `:/?#[]@!$&'()*+,;=`
var buf strings.Builder
for _, b := range []byte(value) {
if isUnreserved(b) || strings.IndexByte(reserved, b) >= 0 {
buf.WriteByte(b)
} else {
fmt.Fprintf(&buf, "%%%02X", b)
}
}
return buf.String()
}
// isUnreserved reports whether the byte is an RFC 3986 unreserved character:
// ALPHA / DIGIT / "-" / "." / "_" / "~"
func isUnreserved(c byte) bool {
return (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '-' || c == '.' || c == '_' || c == '~'
}

58
vendor/github.com/oapi-codegen/runtime/types/date.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
package types
import (
"encoding/json"
"time"
)
const DateFormat = "2006-01-02"
type Date struct {
time.Time
}
func (d Date) MarshalJSON() ([]byte, error) {
return json.Marshal(d.Format(DateFormat))
}
func (d *Date) UnmarshalJSON(data []byte) error {
var dateStr string
err := json.Unmarshal(data, &dateStr)
if err != nil {
return err
}
parsed, err := time.Parse(DateFormat, dateStr)
if err != nil {
return err
}
d.Time = parsed
return nil
}
func (d Date) String() string {
return d.Format(DateFormat)
}
func (d *Date) UnmarshalText(data []byte) error {
parsed, err := time.Parse(DateFormat, string(data))
if err != nil {
return err
}
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
}

43
vendor/github.com/oapi-codegen/runtime/types/email.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
package types
import (
"encoding/json"
"errors"
"net/mail"
)
// ErrValidationEmail is the sentinel error returned when an email fails validation
var ErrValidationEmail = errors.New("email: failed to pass regex validation")
// Email represents an email address.
// It is a string type that must pass regex validation before being marshalled
// to JSON or unmarshalled from JSON.
type Email string
func (e Email) MarshalJSON() ([]byte, error) {
m, err := mail.ParseAddress(string(e))
if err != nil {
return nil, ErrValidationEmail
}
return json.Marshal(m.Address)
}
func (e *Email) UnmarshalJSON(data []byte) error {
if e == nil {
return nil
}
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
m, err := mail.ParseAddress(s)
if err != nil {
return ErrValidationEmail
}
*e = Email(m.Address)
return nil
}

71
vendor/github.com/oapi-codegen/runtime/types/file.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
package types
import (
"bytes"
"encoding/json"
"io"
"mime/multipart"
)
type File struct {
multipart *multipart.FileHeader
data []byte
filename string
}
func (file *File) InitFromMultipart(header *multipart.FileHeader) {
file.multipart = header
file.data = nil
file.filename = ""
}
func (file *File) InitFromBytes(data []byte, filename string) {
file.data = data
file.filename = filename
file.multipart = nil
}
func (file File) MarshalJSON() ([]byte, error) {
b, err := file.Bytes()
if err != nil {
return nil, err
}
return json.Marshal(b)
}
func (file *File) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &file.data)
}
func (file File) Bytes() ([]byte, error) {
if file.multipart != nil {
f, err := file.multipart.Open()
if err != nil {
return nil, err
}
defer func() { _ = f.Close() }()
return io.ReadAll(f)
}
return file.data, nil
}
func (file File) Reader() (io.ReadCloser, error) {
if file.multipart != nil {
return file.multipart.Open()
}
return io.NopCloser(bytes.NewReader(file.data)), nil
}
func (file File) Filename() string {
if file.multipart != nil {
return file.multipart.Filename
}
return file.filename
}
func (file File) FileSize() int64 {
if file.multipart != nil {
return file.multipart.Size
}
return int64(len(file.data))
}

7
vendor/github.com/oapi-codegen/runtime/types/uuid.go generated vendored Normal file
View File

@@ -0,0 +1,7 @@
package types
import (
"github.com/google/uuid"
)
type UUID = uuid.UUID