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
}