go.mod: bump github.com/getkin/kin-openapi to v0.131.0

As deepmap/oapi-codegen didn't work with this newer version, upgrade to
oapi-codegen/oapi-codegen v2.

Mitigating CVE-2025-30153
This commit is contained in:
Sanne Raymaekers 2025-03-21 11:50:30 +01:00 committed by Ondřej Budai
parent c5cb0d0618
commit b2700903ae
403 changed files with 44758 additions and 16347 deletions

201
vendor/github.com/oapi-codegen/oapi-codegen/v2/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.

View file

@ -0,0 +1,562 @@
// 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 main
import (
"flag"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"runtime/debug"
"strings"
"gopkg.in/yaml.v2"
"github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen"
"github.com/oapi-codegen/oapi-codegen/v2/pkg/util"
)
func errExit(format string, args ...interface{}) {
if !strings.HasSuffix(format, "\n") {
format = format + "\n"
}
_, _ = fmt.Fprintf(os.Stderr, format, args...)
os.Exit(1)
}
var (
flagOutputFile string
flagConfigFile string
flagOldConfigStyle bool
flagOutputConfig bool
flagPrintVersion bool
flagPackageName string
flagPrintUsage bool
flagGenerate string
flagTemplatesDir string
// Deprecated: The options below will be removed in a future
// release. Please use the new config file format.
flagIncludeTags string
flagExcludeTags string
flagIncludeOperationIDs string
flagExcludeOperationIDs string
flagImportMapping string
flagExcludeSchemas string
flagResponseTypeSuffix string
flagAliasTypes bool
flagInitialismOverrides bool
)
type configuration struct {
codegen.Configuration `yaml:",inline"`
// OutputFile is the filename to output.
OutputFile string `yaml:"output,omitempty"`
}
// oldConfiguration is deprecated. Please add no more flags here. It is here
// for backwards compatibility, and it will be removed in the future.
type oldConfiguration struct {
PackageName string `yaml:"package"`
GenerateTargets []string `yaml:"generate"`
OutputFile string `yaml:"output"`
IncludeTags []string `yaml:"include-tags"`
ExcludeTags []string `yaml:"exclude-tags"`
IncludeOperationIDs []string `yaml:"include-operation-ids"`
ExcludeOperationIDs []string `yaml:"exclude-operation-ids"`
TemplatesDir string `yaml:"templates"`
ImportMapping map[string]string `yaml:"import-mapping"`
ExcludeSchemas []string `yaml:"exclude-schemas"`
ResponseTypeSuffix string `yaml:"response-type-suffix"`
Compatibility codegen.CompatibilityOptions `yaml:"compatibility"`
}
// noVCSVersionOverride allows overriding the version of the application for cases where no Version Control System (VCS) is available when building, for instance when using a Nix derivation.
// See documentation for how to use it in examples/no-vcs-version-override/README.md
var noVCSVersionOverride string
func main() {
flag.StringVar(&flagOutputFile, "o", "", "Where to output generated code, stdout is default.")
flag.BoolVar(&flagOldConfigStyle, "old-config-style", false, "Whether to use the older style config file format.")
flag.BoolVar(&flagOutputConfig, "output-config", false, "When true, outputs a configuration file for oapi-codegen using current settings.")
flag.StringVar(&flagConfigFile, "config", "", "A YAML config file that controls oapi-codegen behavior.")
flag.BoolVar(&flagPrintVersion, "version", false, "When specified, print version and exit.")
flag.StringVar(&flagPackageName, "package", "", "The package name for generated code.")
flag.BoolVar(&flagPrintUsage, "help", false, "Show this help and exit.")
flag.BoolVar(&flagPrintUsage, "h", false, "Same as -help.")
// All flags below are deprecated, and will be removed in a future release. Please do not
// update their behavior.
flag.StringVar(&flagGenerate, "generate", "types,client,server,spec",
`Comma-separated list of code to generate; valid options: "types", "client", "chi-server", "server", "gin", "gorilla", "spec", "skip-fmt", "skip-prune", "fiber", "iris", "std-http".`)
flag.StringVar(&flagIncludeTags, "include-tags", "", "Only include operations with the given tags. Comma-separated list of tags.")
flag.StringVar(&flagExcludeTags, "exclude-tags", "", "Exclude operations that are tagged with the given tags. Comma-separated list of tags.")
flag.StringVar(&flagIncludeOperationIDs, "include-operation-ids", "", "Only include operations with the given operation-ids. Comma-separated list of operation-ids.")
flag.StringVar(&flagExcludeOperationIDs, "exclude-operation-ids", "", "Exclude operations with the given operation-ids. Comma-separated list of operation-ids.")
flag.StringVar(&flagTemplatesDir, "templates", "", "Path to directory containing user templates.")
flag.StringVar(&flagImportMapping, "import-mapping", "", "A dict from the external reference to golang package path.")
flag.StringVar(&flagExcludeSchemas, "exclude-schemas", "", "A comma separated list of schemas which must be excluded from generation.")
flag.StringVar(&flagResponseTypeSuffix, "response-type-suffix", "", "The suffix used for responses types.")
flag.BoolVar(&flagAliasTypes, "alias-types", false, "Alias type declarations of possible.")
flag.BoolVar(&flagInitialismOverrides, "initialism-overrides", false, "Use initialism overrides.")
flag.Parse()
if flagPrintUsage {
flag.Usage()
os.Exit(0)
}
if flagPrintVersion {
bi, ok := debug.ReadBuildInfo()
if !ok {
fmt.Fprintln(os.Stderr, "error reading build info")
os.Exit(1)
}
fmt.Println(bi.Main.Path + "/cmd/oapi-codegen")
version := bi.Main.Version
if len(noVCSVersionOverride) > 0 {
version = noVCSVersionOverride
}
fmt.Println(version)
return
}
if flag.NArg() < 1 {
errExit("Please specify a path to a OpenAPI 3.0 spec file\n")
} else if flag.NArg() > 1 {
errExit("Only one OpenAPI 3.0 spec file is accepted and it must be the last CLI argument\n")
}
// We will try to infer whether the user has an old-style config, or a new
// style. Start with the command line argument. If it's true, we know it's
// old config style.
var oldConfigStyle *bool
if flagOldConfigStyle {
oldConfigStyle = &flagOldConfigStyle
}
// We don't know yet, so keep looking. Try to parse the configuration file,
// if given.
if oldConfigStyle == nil && (flagConfigFile != "") {
configFile, err := os.ReadFile(flagConfigFile)
if err != nil {
errExit("error reading config file '%s': %v\n", flagConfigFile, err)
}
var oldConfig oldConfiguration
oldErr := yaml.UnmarshalStrict(configFile, &oldConfig)
var newConfig configuration
newErr := yaml.UnmarshalStrict(configFile, &newConfig)
// If one of the two files parses, but the other fails, we know the
// answer.
if oldErr != nil && newErr == nil {
f := false
oldConfigStyle = &f
} else if oldErr == nil && newErr != nil {
t := true
oldConfigStyle = &t
} else if oldErr != nil && newErr != nil {
errExit("error parsing configuration style as old version or new version\n\nerror when parsing using old config version:\n%v\n\nerror when parsing using new config version:\n%v\n", oldErr, newErr)
}
// Else we fall through, and we still don't know, so we need to infer it from flags.
}
if oldConfigStyle == nil {
// If any deprecated flag is present, and config file structure is unknown,
// the presence of the deprecated flag means we must be using the old
// config style. It should work correctly if we go down the old path,
// even if we have a simple config file readable as both types.
deprecatedFlagNames := map[string]bool{
"include-tags": true,
"exclude-tags": true,
"import-mapping": true,
"exclude-schemas": true,
"response-type-suffix": true,
"alias-types": true,
}
hasDeprecatedFlag := false
flag.Visit(func(f *flag.Flag) {
if deprecatedFlagNames[f.Name] {
hasDeprecatedFlag = true
}
})
if hasDeprecatedFlag {
t := true
oldConfigStyle = &t
} else {
f := false
oldConfigStyle = &f
}
}
var opts configuration
if !*oldConfigStyle {
// We simply read the configuration from disk.
if flagConfigFile != "" {
buf, err := os.ReadFile(flagConfigFile)
if err != nil {
errExit("error reading config file '%s': %v\n", flagConfigFile, err)
}
err = yaml.Unmarshal(buf, &opts)
if err != nil {
errExit("error parsing'%s' as YAML: %v\n", flagConfigFile, err)
}
} else {
// In the case where no config file is provided, we assume some
// defaults, so that when this is invoked very simply, it's similar
// to old behavior.
opts = configuration{
Configuration: codegen.Configuration{
Generate: codegen.GenerateOptions{
EchoServer: true,
Client: true,
Models: true,
EmbeddedSpec: true,
},
},
OutputFile: flagOutputFile,
}
}
if err := updateConfigFromFlags(&opts); err != nil {
errExit("error processing flags: %v\n", err)
}
} else {
var oldConfig oldConfiguration
if flagConfigFile != "" {
buf, err := os.ReadFile(flagConfigFile)
if err != nil {
errExit("error reading config file '%s': %v\n", flagConfigFile, err)
}
err = yaml.Unmarshal(buf, &oldConfig)
if err != nil {
errExit("error parsing'%s' as YAML: %v\n", flagConfigFile, err)
}
}
var err error
opts, err = newConfigFromOldConfig(oldConfig)
if err != nil {
flag.PrintDefaults()
errExit("error creating new config from old config: %v\n", err)
}
}
// Ensure default values are set if user hasn't specified some needed
// fields.
opts.Configuration = opts.UpdateDefaults()
if err := detectPackageName(&opts); err != nil {
errExit("%s\n", err)
}
// Now, ensure that the config options are valid.
if err := opts.Validate(); err != nil {
errExit("configuration error: %v\n", err)
}
// If the user asked to output configuration, output it to stdout and exit
if flagOutputConfig {
buf, err := yaml.Marshal(opts)
if err != nil {
errExit("error YAML marshaling configuration: %v\n", err)
}
fmt.Print(string(buf))
return
}
overlayOpts := util.LoadSwaggerWithOverlayOpts{
Path: opts.OutputOptions.Overlay.Path,
// default to strict, but can be overridden
Strict: true,
}
if opts.OutputOptions.Overlay.Strict != nil {
overlayOpts.Strict = *opts.OutputOptions.Overlay.Strict
}
swagger, err := util.LoadSwaggerWithOverlay(flag.Arg(0), overlayOpts)
if err != nil {
errExit("error loading swagger spec in %s\n: %s\n", flag.Arg(0), err)
}
if strings.HasPrefix(swagger.OpenAPI, "3.1.") {
fmt.Println("WARNING: You are using an OpenAPI 3.1.x specification, which is not yet supported by oapi-codegen (https://github.com/oapi-codegen/oapi-codegen/issues/373) and so some functionality may not be available. Until oapi-codegen supports OpenAPI 3.1, it is recommended to downgrade your spec to 3.0.x")
}
if len(noVCSVersionOverride) > 0 {
opts.Configuration.NoVCSVersionOverride = &noVCSVersionOverride
}
code, err := codegen.Generate(swagger, opts.Configuration)
if err != nil {
errExit("error generating code: %s\n", err)
}
if opts.OutputFile != "" {
err = os.WriteFile(opts.OutputFile, []byte(code), 0o644)
if err != nil {
errExit("error writing generated code to file: %s\n", err)
}
} else {
fmt.Print(code)
}
}
func loadTemplateOverrides(templatesDir string) (map[string]string, error) {
templates := make(map[string]string)
if templatesDir == "" {
return templates, nil
}
files, err := os.ReadDir(templatesDir)
if err != nil {
return nil, err
}
for _, f := range files {
// Recursively load subdirectory files, using the path relative to the templates
// directory as the key. This allows for overriding the files in the service-specific
// directories (e.g. echo, chi, fiber, etc.).
if f.IsDir() {
subFiles, err := loadTemplateOverrides(path.Join(templatesDir, f.Name()))
if err != nil {
return nil, err
}
for subDir, subFile := range subFiles {
templates[path.Join(f.Name(), subDir)] = subFile
}
continue
}
data, err := os.ReadFile(path.Join(templatesDir, f.Name()))
if err != nil {
return nil, err
}
templates[f.Name()] = string(data)
}
return templates, nil
}
// detectPackageName detects and sets PackageName if not already set.
func detectPackageName(cfg *configuration) error {
if cfg.PackageName != "" {
return nil
}
if cfg.OutputFile != "" {
// Determine from the package name of the output file.
dir := filepath.Dir(cfg.PackageName)
cmd := exec.Command("go", "list", "-f", "{{.Name}}", dir)
out, err := cmd.CombinedOutput()
if err != nil {
outStr := string(out)
switch {
case strings.Contains(outStr, "expected 'package', found 'EOF'"):
// Redirecting the output to current directory which hasn't
// written anything yet, ignore.
case strings.HasPrefix(outStr, "no Go files in"):
// No go files yet, ignore.
default:
// Unexpected failure report.
return fmt.Errorf("detect package name for %q output: %q: %w", dir, string(out), err)
}
} else {
cfg.PackageName = string(out)
return nil
}
}
// Fallback to determining from the spec file name.
parts := strings.Split(filepath.Base(flag.Arg(0)), ".")
cfg.PackageName = codegen.LowercaseFirstCharacter(codegen.ToCamelCase(parts[0]))
return nil
}
// updateConfigFromFlags updates a loaded configuration from flags. Flags
// override anything in the file. We generate errors for any unsupported
// command line flags.
func updateConfigFromFlags(cfg *configuration) error {
if flagPackageName != "" {
cfg.PackageName = flagPackageName
}
if flagGenerate != "types,client,server,spec" {
// Override generation and output options from generate command line flag.
if err := generationTargets(&cfg.Configuration, util.ParseCommandLineList(flagGenerate)); err != nil {
return err
}
}
if flagIncludeTags != "" {
cfg.OutputOptions.IncludeTags = util.ParseCommandLineList(flagIncludeTags)
}
if flagExcludeTags != "" {
cfg.OutputOptions.ExcludeTags = util.ParseCommandLineList(flagExcludeTags)
}
if flagIncludeOperationIDs != "" {
cfg.OutputOptions.IncludeOperationIDs = util.ParseCommandLineList(flagIncludeOperationIDs)
}
if flagExcludeOperationIDs != "" {
cfg.OutputOptions.ExcludeOperationIDs = util.ParseCommandLineList(flagExcludeOperationIDs)
}
if flagTemplatesDir != "" {
templates, err := loadTemplateOverrides(flagTemplatesDir)
if err != nil {
return fmt.Errorf("load templates from %q: %w", flagTemplatesDir, err)
}
cfg.OutputOptions.UserTemplates = templates
}
if flagImportMapping != "" {
var err error
cfg.ImportMapping, err = util.ParseCommandlineMap(flagImportMapping)
if err != nil {
return err
}
}
if flagExcludeSchemas != "" {
cfg.OutputOptions.ExcludeSchemas = util.ParseCommandLineList(flagExcludeSchemas)
}
if flagResponseTypeSuffix != "" {
cfg.OutputOptions.ResponseTypeSuffix = flagResponseTypeSuffix
}
if flagAliasTypes {
return fmt.Errorf("--alias-types isn't supported any more")
}
if cfg.OutputFile == "" {
cfg.OutputFile = flagOutputFile
}
cfg.OutputOptions.InitialismOverrides = flagInitialismOverrides
return nil
}
// updateOldConfigFromFlags parses the flags and the config file. Anything which is
// a zerovalue in the configuration file will be replaced with the flag
// value, this means that the config file overrides flag values.
func updateOldConfigFromFlags(cfg oldConfiguration) oldConfiguration {
if cfg.PackageName == "" {
cfg.PackageName = flagPackageName
}
if cfg.GenerateTargets == nil {
cfg.GenerateTargets = util.ParseCommandLineList(flagGenerate)
}
if cfg.IncludeTags == nil {
cfg.IncludeTags = util.ParseCommandLineList(flagIncludeTags)
}
if cfg.ExcludeTags == nil {
cfg.ExcludeTags = util.ParseCommandLineList(flagExcludeTags)
}
if cfg.TemplatesDir == "" {
cfg.TemplatesDir = flagTemplatesDir
}
if cfg.ImportMapping == nil && flagImportMapping != "" {
var err error
cfg.ImportMapping, err = util.ParseCommandlineMap(flagImportMapping)
if err != nil {
errExit("error parsing import-mapping: %s\n", err)
}
}
if cfg.ExcludeSchemas == nil {
cfg.ExcludeSchemas = util.ParseCommandLineList(flagExcludeSchemas)
}
if cfg.OutputFile == "" {
cfg.OutputFile = flagOutputFile
}
return cfg
}
// generationTargets sets cfg options based on the generation targets.
func generationTargets(cfg *codegen.Configuration, targets []string) error {
opts := codegen.GenerateOptions{} // Blank to start with.
for _, opt := range targets {
switch opt {
case "iris", "iris-server":
opts.IrisServer = true
case "chi-server", "chi":
opts.ChiServer = true
case "fiber-server", "fiber":
opts.FiberServer = true
case "server", "echo-server", "echo":
opts.EchoServer = true
case "gin", "gin-server":
opts.GinServer = true
case "gorilla", "gorilla-server":
opts.GorillaServer = true
case "std-http", "std-http-server":
opts.StdHTTPServer = true
case "strict-server":
opts.Strict = true
case "client":
opts.Client = true
case "types", "models":
opts.Models = true
case "spec", "embedded-spec":
opts.EmbeddedSpec = true
case "skip-fmt":
cfg.OutputOptions.SkipFmt = true
case "skip-prune":
cfg.OutputOptions.SkipPrune = true
default:
return fmt.Errorf("unknown generate option %q", opt)
}
}
cfg.Generate = opts
return nil
}
func newConfigFromOldConfig(c oldConfiguration) (configuration, error) {
// Take flags into account.
cfg := updateOldConfigFromFlags(c)
// Now, copy over field by field, translating flags and old values as
// necessary.
opts := codegen.Configuration{
PackageName: cfg.PackageName,
}
opts.OutputOptions.ResponseTypeSuffix = flagResponseTypeSuffix
if err := generationTargets(&opts, cfg.GenerateTargets); err != nil {
return configuration{}, fmt.Errorf("generation targets: %w", err)
}
opts.OutputOptions.IncludeTags = cfg.IncludeTags
opts.OutputOptions.ExcludeTags = cfg.ExcludeTags
opts.OutputOptions.ExcludeSchemas = cfg.ExcludeSchemas
templates, err := loadTemplateOverrides(cfg.TemplatesDir)
if err != nil {
return configuration{}, fmt.Errorf("loading template overrides: %w", err)
}
opts.OutputOptions.UserTemplates = templates
opts.ImportMapping = cfg.ImportMapping
opts.Compatibility = cfg.Compatibility
return configuration{
Configuration: opts,
OutputFile: cfg.OutputFile,
}, nil
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,254 @@
package codegen
import (
"errors"
"fmt"
"reflect"
)
type AdditionalImport struct {
Alias string `yaml:"alias,omitempty"`
Package string `yaml:"package"`
}
// Configuration defines code generation customizations
type Configuration struct {
// PackageName to generate the code under
PackageName string `yaml:"package"`
// Generate specifies which supported output formats to generate
Generate GenerateOptions `yaml:"generate,omitempty"`
// CompatibilityOptions specifies backward compatibility settings for the code generator
Compatibility CompatibilityOptions `yaml:"compatibility,omitempty"`
// OutputOptions are used to modify the output code in some way.
OutputOptions OutputOptions `yaml:"output-options,omitempty"`
// ImportMapping specifies the golang package path for each external reference
ImportMapping map[string]string `yaml:"import-mapping,omitempty"`
// AdditionalImports defines any additional Go imports to add to the generated code
AdditionalImports []AdditionalImport `yaml:"additional-imports,omitempty"`
// NoVCSVersionOverride allows overriding the version of the application for cases where no Version Control System (VCS) is available when building, for instance when using a Nix derivation.
// See documentation for how to use it in examples/no-vcs-version-override/README.md
NoVCSVersionOverride *string `yaml:"-"`
}
// Validate checks whether Configuration represent a valid configuration
func (o Configuration) Validate() error {
if o.PackageName == "" {
return errors.New("package name must be specified")
}
// Only one server type should be specified at a time.
nServers := 0
if o.Generate.IrisServer {
nServers++
}
if o.Generate.ChiServer {
nServers++
}
if o.Generate.FiberServer {
nServers++
}
if o.Generate.EchoServer {
nServers++
}
if o.Generate.GorillaServer {
nServers++
}
if o.Generate.StdHTTPServer {
nServers++
}
if o.Generate.GinServer {
nServers++
}
if nServers > 1 {
return errors.New("only one server type is supported at a time")
}
var errs []error
if problems := o.Generate.Validate(); problems != nil {
for k, v := range problems {
errs = append(errs, fmt.Errorf("`generate` configuration for %v was incorrect: %v", k, v))
}
}
if problems := o.Compatibility.Validate(); problems != nil {
for k, v := range problems {
errs = append(errs, fmt.Errorf("`compatibility-options` configuration for %v was incorrect: %v", k, v))
}
}
if problems := o.OutputOptions.Validate(); problems != nil {
for k, v := range problems {
errs = append(errs, fmt.Errorf("`output-options` configuration for %v was incorrect: %v", k, v))
}
}
err := errors.Join(errs...)
if err != nil {
return fmt.Errorf("failed to validate configuration: %w", err)
}
return nil
}
// UpdateDefaults sets reasonable default values for unset fields in Configuration
func (o Configuration) UpdateDefaults() Configuration {
if reflect.ValueOf(o.Generate).IsZero() {
o.Generate = GenerateOptions{
EchoServer: true,
Models: true,
EmbeddedSpec: true,
}
}
return o
}
// GenerateOptions specifies which supported output formats to generate.
type GenerateOptions struct {
// IrisServer specifies whether to generate iris server boilerplate
IrisServer bool `yaml:"iris-server,omitempty"`
// ChiServer specifies whether to generate chi server boilerplate
ChiServer bool `yaml:"chi-server,omitempty"`
// FiberServer specifies whether to generate fiber server boilerplate
FiberServer bool `yaml:"fiber-server,omitempty"`
// EchoServer specifies whether to generate echo server boilerplate
EchoServer bool `yaml:"echo-server,omitempty"`
// GinServer specifies whether to generate gin server boilerplate
GinServer bool `yaml:"gin-server,omitempty"`
// GorillaServer specifies whether to generate Gorilla server boilerplate
GorillaServer bool `yaml:"gorilla-server,omitempty"`
// StdHTTPServer specifies whether to generate stdlib http server boilerplate
StdHTTPServer bool `yaml:"std-http-server,omitempty"`
// Strict specifies whether to generate strict server wrapper
Strict bool `yaml:"strict-server,omitempty"`
// Client specifies whether to generate client boilerplate
Client bool `yaml:"client,omitempty"`
// Models specifies whether to generate type definitions
Models bool `yaml:"models,omitempty"`
// EmbeddedSpec indicates whether to embed the swagger spec in the generated code
EmbeddedSpec bool `yaml:"embedded-spec,omitempty"`
}
func (oo GenerateOptions) Validate() map[string]string {
return nil
}
// CompatibilityOptions specifies backward compatibility settings for the
// code generator.
type CompatibilityOptions struct {
// In the past, we merged schemas for `allOf` by inlining each schema
// within the schema list. This approach, though, is incorrect because
// `allOf` merges at the schema definition level, not at the resulting model
// level. So, new behavior merges OpenAPI specs but generates different code
// than we have in the past. Set OldMergeSchemas to true for the old behavior.
// Please see https://github.com/oapi-codegen/oapi-codegen/issues/531
OldMergeSchemas bool `yaml:"old-merge-schemas,omitempty"`
// Enum values can generate conflicting typenames, so we've updated the
// code for enum generation to avoid these conflicts, but it will result
// in some enum types being renamed in existing code. Set OldEnumConflicts to true
// to revert to old behavior. Please see:
// Please see https://github.com/oapi-codegen/oapi-codegen/issues/549
OldEnumConflicts bool `yaml:"old-enum-conflicts,omitempty"`
// It was a mistake to generate a go type definition for every $ref in
// the OpenAPI schema. New behavior uses type aliases where possible, but
// this can generate code which breaks existing builds. Set OldAliasing to true
// for old behavior.
// Please see https://github.com/oapi-codegen/oapi-codegen/issues/549
OldAliasing bool `yaml:"old-aliasing,omitempty"`
// When an object contains no members, and only an additionalProperties specification,
// it is flattened to a map
DisableFlattenAdditionalProperties bool `yaml:"disable-flatten-additional-properties,omitempty"`
// When an object property is both required and readOnly the go model is generated
// as a pointer. Set DisableRequiredReadOnlyAsPointer to true to mark them as non pointer.
// Please see https://github.com/oapi-codegen/oapi-codegen/issues/604
DisableRequiredReadOnlyAsPointer bool `yaml:"disable-required-readonly-as-pointer,omitempty"`
// When set to true, always prefix enum values with their type name instead of only
// when typenames would be conflicting.
AlwaysPrefixEnumValues bool `yaml:"always-prefix-enum-values,omitempty"`
// Our generated code for Chi has historically inverted the order in which Chi middleware is
// applied such that the last invoked middleware ends up executing first in the Chi chain
// This resolves the behavior such that middlewares are chained in the order they are invoked.
// Please see https://github.com/oapi-codegen/oapi-codegen/issues/786
ApplyChiMiddlewareFirstToLast bool `yaml:"apply-chi-middleware-first-to-last,omitempty"`
// Our generated code for gorilla/mux has historically inverted the order in which gorilla/mux middleware is
// applied such that the last invoked middleware ends up executing first in the middlewares chain
// This resolves the behavior such that middlewares are chained in the order they are invoked.
// Please see https://github.com/oapi-codegen/oapi-codegen/issues/841
ApplyGorillaMiddlewareFirstToLast bool `yaml:"apply-gorilla-middleware-first-to-last,omitempty"`
// CircularReferenceLimit allows controlling the limit for circular reference checking.
// In some OpenAPI specifications, we have a higher number of circular
// references than is allowed out-of-the-box, but can be tuned to allow
// traversing them.
// Deprecated: In kin-openapi v0.126.0 (https://github.com/getkin/kin-openapi/tree/v0.126.0?tab=readme-ov-file#v01260) the Circular Reference Counter functionality was removed, instead resolving all references with backtracking, to avoid needing to provide a limit to reference counts.
CircularReferenceLimit int `yaml:"circular-reference-limit"`
// AllowUnexportedStructFieldNames makes it possible to output structs that have fields that are unexported.
//
// This is expected to be used in conjunction with `x-go-name` and `x-oapi-codegen-only-honour-go-name` to override the resulting output field name, and `x-oapi-codegen-extra-tags` to not produce JSON tags for `encoding/json`, such as:
//
// ```yaml
// id:
// type: string
// x-go-name: accountIdentifier
// x-oapi-codegen-extra-tags:
// json: "-"
// x-oapi-codegen-only-honour-go-name: true
// ```
//
// NOTE that this can be confusing to users of your OpenAPI specification, who may see a field present and therefore be expecting to see/use it in the request/response, without understanding the nuance of how `oapi-codegen` generates the code.
AllowUnexportedStructFieldNames bool `yaml:"allow-unexported-struct-field-names"`
}
func (co CompatibilityOptions) Validate() map[string]string {
return nil
}
// OutputOptions are used to modify the output code in some way.
type OutputOptions struct {
// Whether to skip go imports on the generated code
SkipFmt bool `yaml:"skip-fmt,omitempty"`
// Whether to skip pruning unused components on the generated code
SkipPrune bool `yaml:"skip-prune,omitempty"`
// Only include operations that have one of these tags. Ignored when empty.
IncludeTags []string `yaml:"include-tags,omitempty"`
// Exclude operations that have one of these tags. Ignored when empty.
ExcludeTags []string `yaml:"exclude-tags,omitempty"`
// Only include operations that have one of these operation-ids. Ignored when empty.
IncludeOperationIDs []string `yaml:"include-operation-ids,omitempty"`
// Exclude operations that have one of these operation-ids. Ignored when empty.
ExcludeOperationIDs []string `yaml:"exclude-operation-ids,omitempty"`
// Override built-in templates from user-provided files
UserTemplates map[string]string `yaml:"user-templates,omitempty"`
// Exclude from generation schemas with given names. Ignored when empty.
ExcludeSchemas []string `yaml:"exclude-schemas,omitempty"`
// The suffix used for responses types
ResponseTypeSuffix string `yaml:"response-type-suffix,omitempty"`
// Override the default generated client type with the value
ClientTypeName string `yaml:"client-type-name,omitempty"`
// Whether to use the initialism overrides
InitialismOverrides bool `yaml:"initialism-overrides,omitempty"`
// Whether to generate nullable type for nullable fields
NullableType bool `yaml:"nullable-type,omitempty"`
// DisableTypeAliasesForType allows defining which OpenAPI `type`s will explicitly not use type aliases
// Currently supports:
// "array"
DisableTypeAliasesForType []string `yaml:"disable-type-aliases-for-type"`
// NameNormalizer is the method used to normalize Go names and types, for instance converting the text `MyApi` to `MyAPI`. Corresponds with the constants defined for `codegen.NameNormalizerFunction`
NameNormalizer string `yaml:"name-normalizer,omitempty"`
// Overlay defines configuration for the OpenAPI Overlay (https://github.com/OAI/Overlay-Specification) to manipulate the OpenAPI specification before generation. This allows modifying the specification without needing to apply changes directly to it, making it easier to keep it up-to-date.
Overlay OutputOptionsOverlay `yaml:"overlay"`
}
func (oo OutputOptions) Validate() map[string]string {
return nil
}
type OutputOptionsOverlay struct {
Path string `yaml:"path"`
// Strict defines whether the Overlay should be applied in a strict way, highlighting any actions that will not take any effect. This can, however, lead to more work when testing new actions in an Overlay, so can be turned off with this setting.
// Defaults to true.
Strict *bool `yaml:"strict,omitempty"`
}

View file

@ -0,0 +1,113 @@
package codegen
import (
"fmt"
)
const (
// extPropGoType overrides the generated type definition.
extPropGoType = "x-go-type"
// extPropGoTypeSkipOptionalPointer specifies that optional fields should
// be the type itself instead of a pointer to the type.
extPropGoTypeSkipOptionalPointer = "x-go-type-skip-optional-pointer"
// extPropGoImport specifies the module to import which provides above type
extPropGoImport = "x-go-type-import"
// extGoName is used to override a field name
extGoName = "x-go-name"
// extGoTypeName is used to override a generated typename for something.
extGoTypeName = "x-go-type-name"
extPropGoJsonIgnore = "x-go-json-ignore"
extPropOmitEmpty = "x-omitempty"
extPropExtraTags = "x-oapi-codegen-extra-tags"
extEnumVarNames = "x-enum-varnames"
extEnumNames = "x-enumNames"
extDeprecationReason = "x-deprecated-reason"
extOrder = "x-order"
// extOapiCodegenOnlyHonourGoName is to be used to explicitly enforce the generation of a field as the `x-go-name` extension has describe it.
// This is intended to be used alongside the `allow-unexported-struct-field-names` Compatibility option
extOapiCodegenOnlyHonourGoName = "x-oapi-codegen-only-honour-go-name"
)
func extString(extPropValue interface{}) (string, error) {
str, ok := extPropValue.(string)
if !ok {
return "", fmt.Errorf("failed to convert type: %T", extPropValue)
}
return str, nil
}
func extTypeName(extPropValue interface{}) (string, error) {
return extString(extPropValue)
}
func extParsePropGoTypeSkipOptionalPointer(extPropValue interface{}) (bool, error) {
goTypeSkipOptionalPointer, ok := extPropValue.(bool)
if !ok {
return false, fmt.Errorf("failed to convert type: %T", extPropValue)
}
return goTypeSkipOptionalPointer, nil
}
func extParseGoFieldName(extPropValue interface{}) (string, error) {
return extString(extPropValue)
}
func extParseOmitEmpty(extPropValue interface{}) (bool, error) {
omitEmpty, ok := extPropValue.(bool)
if !ok {
return false, fmt.Errorf("failed to convert type: %T", extPropValue)
}
return omitEmpty, nil
}
func extExtraTags(extPropValue interface{}) (map[string]string, error) {
tagsI, ok := extPropValue.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("failed to convert type: %T", extPropValue)
}
tags := make(map[string]string, len(tagsI))
for k, v := range tagsI {
vs, ok := v.(string)
if !ok {
return nil, fmt.Errorf("failed to convert type: %T", v)
}
tags[k] = vs
}
return tags, nil
}
func extParseGoJsonIgnore(extPropValue interface{}) (bool, error) {
goJsonIgnore, ok := extPropValue.(bool)
if !ok {
return false, fmt.Errorf("failed to convert type: %T", extPropValue)
}
return goJsonIgnore, nil
}
func extParseEnumVarNames(extPropValue interface{}) ([]string, error) {
namesI, ok := extPropValue.([]interface{})
if !ok {
return nil, fmt.Errorf("failed to convert type: %T", extPropValue)
}
names := make([]string, len(namesI))
for i, v := range namesI {
vs, ok := v.(string)
if !ok {
return nil, fmt.Errorf("failed to convert type: %T", v)
}
names[i] = vs
}
return names, nil
}
func extParseDeprecationReason(extPropValue interface{}) (string, error) {
return extString(extPropValue)
}
func extParseOapiCodegenOnlyHonourGoName(extPropValue interface{}) (bool, error) {
onlyHonourGoName, ok := extPropValue.(bool)
if !ok {
return false, fmt.Errorf("failed to convert type: %T", extPropValue)
}
return onlyHonourGoName, nil
}

View file

@ -0,0 +1,80 @@
package codegen
import (
"fmt"
"strings"
)
// ensureExternalRefsInRequestBodyDefinitions ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type.
// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file
// NOTE that the pointer here allows us to pass in a reference and edit in-place
func ensureExternalRefsInRequestBodyDefinitions(defs *[]RequestBodyDefinition, ref string) {
if ref == "" {
return
}
for i, rbd := range *defs {
ensureExternalRefsInSchema(&rbd.Schema, ref)
// make sure we then update it in-place
(*defs)[i] = rbd
}
}
// ensureExternalRefsInResponseDefinitions ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type.
// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file
// NOTE that the pointer here allows us to pass in a reference and edit in-place
func ensureExternalRefsInResponseDefinitions(defs *[]ResponseDefinition, ref string) {
if ref == "" {
return
}
for i, rd := range *defs {
for j, rcd := range rd.Contents {
ensureExternalRefsInSchema(&rcd.Schema, ref)
// make sure we then update it in-place
rd.Contents[j] = rcd
}
// make sure we then update it in-place
(*defs)[i] = rd
}
}
// ensureExternalRefsInParameterDefinitions ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type.
// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file
// NOTE that the pointer here allows us to pass in a reference and edit in-place
func ensureExternalRefsInParameterDefinitions(defs *[]ParameterDefinition, ref string) {
if ref == "" {
return
}
for i, pd := range *defs {
ensureExternalRefsInSchema(&pd.Schema, ref)
// make sure we then update it in-place
(*defs)[i] = pd
}
}
// ensureExternalRefsInSchema ensures that when an externalRef (`$ref` that points to a file that isn't the current spec) is encountered, we make sure we update our underlying `RefType` to make sure that we point to that type.
//
// This only happens if we have a non-empty `ref` passed in, and that `ref` isn't pointing to something in our file
//
// NOTE that the pointer here allows us to pass in a reference and edit in-place
func ensureExternalRefsInSchema(schema *Schema, ref string) {
if ref == "" {
return
}
// if this is already defined as the start of a struct, we shouldn't inject **??**
if strings.HasPrefix(schema.GoType, "struct {") {
return
}
parts := strings.SplitN(ref, "#", 2)
if pack, ok := globalState.importMapping[parts[0]]; ok {
schema.RefType = fmt.Sprintf("%s.%s", pack.Name, schema.GoType)
}
}

View file

@ -0,0 +1,88 @@
package codegen
import "github.com/getkin/kin-openapi/openapi3"
func sliceToMap(items []string) map[string]bool {
m := make(map[string]bool, len(items))
for _, item := range items {
m[item] = true
}
return m
}
func filterOperationsByTag(swagger *openapi3.T, opts Configuration) {
if len(opts.OutputOptions.ExcludeTags) > 0 {
operationsWithTags(swagger.Paths, sliceToMap(opts.OutputOptions.ExcludeTags), true)
}
if len(opts.OutputOptions.IncludeTags) > 0 {
operationsWithTags(swagger.Paths, sliceToMap(opts.OutputOptions.IncludeTags), false)
}
}
func operationsWithTags(paths *openapi3.Paths, tags map[string]bool, exclude bool) {
if paths == nil {
return
}
for _, pathItem := range paths.Map() {
ops := pathItem.Operations()
names := make([]string, 0, len(ops))
for name, op := range ops {
if operationHasTag(op, tags) == exclude {
names = append(names, name)
}
}
for _, name := range names {
pathItem.SetOperation(name, nil)
}
}
}
// operationHasTag returns true if the operation is tagged with any of tags
func operationHasTag(op *openapi3.Operation, tags map[string]bool) bool {
if op == nil {
return false
}
for _, hasTag := range op.Tags {
if tags[hasTag] {
return true
}
}
return false
}
func filterOperationsByOperationID(swagger *openapi3.T, opts Configuration) {
if len(opts.OutputOptions.ExcludeOperationIDs) > 0 {
operationsWithOperationIDs(swagger.Paths, sliceToMap(opts.OutputOptions.ExcludeOperationIDs), true)
}
if len(opts.OutputOptions.IncludeOperationIDs) > 0 {
operationsWithOperationIDs(swagger.Paths, sliceToMap(opts.OutputOptions.IncludeOperationIDs), false)
}
}
func operationsWithOperationIDs(paths *openapi3.Paths, operationIDs map[string]bool, exclude bool) {
if paths == nil {
return
}
for _, pathItem := range paths.Map() {
ops := pathItem.Operations()
names := make([]string, 0, len(ops))
for name, op := range ops {
if operationHasOperationID(op, operationIDs) == exclude {
names = append(names, name)
}
}
for _, name := range names {
pathItem.SetOperation(name, nil)
}
}
}
// operationHasOperationID returns true if the operation has operation id is included in operation ids
func operationHasOperationID(op *openapi3.Operation, operationIDs map[string]bool) bool {
if op == nil {
return false
}
return operationIDs[op.OperationID]
}

View file

@ -0,0 +1,77 @@
// 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 codegen
import (
"bytes"
"compress/gzip"
"context"
"encoding/base64"
"fmt"
"text/template"
"github.com/getkin/kin-openapi/openapi3"
)
// GenerateInlinedSpec generates a gzipped, base64 encoded JSON representation of the
// swagger definition, which we embed inside the generated code.
func GenerateInlinedSpec(t *template.Template, importMapping importMap, swagger *openapi3.T) (string, error) {
// ensure that any external file references are embedded into the embedded spec
swagger.InternalizeRefs(context.Background(), nil)
// Marshal to json
encoded, err := swagger.MarshalJSON()
if err != nil {
return "", fmt.Errorf("error marshaling swagger: %w", err)
}
// gzip
var buf bytes.Buffer
zw, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
if err != nil {
return "", fmt.Errorf("error creating gzip compressor: %w", err)
}
_, err = zw.Write(encoded)
if err != nil {
return "", fmt.Errorf("error gzipping swagger file: %w", err)
}
err = zw.Close()
if err != nil {
return "", fmt.Errorf("error gzipping swagger file: %w", err)
}
str := base64.StdEncoding.EncodeToString(buf.Bytes())
var parts []string
const width = 80
// Chop up the string into an array of strings.
for len(str) > width {
part := str[0:width]
parts = append(parts, part)
str = str[width:]
}
if len(str) > 0 {
parts = append(parts, str)
}
return GenerateTemplates(
[]string{"inline.tmpl"},
t,
struct {
SpecParts []string
ImportMapping importMap
}{
SpecParts: parts,
ImportMapping: importMapping,
})
}

View file

@ -0,0 +1,249 @@
package codegen
import (
"errors"
"fmt"
"strings"
"github.com/getkin/kin-openapi/openapi3"
)
// MergeSchemas merges all the fields in the schemas supplied into one giant schema.
// The idea is that we merge all fields together into one schema.
func MergeSchemas(allOf []*openapi3.SchemaRef, path []string) (Schema, error) {
// If someone asked for the old way, for backward compatibility, return the
// old style result.
if globalState.options.Compatibility.OldMergeSchemas {
return mergeSchemasV1(allOf, path)
}
return mergeSchemas(allOf, path)
}
func mergeSchemas(allOf []*openapi3.SchemaRef, path []string) (Schema, error) {
n := len(allOf)
if n == 1 {
return GenerateGoSchema(allOf[0], path)
}
schema, err := valueWithPropagatedRef(allOf[0])
if err != nil {
return Schema{}, err
}
for i := 1; i < n; i++ {
var err error
oneOfSchema, err := valueWithPropagatedRef(allOf[i])
if err != nil {
return Schema{}, err
}
schema, err = mergeOpenapiSchemas(schema, oneOfSchema, true)
if err != nil {
return Schema{}, fmt.Errorf("error merging schemas for AllOf: %w", err)
}
}
return GenerateGoSchema(openapi3.NewSchemaRef("", &schema), path)
}
// valueWithPropagatedRef returns a copy of ref schema with its Properties refs
// updated if ref itself is external. Otherwise, return ref.Value as-is.
func valueWithPropagatedRef(ref *openapi3.SchemaRef) (openapi3.Schema, error) {
if len(ref.Ref) == 0 || ref.Ref[0] == '#' {
return *ref.Value, nil
}
pathParts := strings.Split(ref.Ref, "#")
if len(pathParts) < 1 || len(pathParts) > 2 {
return openapi3.Schema{}, fmt.Errorf("unsupported reference: %s", ref.Ref)
}
remoteComponent := pathParts[0]
// remote ref
schema := *ref.Value
for _, value := range schema.Properties {
if len(value.Ref) > 0 && value.Ref[0] == '#' {
// local reference, should propagate remote
value.Ref = remoteComponent + value.Ref
}
}
return schema, nil
}
func mergeAllOf(allOf []*openapi3.SchemaRef) (openapi3.Schema, error) {
var schema openapi3.Schema
for _, schemaRef := range allOf {
var err error
schema, err = mergeOpenapiSchemas(schema, *schemaRef.Value, true)
if err != nil {
return openapi3.Schema{}, fmt.Errorf("error merging schemas for AllOf: %w", err)
}
}
return schema, nil
}
// mergeOpenapiSchemas merges two openAPI schemas and returns the schema
// all of whose fields are composed.
func mergeOpenapiSchemas(s1, s2 openapi3.Schema, allOf bool) (openapi3.Schema, error) {
var result openapi3.Schema
result.Extensions = make(map[string]interface{})
for k, v := range s1.Extensions {
result.Extensions[k] = v
}
for k, v := range s2.Extensions {
// TODO: Check for collisions
result.Extensions[k] = v
}
result.OneOf = append(s1.OneOf, s2.OneOf...)
// We are going to make AllOf transitive, so that merging an AllOf that
// contains AllOf's will result in a flat object.
var err error
if s1.AllOf != nil {
var merged openapi3.Schema
merged, err = mergeAllOf(s1.AllOf)
if err != nil {
return openapi3.Schema{}, fmt.Errorf("error transitive merging AllOf on schema 1")
}
s1 = merged
}
if s2.AllOf != nil {
var merged openapi3.Schema
merged, err = mergeAllOf(s2.AllOf)
if err != nil {
return openapi3.Schema{}, fmt.Errorf("error transitive merging AllOf on schema 2")
}
s2 = merged
}
result.AllOf = append(s1.AllOf, s2.AllOf...)
if s1.Type.Slice() != nil && s2.Type.Slice() != nil && !equalTypes(s1.Type, s2.Type) {
return openapi3.Schema{}, fmt.Errorf("can not merge incompatible types: %v, %v", s1.Type.Slice(), s2.Type.Slice())
}
result.Type = s1.Type
if s1.Format != s2.Format {
return openapi3.Schema{}, errors.New("can not merge incompatible formats")
}
result.Format = s1.Format
// For Enums, do we union, or intersect? This is a bit vague. I choose
// to be more permissive and union.
result.Enum = append(s1.Enum, s2.Enum...)
// I don't know how to handle two different defaults.
if s1.Default != nil || s2.Default != nil {
return openapi3.Schema{}, errors.New("merging two sets of defaults is undefined")
}
if s1.Default != nil {
result.Default = s1.Default
}
if s2.Default != nil {
result.Default = s2.Default
}
// We skip Example
// We skip ExternalDocs
// If two schemas disagree on any of these flags, we error out.
if s1.UniqueItems != s2.UniqueItems {
return openapi3.Schema{}, errors.New("merging two schemas with different UniqueItems")
}
result.UniqueItems = s1.UniqueItems
if s1.ExclusiveMin != s2.ExclusiveMin {
return openapi3.Schema{}, errors.New("merging two schemas with different ExclusiveMin")
}
result.ExclusiveMin = s1.ExclusiveMin
if s1.ExclusiveMax != s2.ExclusiveMax {
return openapi3.Schema{}, errors.New("merging two schemas with different ExclusiveMax")
}
result.ExclusiveMax = s1.ExclusiveMax
if s1.Nullable != s2.Nullable {
return openapi3.Schema{}, errors.New("merging two schemas with different Nullable")
}
result.Nullable = s1.Nullable
if s1.ReadOnly != s2.ReadOnly {
return openapi3.Schema{}, errors.New("merging two schemas with different ReadOnly")
}
result.ReadOnly = s1.ReadOnly
if s1.WriteOnly != s2.WriteOnly {
return openapi3.Schema{}, errors.New("merging two schemas with different WriteOnly")
}
result.WriteOnly = s1.WriteOnly
if s1.AllowEmptyValue != s2.AllowEmptyValue {
return openapi3.Schema{}, errors.New("merging two schemas with different AllowEmptyValue")
}
result.AllowEmptyValue = s1.AllowEmptyValue
// Required. We merge these.
result.Required = append(s1.Required, s2.Required...)
// We merge all properties
result.Properties = make(map[string]*openapi3.SchemaRef)
for k, v := range s1.Properties {
result.Properties[k] = v
}
for k, v := range s2.Properties {
// TODO: detect conflicts
result.Properties[k] = v
}
if isAdditionalPropertiesExplicitFalse(&s1) || isAdditionalPropertiesExplicitFalse(&s2) {
result.WithoutAdditionalProperties()
} else if s1.AdditionalProperties.Schema != nil {
if s2.AdditionalProperties.Schema != nil {
return openapi3.Schema{}, errors.New("merging two schemas with additional properties, this is unhandled")
} else {
result.AdditionalProperties.Schema = s1.AdditionalProperties.Schema
}
} else {
if s2.AdditionalProperties.Schema != nil {
result.AdditionalProperties.Schema = s2.AdditionalProperties.Schema
} else {
if s1.AdditionalProperties.Has != nil || s2.AdditionalProperties.Has != nil {
result.WithAnyAdditionalProperties()
}
}
}
// Allow discriminators for allOf merges, but disallow for one/anyOfs.
if !allOf && (s1.Discriminator != nil || s2.Discriminator != nil) {
return openapi3.Schema{}, errors.New("merging two schemas with discriminators is not supported")
}
return result, nil
}
func equalTypes(t1 *openapi3.Types, t2 *openapi3.Types) bool {
s1 := t1.Slice()
s2 := t2.Slice()
if len(s1) != len(s2) {
return false
}
// NOTE that ideally we'd use `slices.Equal` but as we're currently supporting Go 1.20+, we can't use it (yet https://github.com/oapi-codegen/oapi-codegen/issues/1634)
for i := range s1 {
if s1[i] != s2[i] {
return false
}
}
return true
}

View file

@ -0,0 +1,111 @@
package codegen
import (
"errors"
"fmt"
"strings"
"github.com/getkin/kin-openapi/openapi3"
)
func mergeSchemasV1(allOf []*openapi3.SchemaRef, path []string) (Schema, error) {
var outSchema Schema
for _, schemaOrRef := range allOf {
ref := schemaOrRef.Ref
var refType string
var err error
if IsGoTypeReference(ref) {
refType, err = RefPathToGoType(ref)
if err != nil {
return Schema{}, fmt.Errorf("error converting reference path to a go type: %w", err)
}
}
schema, err := GenerateGoSchema(schemaOrRef, path)
if err != nil {
return Schema{}, fmt.Errorf("error generating Go schema in allOf: %w", err)
}
schema.RefType = refType
for _, p := range schema.Properties {
err = outSchema.AddProperty(p)
if err != nil {
return Schema{}, fmt.Errorf("error merging properties: %w", err)
}
}
if schema.HasAdditionalProperties {
if outSchema.HasAdditionalProperties {
// Both this schema, and the aggregate schema have additional
// properties, they must match.
if schema.AdditionalPropertiesType.TypeDecl() != outSchema.AdditionalPropertiesType.TypeDecl() {
return Schema{}, errors.New("additional properties in allOf have incompatible types")
}
} else {
// We're switching from having no additional properties to having
// them
outSchema.HasAdditionalProperties = true
outSchema.AdditionalPropertiesType = schema.AdditionalPropertiesType
}
}
}
// Now, we generate the struct which merges together all the fields.
var err error
outSchema.GoType, err = GenStructFromAllOf(allOf, path)
if err != nil {
return Schema{}, fmt.Errorf("unable to generate aggregate type for AllOf: %w", err)
}
return outSchema, nil
}
// GenStructFromAllOf generates an object that is the union of the objects in the
// input array. In the case of Ref objects, we use an embedded struct, otherwise,
// we inline the fields.
func GenStructFromAllOf(allOf []*openapi3.SchemaRef, path []string) (string, error) {
// Start out with struct {
objectParts := []string{"struct {"}
for _, schemaOrRef := range allOf {
ref := schemaOrRef.Ref
if IsGoTypeReference(ref) {
// We have a referenced type, we will generate an inlined struct
// member.
// struct {
// InlinedMember
// ...
// }
goType, err := RefPathToGoType(ref)
if err != nil {
return "", err
}
objectParts = append(objectParts,
fmt.Sprintf(" // Embedded struct due to allOf(%s)", ref))
objectParts = append(objectParts,
fmt.Sprintf(" %s `yaml:\",inline\"`", goType))
} else {
// Inline all the fields from the schema into the output struct,
// just like in the simple case of generating an object.
goSchema, err := GenerateGoSchema(schemaOrRef, path)
if err != nil {
return "", err
}
objectParts = append(objectParts, " // Embedded fields due to inline allOf schema")
objectParts = append(objectParts, GenFieldsFromProperties(goSchema.Properties)...)
if goSchema.HasAdditionalProperties {
addPropsType := goSchema.AdditionalPropertiesType.GoType
if goSchema.AdditionalPropertiesType.RefType != "" {
addPropsType = goSchema.AdditionalPropertiesType.RefType
}
additionalPropertiesPart := fmt.Sprintf("AdditionalProperties map[string]%s `json:\"-\"`", addPropsType)
if !StringInArray(additionalPropertiesPart, objectParts) {
objectParts = append(objectParts, additionalPropertiesPart)
}
}
}
}
objectParts = append(objectParts, "}")
return strings.Join(objectParts, "\n"), nil
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,489 @@
package codegen
import (
"fmt"
"github.com/getkin/kin-openapi/openapi3"
)
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
type RefWrapper struct {
Ref string
HasValue bool
SourceRef interface{}
}
func walkSwagger(swagger *openapi3.T, doFn func(RefWrapper) (bool, error)) error {
if swagger == nil || swagger.Paths == nil {
return nil
}
for _, p := range swagger.Paths.Map() {
for _, param := range p.Parameters {
_ = walkParameterRef(param, doFn)
}
for _, op := range p.Operations() {
_ = walkOperation(op, doFn)
}
}
_ = walkComponents(swagger.Components, doFn)
return nil
}
func walkOperation(op *openapi3.Operation, doFn func(RefWrapper) (bool, error)) error {
// Not a valid ref, ignore it and continue
if op == nil {
return nil
}
for _, param := range op.Parameters {
_ = walkParameterRef(param, doFn)
}
_ = walkRequestBodyRef(op.RequestBody, doFn)
if op.Responses != nil {
for _, response := range op.Responses.Map() {
_ = walkResponseRef(response, doFn)
}
}
for _, callback := range op.Callbacks {
_ = walkCallbackRef(callback, doFn)
}
return nil
}
func walkComponents(components *openapi3.Components, doFn func(RefWrapper) (bool, error)) error {
// Not a valid ref, ignore it and continue
if components == nil {
return nil
}
for _, schema := range components.Schemas {
_ = walkSchemaRef(schema, doFn)
}
for _, param := range components.Parameters {
_ = walkParameterRef(param, doFn)
}
for _, header := range components.Headers {
_ = walkHeaderRef(header, doFn)
}
for _, requestBody := range components.RequestBodies {
_ = walkRequestBodyRef(requestBody, doFn)
}
for _, response := range components.Responses {
_ = walkResponseRef(response, doFn)
}
for _, securityScheme := range components.SecuritySchemes {
_ = walkSecuritySchemeRef(securityScheme, doFn)
}
for _, example := range components.Examples {
_ = walkExampleRef(example, doFn)
}
for _, link := range components.Links {
_ = walkLinkRef(link, doFn)
}
for _, callback := range components.Callbacks {
_ = walkCallbackRef(callback, doFn)
}
return nil
}
func walkSchemaRef(ref *openapi3.SchemaRef, doFn func(RefWrapper) (bool, error)) error {
// Not a valid ref, ignore it and continue
if ref == nil {
return nil
}
refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
shouldContinue, err := doFn(refWrapper)
if err != nil {
return err
}
if !shouldContinue {
return nil
}
if ref.Value == nil {
return nil
}
for _, ref := range ref.Value.OneOf {
_ = walkSchemaRef(ref, doFn)
}
for _, ref := range ref.Value.AnyOf {
_ = walkSchemaRef(ref, doFn)
}
for _, ref := range ref.Value.AllOf {
_ = walkSchemaRef(ref, doFn)
}
_ = walkSchemaRef(ref.Value.Not, doFn)
_ = walkSchemaRef(ref.Value.Items, doFn)
for _, ref := range ref.Value.Properties {
_ = walkSchemaRef(ref, doFn)
}
_ = walkSchemaRef(ref.Value.AdditionalProperties.Schema, doFn)
return nil
}
func walkParameterRef(ref *openapi3.ParameterRef, doFn func(RefWrapper) (bool, error)) error {
// Not a valid ref, ignore it and continue
if ref == nil {
return nil
}
refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
shouldContinue, err := doFn(refWrapper)
if err != nil {
return err
}
if !shouldContinue {
return nil
}
if ref.Value == nil {
return nil
}
_ = walkSchemaRef(ref.Value.Schema, doFn)
for _, example := range ref.Value.Examples {
_ = walkExampleRef(example, doFn)
}
for _, mediaType := range ref.Value.Content {
if mediaType == nil {
continue
}
_ = walkSchemaRef(mediaType.Schema, doFn)
for _, example := range mediaType.Examples {
_ = walkExampleRef(example, doFn)
}
}
return nil
}
func walkRequestBodyRef(ref *openapi3.RequestBodyRef, doFn func(RefWrapper) (bool, error)) error {
// Not a valid ref, ignore it and continue
if ref == nil {
return nil
}
refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
shouldContinue, err := doFn(refWrapper)
if err != nil {
return err
}
if !shouldContinue {
return nil
}
if ref.Value == nil {
return nil
}
for _, mediaType := range ref.Value.Content {
if mediaType == nil {
continue
}
_ = walkSchemaRef(mediaType.Schema, doFn)
for _, example := range mediaType.Examples {
_ = walkExampleRef(example, doFn)
}
}
return nil
}
func walkResponseRef(ref *openapi3.ResponseRef, doFn func(RefWrapper) (bool, error)) error {
// Not a valid ref, ignore it and continue
if ref == nil {
return nil
}
refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
shouldContinue, err := doFn(refWrapper)
if err != nil {
return err
}
if !shouldContinue {
return nil
}
if ref.Value == nil {
return nil
}
for _, header := range ref.Value.Headers {
_ = walkHeaderRef(header, doFn)
}
for _, mediaType := range ref.Value.Content {
if mediaType == nil {
continue
}
_ = walkSchemaRef(mediaType.Schema, doFn)
for _, example := range mediaType.Examples {
_ = walkExampleRef(example, doFn)
}
}
for _, link := range ref.Value.Links {
_ = walkLinkRef(link, doFn)
}
return nil
}
func walkCallbackRef(ref *openapi3.CallbackRef, doFn func(RefWrapper) (bool, error)) error {
// Not a valid ref, ignore it and continue
if ref == nil {
return nil
}
refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
shouldContinue, err := doFn(refWrapper)
if err != nil {
return err
}
if !shouldContinue {
return nil
}
if ref.Value == nil {
return nil
}
for _, pathItem := range ref.Value.Map() {
for _, parameter := range pathItem.Parameters {
_ = walkParameterRef(parameter, doFn)
}
_ = walkOperation(pathItem.Connect, doFn)
_ = walkOperation(pathItem.Delete, doFn)
_ = walkOperation(pathItem.Get, doFn)
_ = walkOperation(pathItem.Head, doFn)
_ = walkOperation(pathItem.Options, doFn)
_ = walkOperation(pathItem.Patch, doFn)
_ = walkOperation(pathItem.Post, doFn)
_ = walkOperation(pathItem.Put, doFn)
_ = walkOperation(pathItem.Trace, doFn)
}
return nil
}
func walkHeaderRef(ref *openapi3.HeaderRef, doFn func(RefWrapper) (bool, error)) error {
// Not a valid ref, ignore it and continue
if ref == nil {
return nil
}
refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
shouldContinue, err := doFn(refWrapper)
if err != nil {
return err
}
if !shouldContinue {
return nil
}
if ref.Value == nil {
return nil
}
_ = walkSchemaRef(ref.Value.Schema, doFn)
return nil
}
func walkSecuritySchemeRef(ref *openapi3.SecuritySchemeRef, doFn func(RefWrapper) (bool, error)) error {
// Not a valid ref, ignore it and continue
if ref == nil {
return nil
}
refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
shouldContinue, err := doFn(refWrapper)
if err != nil {
return err
}
if !shouldContinue {
return nil
}
if ref.Value == nil {
return nil
}
// NOTE: `SecuritySchemeRef`s don't contain any children that can contain refs
return nil
}
func walkLinkRef(ref *openapi3.LinkRef, doFn func(RefWrapper) (bool, error)) error {
// Not a valid ref, ignore it and continue
if ref == nil {
return nil
}
refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
shouldContinue, err := doFn(refWrapper)
if err != nil {
return err
}
if !shouldContinue {
return nil
}
if ref.Value == nil {
return nil
}
return nil
}
func walkExampleRef(ref *openapi3.ExampleRef, doFn func(RefWrapper) (bool, error)) error {
// Not a valid ref, ignore it and continue
if ref == nil {
return nil
}
refWrapper := RefWrapper{Ref: ref.Ref, HasValue: ref.Value != nil, SourceRef: ref}
shouldContinue, err := doFn(refWrapper)
if err != nil {
return err
}
if !shouldContinue {
return nil
}
if ref.Value == nil {
return nil
}
// NOTE: `ExampleRef`s don't contain any children that can contain refs
return nil
}
func findComponentRefs(swagger *openapi3.T) []string {
refs := []string{}
_ = walkSwagger(swagger, func(ref RefWrapper) (bool, error) {
if ref.Ref != "" {
refs = append(refs, ref.Ref)
return false, nil
}
return true, nil
})
return refs
}
func removeOrphanedComponents(swagger *openapi3.T, refs []string) int {
if swagger.Components == nil {
return 0
}
countRemoved := 0
for key := range swagger.Components.Schemas {
ref := fmt.Sprintf("#/components/schemas/%s", key)
if !stringInSlice(ref, refs) {
countRemoved++
delete(swagger.Components.Schemas, key)
}
}
for key := range swagger.Components.Parameters {
ref := fmt.Sprintf("#/components/parameters/%s", key)
if !stringInSlice(ref, refs) {
countRemoved++
delete(swagger.Components.Parameters, key)
}
}
// securitySchemes are an exception. definitions in securitySchemes
// are referenced directly by name. and not by $ref
// for key, _ := range swagger.Components.SecuritySchemes {
// ref := fmt.Sprintf("#/components/securitySchemes/%s", key)
// if !stringInSlice(ref, refs) {
// countRemoved++
// delete(swagger.Components.SecuritySchemes, key)
// }
// }
for key := range swagger.Components.RequestBodies {
ref := fmt.Sprintf("#/components/requestBodies/%s", key)
if !stringInSlice(ref, refs) {
countRemoved++
delete(swagger.Components.RequestBodies, key)
}
}
for key := range swagger.Components.Responses {
ref := fmt.Sprintf("#/components/responses/%s", key)
if !stringInSlice(ref, refs) {
countRemoved++
delete(swagger.Components.Responses, key)
}
}
for key := range swagger.Components.Headers {
ref := fmt.Sprintf("#/components/headers/%s", key)
if !stringInSlice(ref, refs) {
countRemoved++
delete(swagger.Components.Headers, key)
}
}
for key := range swagger.Components.Examples {
ref := fmt.Sprintf("#/components/examples/%s", key)
if !stringInSlice(ref, refs) {
countRemoved++
delete(swagger.Components.Examples, key)
}
}
for key := range swagger.Components.Links {
ref := fmt.Sprintf("#/components/links/%s", key)
if !stringInSlice(ref, refs) {
countRemoved++
delete(swagger.Components.Links, key)
}
}
for key := range swagger.Components.Callbacks {
ref := fmt.Sprintf("#/components/callbacks/%s", key)
if !stringInSlice(ref, refs) {
countRemoved++
delete(swagger.Components.Callbacks, key)
}
}
return countRemoved
}
func pruneUnusedComponents(swagger *openapi3.T) {
for {
refs := findComponentRefs(swagger)
countRemoved := removeOrphanedComponents(swagger, refs)
if countRemoved < 1 {
break
}
}
}

View file

@ -0,0 +1,891 @@
package codegen
import (
"errors"
"fmt"
"strings"
"github.com/getkin/kin-openapi/openapi3"
)
// Schema describes an OpenAPI schema, with lots of helper fields to use in the
// templating engine.
type Schema struct {
GoType string // The Go type needed to represent the schema
RefType string // If the type has a type name, this is set
ArrayType *Schema // The schema of array element
EnumValues map[string]string // Enum values
Properties []Property // For an object, the fields with names
HasAdditionalProperties bool // Whether we support additional properties
AdditionalPropertiesType *Schema // And if we do, their type
AdditionalTypes []TypeDefinition // We may need to generate auxiliary helper types, stored here
SkipOptionalPointer bool // Some types don't need a * in front when they're optional
Description string // The description of the element
UnionElements []UnionElement // Possible elements of oneOf/anyOf union
Discriminator *Discriminator // Describes which value is stored in a union
// If this is set, the schema will declare a type via alias, eg,
// `type Foo = bool`. If this is not set, we will define this type via
// type definition `type Foo bool`
//
// Can be overriden by the OutputOptions#DisableTypeAliasesForType field
DefineViaAlias bool
// The original OpenAPIv3 Schema.
OAPISchema *openapi3.Schema
}
func (s Schema) IsRef() bool {
return s.RefType != ""
}
func (s Schema) IsExternalRef() bool {
if !s.IsRef() {
return false
}
return strings.Contains(s.RefType, ".")
}
func (s Schema) TypeDecl() string {
if s.IsRef() {
return s.RefType
}
return s.GoType
}
// AddProperty adds a new property to the current Schema, and returns an error
// if it collides. Two identical fields will not collide, but two properties by
// the same name, but different definition, will collide. It's safe to merge the
// fields of two schemas with overlapping properties if those properties are
// identical.
func (s *Schema) AddProperty(p Property) error {
// Scan all existing properties for a conflict
for _, e := range s.Properties {
if e.JsonFieldName == p.JsonFieldName && !PropertiesEqual(e, p) {
return fmt.Errorf("property '%s' already exists with a different type", e.JsonFieldName)
}
}
s.Properties = append(s.Properties, p)
return nil
}
func (s Schema) GetAdditionalTypeDefs() []TypeDefinition {
return s.AdditionalTypes
}
type Property struct {
Description string
JsonFieldName string
Schema Schema
Required bool
Nullable bool
ReadOnly bool
WriteOnly bool
NeedsFormTag bool
Extensions map[string]interface{}
Deprecated bool
}
func (p Property) GoFieldName() string {
goFieldName := p.JsonFieldName
if extension, ok := p.Extensions[extGoName]; ok {
if extGoFieldName, err := extParseGoFieldName(extension); err == nil {
goFieldName = extGoFieldName
}
}
if globalState.options.Compatibility.AllowUnexportedStructFieldNames {
if extension, ok := p.Extensions[extOapiCodegenOnlyHonourGoName]; ok {
if extOapiCodegenOnlyHonourGoName, err := extParseOapiCodegenOnlyHonourGoName(extension); err == nil {
if extOapiCodegenOnlyHonourGoName {
return goFieldName
}
}
}
}
return SchemaNameToTypeName(goFieldName)
}
func (p Property) GoTypeDef() string {
typeDef := p.Schema.TypeDecl()
if globalState.options.OutputOptions.NullableType && p.Nullable {
return "nullable.Nullable[" + typeDef + "]"
}
if !p.Schema.SkipOptionalPointer &&
(!p.Required || p.Nullable ||
(p.ReadOnly && (!p.Required || !globalState.options.Compatibility.DisableRequiredReadOnlyAsPointer)) ||
p.WriteOnly) {
typeDef = "*" + typeDef
}
return typeDef
}
// EnumDefinition holds type information for enum
type EnumDefinition struct {
// Schema is the scheme of a type which has a list of enum values, eg, the
// "container" of the enum.
Schema Schema
// TypeName is the name of the enum's type, usually aliased from something.
TypeName string
// ValueWrapper wraps the value. It's used to conditionally apply quotes
// around strings.
ValueWrapper string
// PrefixTypeName determines if the enum value is prefixed with its TypeName.
// This is set to true when this enum conflicts with another in terms of
// TypeNames or when explicitly requested via the
// `compatibility.always-prefix-enum-values` option.
PrefixTypeName bool
}
// GetValues generates enum names in a way to minimize global conflicts
func (e *EnumDefinition) GetValues() map[string]string {
// in case there are no conflicts, it's safe to use the values as-is
if !e.PrefixTypeName {
return e.Schema.EnumValues
}
// If we do have conflicts, we will prefix the enum's typename to the values.
newValues := make(map[string]string, len(e.Schema.EnumValues))
for k, v := range e.Schema.EnumValues {
newName := e.TypeName + UppercaseFirstCharacter(k)
newValues[newName] = v
}
return newValues
}
type Constants struct {
// SecuritySchemeProviderNames holds all provider names for security schemes.
SecuritySchemeProviderNames []string
// EnumDefinitions holds type and value information for all enums
EnumDefinitions []EnumDefinition
}
// TypeDefinition describes a Go type definition in generated code.
//
// Let's use this example schema:
// components:
//
// schemas:
// Person:
// type: object
// properties:
// name:
// type: string
type TypeDefinition struct {
// The name of the type, eg, type <...> Person
TypeName string
// The name of the corresponding JSON description, as it will sometimes
// differ due to invalid characters.
JsonName string
// This is the Schema wrapper is used to populate the type description
Schema Schema
}
// ResponseTypeDefinition is an extension of TypeDefinition, specifically for
// response unmarshaling in ClientWithResponses.
type ResponseTypeDefinition struct {
TypeDefinition
// The content type name where this is used, eg, application/json
ContentTypeName string
// The type name of a response model.
ResponseName string
AdditionalTypeDefinitions []TypeDefinition
}
func (t *TypeDefinition) IsAlias() bool {
return !globalState.options.Compatibility.OldAliasing && t.Schema.DefineViaAlias
}
type Discriminator struct {
// maps discriminator value to go type
Mapping map[string]string
// JSON property name that holds the discriminator
Property string
}
func (d *Discriminator) JSONTag() string {
return fmt.Sprintf("`json:\"%s\"`", d.Property)
}
func (d *Discriminator) PropertyName() string {
return SchemaNameToTypeName(d.Property)
}
// UnionElement describe union element, based on prefix externalRef\d+ and real ref name from external schema.
type UnionElement string
// String returns externalRef\d+ and real ref name from external schema, like externalRef0.SomeType.
func (u UnionElement) String() string {
return string(u)
}
// Method generate union method name for template functions `As/From/Merge`.
func (u UnionElement) Method() string {
var method string
for _, part := range strings.Split(string(u), `.`) {
method += UppercaseFirstCharacter(part)
}
return method
}
func PropertiesEqual(a, b Property) bool {
return a.JsonFieldName == b.JsonFieldName && a.Schema.TypeDecl() == b.Schema.TypeDecl() && a.Required == b.Required
}
func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
// Add a fallback value in case the sref is nil.
// i.e. the parent schema defines a type:array, but the array has
// no items defined. Therefore, we have at least valid Go-Code.
if sref == nil {
return Schema{GoType: "interface{}"}, nil
}
schema := sref.Value
// If Ref is set on the SchemaRef, it means that this type is actually a reference to
// another type. We're not de-referencing, so simply use the referenced type.
if IsGoTypeReference(sref.Ref) {
// Convert the reference path to Go type
refType, err := RefPathToGoType(sref.Ref)
if err != nil {
return Schema{}, fmt.Errorf("error turning reference (%s) into a Go type: %s",
sref.Ref, err)
}
return Schema{
GoType: refType,
Description: schema.Description,
DefineViaAlias: true,
OAPISchema: schema,
}, nil
}
outSchema := Schema{
Description: schema.Description,
OAPISchema: schema,
}
// AllOf is interesting, and useful. It's the union of a number of other
// schemas. A common usage is to create a union of an object with an ID,
// so that in a RESTful paradigm, the Create operation can return
// (object, id), so that other operations can refer to (id)
if schema.AllOf != nil {
mergedSchema, err := MergeSchemas(schema.AllOf, path)
if err != nil {
return Schema{}, fmt.Errorf("error merging schemas: %w", err)
}
mergedSchema.OAPISchema = schema
return mergedSchema, nil
}
// Check x-go-type, which will completely override the definition of this
// schema with the provided type.
if extension, ok := schema.Extensions[extPropGoType]; ok {
typeName, err := extTypeName(extension)
if err != nil {
return outSchema, fmt.Errorf("invalid value for %q: %w", extPropGoType, err)
}
outSchema.GoType = typeName
outSchema.DefineViaAlias = true
return outSchema, nil
}
// Check x-go-type-skip-optional-pointer, which will override if the type
// should be a pointer or not when the field is optional.
if extension, ok := schema.Extensions[extPropGoTypeSkipOptionalPointer]; ok {
skipOptionalPointer, err := extParsePropGoTypeSkipOptionalPointer(extension)
if err != nil {
return outSchema, fmt.Errorf("invalid value for %q: %w", extPropGoTypeSkipOptionalPointer, err)
}
outSchema.SkipOptionalPointer = skipOptionalPointer
}
// Schema type and format, eg. string / binary
t := schema.Type
// Handle objects and empty schemas first as a special case
if t.Slice() == nil || t.Is("object") {
var outType string
if len(schema.Properties) == 0 && !SchemaHasAdditionalProperties(schema) && schema.AnyOf == nil && schema.OneOf == nil {
// If the object has no properties or additional properties, we
// have some special cases for its type.
if t.Is("object") {
// We have an object with no properties. This is a generic object
// expressed as a map.
outType = "map[string]interface{}"
} else { // t == ""
// If we don't even have the object designator, we're a completely
// generic type.
outType = "interface{}"
}
outSchema.GoType = outType
outSchema.DefineViaAlias = true
} else {
// When we define an object, we want it to be a type definition,
// not a type alias, eg, "type Foo struct {...}"
outSchema.DefineViaAlias = false
// If the schema has additional properties, we need to special case
// a lot of behaviors.
outSchema.HasAdditionalProperties = SchemaHasAdditionalProperties(schema)
// Until we have a concrete additional properties type, we default to
// any schema.
outSchema.AdditionalPropertiesType = &Schema{
GoType: "interface{}",
}
// If additional properties are defined, we will override the default
// above with the specific definition.
if schema.AdditionalProperties.Schema != nil {
additionalSchema, err := GenerateGoSchema(schema.AdditionalProperties.Schema, path)
if err != nil {
return Schema{}, fmt.Errorf("error generating type for additional properties: %w", err)
}
if additionalSchema.HasAdditionalProperties || len(additionalSchema.UnionElements) != 0 {
// If we have fields present which have additional properties or union values,
// but are not a pre-defined type, we need to define a type
// for them, which will be based on the field names we followed
// to get to the type.
typeName := PathToTypeName(append(path, "AdditionalProperties"))
typeDef := TypeDefinition{
TypeName: typeName,
JsonName: strings.Join(append(path, "AdditionalProperties"), "."),
Schema: additionalSchema,
}
additionalSchema.RefType = typeName
additionalSchema.AdditionalTypes = append(additionalSchema.AdditionalTypes, typeDef)
}
outSchema.AdditionalPropertiesType = &additionalSchema
outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, additionalSchema.AdditionalTypes...)
}
// If the schema has no properties, and only additional properties, we will
// early-out here and generate a map[string]<schema> instead of an object
// that contains this map. We skip over anyOf/oneOf here because they can
// introduce properties. allOf was handled above.
if !globalState.options.Compatibility.DisableFlattenAdditionalProperties &&
len(schema.Properties) == 0 && schema.AnyOf == nil && schema.OneOf == nil {
// We have a dictionary here. Returns the goType to be just a map from
// string to the property type. HasAdditionalProperties=false means
// that we won't generate custom json.Marshaler and json.Unmarshaler functions,
// since we don't need them for a simple map.
outSchema.HasAdditionalProperties = false
outSchema.GoType = fmt.Sprintf("map[string]%s", additionalPropertiesType(outSchema))
return outSchema, nil
}
// We've got an object with some properties.
for _, pName := range SortedSchemaKeys(schema.Properties) {
p := schema.Properties[pName]
propertyPath := append(path, pName)
pSchema, err := GenerateGoSchema(p, propertyPath)
if err != nil {
return Schema{}, fmt.Errorf("error generating Go schema for property '%s': %w", pName, err)
}
required := StringInArray(pName, schema.Required)
if (pSchema.HasAdditionalProperties || len(pSchema.UnionElements) != 0) && pSchema.RefType == "" {
// If we have fields present which have additional properties or union values,
// but are not a pre-defined type, we need to define a type
// for them, which will be based on the field names we followed
// to get to the type.
typeName := PathToTypeName(propertyPath)
typeDef := TypeDefinition{
TypeName: typeName,
JsonName: strings.Join(propertyPath, "."),
Schema: pSchema,
}
pSchema.AdditionalTypes = append(pSchema.AdditionalTypes, typeDef)
pSchema.RefType = typeName
}
description := ""
if p.Value != nil {
description = p.Value.Description
}
prop := Property{
JsonFieldName: pName,
Schema: pSchema,
Required: required,
Description: description,
Nullable: p.Value.Nullable,
ReadOnly: p.Value.ReadOnly,
WriteOnly: p.Value.WriteOnly,
Extensions: p.Value.Extensions,
Deprecated: p.Value.Deprecated,
}
outSchema.Properties = append(outSchema.Properties, prop)
if len(pSchema.AdditionalTypes) > 0 {
outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, pSchema.AdditionalTypes...)
}
}
if schema.AnyOf != nil {
if err := generateUnion(&outSchema, schema.AnyOf, schema.Discriminator, path); err != nil {
return Schema{}, fmt.Errorf("error generating type for anyOf: %w", err)
}
}
if schema.OneOf != nil {
if err := generateUnion(&outSchema, schema.OneOf, schema.Discriminator, path); err != nil {
return Schema{}, fmt.Errorf("error generating type for oneOf: %w", err)
}
}
outSchema.GoType = GenStructFromSchema(outSchema)
}
// Check for x-go-type-name. It behaves much like x-go-type, however, it will
// create a type definition for the named type, and use the named type in place
// of this schema.
if extension, ok := schema.Extensions[extGoTypeName]; ok {
typeName, err := extTypeName(extension)
if err != nil {
return outSchema, fmt.Errorf("invalid value for %q: %w", extGoTypeName, err)
}
newTypeDef := TypeDefinition{
TypeName: typeName,
Schema: outSchema,
}
outSchema = Schema{
Description: newTypeDef.Schema.Description,
GoType: typeName,
DefineViaAlias: true,
AdditionalTypes: append(outSchema.AdditionalTypes, newTypeDef),
}
}
return outSchema, nil
} else if len(schema.Enum) > 0 {
err := oapiSchemaToGoType(schema, path, &outSchema)
// Enums need to be typed, so that the values aren't interchangeable,
// so no matter what schema conversion thinks, we need to define a
// new type.
outSchema.DefineViaAlias = false
if err != nil {
return Schema{}, fmt.Errorf("error resolving primitive type: %w", err)
}
enumValues := make([]string, len(schema.Enum))
for i, enumValue := range schema.Enum {
enumValues[i] = fmt.Sprintf("%v", enumValue)
}
enumNames := enumValues
for _, key := range []string{extEnumVarNames, extEnumNames} {
if extension, ok := schema.Extensions[key]; ok {
if extEnumNames, err := extParseEnumVarNames(extension); err == nil {
enumNames = extEnumNames
break
}
}
}
sanitizedValues := SanitizeEnumNames(enumNames, enumValues)
outSchema.EnumValues = make(map[string]string, len(sanitizedValues))
for k, v := range sanitizedValues {
var enumName string
if v == "" {
enumName = "Empty"
} else {
enumName = k
}
if globalState.options.Compatibility.OldEnumConflicts {
outSchema.EnumValues[SchemaNameToTypeName(PathToTypeName(append(path, enumName)))] = v
} else {
outSchema.EnumValues[SchemaNameToTypeName(k)] = v
}
}
if len(path) > 1 { // handle additional type only on non-toplevel types
// Allow overriding autogenerated enum type names, since these may
// cause conflicts - see https://github.com/oapi-codegen/oapi-codegen/issues/832
var typeName string
if extension, ok := schema.Extensions[extGoTypeName]; ok {
typeName, err = extString(extension)
if err != nil {
return outSchema, fmt.Errorf("invalid value for %q: %w", extGoTypeName, err)
}
} else {
typeName = SchemaNameToTypeName(PathToTypeName(path))
}
typeDef := TypeDefinition{
TypeName: typeName,
JsonName: strings.Join(path, "."),
Schema: outSchema,
}
outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, typeDef)
outSchema.RefType = typeName
}
} else {
err := oapiSchemaToGoType(schema, path, &outSchema)
if err != nil {
return Schema{}, fmt.Errorf("error resolving primitive type: %w", err)
}
}
return outSchema, nil
}
// oapiSchemaToGoType converts an OpenApi schema into a Go type definition for
// all non-object types.
func oapiSchemaToGoType(schema *openapi3.Schema, path []string, outSchema *Schema) error {
f := schema.Format
t := schema.Type
if t.Is("array") {
// For arrays, we'll get the type of the Items and throw a
// [] in front of it.
arrayType, err := GenerateGoSchema(schema.Items, path)
if err != nil {
return fmt.Errorf("error generating type for array: %w", err)
}
if (arrayType.HasAdditionalProperties || len(arrayType.UnionElements) != 0) && arrayType.RefType == "" {
// If we have items which have additional properties or union values,
// but are not a pre-defined type, we need to define a type
// for them, which will be based on the field names we followed
// to get to the type.
typeName := PathToTypeName(append(path, "Item"))
typeDef := TypeDefinition{
TypeName: typeName,
JsonName: strings.Join(append(path, "Item"), "."),
Schema: arrayType,
}
arrayType.AdditionalTypes = append(arrayType.AdditionalTypes, typeDef)
arrayType.RefType = typeName
}
outSchema.ArrayType = &arrayType
outSchema.GoType = "[]" + arrayType.TypeDecl()
outSchema.AdditionalTypes = arrayType.AdditionalTypes
outSchema.Properties = arrayType.Properties
outSchema.DefineViaAlias = true
if sliceContains(globalState.options.OutputOptions.DisableTypeAliasesForType, "array") {
outSchema.DefineViaAlias = false
}
} else if t.Is("integer") {
// We default to int if format doesn't ask for something else.
if f == "int64" {
outSchema.GoType = "int64"
} else if f == "int32" {
outSchema.GoType = "int32"
} else if f == "int16" {
outSchema.GoType = "int16"
} else if f == "int8" {
outSchema.GoType = "int8"
} else if f == "int" {
outSchema.GoType = "int"
} else if f == "uint64" {
outSchema.GoType = "uint64"
} else if f == "uint32" {
outSchema.GoType = "uint32"
} else if f == "uint16" {
outSchema.GoType = "uint16"
} else if f == "uint8" {
outSchema.GoType = "uint8"
} else if f == "uint" {
outSchema.GoType = "uint"
} else {
outSchema.GoType = "int"
}
outSchema.DefineViaAlias = true
} else if t.Is("number") {
// We default to float for "number"
if f == "double" {
outSchema.GoType = "float64"
} else if f == "float" || f == "" {
outSchema.GoType = "float32"
} else {
return fmt.Errorf("invalid number format: %s", f)
}
outSchema.DefineViaAlias = true
} else if t.Is("boolean") {
if f != "" {
return fmt.Errorf("invalid format (%s) for boolean", f)
}
outSchema.GoType = "bool"
outSchema.DefineViaAlias = true
} else if t.Is("string") {
// Special case string formats here.
switch f {
case "byte":
outSchema.GoType = "[]byte"
case "email":
outSchema.GoType = "openapi_types.Email"
case "date":
outSchema.GoType = "openapi_types.Date"
case "date-time":
outSchema.GoType = "time.Time"
case "json":
outSchema.GoType = "json.RawMessage"
outSchema.SkipOptionalPointer = true
case "uuid":
outSchema.GoType = "openapi_types.UUID"
case "binary":
outSchema.GoType = "openapi_types.File"
default:
// All unrecognized formats are simply a regular string.
outSchema.GoType = "string"
}
outSchema.DefineViaAlias = true
} else {
return fmt.Errorf("unhandled Schema type: %v", t)
}
return nil
}
// SchemaDescriptor describes a Schema, a type definition.
type SchemaDescriptor struct {
Fields []FieldDescriptor
HasAdditionalProperties bool
AdditionalPropertiesType string
}
type FieldDescriptor struct {
Required bool // Is the schema required? If not, we'll pass by pointer
GoType string // The Go type needed to represent the json type.
GoName string // The Go compatible type name for the type
JsonName string // The json type name for the type
IsRef bool // Is this schema a reference to predefined object?
}
// GenFieldsFromProperties produce corresponding field names with JSON annotations,
// given a list of schema descriptors
func GenFieldsFromProperties(props []Property) []string {
var fields []string
for i, p := range props {
field := ""
goFieldName := p.GoFieldName()
// Add a comment to a field in case we have one, otherwise skip.
if p.Description != "" {
// Separate the comment from a previous-defined, unrelated field.
// Make sure the actual field is separated by a newline.
if i != 0 {
field += "\n"
}
field += fmt.Sprintf("%s\n", StringWithTypeNameToGoComment(p.Description, p.GoFieldName()))
}
if p.Deprecated {
// This comment has to be on its own line for godoc & IDEs to pick up
var deprecationReason string
if extension, ok := p.Extensions[extDeprecationReason]; ok {
if extOmitEmpty, err := extParseDeprecationReason(extension); err == nil {
deprecationReason = extOmitEmpty
}
}
field += fmt.Sprintf("%s\n", DeprecationComment(deprecationReason))
}
// Check x-go-type-skip-optional-pointer, which will override if the type
// should be a pointer or not when the field is optional.
if extension, ok := p.Extensions[extPropGoTypeSkipOptionalPointer]; ok {
if skipOptionalPointer, err := extParsePropGoTypeSkipOptionalPointer(extension); err == nil {
p.Schema.SkipOptionalPointer = skipOptionalPointer
}
}
field += fmt.Sprintf(" %s %s", goFieldName, p.GoTypeDef())
shouldOmitEmpty := (!p.Required || p.ReadOnly || p.WriteOnly) &&
(!p.Required || !p.ReadOnly || !globalState.options.Compatibility.DisableRequiredReadOnlyAsPointer)
omitEmpty := !p.Nullable && shouldOmitEmpty
if p.Nullable && globalState.options.OutputOptions.NullableType {
omitEmpty = shouldOmitEmpty
}
// Support x-omitempty
if extOmitEmptyValue, ok := p.Extensions[extPropOmitEmpty]; ok {
if extOmitEmpty, err := extParseOmitEmpty(extOmitEmptyValue); err == nil {
omitEmpty = extOmitEmpty
}
}
fieldTags := make(map[string]string)
if !omitEmpty {
fieldTags["json"] = p.JsonFieldName
if p.NeedsFormTag {
fieldTags["form"] = p.JsonFieldName
}
} else {
fieldTags["json"] = p.JsonFieldName + ",omitempty"
if p.NeedsFormTag {
fieldTags["form"] = p.JsonFieldName + ",omitempty"
}
}
// Support x-go-json-ignore
if extension, ok := p.Extensions[extPropGoJsonIgnore]; ok {
if goJsonIgnore, err := extParseGoJsonIgnore(extension); err == nil && goJsonIgnore {
fieldTags["json"] = "-"
}
}
// Support x-oapi-codegen-extra-tags
if extension, ok := p.Extensions[extPropExtraTags]; ok {
if tags, err := extExtraTags(extension); err == nil {
keys := SortedMapKeys(tags)
for _, k := range keys {
fieldTags[k] = tags[k]
}
}
}
// Convert the fieldTags map into Go field annotations.
keys := SortedMapKeys(fieldTags)
tags := make([]string, len(keys))
for i, k := range keys {
tags[i] = fmt.Sprintf(`%s:"%s"`, k, fieldTags[k])
}
field += "`" + strings.Join(tags, " ") + "`"
fields = append(fields, field)
}
return fields
}
func additionalPropertiesType(schema Schema) string {
addPropsType := schema.AdditionalPropertiesType.GoType
if schema.AdditionalPropertiesType.RefType != "" {
addPropsType = schema.AdditionalPropertiesType.RefType
}
if schema.AdditionalPropertiesType.OAPISchema != nil && schema.AdditionalPropertiesType.OAPISchema.Nullable {
addPropsType = "*" + addPropsType
}
return addPropsType
}
func GenStructFromSchema(schema Schema) string {
// Start out with struct {
objectParts := []string{"struct {"}
// Append all the field definitions
objectParts = append(objectParts, GenFieldsFromProperties(schema.Properties)...)
// Close the struct
if schema.HasAdditionalProperties {
objectParts = append(objectParts,
fmt.Sprintf("AdditionalProperties map[string]%s `json:\"-\"`",
additionalPropertiesType(schema)))
}
if len(schema.UnionElements) != 0 {
objectParts = append(objectParts, "union json.RawMessage")
}
objectParts = append(objectParts, "}")
return strings.Join(objectParts, "\n")
}
// This constructs a Go type for a parameter, looking at either the schema or
// the content, whichever is available
func paramToGoType(param *openapi3.Parameter, path []string) (Schema, error) {
if param.Content == nil && param.Schema == nil {
return Schema{}, fmt.Errorf("parameter '%s' has no schema or content", param.Name)
}
// We can process the schema through the generic schema processor
if param.Schema != nil {
return GenerateGoSchema(param.Schema, path)
}
// At this point, we have a content type. We know how to deal with
// application/json, but if multiple formats are present, we can't do anything,
// so we'll return the parameter as a string, not bothering to decode it.
if len(param.Content) > 1 {
return Schema{
GoType: "string",
Description: StringToGoComment(param.Description),
}, nil
}
// Otherwise, look for application/json in there
mt, found := param.Content["application/json"]
if !found {
// If we don't have json, it's a string
return Schema{
GoType: "string",
Description: StringToGoComment(param.Description),
}, nil
}
// For json, we go through the standard schema mechanism
return GenerateGoSchema(mt.Schema, path)
}
func generateUnion(outSchema *Schema, elements openapi3.SchemaRefs, discriminator *openapi3.Discriminator, path []string) error {
if discriminator != nil {
outSchema.Discriminator = &Discriminator{
Property: discriminator.PropertyName,
Mapping: make(map[string]string),
}
}
refToGoTypeMap := make(map[string]string)
for i, element := range elements {
elementPath := append(path, fmt.Sprint(i))
elementSchema, err := GenerateGoSchema(element, elementPath)
if err != nil {
return err
}
if element.Ref == "" {
elementName := SchemaNameToTypeName(PathToTypeName(elementPath))
if elementSchema.TypeDecl() == elementName {
elementSchema.GoType = elementName
} else {
td := TypeDefinition{Schema: elementSchema, TypeName: elementName}
outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, td)
elementSchema.GoType = td.TypeName
}
outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, elementSchema.AdditionalTypes...)
} else {
refToGoTypeMap[element.Ref] = elementSchema.GoType
}
if discriminator != nil {
if len(discriminator.Mapping) != 0 && element.Ref == "" {
return errors.New("ambiguous discriminator.mapping: please replace inlined object with $ref")
}
// Explicit mapping.
var mapped bool
for k, v := range discriminator.Mapping {
if v == element.Ref {
outSchema.Discriminator.Mapping[k] = elementSchema.GoType
mapped = true
break
}
}
// Implicit mapping.
if !mapped {
outSchema.Discriminator.Mapping[RefPathToObjName(element.Ref)] = elementSchema.GoType
}
}
outSchema.UnionElements = append(outSchema.UnionElements, UnionElement(elementSchema.GoType))
}
if (outSchema.Discriminator != nil) && len(outSchema.Discriminator.Mapping) != len(elements) {
return errors.New("discriminator: not all schemas were mapped")
}
return nil
}

View file

@ -0,0 +1,326 @@
// 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 codegen
import (
"bytes"
"fmt"
"os"
"strings"
"text/template"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"github.com/oapi-codegen/oapi-codegen/v2/pkg/util"
)
const (
// These allow the case statements to be sorted later:
prefixLeastSpecific = "9"
defaultClientTypeName = "Client"
)
var (
contentTypesJSON = []string{"application/json", "text/x-json", "application/problem+json"}
contentTypesHalJSON = []string{"application/hal+json"}
contentTypesYAML = []string{"application/yaml", "application/x-yaml", "text/yaml", "text/x-yaml"}
contentTypesXML = []string{"application/xml", "text/xml", "application/problems+xml"}
responseTypeSuffix = "Response"
titleCaser = cases.Title(language.English)
)
// genParamArgs takes an array of Parameter definition, and generates a valid
// Go parameter declaration from them, eg:
// ", foo int, bar string, baz float32". The preceding comma is there to save
// a lot of work in the template engine.
func genParamArgs(params []ParameterDefinition) string {
if len(params) == 0 {
return ""
}
parts := make([]string, len(params))
for i, p := range params {
paramName := p.GoVariableName()
parts[i] = fmt.Sprintf("%s %s", paramName, p.TypeDef())
}
return ", " + strings.Join(parts, ", ")
}
// genParamTypes is much like the one above, except it only produces the
// types of the parameters for a type declaration. It would produce this
// from the same input as above:
// ", int, string, float32".
func genParamTypes(params []ParameterDefinition) string {
if len(params) == 0 {
return ""
}
parts := make([]string, len(params))
for i, p := range params {
parts[i] = p.TypeDef()
}
return ", " + strings.Join(parts, ", ")
}
// This is another variation of the function above which generates only the
// parameter names:
// ", foo, bar, baz"
func genParamNames(params []ParameterDefinition) string {
if len(params) == 0 {
return ""
}
parts := make([]string, len(params))
for i, p := range params {
parts[i] = p.GoVariableName()
}
return ", " + strings.Join(parts, ", ")
}
// genResponsePayload generates the payload returned at the end of each client request function
func genResponsePayload(operationID string) string {
var buffer = bytes.NewBufferString("")
// Here is where we build up a response:
fmt.Fprintf(buffer, "&%s{\n", genResponseTypeName(operationID))
fmt.Fprintf(buffer, "Body: bodyBytes,\n")
fmt.Fprintf(buffer, "HTTPResponse: rsp,\n")
fmt.Fprintf(buffer, "}")
return buffer.String()
}
// genResponseUnmarshal generates unmarshaling steps for structured response payloads
func genResponseUnmarshal(op *OperationDefinition) string {
var handledCaseClauses = make(map[string]string)
var unhandledCaseClauses = make(map[string]string)
// Get the type definitions from the operation:
typeDefinitions, err := op.GetResponseTypeDefinitions()
if err != nil {
panic(err)
}
if len(typeDefinitions) == 0 {
// No types.
return ""
}
// Add a case for each possible response:
buffer := new(bytes.Buffer)
responses := op.Spec.Responses
for _, typeDefinition := range typeDefinitions {
responseRef := responses.Value(typeDefinition.ResponseName)
if responseRef == nil {
continue
}
// We can't do much without a value:
if responseRef.Value == nil {
fmt.Fprintf(os.Stderr, "Response %s.%s has nil value\n", op.OperationId, typeDefinition.ResponseName)
continue
}
// If there is no content-type then we have no unmarshaling to do:
if len(responseRef.Value.Content) == 0 {
caseAction := "break // No content-type"
caseClauseKey := "case " + getConditionOfResponseName("rsp.StatusCode", typeDefinition.ResponseName) + ":"
unhandledCaseClauses[prefixLeastSpecific+caseClauseKey] = fmt.Sprintf("%s\n%s\n", caseClauseKey, caseAction)
continue
}
// If we made it this far then we need to handle unmarshaling for each content-type:
SortedMapKeys := SortedMapKeys(responseRef.Value.Content)
jsonCount := 0
for _, contentTypeName := range SortedMapKeys {
if StringInArray(contentTypeName, contentTypesJSON) || util.IsMediaTypeJson(contentTypeName) {
jsonCount++
}
}
for _, contentTypeName := range SortedMapKeys {
// We get "interface{}" when using "anyOf" or "oneOf" (which doesn't work with Go types):
if typeDefinition.TypeName == "interface{}" {
// Unable to unmarshal this, so we leave it out:
continue
}
// Add content-types here (json / yaml / xml etc):
switch {
// JSON:
case StringInArray(contentTypeName, contentTypesJSON) || util.IsMediaTypeJson(contentTypeName):
if typeDefinition.ContentTypeName == contentTypeName {
caseAction := fmt.Sprintf("var dest %s\n"+
"if err := json.Unmarshal(bodyBytes, &dest); err != nil { \n"+
" return nil, err \n"+
"}\n"+
"response.%s = &dest",
typeDefinition.Schema.TypeDecl(),
typeDefinition.TypeName)
if jsonCount > 1 {
caseKey, caseClause := buildUnmarshalCaseStrict(typeDefinition, caseAction, contentTypeName)
handledCaseClauses[caseKey] = caseClause
} else {
caseKey, caseClause := buildUnmarshalCase(typeDefinition, caseAction, "json")
handledCaseClauses[caseKey] = caseClause
}
}
// YAML:
case StringInArray(contentTypeName, contentTypesYAML):
if typeDefinition.ContentTypeName == contentTypeName {
caseAction := fmt.Sprintf("var dest %s\n"+
"if err := yaml.Unmarshal(bodyBytes, &dest); err != nil { \n"+
" return nil, err \n"+
"}\n"+
"response.%s = &dest",
typeDefinition.Schema.TypeDecl(),
typeDefinition.TypeName)
caseKey, caseClause := buildUnmarshalCase(typeDefinition, caseAction, "yaml")
handledCaseClauses[caseKey] = caseClause
}
// XML:
case StringInArray(contentTypeName, contentTypesXML):
if typeDefinition.ContentTypeName == contentTypeName {
caseAction := fmt.Sprintf("var dest %s\n"+
"if err := xml.Unmarshal(bodyBytes, &dest); err != nil { \n"+
" return nil, err \n"+
"}\n"+
"response.%s = &dest",
typeDefinition.Schema.TypeDecl(),
typeDefinition.TypeName)
caseKey, caseClause := buildUnmarshalCase(typeDefinition, caseAction, "xml")
handledCaseClauses[caseKey] = caseClause
}
// Everything else:
default:
caseAction := fmt.Sprintf("// Content-type (%s) unsupported", contentTypeName)
caseClauseKey := "case " + getConditionOfResponseName("rsp.StatusCode", typeDefinition.ResponseName) + ":"
unhandledCaseClauses[prefixLeastSpecific+caseClauseKey] = fmt.Sprintf("%s\n%s\n", caseClauseKey, caseAction)
}
}
}
if len(handledCaseClauses)+len(unhandledCaseClauses) == 0 {
// switch would be empty.
return ""
}
// Now build the switch statement in order of most-to-least specific:
// See: https://github.com/oapi-codegen/oapi-codegen/issues/127 for why we handle this in two separate
// groups.
fmt.Fprintf(buffer, "switch {\n")
for _, caseClauseKey := range SortedMapKeys(handledCaseClauses) {
fmt.Fprintf(buffer, "%s\n", handledCaseClauses[caseClauseKey])
}
for _, caseClauseKey := range SortedMapKeys(unhandledCaseClauses) {
fmt.Fprintf(buffer, "%s\n", unhandledCaseClauses[caseClauseKey])
}
fmt.Fprintf(buffer, "}\n")
return buffer.String()
}
// buildUnmarshalCase builds an unmarshaling case clause for different content-types:
func buildUnmarshalCase(typeDefinition ResponseTypeDefinition, caseAction string, contentType string) (caseKey string, caseClause string) {
caseKey = fmt.Sprintf("%s.%s.%s", prefixLeastSpecific, contentType, typeDefinition.ResponseName)
caseClauseKey := getConditionOfResponseName("rsp.StatusCode", typeDefinition.ResponseName)
caseClause = fmt.Sprintf("case strings.Contains(rsp.Header.Get(\"%s\"), \"%s\") && %s:\n%s\n", "Content-Type", contentType, caseClauseKey, caseAction)
return caseKey, caseClause
}
func buildUnmarshalCaseStrict(typeDefinition ResponseTypeDefinition, caseAction string, contentType string) (caseKey string, caseClause string) {
caseKey = fmt.Sprintf("%s.%s.%s", prefixLeastSpecific, contentType, typeDefinition.ResponseName)
caseClauseKey := getConditionOfResponseName("rsp.StatusCode", typeDefinition.ResponseName)
caseClause = fmt.Sprintf("case rsp.Header.Get(\"%s\") == \"%s\" && %s:\n%s\n", "Content-Type", contentType, caseClauseKey, caseAction)
return caseKey, caseClause
}
// genResponseTypeName creates the name of generated response types (given the operationID):
func genResponseTypeName(operationID string) string {
return fmt.Sprintf("%s%s", UppercaseFirstCharacter(operationID), responseTypeSuffix)
}
func getResponseTypeDefinitions(op *OperationDefinition) []ResponseTypeDefinition {
td, err := op.GetResponseTypeDefinitions()
if err != nil {
panic(err)
}
return td
}
// Return the statusCode comparison clause from the response name.
func getConditionOfResponseName(statusCodeVar, responseName string) string {
switch responseName {
case "default":
return "true"
case "1XX", "2XX", "3XX", "4XX", "5XX":
return fmt.Sprintf("%s / 100 == %s", statusCodeVar, responseName[:1])
default:
return fmt.Sprintf("%s == %s", statusCodeVar, responseName)
}
}
// This outputs a string array
func toStringArray(sarr []string) string {
s := strings.Join(sarr, `","`)
if len(s) > 0 {
s = `"` + s + `"`
}
return `[]string{` + s + `}`
}
func stripNewLines(s string) string {
r := strings.NewReplacer("\n", "")
return r.Replace(s)
}
// TemplateFunctions is passed to the template engine, and we can call each
// function here by keyName from the template code.
var TemplateFunctions = template.FuncMap{
"genParamArgs": genParamArgs,
"genParamTypes": genParamTypes,
"genParamNames": genParamNames,
"genParamFmtString": ReplacePathParamsWithStr,
"swaggerUriToIrisUri": SwaggerUriToIrisUri,
"swaggerUriToEchoUri": SwaggerUriToEchoUri,
"swaggerUriToFiberUri": SwaggerUriToFiberUri,
"swaggerUriToChiUri": SwaggerUriToChiUri,
"swaggerUriToGinUri": SwaggerUriToGinUri,
"swaggerUriToGorillaUri": SwaggerUriToGorillaUri,
"swaggerUriToStdHttpUri": SwaggerUriToStdHttpUri,
"lcFirst": LowercaseFirstCharacter,
"ucFirst": UppercaseFirstCharacter,
"ucFirstWithPkgName": UppercaseFirstCharacterWithPkgName,
"camelCase": ToCamelCase,
"genResponsePayload": genResponsePayload,
"genResponseTypeName": genResponseTypeName,
"genResponseUnmarshal": genResponseUnmarshal,
"getResponseTypeDefinitions": getResponseTypeDefinitions,
"toStringArray": toStringArray,
"lower": strings.ToLower,
"title": titleCaser.String,
"stripNewLines": stripNewLines,
"sanitizeGoIdentity": SanitizeGoIdentity,
"toGoComment": StringWithTypeNameToGoComment,
}

View file

@ -0,0 +1,72 @@
{{range .Types}}{{$addType := .Schema.AdditionalPropertiesType.TypeDecl}}
// Getter for additional properties for {{.TypeName}}. Returns the specified
// element and whether it was found
func (a {{.TypeName}}) Get(fieldName string) (value {{$addType}}, found bool) {
if a.AdditionalProperties != nil {
value, found = a.AdditionalProperties[fieldName]
}
return
}
// Setter for additional properties for {{.TypeName}}
func (a *{{.TypeName}}) Set(fieldName string, value {{$addType}}) {
if a.AdditionalProperties == nil {
a.AdditionalProperties = make(map[string]{{$addType}})
}
a.AdditionalProperties[fieldName] = value
}
{{if eq 0 (len .Schema.UnionElements) -}}
// Override default JSON handling for {{.TypeName}} to handle AdditionalProperties
func (a *{{.TypeName}}) UnmarshalJSON(b []byte) error {
object := make(map[string]json.RawMessage)
err := json.Unmarshal(b, &object)
if err != nil {
return err
}
{{range .Schema.Properties}}
if raw, found := object["{{.JsonFieldName}}"]; found {
err = json.Unmarshal(raw, &a.{{.GoFieldName}})
if err != nil {
return fmt.Errorf("error reading '{{.JsonFieldName}}': %w", err)
}
delete(object, "{{.JsonFieldName}}")
}
{{end}}
if len(object) != 0 {
a.AdditionalProperties = make(map[string]{{$addType}})
for fieldName, fieldBuf := range object {
var fieldVal {{$addType}}
err := json.Unmarshal(fieldBuf, &fieldVal)
if err != nil {
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
}
a.AdditionalProperties[fieldName] = fieldVal
}
}
return nil
}
// Override default JSON handling for {{.TypeName}} to handle AdditionalProperties
func (a {{.TypeName}}) MarshalJSON() ([]byte, error) {
var err error
object := make(map[string]json.RawMessage)
{{range .Schema.Properties}}
{{if not .Required}}if a.{{.GoFieldName}} != nil { {{end}}
object["{{.JsonFieldName}}"], err = json.Marshal(a.{{.GoFieldName}})
if err != nil {
return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err)
}
{{if not .Required}} }{{end}}
{{end}}
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
if err != nil {
return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
}
}
return json.Marshal(object)
}
{{end}}
{{end}}

View file

@ -0,0 +1,50 @@
// Handler creates http.Handler with routing matching OpenAPI spec.
func Handler(si ServerInterface) http.Handler {
return HandlerWithOptions(si, ChiServerOptions{})
}
type ChiServerOptions struct {
BaseURL string
BaseRouter chi.Router
Middlewares []MiddlewareFunc
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler {
return HandlerWithOptions(si, ChiServerOptions {
BaseRouter: r,
})
}
func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler {
return HandlerWithOptions(si, ChiServerOptions {
BaseURL: baseURL,
BaseRouter: r,
})
}
// HandlerWithOptions creates http.Handler with additional options
func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler {
r := options.BaseRouter
if r == nil {
r = chi.NewRouter()
}
if options.ErrorHandlerFunc == nil {
options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}
{{if .}}wrapper := ServerInterfaceWrapper{
Handler: si,
HandlerMiddlewares: options.Middlewares,
ErrorHandlerFunc: options.ErrorHandlerFunc,
}
{{end}}
{{range .}}r.Group(func(r chi.Router) {
r.{{.Method | lower | title }}(options.BaseURL+"{{.Path | swaggerUriToChiUri}}", wrapper.{{.OperationId}})
})
{{end}}
return r
}

View file

@ -0,0 +1,17 @@
// ServerInterface represents all server handlers.
type ServerInterface interface {
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}})
{{end}}
}
// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint.
type Unimplemented struct {}
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
func (_ Unimplemented) {{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) {
w.WriteHeader(http.StatusNotImplemented)
}
{{end}}

View file

@ -0,0 +1,266 @@
// ServerInterfaceWrapper converts contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
HandlerMiddlewares []MiddlewareFunc
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
type MiddlewareFunc func(http.Handler) http.Handler
{{range .}}{{$opid := .OperationId}}
// {{$opid}} operation middleware
func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Request) {
{{if or .RequiresParamObject (gt (len .PathParams) 0) }}
var err error
{{end}}
{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" -------------
var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}}
{{if .IsPassThrough}}
{{$varName}} = chi.URLParam(r, "{{.ParamName}}")
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(chi.URLParam(r, "{{.ParamName}}")), &{{$varName}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", chi.URLParam(r, "{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{end}}
{{if .SecurityDefinitions -}}
ctx := r.Context()
{{range .SecurityDefinitions}}
ctx = context.WithValue(ctx, {{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}})
{{end}}
r = r.WithContext(ctx)
{{end}}
{{if .RequiresParamObject}}
// Parameter object where we will unmarshal all parameters from the context
var params {{.OperationId}}Params
{{range $paramIdx, $param := .QueryParams}}
{{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
{{ end }}
{{ if (or (or .Required .IsPassThrough) .IsJson) }}
if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
err = json.Unmarshal([]byte(paramValue), &value)
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}{{if .Required}} else {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
return
}{{end}}
{{end}}
{{if .IsStyled}}
err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), &params.{{.GoName}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{end}}
{{if .HeaderParams}}
headers := r.Header
{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" -------------
if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found {
var {{.GoName}} {{.TypeDef}}
n := len(valueList)
if n != 1 {
siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "{{.ParamName}}", Count: n})
return
}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
} {{if .Required}}else {
err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found")
siw.ErrorHandlerFunc(w, r, &RequiredHeaderError{ParamName: "{{.ParamName}}", Err: err})
return
}{{end}}
{{end}}
{{end}}
{{range .CookieParams}}
{
var cookie *http.Cookie
if cookie, err = r.Cookie("{{.ParamName}}"); err == nil {
{{- if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
{{end}}
{{- if .IsJson}}
var value {{.TypeDef}}
var decoded string
decoded, err := url.QueryUnescape(cookie.Value)
if err != nil {
err = fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}'")
siw.ErrorHandlerFunc(w, r, &UnescapedCookieParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
err = json.Unmarshal([]byte(decoded), &value)
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
{{- if .IsStyled}}
var value {{.TypeDef}}
err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}
{{- if .Required}} else {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
return
}
{{- end}}
}
{{end}}
{{end}}
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}})
}))
{{if opts.Compatibility.ApplyChiMiddlewareFirstToLast}}
for i := len(siw.HandlerMiddlewares) -1; i >= 0; i-- {
handler = siw.HandlerMiddlewares[i](handler)
}
{{else}}
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
{{end}}
handler.ServeHTTP(w, r)
}
{{end}}
type UnescapedCookieParamError struct {
ParamName string
Err error
}
func (e *UnescapedCookieParamError) Error() string {
return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName)
}
func (e *UnescapedCookieParamError) Unwrap() error {
return e.Err
}
type UnmarshalingParamError struct {
ParamName string
Err error
}
func (e *UnmarshalingParamError) Error() string {
return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error())
}
func (e *UnmarshalingParamError) Unwrap() error {
return e.Err
}
type RequiredParamError struct {
ParamName string
}
func (e *RequiredParamError) Error() string {
return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName)
}
type RequiredHeaderError struct {
ParamName string
Err error
}
func (e *RequiredHeaderError) Error() string {
return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName)
}
func (e *RequiredHeaderError) Unwrap() error {
return e.Err
}
type InvalidParamFormatError struct {
ParamName string
Err error
}
func (e *InvalidParamFormatError) Error() string {
return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error())
}
func (e *InvalidParamFormatError) Unwrap() error {
return e.Err
}
type TooManyValuesForParamError struct {
ParamName string
Count int
}
func (e *TooManyValuesForParamError) Error() string {
return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count)
}

View file

@ -0,0 +1,127 @@
// ClientWithResponses builds on ClientInterface to offer response payloads
type ClientWithResponses struct {
ClientInterface
}
// NewClientWithResponses creates a new ClientWithResponses, which wraps
// Client with return type handling
func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) {
client, err := NewClient(server, opts...)
if err != nil {
return nil, err
}
return &ClientWithResponses{client}, nil
}
{{$clientTypeName := opts.OutputOptions.ClientTypeName -}}
// WithBaseURL overrides the baseURL.
func WithBaseURL(baseURL string) ClientOption {
return func(c *{{ $clientTypeName }}) error {
newBaseURL, err := url.Parse(baseURL)
if err != nil {
return err
}
c.Server = newBaseURL.String()
return nil
}
}
// ClientWithResponsesInterface is the interface specification for the client with responses above.
type ClientWithResponsesInterface interface {
{{range . -}}
{{$hasParams := .RequiresParamObject -}}
{{$pathParams := .PathParams -}}
{{$opid := .OperationId -}}
// {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse request{{if .HasBody}} with any body{{end}}
{{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error)
{{range .Bodies}}
{{if .IsSupportedByClient -}}
{{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error)
{{end -}}
{{end}}{{/* range .Bodies */}}
{{end}}{{/* range . $opid := .OperationId */}}
}
{{range .}}{{$opid := .OperationId}}{{$op := .}}
{{$responseTypeDefinitions := getResponseTypeDefinitions .}}
type {{genResponseTypeName $opid | ucFirst}} struct {
Body []byte
HTTPResponse *http.Response
{{- range $responseTypeDefinitions}}
{{.TypeName}} *{{.Schema.TypeDecl}}
{{- end}}
}
{{- range $responseTypeDefinitions}}
{{- range .AdditionalTypeDefinitions}}
type {{.TypeName}} {{if .IsAlias }}={{end}} {{.Schema.TypeDecl}}
{{- end}}
{{- end}}
// Status returns HTTPResponse.Status
func (r {{genResponseTypeName $opid | ucFirst}}) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r {{genResponseTypeName $opid | ucFirst}}) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
{{end}}
{{range .}}
{{$opid := .OperationId -}}
{{/* Generate client methods (with responses)*/}}
// {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse request{{if .HasBody}} with arbitrary body{{end}} returning *{{genResponseTypeName $opid}}
func (c *ClientWithResponses) {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error){
rsp, err := c.{{$opid}}{{if .HasBody}}WithBody{{end}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}{{if .HasBody}}, contentType, body{{end}}, reqEditors...)
if err != nil {
return nil, err
}
return Parse{{genResponseTypeName $opid | ucFirst}}(rsp)
}
{{$hasParams := .RequiresParamObject -}}
{{$pathParams := .PathParams -}}
{{$bodyRequired := .BodyRequired -}}
{{range .Bodies}}
{{if .IsSupportedByClient -}}
func (c *ClientWithResponses) {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}, error) {
rsp, err := c.{{$opid}}{{.Suffix}}(ctx{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body, reqEditors...)
if err != nil {
return nil, err
}
return Parse{{genResponseTypeName $opid | ucFirst}}(rsp)
}
{{end}}
{{end}}
{{end}}{{/* operations */}}
{{/* Generate parse functions for responses*/}}
{{range .}}{{$opid := .OperationId}}
// Parse{{genResponseTypeName $opid | ucFirst}} parses an HTTP response from a {{$opid}}WithResponse call
func Parse{{genResponseTypeName $opid | ucFirst}}(rsp *http.Response) (*{{genResponseTypeName $opid}}, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := {{genResponsePayload $opid}}
{{genResponseUnmarshal .}}
return response, nil
}
{{end}}{{/* range . $opid := .OperationId */}}

View file

@ -0,0 +1,312 @@
// RequestEditorFn is the function signature for the RequestEditor callback function
type RequestEditorFn func(ctx context.Context, req *http.Request) error
// Doer performs HTTP requests.
//
// The standard http.Client implements this interface.
type HttpRequestDoer interface {
Do(req *http.Request) (*http.Response, error)
}
{{$clientTypeName := opts.OutputOptions.ClientTypeName -}}
// {{ $clientTypeName }} which conforms to the OpenAPI3 specification for this service.
type {{ $clientTypeName }} struct {
// The endpoint of the server conforming to this interface, with scheme,
// https://api.deepmap.com for example. This can contain a path relative
// to the server, such as https://api.deepmap.com/dev-test, and all the
// paths in the swagger spec will be appended to the server.
Server string
// Doer for performing requests, typically a *http.Client with any
// customized settings, such as certificate chains.
Client HttpRequestDoer
// A list of callbacks for modifying requests which are generated before sending over
// the network.
RequestEditors []RequestEditorFn
}
// ClientOption allows setting custom parameters during construction
type ClientOption func(*{{ $clientTypeName }}) error
// Creates a new {{ $clientTypeName }}, with reasonable defaults
func NewClient(server string, opts ...ClientOption) (*{{ $clientTypeName }}, error) {
// create a client with sane default values
client := {{ $clientTypeName }}{
Server: server,
}
// mutate client and add all optional params
for _, o := range opts {
if err := o(&client); err != nil {
return nil, err
}
}
// ensure the server URL always has a trailing slash
if !strings.HasSuffix(client.Server, "/") {
client.Server += "/"
}
// create httpClient, if not already present
if client.Client == nil {
client.Client = &http.Client{}
}
return &client, nil
}
// WithHTTPClient allows overriding the default Doer, which is
// automatically created using http.Client. This is useful for tests.
func WithHTTPClient(doer HttpRequestDoer) ClientOption {
return func(c *{{ $clientTypeName }}) error {
c.Client = doer
return nil
}
}
// WithRequestEditorFn allows setting up a callback function, which will be
// called right before sending the request. This can be used to mutate the request.
func WithRequestEditorFn(fn RequestEditorFn) ClientOption {
return func(c *{{ $clientTypeName }}) error {
c.RequestEditors = append(c.RequestEditors, fn)
return nil
}
}
// The interface specification for the client above.
type ClientInterface interface {
{{range . -}}
{{$hasParams := .RequiresParamObject -}}
{{$pathParams := .PathParams -}}
{{$opid := .OperationId -}}
// {{$opid}}{{if .HasBody}}WithBody{{end}} request{{if .HasBody}} with any body{{end}}
{{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*http.Response, error)
{{range .Bodies}}
{{if .IsSupportedByClient -}}
{{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error)
{{end -}}
{{end}}{{/* range .Bodies */}}
{{end}}{{/* range . $opid := .OperationId */}}
}
{{/* Generate client methods */}}
{{range . -}}
{{$hasParams := .RequiresParamObject -}}
{{$pathParams := .PathParams -}}
{{$opid := .OperationId -}}
func (c *{{ $clientTypeName }}) {{$opid}}{{if .HasBody}}WithBody{{end}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*http.Response, error) {
req, err := New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(c.Server{{genParamNames .PathParams}}{{if $hasParams}}, params{{end}}{{if .HasBody}}, contentType, body{{end}})
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
{{range .Bodies}}
{{if .IsSupportedByClient -}}
func (c *{{ $clientTypeName }}) {{$opid}}{{.Suffix}}(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*http.Response, error) {
req, err := New{{$opid}}Request{{.Suffix}}(c.Server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
{{end -}}{{/* if .IsSupported */}}
{{end}}{{/* range .Bodies */}}
{{end}}
{{/* Generate request builders */}}
{{range .}}
{{$hasParams := .RequiresParamObject -}}
{{$pathParams := .PathParams -}}
{{$bodyRequired := .BodyRequired -}}
{{$opid := .OperationId -}}
{{range .Bodies}}
{{if .IsSupportedByClient -}}
// New{{$opid}}Request{{.Suffix}} calls the generic {{$opid}} builder with {{.ContentType}} body
func New{{$opid}}Request{{.Suffix}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody) (*http.Request, error) {
var bodyReader io.Reader
{{if .IsJSON -}}
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(buf)
{{else if eq .NameTag "Formdata" -}}
bodyStr, err := runtime.MarshalForm(body, nil)
if err != nil {
return nil, err
}
bodyReader = strings.NewReader(bodyStr.Encode())
{{else if eq .NameTag "Text" -}}
bodyReader = strings.NewReader(string(body))
{{end -}}
return New{{$opid}}RequestWithBody(server{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, "{{.ContentType}}", bodyReader)
}
{{end -}}
{{end}}
// New{{$opid}}Request{{if .HasBody}}WithBody{{end}} generates requests for {{$opid}}{{if .HasBody}} with any type of body{{end}}
func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}) (*http.Request, error) {
var err error
{{range $paramIdx, $param := .PathParams}}
var pathParam{{$paramIdx}} string
{{if .IsPassThrough}}
pathParam{{$paramIdx}} = {{.GoVariableName}}
{{end}}
{{if .IsJson}}
var pathParamBuf{{$paramIdx}} []byte
pathParamBuf{{$paramIdx}}, err = json.Marshal({{.GoVariableName}})
if err != nil {
return nil, err
}
pathParam{{$paramIdx}} = string(pathParamBuf{{$paramIdx}})
{{end}}
{{if .IsStyled}}
pathParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationPath, {{.GoVariableName}})
if err != nil {
return nil, err
}
{{end}}
{{end}}
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("{{genParamFmtString .Path}}"{{range $paramIdx, $param := .PathParams}}, pathParam{{$paramIdx}}{{end}})
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
{{if .QueryParams}}
if params != nil {
queryValues := queryURL.Query()
{{range $paramIdx, $param := .QueryParams}}
{{if not .Required}} if params.{{.GoName}} != nil { {{end}}
{{if .IsPassThrough}}
queryValues.Add("{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}})
{{end}}
{{if .IsJson}}
if queryParamBuf, err := json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}); err != nil {
return nil, err
} else {
queryValues.Add("{{.ParamName}}", string(queryParamBuf))
}
{{end}}
{{if .IsStyled}}
if queryFrag, err := runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationQuery, {{if not .Required}}*{{end}}params.{{.GoName}}); err != nil {
return nil, err
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
return nil, err
} else {
for k, v := range parsed {
for _, v2 := range v {
queryValues.Add(k, v2)
}
}
}
{{end}}
{{if not .Required}}}{{end}}
{{end}}
queryURL.RawQuery = queryValues.Encode()
}
{{end}}{{/* if .QueryParams */}}
req, err := http.NewRequest("{{.Method}}", queryURL.String(), {{if .HasBody}}body{{else}}nil{{end}})
if err != nil {
return nil, err
}
{{if .HasBody}}req.Header.Add("Content-Type", contentType){{end}}
{{ if .HeaderParams }}
if params != nil {
{{range $paramIdx, $param := .HeaderParams}}
{{if not .Required}} if params.{{.GoName}} != nil { {{end}}
var headerParam{{$paramIdx}} string
{{if .IsPassThrough}}
headerParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}}
{{end}}
{{if .IsJson}}
var headerParamBuf{{$paramIdx}} []byte
headerParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}})
if err != nil {
return nil, err
}
headerParam{{$paramIdx}} = string(headerParamBuf{{$paramIdx}})
{{end}}
{{if .IsStyled}}
headerParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, {{if not .Required}}*{{end}}params.{{.GoName}})
if err != nil {
return nil, err
}
{{end}}
req.Header.Set("{{.ParamName}}", headerParam{{$paramIdx}})
{{if not .Required}}}{{end}}
{{end}}
}
{{- end }}{{/* if .HeaderParams */}}
{{ if .CookieParams }}
if params != nil {
{{range $paramIdx, $param := .CookieParams}}
{{if not .Required}} if params.{{.GoName}} != nil { {{end}}
var cookieParam{{$paramIdx}} string
{{if .IsPassThrough}}
cookieParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}}
{{end}}
{{if .IsJson}}
var cookieParamBuf{{$paramIdx}} []byte
cookieParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}})
if err != nil {
return nil, err
}
cookieParam{{$paramIdx}} = url.QueryEscape(string(cookieParamBuf{{$paramIdx}}))
{{end}}
{{if .IsStyled}}
cookieParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("simple", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationCookie, {{if not .Required}}*{{end}}params.{{.GoName}})
if err != nil {
return nil, err
}
{{end}}
cookie{{$paramIdx}} := &http.Cookie{
Name:"{{.ParamName}}",
Value:cookieParam{{$paramIdx}},
}
req.AddCookie(cookie{{$paramIdx}})
{{if not .Required}}}{{end}}
{{ end -}}
}
{{- end }}{{/* if .CookieParams */}}
return req, nil
}
{{end}}{{/* Range */}}
func (c *{{ $clientTypeName }}) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error {
for _, r := range c.RequestEditors {
if err := r(ctx, req); err != nil {
return err
}
}
for _, r := range additionalEditors {
if err := r(ctx, req); err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,15 @@
{{- if gt (len .SecuritySchemeProviderNames) 0 }}
const (
{{range $ProviderName := .SecuritySchemeProviderNames}}
{{- $ProviderName | sanitizeGoIdentity | ucFirst}}Scopes = "{{$ProviderName}}.Scopes"
{{end}}
)
{{end}}
{{range $Enum := .EnumDefinitions}}
// Defines values for {{$Enum.TypeName}}.
const (
{{range $name, $value := $Enum.GetValues}}
{{$name}} {{$Enum.TypeName}} = {{$Enum.ValueWrapper}}{{$value}}{{$Enum.ValueWrapper -}}
{{end}}
)
{{end}}

View file

@ -0,0 +1,7 @@
// ServerInterface represents all server handlers.
type ServerInterface interface {
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{.OperationId}}(ctx echo.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error
{{end}}
}

View file

@ -0,0 +1,33 @@
// This is a simple interface which specifies echo.Route addition functions which
// are present on both echo.Echo and echo.Group, since we want to allow using
// either of them for path registration
type EchoRouter interface {
CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
}
// RegisterHandlers adds each server route to the EchoRouter.
func RegisterHandlers(router EchoRouter, si ServerInterface) {
RegisterHandlersWithBaseURL(router, si, "")
}
// Registers handlers, and prepends BaseURL to the paths, so that the paths
// can be served under a prefix.
func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) {
{{if .}}
wrapper := ServerInterfaceWrapper{
Handler: si,
}
{{end}}
{{range .}}router.{{.Method}}(baseURL + "{{.Path | swaggerUriToEchoUri}}", wrapper.{{.OperationId}})
{{end}}
}

View file

@ -0,0 +1,131 @@
// ServerInterfaceWrapper converts echo contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
}
{{range .}}{{$opid := .OperationId}}// {{$opid}} converts echo context to params.
func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error {
var err error
{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" -------------
var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}}
{{if .IsPassThrough}}
{{$varName}} = ctx.Param("{{.ParamName}}")
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(ctx.Param("{{.ParamName}}")), &{{$varName}})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON")
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", ctx.Param("{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err))
}
{{end}}
{{end}}
{{range .SecurityDefinitions}}
ctx.Set({{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}})
{{end}}
{{if .RequiresParamObject}}
// Parameter object where we will unmarshal all parameters from the context
var params {{.OperationId}}Params
{{range $paramIdx, $param := .QueryParams}}
{{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
{{ end }}
{{if .IsStyled}}
err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", ctx.QueryParams(), &params.{{.GoName}})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err))
}
{{else}}
if paramValue := ctx.QueryParam("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
err = json.Unmarshal([]byte(paramValue), &value)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON")
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}{{if .Required}} else {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found"))
}{{end}}
{{end}}
{{end}}
{{if .HeaderParams}}
headers := ctx.Request().Header
{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" -------------
if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found {
var {{.GoName}} {{.TypeDef}}
n := len(valueList)
if n != 1 {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Expected one value for {{.ParamName}}, got %d", n))
}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON")
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err))
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
} {{if .Required}}else {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found"))
}{{end}}
{{end}}
{{end}}
{{range .CookieParams}}
if cookie, err := ctx.Cookie("{{.ParamName}}"); err == nil {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
var decoded string
decoded, err := url.QueryUnescape(cookie.Value)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Error unescaping cookie parameter '{{.ParamName}}'")
}
err = json.Unmarshal([]byte(decoded), &value)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON")
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
{{if .IsStyled}}
var value {{.TypeDef}}
err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err))
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}{{if .Required}} else {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found"))
}{{end}}
{{end}}{{/* .CookieParams */}}
{{end}}{{/* .RequiresParamObject */}}
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.{{.OperationId}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}})
return err
}
{{end}}

View file

@ -0,0 +1,25 @@
// FiberServerOptions provides options for the Fiber server.
type FiberServerOptions struct {
BaseURL string
Middlewares []MiddlewareFunc
}
// RegisterHandlers creates http.Handler with routing matching OpenAPI spec.
func RegisterHandlers(router fiber.Router, si ServerInterface) {
RegisterHandlersWithOptions(router, si, FiberServerOptions{})
}
// RegisterHandlersWithOptions creates http.Handler with additional options
func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, options FiberServerOptions) {
{{if .}}wrapper := ServerInterfaceWrapper{
Handler: si,
}
for _, m := range options.Middlewares {
router.Use(fiber.Handler(m))
}
{{end}}
{{range .}}
router.{{.Method | lower | title }}(options.BaseURL+"{{.Path | swaggerUriToFiberUri}}", wrapper.{{.OperationId}})
{{end}}
}

View file

@ -0,0 +1,7 @@
// ServerInterface represents all server handlers.
type ServerInterface interface {
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{.OperationId}}(c *fiber.Ctx{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error
{{end}}
}

View file

@ -0,0 +1,169 @@
// ServerInterfaceWrapper converts contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
}
type MiddlewareFunc fiber.Handler
{{range .}}{{$opid := .OperationId}}
// {{$opid}} operation middleware
func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error {
{{if or .RequiresParamObject (gt (len .PathParams) 0) }}
var err error
{{end}}
{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" -------------
var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}}
{{if .IsPassThrough}}
{{$varName}} = c.Query("{{.ParamName}}")
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(c.Query("{{.ParamName}}")), &{{$varName}})
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error())
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", c.Params("{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error())
}
{{end}}
{{end}}
{{range .SecurityDefinitions}}
c.Context().SetUserValue({{.ProviderName | ucFirst}}Scopes, {{toStringArray .Scopes}})
{{end}}
{{if .RequiresParamObject}}
// Parameter object where we will unmarshal all parameters from the context
var params {{.OperationId}}Params
{{if .QueryParams}}
var query url.Values
query, err = url.ParseQuery(string(c.Request().URI().QueryString()))
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for query string: %w", err).Error())
}
{{end}}
{{range $paramIdx, $param := .QueryParams}}
{{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
{{ end }}
{{ if (or (or .Required .IsPassThrough) .IsJson) }}
if paramValue := c.Query("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
err = json.Unmarshal([]byte(paramValue), &value)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error())
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}{{if .Required}} else {
err = fmt.Errorf("Query argument {{.ParamName}} is required, but not found")
c.Status(fiber.StatusBadRequest).JSON(err)
return err
}{{end}}
{{end}}
{{if .IsStyled}}
err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", query, &params.{{.GoName}})
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error())
}
{{end}}
{{end}}
{{if .HeaderParams}}
headers := c.GetReqHeaders()
{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" -------------
if value, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found {
var {{.GoName}} {{.TypeDef}}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(value), &{{.GoName}})
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error())
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", value, &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error())
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
} {{if .Required}}else {
err = fmt.Errorf("Header parameter {{.ParamName}} is required, but not found: %w", err)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}{{end}}
{{end}}
{{end}}
{{range .CookieParams}}
var cookie string
if cookie = c.Cookies("{{.ParamName}}"); cookie == "" {
{{- if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie
{{end}}
{{- if .IsJson}}
var value {{.TypeDef}}
var decoded string
decoded, err := url.QueryUnescape(cookie)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}': %w", err).Error())
}
err = json.Unmarshal([]byte(decoded), &value)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error())
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
{{- if .IsStyled}}
var value {{.TypeDef}}
err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err).Error())
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}
{{- if .Required}} else {
err = fmt.Errorf("Query argument {{.ParamName}} is required, but not found")
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
{{- end}}
{{end}}
{{end}}
return siw.Handler.{{.OperationId}}(c{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}})
}
{{end}}

View file

@ -0,0 +1,7 @@
// ServerInterface represents all server handlers.
type ServerInterface interface {
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{.OperationId}}(c *gin.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}})
{{end}}
}

View file

@ -0,0 +1,33 @@
// GinServerOptions provides options for the Gin server.
type GinServerOptions struct {
BaseURL string
Middlewares []MiddlewareFunc
ErrorHandler func(*gin.Context, error, int)
}
// RegisterHandlers creates http.Handler with routing matching OpenAPI spec.
func RegisterHandlers(router gin.IRouter, si ServerInterface) {
RegisterHandlersWithOptions(router, si, GinServerOptions{})
}
// RegisterHandlersWithOptions creates http.Handler with additional options
func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) {
{{- if . -}}
errorHandler := options.ErrorHandler
if errorHandler == nil {
errorHandler = func(c *gin.Context, err error, statusCode int) {
c.JSON(statusCode, gin.H{"msg": err.Error()})
}
}
wrapper := ServerInterfaceWrapper{
Handler: si,
HandlerMiddlewares: options.Middlewares,
ErrorHandler: errorHandler,
}
{{end}}
{{range . -}}
router.{{.Method }}(options.BaseURL+"{{.Path | swaggerUriToGinUri }}", wrapper.{{.OperationId}})
{{end -}}
}

View file

@ -0,0 +1,186 @@
// ServerInterfaceWrapper converts contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
HandlerMiddlewares []MiddlewareFunc
ErrorHandler func(*gin.Context, error, int)
}
type MiddlewareFunc func(c *gin.Context)
{{range .}}{{$opid := .OperationId}}
// {{$opid}} operation middleware
func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) {
{{if or .RequiresParamObject (gt (len .PathParams) 0) }}
var err error
{{end}}
{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" -------------
var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}}
{{if .IsPassThrough}}
{{$varName}} = c.Query("{{.ParamName}}")
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(c.Query("{{.ParamName}}")), &{{$varName}})
if err != nil {
siw.ErrorHandler(c, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest)
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", c.Param("{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err), http.StatusBadRequest)
return
}
{{end}}
{{end}}
{{range .SecurityDefinitions}}
c.Set({{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}})
{{end}}
{{if .RequiresParamObject}}
// Parameter object where we will unmarshal all parameters from the context
var params {{.OperationId}}Params
{{range $paramIdx, $param := .QueryParams}}
{{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
{{ end }}
{{ if (or (or .Required .IsPassThrough) .IsJson) }}
if paramValue := c.Query("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
err = json.Unmarshal([]byte(paramValue), &value)
if err != nil {
siw.ErrorHandler(c, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err), http.StatusBadRequest)
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}{{if .Required}} else {
siw.ErrorHandler(c, fmt.Errorf("Query argument {{.ParamName}} is required, but not found"), http.StatusBadRequest)
return
}{{end}}
{{end}}
{{if .IsStyled}}
err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", c.Request.URL.Query(), &params.{{.GoName}})
if err != nil {
siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err), http.StatusBadRequest)
return
}
{{end}}
{{end}}
{{if .HeaderParams}}
headers := c.Request.Header
{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" -------------
if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found {
var {{.GoName}} {{.TypeDef}}
n := len(valueList)
if n != 1 {
siw.ErrorHandler(c, fmt.Errorf("Expected one value for {{.ParamName}}, got %d", n), http.StatusBadRequest)
return
}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}})
if err != nil {
siw.ErrorHandler(c, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest)
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err), http.StatusBadRequest)
return
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
} {{if .Required}}else {
siw.ErrorHandler(c, fmt.Errorf("Header parameter {{.ParamName}} is required, but not found"), http.StatusBadRequest)
return
}{{end}}
{{end}}
{{end}}
{{range .CookieParams}}
{
var cookie string
if cookie, err = c.Cookie("{{.ParamName}}"); err == nil {
{{- if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie
{{end}}
{{- if .IsJson}}
var value {{.TypeDef}}
var decoded string
decoded, err := url.QueryUnescape(cookie)
if err != nil {
siw.ErrorHandler(c, fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}'"), http.StatusBadRequest)
return
}
err = json.Unmarshal([]byte(decoded), &value)
if err != nil {
siw.ErrorHandler(c, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON"), http.StatusBadRequest)
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
{{- if .IsStyled}}
var value {{.TypeDef}}
err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err), http.StatusBadRequest)
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}
{{- if .Required}} else {
siw.ErrorHandler(c, fmt.Errorf("Query argument {{.ParamName}} is required, but not found"), http.StatusBadRequest)
return
}
{{- end}}
}
{{end}}
{{end}}
for _, middleware := range siw.HandlerMiddlewares {
middleware(c)
if c.IsAborted() {
return
}
}
siw.Handler.{{.OperationId}}(c{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}})
}
{{end}}

View file

@ -0,0 +1,7 @@
// ServerInterface represents all server handlers.
type ServerInterface interface {
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}})
{{end}}
}

View file

@ -0,0 +1,266 @@
// ServerInterfaceWrapper converts contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
HandlerMiddlewares []MiddlewareFunc
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
type MiddlewareFunc func(http.Handler) http.Handler
{{range .}}{{$opid := .OperationId}}
// {{$opid}} operation middleware
func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Request) {
{{if or .RequiresParamObject (gt (len .PathParams) 0) }}
var err error
{{end}}
{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" -------------
var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}}
{{if .IsPassThrough}}
{{$varName}} = mux.Vars(r)["{{.ParamName}}"]
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(mux.Vars(r)["{{.ParamName}}"]), &{{$varName}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", mux.Vars(r)["{{.ParamName}}"], &{{$varName}}, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{end}}
{{if .SecurityDefinitions -}}
ctx := r.Context()
{{range .SecurityDefinitions}}
ctx = context.WithValue(ctx, {{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}})
{{end}}
r = r.WithContext(ctx)
{{end}}
{{if .RequiresParamObject}}
// Parameter object where we will unmarshal all parameters from the context
var params {{.OperationId}}Params
{{range $paramIdx, $param := .QueryParams}}
{{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
{{ end }}
{{ if (or (or .Required .IsPassThrough) .IsJson) }}
if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
err = json.Unmarshal([]byte(paramValue), &value)
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}{{if .Required}} else {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
return
}{{end}}
{{end}}
{{if .IsStyled}}
err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), &params.{{.GoName}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{end}}
{{if .HeaderParams}}
headers := r.Header
{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" -------------
if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found {
var {{.GoName}} {{.TypeDef}}
n := len(valueList)
if n != 1 {
siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "{{.ParamName}}", Count: n})
return
}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
} {{if .Required}}else {
err = fmt.Errorf("Header parameter {{.ParamName}} is required, but not found")
siw.ErrorHandlerFunc(w, r, &RequiredHeaderError{ParamName: "{{.ParamName}}", Err: err})
return
}{{end}}
{{end}}
{{end}}
{{range .CookieParams}}
{
var cookie *http.Cookie
if cookie, err = r.Cookie("{{.ParamName}}"); err == nil {
{{- if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
{{end}}
{{- if .IsJson}}
var value {{.TypeDef}}
var decoded string
decoded, err := url.QueryUnescape(cookie.Value)
if err != nil {
err = fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}'")
siw.ErrorHandlerFunc(w, r, &UnescapedCookieParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
err = json.Unmarshal([]byte(decoded), &value)
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
{{- if .IsStyled}}
var value {{.TypeDef}}
err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}
{{- if .Required}} else {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
return
}
{{- end}}
}
{{end}}
{{end}}
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}})
}))
{{if opts.Compatibility.ApplyGorillaMiddlewareFirstToLast}}
for i := len(siw.HandlerMiddlewares) -1; i >= 0; i-- {
handler = siw.HandlerMiddlewares[i](handler)
}
{{else}}
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
{{end}}
handler.ServeHTTP(w, r)
}
{{end}}
type UnescapedCookieParamError struct {
ParamName string
Err error
}
func (e *UnescapedCookieParamError) Error() string {
return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName)
}
func (e *UnescapedCookieParamError) Unwrap() error {
return e.Err
}
type UnmarshalingParamError struct {
ParamName string
Err error
}
func (e *UnmarshalingParamError) Error() string {
return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error())
}
func (e *UnmarshalingParamError) Unwrap() error {
return e.Err
}
type RequiredParamError struct {
ParamName string
}
func (e *RequiredParamError) Error() string {
return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName)
}
type RequiredHeaderError struct {
ParamName string
Err error
}
func (e *RequiredHeaderError) Error() string {
return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName)
}
func (e *RequiredHeaderError) Unwrap() error {
return e.Err
}
type InvalidParamFormatError struct {
ParamName string
Err error
}
func (e *InvalidParamFormatError) Error() string {
return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error())
}
func (e *InvalidParamFormatError) Unwrap() error {
return e.Err
}
type TooManyValuesForParamError struct {
ParamName string
Count int
}
func (e *TooManyValuesForParamError) Error() string {
return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count)
}

View file

@ -0,0 +1,49 @@
// Handler creates http.Handler with routing matching OpenAPI spec.
func Handler(si ServerInterface) http.Handler {
return HandlerWithOptions(si, GorillaServerOptions{})
}
type GorillaServerOptions struct {
BaseURL string
BaseRouter *mux.Router
Middlewares []MiddlewareFunc
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
func HandlerFromMux(si ServerInterface, r *mux.Router) http.Handler {
return HandlerWithOptions(si, GorillaServerOptions {
BaseRouter: r,
})
}
func HandlerFromMuxWithBaseURL(si ServerInterface, r *mux.Router, baseURL string) http.Handler {
return HandlerWithOptions(si, GorillaServerOptions {
BaseURL: baseURL,
BaseRouter: r,
})
}
// HandlerWithOptions creates http.Handler with additional options
func HandlerWithOptions(si ServerInterface, options GorillaServerOptions) http.Handler {
r := options.BaseRouter
if r == nil {
r = mux.NewRouter()
}
if options.ErrorHandlerFunc == nil {
options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}
{{if .}}wrapper := ServerInterfaceWrapper{
Handler: si,
HandlerMiddlewares: options.Middlewares,
ErrorHandlerFunc: options.ErrorHandlerFunc,
}
{{end}}
{{range .}}
r.HandleFunc(options.BaseURL+"{{.Path | swaggerUriToGorillaUri }}", wrapper.{{.OperationId}}).Methods("{{.Method }}")
{{end}}
return r
}

View file

@ -0,0 +1,50 @@
{{- if opts.Generate.StdHTTPServer}}//go:build go1.22
{{- end}}
// Package {{.PackageName}} provides primitives to interact with the openapi HTTP API.
//
// Code generated by {{.ModuleName}} version {{.Version}} DO NOT EDIT.
package {{.PackageName}}
import (
"bytes"
"compress/gzip"
"context"
"encoding/base64"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"gopkg.in/yaml.v2"
"io"
"os"
"mime"
"mime/multipart"
"net/http"
"net/url"
"path"
"strings"
"time"
"github.com/oapi-codegen/runtime"
"github.com/oapi-codegen/nullable"
strictecho "github.com/oapi-codegen/runtime/strictmiddleware/echo"
strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin"
strictiris "github.com/oapi-codegen/runtime/strictmiddleware/iris"
strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp"
openapi_types "github.com/oapi-codegen/runtime/types"
"github.com/getkin/kin-openapi/openapi3"
"github.com/go-chi/chi/v5"
"github.com/labstack/echo/v4"
"github.com/gin-gonic/gin"
"github.com/gofiber/fiber/v2"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/core/router"
"github.com/gorilla/mux"
{{- range .ExternalImports}}
{{ . }}
{{- end}}
{{- range .AdditionalImports}}
{{.Alias}} "{{.Package}}"
{{- end}}
)

View file

@ -0,0 +1,84 @@
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
{{range .SpecParts}}
"{{.}}",{{end}}
}
// GetSwagger returns the content of the embedded swagger specification file
// or error if failed to decode
func decodeSpec() ([]byte, error) {
zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, ""))
if err != nil {
return nil, fmt.Errorf("error base64 decoding spec: %w", err)
}
zr, err := gzip.NewReader(bytes.NewReader(zipped))
if err != nil {
return nil, fmt.Errorf("error decompressing spec: %w", err)
}
var buf bytes.Buffer
_, err = buf.ReadFrom(zr)
if err != nil {
return nil, fmt.Errorf("error decompressing spec: %w", err)
}
return buf.Bytes(), nil
}
var rawSpec = decodeSpecCached()
// a naive cached of a decoded swagger spec
func decodeSpecCached() func() ([]byte, error) {
data, err := decodeSpec()
return func() ([]byte, error) {
return data, err
}
}
// Constructs a synthetic filesystem for resolving external references when loading openapi specifications.
func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) {
res := make(map[string]func() ([]byte, error))
if len(pathToFile) > 0 {
res[pathToFile] = rawSpec
}
{{ range $key, $value := .ImportMapping }}{{- if ne $value.Path "-"}}
for rawPath, rawFunc := range {{ $value.Name }}.PathToRawSpec(path.Join(path.Dir(pathToFile), "{{ $key }}")) {
if _, ok := res[rawPath]; ok {
// it is not possible to compare functions in golang, so always overwrite the old value
}
res[rawPath] = rawFunc
}
{{- end }}{{- end }}
return res
}
// GetSwagger returns the Swagger specification corresponding to the generated code
// in this file. The external references of Swagger specification are resolved.
// The logic of resolving external references is tightly connected to "import-mapping" feature.
// Externally referenced files must be embedded in the corresponding golang packages.
// Urls can be supported but this task was out of the scope.
func GetSwagger() (swagger *openapi3.T, err error) {
resolvePath := PathToRawSpec("")
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) {
pathToFile := url.String()
pathToFile = path.Clean(pathToFile)
getSpec, ok := resolvePath[pathToFile]
if !ok {
err1 := fmt.Errorf("path not found: %s", pathToFile)
return nil, err1
}
return getSpec()
}
var specData []byte
specData, err = rawSpec()
if err != nil {
return
}
swagger, err = loader.LoadFromData(specData)
if err != nil {
return
}
return
}

View file

@ -0,0 +1,23 @@
// IrisServerOption is the option for iris server
type IrisServerOptions struct {
BaseURL string
Middlewares []MiddlewareFunc
}
// RegisterHandlers creates http.Handler with routing matching OpenAPI spec.
func RegisterHandlers(router *iris.Application, si ServerInterface) {
RegisterHandlersWithOptions(router, si, IrisServerOptions{})
}
// RegisterHandlersWithOptions creates http.Handler with additional options
func RegisterHandlersWithOptions(router *iris.Application, si ServerInterface, options IrisServerOptions) {
{{if .}}
wrapper := ServerInterfaceWrapper{
Handler: si,
}
{{end}}
{{range .}}router.{{.Method | lower | title}}(options.BaseURL + "{{.Path | swaggerUriToIrisUri}}", wrapper.{{.OperationId}})
{{end}}
router.Build()
}

View file

@ -0,0 +1,7 @@
// ServerInterface represents all server handlers.
type ServerInterface interface {
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{.OperationId}}(ctx iris.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}})
{{end}}
}

View file

@ -0,0 +1,161 @@
// ServerInterfaceWrapper converts echo contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
}
type MiddlewareFunc iris.Handler
{{range .}}{{$opid := .OperationId}}// {{$opid}} converts iris context to params.
func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) {
{{if or .RequiresParamObject (gt (len .PathParams) 0) }}
var err error
{{end}}
{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" -------------
var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}}
{{if .IsPassThrough}}
{{$varName}} = ctx.URLParam("{{.ParamName}}")
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(ctx.URLParam("{{.ParamName}}")), &{{$varName}})
if err != nil {
ctx.StatusCode(http.StatusBadRequest)
ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON")
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", ctx.Params().Get("{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
ctx.StatusCode(http.StatusBadRequest)
ctx.Writef("Invalid format for parameter {{.ParamName}}: %s", err)
return
}
{{end}}
{{end}}
{{range .SecurityDefinitions}}
ctx.Set({{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}})
{{end}}
{{if .RequiresParamObject}}
// Parameter object where we will unmarshal all parameters from the context
var params {{.OperationId}}Params
{{range $paramIdx, $param := .QueryParams}}
{{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
{{ end }}
{{if .IsStyled}}
err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", ctx.Request().URL.Query(), &params.{{.GoName}})
if err != nil {
ctx.StatusCode(http.StatusBadRequest)
ctx.Writef("Invalid format for parameter {{.ParamName}}: %s", err)
return
}
{{else}}
if paramValue := ctx.QueryParam("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
err = json.Unmarshal([]byte(paramValue), &value)
if err != nil {
ctx.StatusCode(http.StatusBadRequest)
ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON")
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}{{if .Required}} else {
ctx.StatusCode(http.StatusBadRequest)
ctx.WriteString("Query argument {{.ParamName}} is required, but not found")
return
}{{end}}
{{end}}
{{end}}
{{if .HeaderParams}}
headers := ctx.Request().Header
{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" -------------
if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found {
var {{.GoName}} {{.TypeDef}}
n := len(valueList)
if n != 1 {
ctx.StatusCode(http.StatusBadRequest)
ctx.Writef("Expected one value for {{.ParamName}}, got %d", n)
return
}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}})
if err != nil {
ctx.StatusCode(http.StatusBadRequest)
ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON")
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
ctx.StatusCode(http.StatusBadRequest)
ctx.Writef("Invalid format for parameter {{.ParamName}}: %s", err)
return
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
} {{if .Required}}else {
ctx.StatusCode(http.StatusBadRequest)
ctx.WriteString("Header {{.ParamName}} is required, but not found")
return
}{{end}}
{{end}}
{{end}}
{{range .CookieParams}}
if cookie, err := ctx.Cookie("{{.ParamName}}"); err == nil {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
var decoded string
decoded, err := url.QueryUnescape(cookie.Value)
if err != nil {
ctx.StatusCode(http.StatusBadRequest)
ctx.WriteString("Error unescaping cookie parameter '{{.ParamName}}'")
return
}
err = json.Unmarshal([]byte(decoded), &value)
if err != nil {
ctx.StatusCode(http.StatusBadRequest)
ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON")
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
{{if .IsStyled}}
var value {{.TypeDef}}
err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationCookie, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
ctx.StatusCode(http.StatusBadRequest)
ctx.Writef("Invalid format for parameter {{.ParamName}}: %s", err)
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}{{if .Required}} else {
ctx.StatusCode(http.StatusBadRequest)
ctx.WriteString("Cookie {{.ParamName}} is required, but not found")
return
}{{end}}
{{end}}{{/* .CookieParams */}}
{{end}}{{/* .RequiresParamObject */}}
// Invoke the callback with all the unmarshaled arguments
w.Handler.{{.OperationId}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}})
}
{{end}}

View file

@ -0,0 +1,6 @@
{{range .}}{{$opid := .OperationId}}
{{range .TypeDefinitions}}
// {{.TypeName}} defines parameters for {{$opid}}.
type {{.TypeName}} {{if .IsAlias}}={{end}} {{.Schema.TypeDecl}}
{{end}}
{{end}}

View file

@ -0,0 +1,11 @@
{{range .}}{{$opid := .OperationId}}
{{range .Bodies}}
{{if .IsSupported -}}
{{$contentType := .ContentType -}}
{{with .TypeDef $opid}}
// {{.TypeName}} defines body for {{$opid}} for {{$contentType}} ContentType.
type {{.TypeName}} {{if .IsAlias}}={{end}} {{.Schema.TypeDecl}}
{{end}}
{{end}}
{{end}}
{{end}}

View file

@ -0,0 +1,55 @@
// Handler creates http.Handler with routing matching OpenAPI spec.
func Handler(si ServerInterface) http.Handler {
return HandlerWithOptions(si, StdHTTPServerOptions{})
}
// ServeMux is an abstraction of http.ServeMux.
type ServeMux interface {
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
ServeHTTP(w http.ResponseWriter, r *http.Request)
}
type StdHTTPServerOptions struct {
BaseURL string
BaseRouter ServeMux
Middlewares []MiddlewareFunc
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler {
return HandlerWithOptions(si, StdHTTPServerOptions {
BaseRouter: m,
})
}
func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler {
return HandlerWithOptions(si, StdHTTPServerOptions {
BaseURL: baseURL,
BaseRouter: m,
})
}
// HandlerWithOptions creates http.Handler with additional options
func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler {
m := options.BaseRouter
if m == nil {
m = http.NewServeMux()
}
if options.ErrorHandlerFunc == nil {
options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}
{{if .}}
wrapper := ServerInterfaceWrapper{
Handler: si,
HandlerMiddlewares: options.Middlewares,
ErrorHandlerFunc: options.ErrorHandlerFunc,
}
{{end}}
{{range .}}m.HandleFunc("{{.Method }} "+options.BaseURL+"{{.Path | swaggerUriToStdHttpUri}}", wrapper.{{.OperationId}})
{{end}}
return m
}

View file

@ -0,0 +1,7 @@
// ServerInterface represents all server handlers.
type ServerInterface interface {
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}})
{{end}}
}

View file

@ -0,0 +1,266 @@
// ServerInterfaceWrapper converts contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
HandlerMiddlewares []MiddlewareFunc
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
type MiddlewareFunc func(http.Handler) http.Handler
{{range .}}{{$opid := .OperationId}}
// {{$opid}} operation middleware
func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Request) {
{{if or .RequiresParamObject (gt (len .PathParams) 0) }}
var err error
{{end}}
{{range .PathParams}}// ------------- Path parameter "{{.ParamName}}" -------------
var {{$varName := .GoVariableName}}{{$varName}} {{.TypeDef}}
{{if .IsPassThrough}}
{{$varName}} = r.PathValue("{{.ParamName}}")
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(r.PathValue("{{.ParamName}}")), &{{$varName}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", r.PathValue("{{.ParamName}}"), &{{$varName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{end}}
{{if .SecurityDefinitions -}}
ctx := r.Context()
{{range .SecurityDefinitions}}
ctx = context.WithValue(ctx, {{.ProviderName | sanitizeGoIdentity | ucFirst}}Scopes, {{toStringArray .Scopes}})
{{end}}
r = r.WithContext(ctx)
{{end}}
{{if .RequiresParamObject}}
// Parameter object where we will unmarshal all parameters from the context
var params {{.OperationId}}Params
{{range $paramIdx, $param := .QueryParams}}
{{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
{{ end }}
{{ if (or (or .Required .IsPassThrough) .IsJson) }}
if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
err = json.Unmarshal([]byte(paramValue), &value)
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}{{if .Required}} else {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
return
}{{end}}
{{end}}
{{if .IsStyled}}
err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), &params.{{.GoName}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{end}}
{{if .HeaderParams}}
headers := r.Header
{{range .HeaderParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} header parameter "{{.ParamName}}" -------------
if valueList, found := headers[http.CanonicalHeaderKey("{{.ParamName}}")]; found {
var {{.GoName}} {{.TypeDef}}
n := len(valueList)
if n != 1 {
siw.ErrorHandlerFunc(w, r, &TooManyValuesForParamError{ParamName: "{{.ParamName}}", Count: n})
return
}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
{{if .IsStyled}}
err = runtime.BindStyledParameterWithOptions("{{.Style}}", "{{.ParamName}}", valueList[0], &{{.GoName}}, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationHeader, Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
} {{if .Required}}else {
err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found")
siw.ErrorHandlerFunc(w, r, &RequiredHeaderError{ParamName: "{{.ParamName}}", Err: err})
return
}{{end}}
{{end}}
{{end}}
{{range .CookieParams}}
{
var cookie *http.Cookie
if cookie, err = r.Cookie("{{.ParamName}}"); err == nil {
{{- if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
{{end}}
{{- if .IsJson}}
var value {{.TypeDef}}
var decoded string
decoded, err := url.QueryUnescape(cookie.Value)
if err != nil {
err = fmt.Errorf("Error unescaping cookie parameter '{{.ParamName}}'")
siw.ErrorHandlerFunc(w, r, &UnescapedCookieParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
err = json.Unmarshal([]byte(decoded), &value)
if err != nil {
siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
{{- if .IsStyled}}
var value {{.TypeDef}}
err = runtime.BindStyledParameterWithOptions("simple", "{{.ParamName}}", cookie.Value, &value, runtime.BindStyledParameterOptions{Explode: {{.Explode}}, Required: {{.Required}}})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
{{end}}
}
{{- if .Required}} else {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
return
}
{{- end}}
}
{{end}}
{{end}}
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}})
}))
{{if opts.Compatibility.ApplyChiMiddlewareFirstToLast}}
for i := len(siw.HandlerMiddlewares) -1; i >= 0; i-- {
handler = siw.HandlerMiddlewares[i](handler)
}
{{else}}
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
{{end}}
handler.ServeHTTP(w, r)
}
{{end}}
type UnescapedCookieParamError struct {
ParamName string
Err error
}
func (e *UnescapedCookieParamError) Error() string {
return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName)
}
func (e *UnescapedCookieParamError) Unwrap() error {
return e.Err
}
type UnmarshalingParamError struct {
ParamName string
Err error
}
func (e *UnmarshalingParamError) Error() string {
return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error())
}
func (e *UnmarshalingParamError) Unwrap() error {
return e.Err
}
type RequiredParamError struct {
ParamName string
}
func (e *RequiredParamError) Error() string {
return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName)
}
type RequiredHeaderError struct {
ParamName string
Err error
}
func (e *RequiredHeaderError) Error() string {
return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName)
}
func (e *RequiredHeaderError) Unwrap() error {
return e.Err
}
type InvalidParamFormatError struct {
ParamName string
Err error
}
func (e *InvalidParamFormatError) Error() string {
return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error())
}
func (e *InvalidParamFormatError) Unwrap() error {
return e.Err
}
type TooManyValuesForParamError struct {
ParamName string
Count int
}
func (e *TooManyValuesForParamError) Error() string {
return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count)
}

View file

@ -0,0 +1,97 @@
type StrictHandlerFunc = strictecho.StrictEchoHandlerFunc
type StrictMiddlewareFunc = strictecho.StrictEchoMiddlewareFunc
func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
return &strictHandler{ssi: ssi, middlewares: middlewares}
}
type strictHandler struct {
ssi StrictServerInterface
middlewares []StrictMiddlewareFunc
}
{{range .}}
{{$opid := .OperationId}}
// {{$opid}} operation middleware
func (sh *strictHandler) {{.OperationId}}(ctx echo.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error {
var request {{$opid | ucFirst}}RequestObject
{{range .PathParams -}}
request.{{.GoName}} = {{.GoVariableName}}
{{end -}}
{{if .RequiresParamObject -}}
request.Params = params
{{end -}}
{{ if .HasMaskedRequestContentTypes -}}
request.ContentType = ctx.Request().Header.Get("Content-Type")
{{end -}}
{{$multipleBodies := gt (len .Bodies) 1 -}}
{{range .Bodies -}}
{{if $multipleBodies}}if strings.HasPrefix(ctx.Request().Header.Get("Content-Type"), "{{.ContentType}}") { {{end}}
{{if .IsJSON -}}
var body {{$opid}}{{.NameTag}}RequestBody
if err := ctx.Bind(&body); err != nil {
return err
}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else if eq .NameTag "Formdata" -}}
if form, err := ctx.FormParams(); err == nil {
var body {{$opid}}{{.NameTag}}RequestBody
if err := runtime.BindForm(&body, form, nil, nil); err != nil {
return err
}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
} else {
return err
}
{{else if eq .NameTag "Multipart" -}}
{{if eq .ContentType "multipart/form-data" -}}
if reader, err := ctx.Request().MultipartReader(); err != nil {
return err
} else {
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader
}
{{else -}}
if _, params, err := mime.ParseMediaType(ctx.Request().Header.Get("Content-Type")); err != nil {
return err
} else if boundary := params["boundary"]; boundary == "" {
return http.ErrMissingBoundary
} else {
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(ctx.Request().Body, boundary)
}
{{end -}}
{{else if eq .NameTag "Text" -}}
data, err := io.ReadAll(ctx.Request().Body)
if err != nil {
return err
}
body := {{$opid}}{{.NameTag}}RequestBody(data)
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else -}}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request().Body
{{end}}{{/* if eq .NameTag "JSON" */ -}}
{{if $multipleBodies}}}{{end}}
{{end}}{{/* range .Bodies */}}
handler := func(ctx echo.Context, request interface{}) (interface{}, error){
return sh.ssi.{{.OperationId}}(ctx.Request().Context(), request.({{$opid | ucFirst}}RequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "{{.OperationId}}")
}
response, err := handler(ctx, request)
if err != nil {
return err
} else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok {
return validResponse.Visit{{$opid}}Response(ctx.Response())
} else if response != nil {
return fmt.Errorf("unexpected response type: %T", response)
}
return nil
}
{{end}}

View file

@ -0,0 +1,145 @@
{{range .}}
{{$opid := .OperationId -}}
type {{$opid | ucFirst}}RequestObject struct {
{{range .PathParams -}}
{{.GoName | ucFirst}} {{.TypeDef}} {{.JsonTag}}
{{end -}}
{{if .RequiresParamObject -}}
Params {{$opid}}Params
{{end -}}
{{if .HasMaskedRequestContentTypes -}}
ContentType string
{{end -}}
{{$multipleBodies := gt (len .Bodies) 1 -}}
{{range .Bodies -}}
{{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if eq .NameTag "Multipart"}}*multipart.Reader{{else if ne .NameTag ""}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}}
{{end -}}
}
type {{$opid | ucFirst}}ResponseObject interface {
Visit{{$opid}}Response(ctx *fiber.Ctx) error
}
{{range .Responses}}
{{$statusCode := .StatusCode -}}
{{$hasHeaders := ne 0 (len .Headers) -}}
{{$fixedStatusCode := .HasFixedStatusCode -}}
{{$isRef := .IsRef -}}
{{$isExternalRef := .IsExternalRef -}}
{{$ref := .Ref | ucFirst -}}
{{$headers := .Headers -}}
{{if (and $hasHeaders (not $isRef)) -}}
type {{$opid}}{{$statusCode}}ResponseHeaders struct {
{{range .Headers -}}
{{.GoName}} {{.Schema.TypeDecl}}
{{end -}}
}
{{end}}
{{range .Contents}}
{{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}}
{{if and $fixedStatusCode $isRef -}}
{{ if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) (eq .NameTag "Multipart") -}}
type {{$receiverTypeName}} {{$ref}}{{.NameTagOrContentType}}Response
{{else if $isExternalRef -}}
type {{$receiverTypeName}} struct { {{$ref}} }
{{else -}}
type {{$receiverTypeName}} struct{ {{$ref}}{{.NameTagOrContentType}}Response }
{{end}}
{{else if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}}
type {{$receiverTypeName}} {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if and .Schema.IsRef (not .Schema.IsExternalRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}}
{{else -}}
type {{$receiverTypeName}} struct {
Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}}
{{if $hasHeaders -}}
Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders
{{end -}}
{{if not $fixedStatusCode -}}
StatusCode int
{{end -}}
{{if not .HasFixedContentType -}}
ContentType string
{{end -}}
{{if not .IsSupported -}}
ContentLength int64
{{end -}}
}
{{end}}
func (response {{$receiverTypeName}}) Visit{{$opid}}Response(ctx *fiber.Ctx) error {
{{range $headers -}}
ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}}))
{{end -}}
{{if eq .NameTag "Multipart" -}}
writer := multipart.NewWriter(ctx.Response().BodyWriter())
{{end -}}
ctx.Response().Header.Set("Content-Type", {{if eq .NameTag "Multipart"}}{{if eq .ContentType "multipart/form-data"}}writer.FormDataContentType(){{else}}mime.FormatMediaType("{{.ContentType}}", map[string]string{"boundary": writer.Boundary()}){{end}}{{else if .HasFixedContentType }}"{{.ContentType}}"{{else}}response.ContentType{{end}})
{{if not .IsSupported -}}
if response.ContentLength != 0 {
ctx.Response().Header.Set("Content-Length", fmt.Sprint(response.ContentLength))
}
{{end -}}
ctx.Status({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}})
{{$hasBodyVar := or ($hasHeaders) (not $fixedStatusCode) (not .IsSupported)}}
{{if .IsJSON }}
{{$hasUnionElements := ne 0 (len .Schema.UnionElements)}}
return ctx.JSON(&{{if $hasBodyVar}}response.Body{{else}}response{{end}}{{if $hasUnionElements}}.union{{end}})
{{else if eq .NameTag "Text" -}}
_, err := ctx.WriteString(string({{if $hasBodyVar}}response.Body{{else}}response{{end}}))
return err
{{else if eq .NameTag "Formdata" -}}
if form, err := runtime.MarshalForm({{if $hasBodyVar}}response.Body{{else}}response{{end}}, nil); err != nil {
return err
} else {
_, err := ctx.WriteString(form.Encode())
return err
}
{{else if eq .NameTag "Multipart" -}}
defer writer.Close()
return {{if $hasBodyVar}}response.Body{{else}}response{{end}}(writer);
{{else -}}
if closer, ok := response.Body.(io.ReadCloser); ok {
defer closer.Close()
}
_, err := io.Copy(ctx.Response().BodyWriter(), response.Body)
return err
{{end}}{{/* if eq .NameTag "JSON" */ -}}
}
{{end}}
{{if eq 0 (len .Contents) -}}
{{if and $fixedStatusCode $isRef -}}
type {{$opid}}{{$statusCode}}Response {{if not $isExternalRef}}={{end}} {{$ref}}Response
{{else -}}
type {{$opid}}{{$statusCode}}Response struct {
{{if $hasHeaders -}}
Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders
{{end}}
{{if not $fixedStatusCode -}}
StatusCode int
{{end -}}
}
{{end -}}
func (response {{$opid}}{{$statusCode}}Response) Visit{{$opid}}Response(ctx *fiber.Ctx) error {
{{range $headers -}}
ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}}))
{{end -}}
ctx.Status({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}})
return nil
}
{{end}}
{{end}}
{{end}}
// StrictServerInterface represents all server handlers.
type StrictServerInterface interface {
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{$opid := .OperationId -}}
{{$opid}}(ctx context.Context, request {{$opid | ucFirst}}RequestObject) ({{$opid | ucFirst}}ResponseObject, error)
{{end}}{{/* range . */ -}}
}

View file

@ -0,0 +1,90 @@
type StrictHandlerFunc func(ctx *fiber.Ctx, args interface{}) (interface{}, error)
type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc
func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
return &strictHandler{ssi: ssi, middlewares: middlewares}
}
type strictHandler struct {
ssi StrictServerInterface
middlewares []StrictMiddlewareFunc
}
{{range .}}
{{$opid := .OperationId}}
// {{$opid}} operation middleware
func (sh *strictHandler) {{.OperationId}}(ctx *fiber.Ctx{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) error {
var request {{$opid | ucFirst}}RequestObject
{{range .PathParams -}}
{{$varName := .GoVariableName -}}
request.{{.GoName}} = {{.GoVariableName}}
{{end -}}
{{if .RequiresParamObject -}}
request.Params = params
{{end -}}
{{ if .HasMaskedRequestContentTypes -}}
request.ContentType = string(ctx.Request().Header.ContentType())
{{end -}}
{{$multipleBodies := gt (len .Bodies) 1 -}}
{{range .Bodies -}}
{{if $multipleBodies}}if strings.HasPrefix(string(ctx.Request().Header.ContentType()), "{{.ContentType}}") { {{end}}
{{if .IsJSON }}
var body {{$opid}}{{.NameTag}}RequestBody
if err := ctx.BodyParser(&body); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else if eq .NameTag "Formdata" -}}
var body {{$opid}}{{.NameTag}}RequestBody
if err := ctx.BodyParser(&body); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else if eq .NameTag "Multipart" -}}
{{if eq .ContentType "multipart/form-data" -}}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(bytes.NewReader(ctx.Request().Body()), string(ctx.Request().Header.MultipartFormBoundary()))
{{else -}}
if _, params, err := mime.ParseMediaType(string(ctx.Request().Header.ContentType())); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else if boundary := params["boundary"]; boundary == "" {
return fiber.NewError(fiber.StatusBadRequest, http.ErrMissingBoundary.Error())
} else {
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(bytes.NewReader(ctx.Request().Body()), boundary)
}
{{end -}}
{{else if eq .NameTag "Text" -}}
data := ctx.Request().Body()
body := {{$opid}}{{.NameTag}}RequestBody(data)
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else -}}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = bytes.NewReader(ctx.Request().Body())
{{end}}{{/* if eq .NameTag "JSON" */ -}}
{{if $multipleBodies}}}{{end}}
{{end}}{{/* range .Bodies */}}
handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) {
return sh.ssi.{{.OperationId}}(ctx.UserContext(), request.({{$opid | ucFirst}}RequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "{{.OperationId}}")
}
response, err := handler(ctx, request)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok {
if err := validResponse.Visit{{$opid}}Response(ctx); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
} else if response != nil {
return fmt.Errorf("unexpected response type: %T", response)
}
return nil
}
{{end}}

View file

@ -0,0 +1,106 @@
type StrictHandlerFunc = strictgin.StrictGinHandlerFunc
type StrictMiddlewareFunc = strictgin.StrictGinMiddlewareFunc
func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
return &strictHandler{ssi: ssi, middlewares: middlewares}
}
type strictHandler struct {
ssi StrictServerInterface
middlewares []StrictMiddlewareFunc
}
{{range .}}
{{$opid := .OperationId}}
// {{$opid}} operation middleware
func (sh *strictHandler) {{.OperationId}}(ctx *gin.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) {
var request {{$opid | ucFirst}}RequestObject
{{range .PathParams -}}
request.{{.GoName}} = {{.GoVariableName}}
{{end -}}
{{if .RequiresParamObject -}}
request.Params = params
{{end -}}
{{ if .HasMaskedRequestContentTypes -}}
request.ContentType = ctx.ContentType()
{{end -}}
{{$multipleBodies := gt (len .Bodies) 1 -}}
{{range .Bodies -}}
{{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end}}
{{if .IsJSON }}
var body {{$opid}}{{.NameTag}}RequestBody
if err := ctx.ShouldBindJSON(&body); err != nil {
ctx.Status(http.StatusBadRequest)
ctx.Error(err)
return
}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else if eq .NameTag "Formdata" -}}
if err := ctx.Request.ParseForm(); err != nil {
ctx.Error(err)
return
}
var body {{$opid}}{{.NameTag}}RequestBody
if err := runtime.BindForm(&body, ctx.Request.Form, nil, nil); err != nil {
ctx.Error(err)
return
}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else if eq .NameTag "Multipart" -}}
{{if eq .ContentType "multipart/form-data" -}}
if reader, err := ctx.Request.MultipartReader(); err == nil {
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader
} else {
ctx.Error(err)
return
}
{{else -}}
if _, params, err := mime.ParseMediaType(ctx.Request.Header.Get("Content-Type")); err != nil {
ctx.Error(err)
return
} else if boundary := params["boundary"]; boundary == "" {
ctx.Error(http.ErrMissingBoundary)
return
} else {
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(ctx.Request.Body, boundary)
}
{{end -}}
{{else if eq .NameTag "Text" -}}
data, err := io.ReadAll(ctx.Request.Body)
if err != nil {
ctx.Error(err)
return
}
body := {{$opid}}{{.NameTag}}RequestBody(data)
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else -}}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request.Body
{{end}}{{/* if eq .NameTag "JSON" */ -}}
{{if $multipleBodies}}}{{end}}
{{end}}{{/* range .Bodies */}}
handler := func(ctx *gin.Context, request interface{}) (interface{}, error) {
return sh.ssi.{{.OperationId}}(ctx, request.({{$opid | ucFirst}}RequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "{{.OperationId}}")
}
response, err := handler(ctx, request)
if err != nil {
ctx.Error(err)
ctx.Status(http.StatusInternalServerError)
} else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok {
if err := validResponse.Visit{{$opid}}Response(ctx.Writer); err != nil {
ctx.Error(err)
}
} else if response != nil {
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
}
}
{{end}}

View file

@ -0,0 +1,121 @@
type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc
type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc
type StrictHTTPServerOptions struct {
RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions {
RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
},
ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusInternalServerError)
},
}}
}
func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface {
return &strictHandler{ssi: ssi, middlewares: middlewares, options: options}
}
type strictHandler struct {
ssi StrictServerInterface
middlewares []StrictMiddlewareFunc
options StrictHTTPServerOptions
}
{{range .}}
{{$opid := .OperationId}}
// {{$opid}} operation middleware
func (sh *strictHandler) {{.OperationId}}(w http.ResponseWriter, r *http.Request{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) {
var request {{$opid | ucFirst}}RequestObject
{{range .PathParams -}}
request.{{.GoName}} = {{.GoVariableName}}
{{end -}}
{{if .RequiresParamObject -}}
request.Params = params
{{end -}}
{{ if .HasMaskedRequestContentTypes -}}
request.ContentType = r.Header.Get("Content-Type")
{{end -}}
{{$multipleBodies := gt (len .Bodies) 1 -}}
{{range .Bodies -}}
{{if $multipleBodies}}if strings.HasPrefix(r.Header.Get("Content-Type"), "{{.ContentType}}") { {{end}}
{{if .IsJSON }}
var body {{$opid}}{{.NameTag}}RequestBody
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err))
return
}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else if eq .NameTag "Formdata" -}}
if err := r.ParseForm(); err != nil {
sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode formdata: %w", err))
return
}
var body {{$opid}}{{.NameTag}}RequestBody
if err := runtime.BindForm(&body, r.Form, nil, nil); err != nil {
sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't bind formdata: %w", err))
return
}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else if eq .NameTag "Multipart" -}}
{{if eq .ContentType "multipart/form-data" -}}
if reader, err := r.MultipartReader(); err != nil {
sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err))
return
} else {
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader
}
{{else -}}
if _, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")); err != nil {
sh.options.RequestErrorHandlerFunc(w, r, err)
return
} else if boundary := params["boundary"]; boundary == "" {
sh.options.RequestErrorHandlerFunc(w, r, http.ErrMissingBoundary)
return
} else {
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(r.Body, boundary)
}
{{end -}}
{{else if eq .NameTag "Text" -}}
data, err := io.ReadAll(r.Body)
if err != nil {
sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err))
return
}
body := {{$opid}}{{.NameTag}}RequestBody(data)
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else -}}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = r.Body
{{end}}{{/* if eq .NameTag "JSON" */ -}}
{{if $multipleBodies}}}{{end}}
{{end}}{{/* range .Bodies */}}
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
return sh.ssi.{{.OperationId}}(ctx, request.({{$opid | ucFirst}}RequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "{{.OperationId}}")
}
response, err := handler(r.Context(), w, r, request)
if err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
} else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok {
if err := validResponse.Visit{{$opid}}Response(w); err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
}
} else if response != nil {
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
}
}
{{end}}

View file

@ -0,0 +1,145 @@
{{range .}}
{{$opid := .OperationId -}}
type {{$opid | ucFirst}}RequestObject struct {
{{range .PathParams -}}
{{.GoName | ucFirst}} {{.TypeDef}} {{.JsonTag}}
{{end -}}
{{if .RequiresParamObject -}}
Params {{$opid}}Params
{{end -}}
{{if .HasMaskedRequestContentTypes -}}
ContentType string
{{end -}}
{{$multipleBodies := gt (len .Bodies) 1 -}}
{{range .Bodies -}}
{{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if eq .NameTag "Multipart"}}*multipart.Reader{{else if ne .NameTag ""}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}}
{{end -}}
}
type {{$opid | ucFirst}}ResponseObject interface {
Visit{{$opid}}Response(w http.ResponseWriter) error
}
{{range .Responses}}
{{$statusCode := .StatusCode -}}
{{$hasHeaders := ne 0 (len .Headers) -}}
{{$fixedStatusCode := .HasFixedStatusCode -}}
{{$isRef := .IsRef -}}
{{$isExternalRef := .IsExternalRef -}}
{{$ref := .Ref | ucFirstWithPkgName -}}
{{$headers := .Headers -}}
{{if (and $hasHeaders (not $isRef)) -}}
type {{$opid}}{{$statusCode}}ResponseHeaders struct {
{{range .Headers -}}
{{.GoName}} {{.Schema.TypeDecl}}
{{end -}}
}
{{end}}
{{range .Contents}}
{{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}}
{{if and $fixedStatusCode $isRef -}}
{{ if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) (eq .NameTag "Multipart") -}}
type {{$receiverTypeName}} {{$ref}}{{.NameTagOrContentType}}Response
{{else if $isExternalRef -}}
type {{$receiverTypeName}} struct { {{$ref}} }
{{else -}}
type {{$receiverTypeName}} struct{ {{$ref}}{{.NameTagOrContentType}}Response }
{{end}}
{{else if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}}
type {{$receiverTypeName}} {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if and .Schema.IsRef (not .Schema.IsExternalRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}}
{{else -}}
type {{$receiverTypeName}} struct {
Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}}
{{if $hasHeaders -}}
Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders
{{end -}}
{{if not $fixedStatusCode -}}
StatusCode int
{{end -}}
{{if not .HasFixedContentType -}}
ContentType string
{{end -}}
{{if not .IsSupported -}}
ContentLength int64
{{end -}}
}
{{end}}
func (response {{$receiverTypeName}}) Visit{{$opid}}Response(w http.ResponseWriter) error {
{{if eq .NameTag "Multipart" -}}
writer := multipart.NewWriter(w)
{{end -}}
w.Header().Set("Content-Type", {{if eq .NameTag "Multipart"}}{{if eq .ContentType "multipart/form-data"}}writer.FormDataContentType(){{else}}mime.FormatMediaType("{{.ContentType}}", map[string]string{"boundary": writer.Boundary()}){{end}}{{else if .HasFixedContentType }}"{{.ContentType}}"{{else}}response.ContentType{{end}})
{{if not .IsSupported -}}
if response.ContentLength != 0 {
w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength))
}
{{end -}}
{{range $headers -}}
w.Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}}))
{{end -}}
w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}})
{{$hasBodyVar := or ($hasHeaders) (not $fixedStatusCode) (not .IsSupported)}}
{{if .IsJSON -}}
{{$hasUnionElements := ne 0 (len .Schema.UnionElements)}}
return json.NewEncoder(w).Encode(response{{if $hasBodyVar}}.Body{{end}}{{if $hasUnionElements}}.union{{end}})
{{else if eq .NameTag "Text" -}}
_, err := w.Write([]byte({{if $hasBodyVar}}response.Body{{else}}response{{end}}))
return err
{{else if eq .NameTag "Formdata" -}}
if form, err := runtime.MarshalForm({{if $hasBodyVar}}response.Body{{else}}response{{end}}, nil); err != nil {
return err
} else {
_, err := w.Write([]byte(form.Encode()))
return err
}
{{else if eq .NameTag "Multipart" -}}
defer writer.Close()
return {{if $hasBodyVar}}response.Body{{else}}response{{end}}(writer);
{{else -}}
if closer, ok := response.Body.(io.ReadCloser); ok {
defer closer.Close()
}
_, err := io.Copy(w, response.Body)
return err
{{end}}{{/* if eq .NameTag "JSON" */ -}}
}
{{end}}
{{if eq 0 (len .Contents) -}}
{{if and $fixedStatusCode $isRef -}}
type {{$opid}}{{$statusCode}}Response {{if not $isExternalRef}}={{end}} {{$ref}}Response
{{else -}}
type {{$opid}}{{$statusCode}}Response struct {
{{if $hasHeaders -}}
Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders
{{end}}
{{if not $fixedStatusCode -}}
StatusCode int
{{end -}}
}
{{end -}}
func (response {{$opid}}{{$statusCode}}Response) Visit{{$opid}}Response(w http.ResponseWriter) error {
{{range $headers -}}
w.Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}}))
{{end -}}
w.WriteHeader({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}})
return nil
}
{{end}}
{{end}}
{{end}}
// StrictServerInterface represents all server handlers.
type StrictServerInterface interface {
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{$opid := .OperationId -}}
{{$opid}}(ctx context.Context, request {{$opid | ucFirst}}RequestObject) ({{$opid | ucFirst}}ResponseObject, error)
{{end}}{{/* range . */ -}}
}

View file

@ -0,0 +1,147 @@
{{range .}}
{{$opid := .OperationId -}}
type {{$opid | ucFirst}}RequestObject struct {
{{range .PathParams -}}
{{.GoName | ucFirst}} {{.TypeDef}} {{.JsonTag}}
{{end -}}
{{if .RequiresParamObject -}}
Params {{$opid}}Params
{{end -}}
{{if .HasMaskedRequestContentTypes -}}
ContentType string
{{end -}}
{{$multipleBodies := gt (len .Bodies) 1 -}}
{{range .Bodies -}}
{{if $multipleBodies}}{{.NameTag}}{{end}}Body {{if eq .NameTag "Multipart"}}*multipart.Reader{{else if ne .NameTag ""}}*{{$opid}}{{.NameTag}}RequestBody{{else}}io.Reader{{end}}
{{end -}}
}
type {{$opid | ucFirst}}ResponseObject interface {
Visit{{$opid}}Response(ctx iris.Context) error
}
{{range .Responses}}
{{$statusCode := .StatusCode -}}
{{$hasHeaders := ne 0 (len .Headers) -}}
{{$fixedStatusCode := .HasFixedStatusCode -}}
{{$isRef := .IsRef -}}
{{$isExternalRef := .IsExternalRef -}}
{{$ref := .Ref | ucFirstWithPkgName -}}
{{$headers := .Headers -}}
{{if (and $hasHeaders (not $isRef)) -}}
type {{$opid}}{{$statusCode}}ResponseHeaders struct {
{{range .Headers -}}
{{.GoName}} {{.Schema.TypeDecl}}
{{end -}}
}
{{end}}
{{range .Contents}}
{{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}}
{{if eq .NameTag "Text" -}}
type {{$receiverTypeName}} string
{{else if and $fixedStatusCode $isRef -}}
{{ if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) (eq .NameTag "Multipart") -}}
type {{$receiverTypeName}} {{$ref}}{{.NameTagOrContentType}}Response
{{else if $isExternalRef -}}
type {{$receiverTypeName}} struct { {{$ref}} }
{{else -}}
type {{$receiverTypeName}} struct{ {{$ref}}{{.NameTagOrContentType}}Response }
{{end}}
{{else if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) -}}
type {{$receiverTypeName}} {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if and .Schema.IsRef (not .Schema.IsExternalRef)}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}}
{{else -}}
type {{$receiverTypeName}} struct {
Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}}
{{if $hasHeaders -}}
Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders
{{end -}}
{{if not $fixedStatusCode -}}
StatusCode int
{{end -}}
{{if not .HasFixedContentType -}}
ContentType string
{{end -}}
{{if not .IsSupported -}}
ContentLength int64
{{end -}}
}
{{end}}
func (response {{$receiverTypeName}}) Visit{{$opid}}Response(ctx iris.Context) error {
{{range $headers -}}
ctx.ResponseWriter().Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}}))
{{end -}}
{{if eq .NameTag "Multipart" -}}
writer := multipart.NewWriter(ctx.ResponseWriter())
{{end -}}
ctx.ResponseWriter().Header().Set("Content-Type", {{if eq .NameTag "Multipart"}}{{if eq .ContentType "multipart/form-data"}}writer.FormDataContentType(){{else}}mime.FormatMediaType("{{.ContentType}}", map[string]string{"boundary": writer.Boundary()}){{end}}{{else if .HasFixedContentType }}"{{.ContentType}}"{{else}}response.ContentType{{end}})
{{if not .IsSupported -}}
if response.ContentLength != 0 {
ctx.ResponseWriter().Header().Set("Content-Length", fmt.Sprint(response.ContentLength))
}
{{end -}}
ctx.StatusCode({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}})
{{$hasBodyVar := or ($hasHeaders) (not $fixedStatusCode) (not .IsSupported)}}
{{if .IsJSON -}}
{{$hasUnionElements := ne 0 (len .Schema.UnionElements)}}
return ctx.JSON(&{{if $hasBodyVar}}response.Body{{else}}response{{end}}{{if $hasUnionElements}}.union{{end}})
{{else if eq .NameTag "Text" -}}
_, err := ctx.WriteString(string({{if $hasBodyVar}}response.Body{{else}}response{{end}}))
return err
{{else if eq .NameTag "Formdata" -}}
if form, err := runtime.MarshalForm({{if $hasBodyVar}}response.Body{{else}}response{{end}}, nil); err != nil {
return err
} else {
_, err := ctx.WriteString(form.Encode())
return err
}
{{else if eq .NameTag "Multipart" -}}
defer writer.Close()
return {{if $hasBodyVar}}response.Body{{else}}response{{end}}(writer);
{{else -}}
if closer, ok := response.Body.(io.ReadCloser); ok {
defer closer.Close()
}
_, err := io.Copy(ctx.ResponseWriter(), response.Body)
return err
{{end}}{{/* if eq .NameTag "JSON" */ -}}
}
{{end}}
{{if eq 0 (len .Contents) -}}
{{if and $fixedStatusCode $isRef -}}
type {{$opid}}{{$statusCode}}Response {{if not $isExternalRef}}={{end}} {{$ref}}Response
{{else -}}
type {{$opid}}{{$statusCode}}Response struct {
{{if $hasHeaders -}}
Headers {{if $isRef}}{{$ref}}{{else}}{{$opid}}{{$statusCode}}{{end}}ResponseHeaders
{{end}}
{{if not $fixedStatusCode -}}
StatusCode int
{{end -}}
}
{{end -}}
func (response {{$opid}}{{$statusCode}}Response) Visit{{$opid}}Response(ctx iris.Context) error {
{{range $headers -}}
ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}}))
{{end -}}
ctx.StatusCode({{if $fixedStatusCode}}{{$statusCode}}{{else}}response.StatusCode{{end}})
return nil
}
{{end}}
{{end}}
{{end}}
// StrictServerInterface represents all server handlers.
type StrictServerInterface interface {
{{range .}}{{.SummaryAsComment }}
// ({{.Method}} {{.Path}})
{{$opid := .OperationId -}}
{{$opid}}(ctx context.Context, request {{$opid | ucFirst}}RequestObject) ({{$opid | ucFirst}}ResponseObject, error)
{{end}}{{/* range . */ -}}
}

View file

@ -0,0 +1,107 @@
type StrictHandlerFunc = strictiris.StrictIrisHandlerFunc
type StrictMiddlewareFunc = strictiris.StrictIrisMiddlewareFunc
func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
return &strictHandler{ssi: ssi, middlewares: middlewares}
}
type strictHandler struct {
ssi StrictServerInterface
middlewares []StrictMiddlewareFunc
}
{{range .}}
{{$opid := .OperationId}}
// {{$opid}} operation middleware
func (sh *strictHandler) {{.OperationId}}(ctx iris.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params {{.OperationId}}Params{{end}}) {
var request {{$opid | ucFirst}}RequestObject
{{range .PathParams -}}
request.{{.GoName}} = {{.GoVariableName}}
{{end -}}
{{if .RequiresParamObject -}}
request.Params = params
{{end -}}
{{ if .HasMaskedRequestContentTypes -}}
request.ContentType = ctx.GetContentTypeRequested()
{{end -}}
{{$multipleBodies := gt (len .Bodies) 1 -}}
{{range .Bodies -}}
{{if $multipleBodies}}if strings.HasPrefix(ctx.GetHeader("Content-Type"), "{{.ContentType}}") { {{end}}
{{if .IsJSON }}
var body {{$opid}}{{.NameTag}}RequestBody
if err := ctx.ReadJSON(&body); err != nil {
ctx.StopWithError(http.StatusBadRequest, err)
return
}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else if eq .NameTag "Formdata" -}}
if err := ctx.Request().ParseForm(); err != nil {
ctx.StopWithError(http.StatusBadRequest, err)
return
}
var body {{$opid}}{{.NameTag}}RequestBody
if err := runtime.BindForm(&body, ctx.Request().Form, nil, nil); err != nil {
ctx.StopWithError(http.StatusBadRequest, err)
return
}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else if eq .NameTag "Multipart" -}}
{{if eq .ContentType "multipart/form-data" -}}
if reader, err := ctx.Request().MultipartReader(); err == nil {
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader
} else {
ctx.StopWithError(http.StatusBadRequest, err)
return
}
{{else -}}
if _, params, err := mime.ParseMediaType(ctx.Request().Header.Get("Content-Type")); err != nil {
ctx.StopWithError(http.StatusBadRequest, err)
return
} else if boundary := params["boundary"]; boundary == "" {
ctx.StopWithError(http.StatusBadRequest, http.ErrMissingBoundary)
return
} else {
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = multipart.NewReader(ctx.Request().Body, boundary)
}
{{end -}}
{{else if eq .NameTag "Text" -}}
data, err := io.ReadAll(ctx.Request().Body)
if err != nil {
ctx.StopWithError(http.StatusBadRequest, err)
return
}
body := {{$opid}}{{.NameTag}}RequestBody(data)
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
{{else -}}
request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = ctx.Request().Body
{{end}}{{/* if eq .NameTag "JSON" */ -}}
{{if $multipleBodies}}}{{end}}
{{end}}{{/* range .Bodies */}}
handler := func(ctx iris.Context, request interface{}) (interface{}, error) {
return sh.ssi.{{.OperationId}}(ctx, request.({{$opid | ucFirst}}RequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "{{.OperationId}}")
}
response, err := handler(ctx, request)
if err != nil {
ctx.StopWithError(http.StatusBadRequest, err)
return
} else if validResponse, ok := response.({{$opid | ucFirst}}ResponseObject); ok {
if err := validResponse.Visit{{$opid}}Response(ctx); err != nil {
ctx.StopWithError(http.StatusBadRequest, err)
return
}
} else if response != nil {
ctx.Writef("Unexpected response type: %T", response)
return
}
}
{{end}}

View file

@ -0,0 +1,41 @@
{{range . -}}
{{$hasHeaders := ne 0 (len .Headers) -}}
{{$name := .GoName | ucFirst -}}
{{if $hasHeaders -}}
type {{$name}}ResponseHeaders struct {
{{range .Headers -}}
{{.GoName}} {{.Schema.TypeDecl}}
{{end -}}
}
{{end -}}
{{range .Contents -}}
{{if and (not $hasHeaders) (.IsSupported) -}}
type {{$name}}{{.NameTagOrContentType}}Response {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{if .Schema.IsRef}}={{end}} {{.Schema.TypeDecl}}{{else}}io.Reader{{end}}
{{else -}}
type {{$name}}{{.NameTagOrContentType}}Response struct {
Body {{if eq .NameTag "Multipart"}}func(writer *multipart.Writer)error{{else if .IsSupported}}{{.Schema.TypeDecl}}{{else}}io.Reader{{end}}
{{if $hasHeaders -}}
Headers {{$name}}ResponseHeaders
{{end -}}
{{if not .HasFixedContentType -}}
ContentType string
{{end -}}
{{if not .IsSupported -}}
ContentLength int64
{{end -}}
}
{{end -}}
{{end -}}
{{if eq 0 (len .Contents) -}}
type {{$name}}Response struct {
{{if $hasHeaders -}}
Headers {{$name}}ResponseHeaders
{{end}}
}
{{end}}
{{end -}}

View file

@ -0,0 +1,4 @@
{{range .Types}}
{{ if .Schema.Description }}{{ toGoComment .Schema.Description .TypeName }}{{ else }}// {{.TypeName}} defines model for {{.JsonName}}.{{ end }}
type {{.TypeName}} {{if .IsAlias }}={{end}} {{.Schema.TypeDecl}}
{{end}}

View file

@ -0,0 +1,72 @@
{{range .Types}}
{{$addType := .Schema.AdditionalPropertiesType.TypeDecl}}
{{$typeName := .TypeName -}}
{{$discriminator := .Schema.Discriminator}}
{{$properties := .Schema.Properties -}}
// Override default JSON handling for {{.TypeName}} to handle AdditionalProperties and union
func (a *{{.TypeName}}) UnmarshalJSON(b []byte) error {
err := a.union.UnmarshalJSON(b)
if err != nil {
return err
}
object := make(map[string]json.RawMessage)
err = json.Unmarshal(b, &object)
if err != nil {
return err
}
{{range .Schema.Properties}}
if raw, found := object["{{.JsonFieldName}}"]; found {
err = json.Unmarshal(raw, &a.{{.GoFieldName}})
if err != nil {
return fmt.Errorf("error reading '{{.JsonFieldName}}': %w", err)
}
delete(object, "{{.JsonFieldName}}")
}
{{end}}
if len(object) != 0 {
a.AdditionalProperties = make(map[string]{{$addType}})
for fieldName, fieldBuf := range object {
var fieldVal {{$addType}}
err := json.Unmarshal(fieldBuf, &fieldVal)
if err != nil {
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
}
a.AdditionalProperties[fieldName] = fieldVal
}
}
return nil
}
// Override default JSON handling for {{.TypeName}} to handle AdditionalProperties and union
func (a {{.TypeName}}) MarshalJSON() ([]byte, error) {
var err error
b, err := a.union.MarshalJSON()
if err != nil {
return nil, err
}
object := make(map[string]json.RawMessage)
if a.union != nil {
err = json.Unmarshal(b, &object)
if err != nil {
return nil, err
}
}
{{range .Schema.Properties}}
{{if not .Required}}if a.{{.GoFieldName}} != nil { {{end}}
object["{{.JsonFieldName}}"], err = json.Marshal(a.{{.GoFieldName}})
if err != nil {
return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err)
}
{{if not .Required}} }{{end}}
{{end}}
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
if err != nil {
return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
}
}
return json.Marshal(object)
}
{{end}}

View file

@ -0,0 +1,140 @@
{{range .Types}}
{{$typeName := .TypeName -}}
{{$discriminator := .Schema.Discriminator}}
{{$properties := .Schema.Properties -}}
{{range .Schema.UnionElements}}
{{$element := . -}}
// As{{ .Method }} returns the union data inside the {{$typeName}} as a {{.}}
func (t {{$typeName}}) As{{ .Method }}() ({{.}}, error) {
var body {{.}}
err := json.Unmarshal(t.union, &body)
return body, err
}
// From{{ .Method }} overwrites any union data inside the {{$typeName}} as the provided {{.}}
func (t *{{$typeName}}) From{{ .Method }} (v {{.}}) error {
{{if $discriminator -}}
{{range $value, $type := $discriminator.Mapping -}}
{{if eq $type $element -}}
{{$hasProperty := false -}}
{{range $properties -}}
{{if eq .GoFieldName $discriminator.PropertyName -}}
t.{{$discriminator.PropertyName}} = "{{$value}}"
{{$hasProperty = true -}}
{{end -}}
{{end -}}
{{if not $hasProperty}}v.{{$discriminator.PropertyName}} = "{{$value}}"{{end}}
{{end -}}
{{end -}}
{{end -}}
b, err := json.Marshal(v)
t.union = b
return err
}
// Merge{{ .Method }} performs a merge with any union data inside the {{$typeName}}, using the provided {{.}}
func (t *{{$typeName}}) Merge{{ .Method }} (v {{.}}) error {
{{if $discriminator -}}
{{range $value, $type := $discriminator.Mapping -}}
{{if eq $type $element -}}
{{$hasProperty := false -}}
{{range $properties -}}
{{if eq .GoFieldName $discriminator.PropertyName -}}
t.{{$discriminator.PropertyName}} = "{{$value}}"
{{$hasProperty = true -}}
{{end -}}
{{end -}}
{{if not $hasProperty}}v.{{$discriminator.PropertyName}} = "{{$value}}"{{end}}
{{end -}}
{{end -}}
{{end -}}
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
{{end}}
{{if $discriminator}}
func (t {{.TypeName}}) Discriminator() (string, error) {
var discriminator struct {
Discriminator string {{$discriminator.JSONTag}}
}
err := json.Unmarshal(t.union, &discriminator)
return discriminator.Discriminator, err
}
{{if ne 0 (len $discriminator.Mapping)}}
func (t {{.TypeName}}) ValueByDiscriminator() (interface{}, error) {
discriminator, err := t.Discriminator()
if err != nil {
return nil, err
}
switch discriminator{
{{range $value, $type := $discriminator.Mapping -}}
case "{{$value}}":
return t.As{{$type}}()
{{end -}}
default:
return nil, errors.New("unknown discriminator value: "+discriminator)
}
}
{{end}}
{{end}}
{{if not .Schema.HasAdditionalProperties}}
func (t {{.TypeName}}) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
{{if ne 0 (len .Schema.Properties) -}}
if err != nil {
return nil, err
}
object := make(map[string]json.RawMessage)
if t.union != nil {
err = json.Unmarshal(b, &object)
if err != nil {
return nil, err
}
}
{{range .Schema.Properties}}
{{if not .Required}}if t.{{.GoFieldName}} != nil { {{end}}
object["{{.JsonFieldName}}"], err = json.Marshal(t.{{.GoFieldName}})
if err != nil {
return nil, fmt.Errorf("error marshaling '{{.JsonFieldName}}': %w", err)
}
{{if not .Required}} }{{end}}
{{end -}}
b, err = json.Marshal(object)
{{end -}}
return b, err
}
func (t *{{.TypeName}}) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
{{if ne 0 (len .Schema.Properties) -}}
if err != nil {
return err
}
object := make(map[string]json.RawMessage)
err = json.Unmarshal(b, &object)
if err != nil {
return err
}
{{range .Schema.Properties}}
if raw, found := object["{{.JsonFieldName}}"]; found {
err = json.Unmarshal(raw, &t.{{.GoFieldName}})
if err != nil {
return fmt.Errorf("error reading '{{.JsonFieldName}}': %w", err)
}
}
{{end}}
{{end -}}
return err
}
{{end}}
{{end}}

View file

@ -0,0 +1,15 @@
{
"title": "node",
"type": "object",
"description": "Represents a node",
"properties": {
"id": {
"type": "string",
"format": "uri-reference"
},
"type": {
"type": "string",
"pattern": "^[A-Z][a-zA-Z0-9]*$"
}
}
}

View file

@ -0,0 +1,207 @@
openapi: 3.0.1
info:
title: OpenAPI-CodeGen Test
description: 'This is a test OpenAPI Spec'
version: 1.0.0
servers:
- url: https://test.oapi-codegen.com/v2
- url: http://test.oapi-codegen.com/v2
paths:
/test/{name}:
get:
tags:
- test
summary: Get test
operationId: getTestByName
parameters:
- name: name
in: path
required: true
schema:
type: string
- name: $top
in: query
required: false
schema:
type: integer
responses:
200:
description: Success
content:
application/xml:
schema:
type: array
items:
$ref: '#/components/schemas/Test'
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Test'
422:
description: InvalidArray
content:
application/xml:
schema:
type: array
application/json:
schema:
type: array
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/cat:
get:
tags:
- cat
summary: Get cat status
operationId: getCatStatus
responses:
200:
description: Success
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/CatAlive'
- $ref: '#/components/schemas/CatDead'
application/xml:
schema:
anyOf:
- $ref: '#/components/schemas/CatAlive'
- $ref: '#/components/schemas/CatDead'
application/yaml:
schema:
allOf:
- $ref: '#/components/schemas/CatAlive'
- $ref: '#/components/schemas/CatDead'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/enum:
get:
tags:
- enum
summary: References enum
operationId: getEnum
responses:
200:
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/EnumTest'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/user:
get:
tags:
- mergeAllOf
summary: Merges allOf ref-ing a JSON schema
operationId: getUser
responses:
200:
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/User'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Test:
properties:
name:
type: string
cases:
type: array
items:
$ref: '#/components/schemas/TestCase'
TestCase:
properties:
name:
type: string
command:
type: string
Error:
properties:
code:
type: integer
format: int32
message:
type: string
CatAlive:
properties:
name:
type: string
alive_since:
type: string
format: date-time
CatDead:
properties:
name:
type: string
dead_since:
type: string
format: date-time
x-oapi-codegen-extra-tags:
tag1: value1
tag2: value2
cause:
type: string
enum: [ car, dog, oldage ]
EnumTest:
properties:
numerics:
type: integer
enum: [0, 1, 2]
enumNames:
type: integer
enum: [0, 1, 2]
x-enumNames:
- zero
- one
- two
enumVarnames:
type: integer
enum: [0, 1, 2]
x-enum-varnames:
- na
- single
- double
User:
allOf:
- $ref: ./test_schema.json
- type: object
additionalProperties: false
properties:
name:
type: string
description: User name

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,79 @@
package util
import (
"fmt"
"strings"
)
// The input mapping is expressed on the command line as `key1:value1,key2:value2,...`
// We parse it here, but need to keep in mind that keys or values may contain
// commas and colons. We will allow escaping those using double quotes, so
// when passing in "key1":"value1", we will not look inside the quoted sections.
func ParseCommandlineMap(src string) (map[string]string, error) {
result := make(map[string]string)
tuples := splitString(src, ',')
for _, t := range tuples {
kv := splitString(t, ':')
if len(kv) != 2 {
return nil, fmt.Errorf("expected key:value, got :%s", t)
}
key := strings.TrimLeft(kv[0], `"`)
key = strings.TrimRight(key, `"`)
value := strings.TrimLeft(kv[1], `"`)
value = strings.TrimRight(value, `"`)
result[key] = value
}
return result, nil
}
// ParseCommandLineList parses comma separated string lists which are passed
// in on the command line. Spaces are trimmed off both sides of result
// strings.
func ParseCommandLineList(input string) []string {
input = strings.TrimSpace(input)
if len(input) == 0 {
return nil
}
splitInput := strings.Split(input, ",")
args := make([]string, 0, len(splitInput))
for _, s := range splitInput {
s = strings.TrimSpace(s)
if len(s) > 0 {
args = append(args, s)
}
}
return args
}
// splitString splits a string along the specified separator, but it
// ignores anything between double quotes for splitting. We do simple
// inside/outside quote counting. Quotes are not stripped from output.
func splitString(s string, sep rune) []string {
const escapeChar rune = '"'
var parts []string
var part string
inQuotes := false
for _, c := range s {
if c == escapeChar {
if inQuotes {
inQuotes = false
} else {
inQuotes = true
}
}
// If we've gotten the separator rune, consider the previous part
// complete, but only if we're outside of quoted sections
if c == sep && !inQuotes {
parts = append(parts, part)
part = ""
continue
}
part += string(c)
}
return append(parts, part)
}

View file

@ -0,0 +1,14 @@
package util
import (
"mime"
"strings"
)
func IsMediaTypeJson(mediaType string) bool {
parsed, _, err := mime.ParseMediaType(mediaType)
if err != nil {
return false
}
return parsed == "application/json" || strings.HasSuffix(parsed, "+json")
}

View file

@ -0,0 +1,94 @@
package util
import (
"bytes"
"fmt"
"net/url"
"strings"
"github.com/getkin/kin-openapi/openapi3"
"github.com/speakeasy-api/openapi-overlay/pkg/loader"
"gopkg.in/yaml.v3"
)
func LoadSwagger(filePath string) (swagger *openapi3.T, err error) {
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
u, err := url.Parse(filePath)
if err == nil && u.Scheme != "" && u.Host != "" {
return loader.LoadFromURI(u)
} else {
return loader.LoadFromFile(filePath)
}
}
// Deprecated: In kin-openapi v0.126.0 (https://github.com/getkin/kin-openapi/tree/v0.126.0?tab=readme-ov-file#v01260) the Circular Reference Counter functionality was removed, instead resolving all references with backtracking, to avoid needing to provide a limit to reference counts.
//
// This is now identital in method as `LoadSwagger`.
func LoadSwaggerWithCircularReferenceCount(filePath string, _ int) (swagger *openapi3.T, err error) {
return LoadSwagger(filePath)
}
type LoadSwaggerWithOverlayOpts struct {
Path string
Strict bool
}
func LoadSwaggerWithOverlay(filePath string, opts LoadSwaggerWithOverlayOpts) (swagger *openapi3.T, err error) {
spec, err := LoadSwagger(filePath)
if err != nil {
return nil, fmt.Errorf("failed to load OpenAPI specification: %w", err)
}
if opts.Path == "" {
return spec, nil
}
// parse out the yaml.Node, which is required by the overlay library
data, err := yaml.Marshal(spec)
if err != nil {
return nil, fmt.Errorf("failed to marshal spec from %#v as YAML: %w", filePath, err)
}
var node yaml.Node
err = yaml.NewDecoder(bytes.NewReader(data)).Decode(&node)
if err != nil {
return nil, fmt.Errorf("failed to parse spec from %#v: %w", filePath, err)
}
overlay, err := loader.LoadOverlay(opts.Path)
if err != nil {
return nil, fmt.Errorf("failed to load Overlay from %#v: %v", opts.Path, err)
}
err = overlay.Validate()
if err != nil {
return nil, fmt.Errorf("The Overlay in %#v was not valid: %v", opts.Path, err)
}
if opts.Strict {
err, vs := overlay.ApplyToStrict(&node)
if err != nil {
return nil, fmt.Errorf("Failed to apply Overlay %#v to specification %#v: %v\nAdditionally, the following validation errors were found:\n- %s", opts.Path, filePath, err, strings.Join(vs, "\n- "))
}
} else {
err = overlay.ApplyTo(&node)
if err != nil {
return nil, fmt.Errorf("Failed to apply Overlay %#v to specification %#v: %v", opts.Path, filePath, err)
}
}
b, err := yaml.Marshal(&node)
if err != nil {
return nil, fmt.Errorf("Failed to serialize Overlay'd specification %#v: %v", opts.Path, err)
}
swagger, err = openapi3.NewLoader().LoadFromData(b)
if err != nil {
return nil, fmt.Errorf("Failed to serialize Overlay'd specification %#v: %v", opts.Path, err)
}
return swagger, nil
}

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.

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

@ -0,0 +1,32 @@
GOBASE=$(shell pwd)
GOBIN=$(GOBASE)/bin
help:
@echo "This is a helper makefile for oapi-codegen"
@echo "Targets:"
@echo " generate: regenerate all generated files"
@echo " test: run all tests"
@echo " gin_example generate gin example server code"
@echo " tidy tidy go mod"
$(GOBIN)/golangci-lint:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.55.2
.PHONY: tools
tools: $(GOBIN)/golangci-lint
lint: tools
$(GOBIN)/golangci-lint run ./...
lint-ci: tools
$(GOBIN)/golangci-lint run ./... --out-format=github-actions --timeout=5m
generate:
go generate ./...
test:
go test -cover ./...
tidy:
@echo "tidy..."
go mod tidy

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

@ -0,0 +1,6 @@
# 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 provides any runtime-specific code that the generated code that oapi-codegen generates may need, and therefore is expected to be used with [deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen).

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
}

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

@ -0,0 +1,318 @@
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
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
}
func marshalFormImpl(v reflect.Value, result url.Values, name string) {
switch v.Kind() {
case reflect.Interface, reflect.Ptr:
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()))
}
}

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

@ -0,0 +1,555 @@
// 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"
)
// 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
}
// 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': %v", paramName, err)
}
case ParamLocationPath:
value, err = url.PathUnescape(value)
if err != nil {
return fmt.Errorf("error unescaping path parameter '%s': %v", 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: %s", 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 {
// 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 {
// 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: %s", value, err)
}
return bindSplitPartsToDestinationArray(parts, dest)
}
// Try to bind the remaining types as a base type.
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: %s", 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.
func BindQueryParameter(style string, explode bool, required bool, paramName string,
queryParams url.Values, dest interface{}) 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{}
if required {
// 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":
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.
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 fmt.Errorf("query parameter '%s' is required", paramName)
} else {
// If an optional parameter is not found, we do nothing,
return nil
}
}
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.
if !fieldsPresent {
return nil
}
default:
// Primitive object case. We expect to have 1 value to
// unmarshal.
if len(values) == 0 {
if required {
return fmt.Errorf("query parameter '%s' is required", paramName)
} else {
return nil
}
}
if len(values) != 1 {
return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
}
if !found {
if required {
return fmt.Errorf("query parameter '%s' is required", paramName)
} 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, and we've successfully unmarshaled
// it, this assigns the new object to the pointer pointer.
if !required {
dv.Set(reflect.ValueOf(output))
}
return nil
} else {
values, found := queryParams[paramName]
if !found {
if required {
return fmt.Errorf("query parameter '%s' is required", paramName)
} else {
return nil
}
}
if len(values) != 1 {
return fmt.Errorf("parameter '%s' is not exploded, but is specified multiple times", paramName)
}
parts = strings.Split(values[0], ",")
}
var err error
switch k {
case reflect.Slice:
err = bindSplitPartsToDestinationArray(parts, output)
case reflect.Struct:
err = bindSplitPartsToDestinationStruct(paramName, parts, explode, output)
default:
if len(parts) == 0 {
if required {
return fmt.Errorf("query parameter '%s' is required", paramName)
} else {
return nil
}
}
if len(parts) != 1 {
return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
}
err = BindStringToObject(parts[0], output)
}
if err != nil {
return err
}
if !required {
dv.Set(reflect.ValueOf(output))
}
return nil
case "deepObject":
if !explode {
return errors.New("deepObjects must be exploded")
}
return UnmarshalDeepObject(dest, paramName, queryParams)
case "spaceDelimited", "pipeDelimited":
return fmt.Errorf("query arguments of style '%s' aren't yet supported", style)
default:
return fmt.Errorf("style '%s' on parameter '%s' is invalid", style, paramName)
}
}
// bindParamsToExplodedObject reflects the destination structure, and pulls the value for
// each settable field from the given parameters map. This is to deal with the
// exploded form styled object which may occupy any number of parameter names.
// 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
}

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

@ -0,0 +1,174 @@
// 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 {
var err error
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.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: %s", src, dst, err)
}
return nil
}
fallthrough
case reflect.Struct:
// if this is not of type Time or of type Date look to see if this is of type Binder.
if dstType, ok := dst.(Binder); ok {
return dstType.Bind(src)
}
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: %s", 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: %s", 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
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
}

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

@ -0,0 +1,371 @@
package runtime
import (
"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]
prefix := "[" + strings.Join(path, "][") + "]"
result = []string{
prefix + fmt.Sprintf("=%v", t),
}
}
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)
}
var i2 interface{}
err = json.Unmarshal(buf, &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.
for i := range fields {
fields[i] = paramName + 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
}
func UnmarshalDeepObject(dst interface{}, paramName string, params url.Values) 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):]
fieldNames = append(fieldNames, pName)
if len(pValues) != 1 {
return fmt.Errorf("%s has multiple values", pName)
}
fieldValues = append(fieldValues, pValues[0])
}
}
// 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))
}
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.
// TODO: why is this marked as an ineffassign?
tm, err = time.Parse(types.DateFormat, pathValues.value) //nolint:ineffassign,staticcheck
if err != nil {
return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %s", pathValues.value, err)
}
return fmt.Errorf("invalid date format: %w", 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))
}
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 {
// Gather up the values
nValues := len(pathValues.fields)
values := make([]string, nValues)
// We expect to have consecutive array indices in the map
for i := 0; i < nValues; i++ {
indexStr := strconv.Itoa(i)
fv, found := pathValues.fields[indexStr]
if !found {
return errors.New("array deepObjects must have consecutive indices")
}
values[i] = fv.value
}
// This could be cleaner, but we can call into assignPathValues to
// avoid recreating this logic.
for i := 0; i < nValues; i++ {
dstElem := dst.Index(i).Addr()
err := assignPathValues(dstElem.Interface(), fieldOrValue{value: values[i]})
if err != nil {
return fmt.Errorf("error binding array: %w", 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
}

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"
]
}

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

@ -0,0 +1,478 @@
// 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/json"
"errors"
"fmt"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/oapi-codegen/runtime/types"
"github.com/google/uuid"
)
// 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)
}
// Given an input value, such as a primitive type, array or object, turn it
// into a parameter based on style/explode definition, performing whatever
// escaping is necessary based on parameter location
func StyleParamWithLocation(style string, explode bool, paramName string, paramLocation ParamLocation, value interface{}) (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: %s", value, err)
}
return stylePrimitive(style, explode, paramName, paramLocation, string(b))
}
}
switch t.Kind() {
case reflect.Slice:
n := v.Len()
sliceVal := make([]interface{}, n)
for i := 0; i < n; i++ {
sliceVal[i] = v.Index(i).Interface()
}
return styleSlice(style, explode, paramName, paramLocation, sliceVal)
case reflect.Struct:
return styleStruct(style, explode, paramName, paramLocation, value)
case reflect.Map:
return styleMap(style, explode, paramName, paramLocation, value)
default:
return stylePrimitive(style, explode, paramName, paramLocation, value)
}
}
func styleSlice(style string, explode bool, paramName string, paramLocation ParamLocation, 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
switch style {
case "simple":
separator = ","
case "label":
prefix = "."
if explode {
separator = "."
} else {
separator = ","
}
case "matrix":
prefix = fmt.Sprintf(";%s=", paramName)
if explode {
separator = prefix
} else {
separator = ","
}
case "form":
prefix = fmt.Sprintf("%s=", paramName)
if explode {
separator = "&" + prefix
} else {
separator = ","
}
case "spaceDelimited":
prefix = fmt.Sprintf("%s=", paramName)
if explode {
separator = "&" + prefix
} else {
separator = " "
}
case "pipeDelimited":
prefix = fmt.Sprintf("%s=", paramName)
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)
parts[i] = part
if err != nil {
return "", fmt.Errorf("error formatting '%s': %s", 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, value interface{}) (string, error) {
if timeVal, ok := marshalKnownTypes(value); ok {
styledVal, err := stylePrimitive(style, explode, paramName, paramLocation, 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 := StyleParamWithLocation(style, explode, paramName, paramLocation, i2)
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': %s", paramName, err)
}
fieldDict[fieldName] = str
}
return processFieldDict(style, explode, paramName, paramLocation, fieldDict)
}
func styleMap(style string, explode bool, paramName string, paramLocation ParamLocation, value interface{}) (string, error) {
if style == "deepObject" {
if !explode {
return "", errors.New("deepObjects must be exploded")
}
return MarshalDeepObject(value, paramName)
}
dict, ok := value.(map[string]interface{})
if !ok {
return "", errors.New("map not of type map[string]interface{}")
}
fieldDict := make(map[string]string)
for fieldName, value := range dict {
str, err := primitiveToString(value)
if err != nil {
return "", fmt.Errorf("error formatting '%s': %s", paramName, err)
}
fieldDict[fieldName] = str
}
return processFieldDict(style, explode, paramName, paramLocation, fieldDict)
}
func processFieldDict(style string, explode bool, paramName string, paramLocation ParamLocation, 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)
parts = append(parts, k+"="+v)
}
} else {
for _, k := range sortedKeys(fieldDict) {
v := escapeParameterString(fieldDict[k], paramLocation)
parts = append(parts, k)
parts = append(parts, v)
}
}
}
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=", paramName)
}
case "form":
if explode {
separator = "&"
} else {
prefix = fmt.Sprintf("%s=", paramName)
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", paramName, 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, value interface{}) (string, error) {
strVal, err := primitiveToString(value)
if err != nil {
return "", err
}
var prefix string
switch style {
case "simple":
case "label":
prefix = "."
case "matrix":
prefix = fmt.Sprintf(";%s=", paramName)
case "form":
prefix = fmt.Sprintf("%s=", paramName)
default:
return "", fmt.Errorf("unsupported style '%s'", style)
}
return prefix + escapeParameterString(strVal, paramLocation), 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
}
// escapeParameterString escapes a parameter value bas 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.
func escapeParameterString(value string, paramLocation ParamLocation) string {
switch paramLocation {
case ParamLocationQuery:
return url.QueryEscape(value)
case ParamLocationPath:
return url.PathEscape(value)
default:
return value
}
}

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

@ -0,0 +1,43 @@
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.Time.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.Time.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
}

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

@ -0,0 +1,40 @@
package types
import (
"encoding/json"
"errors"
)
// 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) {
if !emailRegex.MatchString(string(e)) {
return nil, ErrValidationEmail
}
return json.Marshal(string(e))
}
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
}
*e = Email(s)
if !emailRegex.MatchString(s) {
return ErrValidationEmail
}
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))
}

View file

@ -0,0 +1,11 @@
package types
import "regexp"
const (
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
)
var (
emailRegex = regexp.MustCompile(emailRegexString)
)

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