chore: bump Go dependencies

This commit is contained in:
lzap 2025-08-03 15:24:15 +00:00 committed by Simon de Vlieger
parent b3d1e4cf13
commit e118df5dfd
1119 changed files with 126580 additions and 8706 deletions

View file

@ -111,7 +111,7 @@ func main() {
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(&flagAliasTypes, "alias-types", false, "Alias type declarations if possible.")
flag.BoolVar(&flagInitialismOverrides, "initialism-overrides", false, "Use initialism overrides.")
flag.Parse()
@ -271,6 +271,15 @@ func main() {
errExit("configuration error: %v\n", err)
}
if warnings := opts.Generate.Warnings(); len(warnings) > 0 {
out := "WARNING: A number of warning(s) were returned when validating the GenerateOptions:"
for k, v := range warnings {
out += "\n- " + k + ": " + v
}
_, _ = fmt.Fprint(os.Stderr, out)
}
// If the user asked to output configuration, output it to stdout and exit
if flagOutputConfig {
buf, err := yaml.Marshal(opts)
@ -297,11 +306,11 @@ func main() {
}
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")
fmt.Fprintln(os.Stderr, "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
opts.NoVCSVersionOverride = &noVCSVersionOverride
}
code, err := codegen.Generate(swagger, opts.Configuration)
@ -310,6 +319,9 @@ func main() {
}
if opts.OutputFile != "" {
if err := os.MkdirAll(filepath.Dir(opts.OutputFile), 0o755); err != nil {
errExit("error unable to create directory: %s\n", err)
}
err = os.WriteFile(opts.OutputFile, []byte(code), 0o644)
if err != nil {
errExit("error writing generated code to file: %s\n", err)

View file

@ -47,6 +47,9 @@ var globalState struct {
options Configuration
spec *openapi3.T
importMapping importMap
// initialismsMap stores initialisms as "lower(initialism) -> initialism" map.
// List of initialisms was taken from https://staticcheck.io/docs/configuration/options/#initialisms.
initialismsMap map[string]string
}
// goImport represents a go package to be imported in the generated code
@ -139,6 +142,12 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {
opts.OutputOptions.NameNormalizer, NameNormalizers.Options())
}
if nameNormalizerFunction != NameNormalizerFunctionToCamelCaseWithInitialisms && len(opts.OutputOptions.AdditionalInitialisms) > 0 {
return "", fmt.Errorf("you have specified `additional-initialisms`, but the `name-normalizer` is not set to `ToCamelCaseWithInitialisms`. Please specify `name-normalizer: ToCamelCaseWithInitialisms` or remove the `additional-initialisms` configuration")
}
globalState.initialismsMap = makeInitialismsMap(opts.OutputOptions.AdditionalInitialisms)
// This creates the golang templates text package
TemplateFunctions["opts"] = func() Configuration { return globalState.options }
t := template.New("oapi-codegen").Funcs(TemplateFunctions)
@ -193,6 +202,14 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {
MergeImports(xGoTypeImports, imprts)
}
var serverURLsDefinitions string
if opts.Generate.ServerURLs {
serverURLsDefinitions, err = GenerateServerURLs(t, spec)
if err != nil {
return "", fmt.Errorf("error generating Server URLs: %w", err)
}
}
var irisServerOut string
if opts.Generate.IrisServer {
irisServerOut, err = GenerateIrisServer(t, ops)
@ -317,6 +334,11 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {
return "", fmt.Errorf("error writing constants: %w", err)
}
_, err = w.WriteString(serverURLsDefinitions)
if err != nil {
return "", fmt.Errorf("error writing Server URLs: %w", err)
}
_, err = w.WriteString(typeDefinitions)
if err != nil {
return "", fmt.Errorf("error writing type definitions: %w", err)
@ -955,7 +977,9 @@ func GetUserTemplateText(inputData string) (template string, err error) {
return "", fmt.Errorf("failed to execute GET request data from %s: %w", inputData, err)
}
if resp != nil {
defer resp.Body.Close()
defer func() {
_ = resp.Body.Close()
}()
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return "", fmt.Errorf("got non %d status code on GET %s", resp.StatusCode, inputData)

View file

@ -126,12 +126,43 @@ type GenerateOptions struct {
Models bool `yaml:"models,omitempty"`
// EmbeddedSpec indicates whether to embed the swagger spec in the generated code
EmbeddedSpec bool `yaml:"embedded-spec,omitempty"`
// ServerURLs generates types for the `Server` definitions' URLs, instead of needing to provide your own values
ServerURLs bool `yaml:"server-urls,omitempty"`
}
func (oo GenerateOptions) Validate() map[string]string {
return nil
}
func (oo GenerateOptions) Warnings() map[string]string {
warnings := make(map[string]string)
if oo.StdHTTPServer {
if warning := oo.warningForStdHTTP(); warning != "" {
warnings["std-http-server"] = warning
}
}
return warnings
}
func (oo GenerateOptions) warningForStdHTTP() string {
pathToGoMod, mod, err := findAndParseGoModuleForDepth(".", maximumDepthToSearchForGoMod)
if err != nil {
return fmt.Sprintf("Encountered an error while trying to find a `go.mod` or a `tools.mod` in this directory, or %d levels above it: %v", maximumDepthToSearchForGoMod, err)
}
if mod == nil {
return fmt.Sprintf("Failed to find a `go.mod` or a `tools.mod` in this directory, or %d levels above it, so unable to validate that you're using Go 1.22+. If you start seeing API interactions resulting in a `404 page not found`, the Go directive (implying source compatibility for this module) needs to be bumped. See also: https://www.jvt.me/posts/2024/03/04/go-net-http-why-404/", maximumDepthToSearchForGoMod)
}
if !hasMinimalMinorGoDirective(minimumGoVersionForGenerateStdHTTPServer, mod) {
return fmt.Sprintf("Found a `go.mod` or a `tools.mod` at path %v, but it only had a version of %v, whereas the minimum required is 1.%d. It's very likely API interactions will result in a `404 page not found`. The Go directive (implying source compatibility for this module) needs to be bumped. See also: https://www.jvt.me/posts/2024/03/04/go-net-http-why-404/", pathToGoMod, mod.Go.Version, minimumGoVersionForGenerateStdHTTPServer)
}
return ""
}
// CompatibilityOptions specifies backward compatibility settings for the
// code generator.
type CompatibilityOptions struct {
@ -195,6 +226,14 @@ type CompatibilityOptions struct {
//
// 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"`
// PreserveOriginalOperationIdCasingInEmbeddedSpec ensures that the `operationId` from the source spec is kept intact in case when embedding it into the Embedded Spec output.
// When `oapi-codegen` parses the original OpenAPI specification, it will apply the configured `output-options.name-normalizer` to each operation's `operationId` before that is used to generate code from.
// However, this is also applied to the copy of the `operationId`s in the `embedded-spec` generation, which means that the embedded OpenAPI specification is then out-of-sync with the input specificiation.
// To ensure that the `operationId` in the embedded spec is preserved as-is from the input specification, set this.
// NOTE that this will not impact generated code.
// NOTE that if you're using `include-operation-ids` or `exclude-operation-ids` you may want to ensure that the `operationId`s used are correct.
PreserveOriginalOperationIdCasingInEmbeddedSpec bool `yaml:"preserve-original-operation-id-casing-in-embedded-spec"`
}
func (co CompatibilityOptions) Validate() map[string]string {
@ -226,6 +265,9 @@ type OutputOptions struct {
ClientTypeName string `yaml:"client-type-name,omitempty"`
// Whether to use the initialism overrides
InitialismOverrides bool `yaml:"initialism-overrides,omitempty"`
// AdditionalInitialisms is a list of additional initialisms to use when generating names.
// NOTE that this has no effect unless the `name-normalizer` is set to `ToCamelCaseWithInitialisms`
AdditionalInitialisms []string `yaml:"additional-initialisms,omitempty"`
// Whether to generate nullable type for nullable fields
NullableType bool `yaml:"nullable-type,omitempty"`
@ -239,9 +281,34 @@ type OutputOptions struct {
// 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"`
// EnableYamlTags adds YAML tags to generated structs, in addition to default JSON ones
EnableYamlTags bool `yaml:"yaml-tags,omitempty"`
// ClientResponseBytesFunction decides whether to enable the generation of a `Bytes()` method on response objects for `ClientWithResponses`
ClientResponseBytesFunction bool `yaml:"client-response-bytes-function,omitempty"`
// PreferSkipOptionalPointer allows defining at a global level whether to omit the pointer for a type to indicate that the field/type is optional.
// This is the same as adding `x-go-type-skip-optional-pointer` to each field (manually, or using an OpenAPI Overlay)
PreferSkipOptionalPointer bool `yaml:"prefer-skip-optional-pointer,omitempty"`
// PreferSkipOptionalPointerWithOmitzero allows generating the `omitzero` JSON tag types that would have had an optional pointer.
// This is the same as adding `x-omitzero` to each field (manually, or using an OpenAPI Overlay).
// A field can set `x-omitzero: false` to disable the `omitzero` JSON tag.
// NOTE that this must be used alongside `prefer-skip-optional-pointer`, otherwise makes no difference.
PreferSkipOptionalPointerWithOmitzero bool `yaml:"prefer-skip-optional-pointer-with-omitzero,omitempty"`
// PreferSkipOptionalPointerOnContainerTypes allows disabling the generation of an "optional pointer" for an optional field that is a container type (such as a slice or a map), which ends up requiring an additional, unnecessary, `... != nil` check
PreferSkipOptionalPointerOnContainerTypes bool `yaml:"prefer-skip-optional-pointer-on-container-types,omitempty"`
}
func (oo OutputOptions) Validate() map[string]string {
if NameNormalizerFunction(oo.NameNormalizer) != NameNormalizerFunctionToCamelCaseWithInitialisms && len(oo.AdditionalInitialisms) > 0 {
return map[string]string{
"additional-initialisms": "You have specified `additional-initialisms`, but the `name-normalizer` is not set to `ToCamelCaseWithInitialisms`. Please specify `name-normalizer: ToCamelCaseWithInitialisms` or remove the `additional-initialisms` configuration",
}
}
return nil
}

View file

@ -18,6 +18,7 @@ const (
extGoTypeName = "x-go-type-name"
extPropGoJsonIgnore = "x-go-json-ignore"
extPropOmitEmpty = "x-omitempty"
extPropOmitZero = "x-omitzero"
extPropExtraTags = "x-oapi-codegen-extra-tags"
extEnumVarNames = "x-enum-varnames"
extEnumNames = "x-enumNames"
@ -60,6 +61,14 @@ func extParseOmitEmpty(extPropValue interface{}) (bool, error) {
return omitEmpty, nil
}
func extParseOmitZero(extPropValue interface{}) (bool, error) {
omitZero, ok := extPropValue.(bool)
if !ok {
return false, fmt.Errorf("failed to convert type: %T", extPropValue)
}
return omitZero, nil
}
func extExtraTags(extPropValue interface{}) (map[string]string, error) {
tagsI, ok := extPropValue.(map[string]interface{})
if !ok {

View file

@ -0,0 +1,91 @@
package codegen
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"golang.org/x/mod/modfile"
)
const maximumDepthToSearchForGoMod = 5
// minimumGoVersionForGenerateStdHTTPServer indicates the Go 1.x minor version that the module the std-http-server is being generated into needs.
// If the version is lower, a warning should be logged.
const minimumGoVersionForGenerateStdHTTPServer = 22
func findAndParseGoModuleForDepth(dir string, maxDepth int) (string, *modfile.File, error) {
absDir, err := filepath.Abs(dir)
if err != nil {
return "", nil, fmt.Errorf("failed to determine absolute path for %v: %w", dir, err)
}
currentDir := absDir
for i := 0; i <= maxDepth; i++ {
goModPath := filepath.Join(currentDir, "go.mod")
if _, err := os.Stat(goModPath); err == nil {
goModContent, err := os.ReadFile(goModPath)
if err != nil {
return "", nil, fmt.Errorf("failed to read `go.mod`: %w", err)
}
mod, err := modfile.ParseLax("go.mod", goModContent, nil)
if err != nil {
return "", nil, fmt.Errorf("failed to parse `go.mod`: %w", err)
}
return goModPath, mod, nil
}
goModPath = filepath.Join(currentDir, "tools.mod")
if _, err := os.Stat(goModPath); err == nil {
goModContent, err := os.ReadFile(goModPath)
if err != nil {
return "", nil, fmt.Errorf("failed to read `tools.mod`: %w", err)
}
parsedModFile, err := modfile.ParseLax("tools.mod", goModContent, nil)
if err != nil {
return "", nil, fmt.Errorf("failed to parse `tools.mod`: %w", err)
}
return goModPath, parsedModFile, nil
}
parentDir := filepath.Dir(currentDir)
// NOTE that this may not work particularly well on Windows
if parentDir == "/" {
break
}
currentDir = parentDir
}
return "", nil, fmt.Errorf("no `go.mod` or `tools.mod` file found within %d levels upwards from %s", maxDepth, absDir)
}
// hasMinimalMinorGoDirective indicates that the Go module (`mod`) has a minor version greater than or equal to the `expected`'s
// This only applies to the `go` directive:
//
// go 1.23
// go 1.22.1
func hasMinimalMinorGoDirective(expected int, mod *modfile.File) bool {
parts := strings.Split(mod.Go.Version, ".")
if len(parts) < 2 {
return false
}
actual, err := strconv.Atoi(parts[1])
if err != nil {
return false
}
if actual < expected {
return false
}
return true
}

View file

@ -135,10 +135,17 @@ func (pd ParameterDefinition) GoName() string {
return SchemaNameToTypeName(goName)
}
// Deprecated: Use HasOptionalPointer, as it is clearer what the intent is.
func (pd ParameterDefinition) IndirectOptional() bool {
return !pd.Required && !pd.Schema.SkipOptionalPointer
}
// HasOptionalPointer indicates whether the generated property has an optional pointer associated with it.
// This takes into account the `x-go-type-skip-optional-pointer` extension, allowing a parameter definition to control whether the pointer should be skipped.
func (pd ParameterDefinition) HasOptionalPointer() bool {
return pd.Required == false && pd.Schema.SkipOptionalPointer == false //nolint:staticcheck
}
type ParameterDefinitions []ParameterDefinition
func (p ParameterDefinitions) FindByName(name string) *ParameterDefinition {
@ -208,7 +215,8 @@ func DescribeSecurityDefinition(securityRequirements openapi3.SecurityRequiremen
// OperationDefinition describes an Operation
type OperationDefinition struct {
OperationId string // The operation_id description from Swagger, used to generate function names
// OperationId is the `operationId` field from the OpenAPI Specification, after going through a `nameNormalizer`, and will be used to generate function names
OperationId string
PathParams []ParameterDefinition // Parameters in the path, eg, /path/:param
HeaderParams []ParameterDefinition // Parameters in HTTP headers
@ -299,7 +307,7 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefini
if contentType.Schema != nil {
responseSchema, err := GenerateGoSchema(contentType.Schema, []string{o.OperationId, responseName})
if err != nil {
return nil, fmt.Errorf("Unable to determine Go type for %s.%s: %w", o.OperationId, contentTypeName, err)
return nil, fmt.Errorf("unable to determine Go type for %s.%s: %w", o.OperationId, contentTypeName, err)
}
var typeName string
@ -308,7 +316,7 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefini
// HAL+JSON:
case StringInArray(contentTypeName, contentTypesHalJSON):
typeName = fmt.Sprintf("HALJSON%s", nameNormalizer(responseName))
case "application/json" == contentTypeName:
case contentTypeName == "application/json":
// if it's the standard application/json
typeName = fmt.Sprintf("JSON%s", nameNormalizer(responseName))
// Vendored JSON
@ -558,25 +566,34 @@ func OperationDefinitions(swagger *openapi3.T, initialismOverrides bool) ([]Oper
// Each path can have a number of operations, POST, GET, OPTIONS, etc.
pathOps := pathItem.Operations()
for _, opName := range SortedMapKeys(pathOps) {
// NOTE that this is a reference to the existing copy of the Operation, so any modifications will modify our shared copy of the spec
op := pathOps[opName]
if pathItem.Servers != nil {
op.Servers = &pathItem.Servers
}
// take a copy of operationId, so we don't modify the underlying spec
operationId := op.OperationID
// We rely on OperationID to generate function names, it's required
if op.OperationID == "" {
op.OperationID, err = generateDefaultOperationID(opName, requestPath, toCamelCaseFunc)
if operationId == "" {
operationId, err = generateDefaultOperationID(opName, requestPath, toCamelCaseFunc)
if err != nil {
return nil, fmt.Errorf("error generating default OperationID for %s/%s: %s",
opName, requestPath, err)
}
} else {
op.OperationID = nameNormalizer(op.OperationID)
operationId = nameNormalizer(operationId)
}
operationId = typeNamePrefix(operationId) + operationId
if !globalState.options.Compatibility.PreserveOriginalOperationIdCasingInEmbeddedSpec {
// update the existing, shared, copy of the spec if we're not wanting to preserve it
op.OperationID = operationId
}
op.OperationID = typeNamePrefix(op.OperationID) + op.OperationID
// These are parameters defined for the specific path method that
// we're iterating over.
localParams, err := DescribeParameters(op.Parameters, []string{op.OperationID + "Params"})
localParams, err := DescribeParameters(op.Parameters, []string{operationId + "Params"})
if err != nil {
return nil, fmt.Errorf("error describing global parameters for %s/%s: %s",
opName, requestPath, err)
@ -599,14 +616,14 @@ func OperationDefinitions(swagger *openapi3.T, initialismOverrides bool) ([]Oper
return nil, err
}
bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(op.OperationID, op.RequestBody)
bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(operationId, op.RequestBody)
if err != nil {
return nil, fmt.Errorf("error generating body definitions: %w", err)
}
ensureExternalRefsInRequestBodyDefinitions(&bodyDefinitions, pathItem.Ref)
responseDefinitions, err := GenerateResponseDefinitions(op.OperationID, op.Responses.Map())
responseDefinitions, err := GenerateResponseDefinitions(operationId, op.Responses.Map())
if err != nil {
return nil, fmt.Errorf("error generating response definitions: %w", err)
}
@ -618,7 +635,7 @@ func OperationDefinitions(swagger *openapi3.T, initialismOverrides bool) ([]Oper
HeaderParams: FilterParameterDefinitionByType(allParams, "header"),
QueryParams: FilterParameterDefinitionByType(allParams, "query"),
CookieParams: FilterParameterDefinitionByType(allParams, "cookie"),
OperationId: nameNormalizer(op.OperationID),
OperationId: nameNormalizer(operationId),
// Replace newlines in summary.
Summary: op.Summary,
Method: opName,

View file

@ -128,6 +128,12 @@ func (p Property) GoTypeDef() string {
return typeDef
}
// HasOptionalPointer indicates whether the generated property has an optional pointer associated with it.
// This takes into account the `x-go-type-skip-optional-pointer` extension, allowing a parameter definition to control whether the pointer should be skipped.
func (p Property) HasOptionalPointer() bool {
return p.Required == false && p.Schema.SkipOptionalPointer == false //nolint:staticcheck
}
// 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
@ -264,16 +270,19 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
sref.Ref, err)
}
return Schema{
GoType: refType,
Description: schema.Description,
DefineViaAlias: true,
OAPISchema: schema,
GoType: refType,
Description: schema.Description,
DefineViaAlias: true,
OAPISchema: schema,
SkipOptionalPointer: globalState.options.OutputOptions.PreferSkipOptionalPointer,
}, nil
}
outSchema := Schema{
Description: schema.Description,
OAPISchema: schema,
// NOTE that SkipOptionalPointer will be defaulted to the global value, but can be overridden on a per-type/-field basis
SkipOptionalPointer: globalState.options.OutputOptions.PreferSkipOptionalPointer,
}
// AllOf is interesting, and useful. It's the union of a number of other
@ -289,6 +298,16 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
return mergedSchema, 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
}
// Check x-go-type, which will completely override the definition of this
// schema with the provided type.
if extension, ok := schema.Extensions[extPropGoType]; ok {
@ -302,16 +321,6 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
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
@ -325,10 +334,13 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
// We have an object with no properties. This is a generic object
// expressed as a map.
outType = "map[string]interface{}"
setSkipOptionalPointerForContainerType(&outSchema)
} else { // t == ""
// If we don't even have the object designator, we're a completely
// generic type.
outType = "interface{}"
// this should never have an "optional pointer", as it doesn't make sense to be a `*interface{}`
outSchema.SkipOptionalPointer = true
}
outSchema.GoType = outType
outSchema.DefineViaAlias = true
@ -385,6 +397,7 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
// since we don't need them for a simple map.
outSchema.HasAdditionalProperties = false
outSchema.GoType = fmt.Sprintf("map[string]%s", additionalPropertiesType(outSchema))
setSkipOptionalPointerForContainerType(&outSchema)
return outSchema, nil
}
@ -580,40 +593,34 @@ func oapiSchemaToGoType(schema *openapi3.Schema, path []string, outSchema *Schem
if sliceContains(globalState.options.OutputOptions.DisableTypeAliasesForType, "array") {
outSchema.DefineViaAlias = false
}
setSkipOptionalPointerForContainerType(outSchema)
} 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 {
switch f {
case "int64",
"int32",
"int16",
"int8",
"int",
"uint64",
"uint32",
"uint16",
"uint8",
"uint":
outSchema.GoType = f
default:
outSchema.GoType = "int"
}
outSchema.DefineViaAlias = true
} else if t.Is("number") {
// We default to float for "number"
if f == "double" {
switch f {
case "double":
outSchema.GoType = "float64"
} else if f == "float" || f == "" {
case "float", "":
outSchema.GoType = "float32"
} else {
default:
return fmt.Errorf("invalid number format: %s", f)
}
outSchema.DefineViaAlias = true
@ -628,6 +635,7 @@ func oapiSchemaToGoType(schema *openapi3.Schema, path []string, outSchema *Schem
switch f {
case "byte":
outSchema.GoType = "[]byte"
setSkipOptionalPointerForContainerType(outSchema)
case "email":
outSchema.GoType = "openapi_types.Email"
case "date":
@ -667,6 +675,13 @@ type FieldDescriptor struct {
IsRef bool // Is this schema a reference to predefined object?
}
func stringOrEmpty(b bool, s string) string {
if b {
return s
}
return ""
}
// GenFieldsFromProperties produce corresponding field names with JSON annotations,
// given a list of schema descriptors
func GenFieldsFromProperties(props []Property) []string {
@ -690,8 +705,8 @@ func GenFieldsFromProperties(props []Property) []string {
// 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
if extDeprecationReason, err := extParseDeprecationReason(extension); err == nil {
deprecationReason = extDeprecationReason
}
}
@ -717,25 +732,37 @@ func GenFieldsFromProperties(props []Property) []string {
omitEmpty = shouldOmitEmpty
}
// Support x-omitempty
omitZero := false
// default, but allow turning of
if shouldOmitEmpty && p.Schema.SkipOptionalPointer && globalState.options.OutputOptions.PreferSkipOptionalPointerWithOmitzero {
omitZero = true
}
// Support x-omitempty and x-omitzero
if extOmitEmptyValue, ok := p.Extensions[extPropOmitEmpty]; ok {
if extOmitEmpty, err := extParseOmitEmpty(extOmitEmptyValue); err == nil {
omitEmpty = extOmitEmpty
if xValue, err := extParseOmitEmpty(extOmitEmptyValue); err == nil {
omitEmpty = xValue
}
}
if extOmitEmptyValue, ok := p.Extensions[extPropOmitZero]; ok {
if xValue, err := extParseOmitZero(extOmitEmptyValue); err == nil {
omitZero = xValue
}
}
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"
}
fieldTags["json"] = p.JsonFieldName +
stringOrEmpty(omitEmpty, ",omitempty") +
stringOrEmpty(omitZero, ",omitzero")
if globalState.options.OutputOptions.EnableYamlTags {
fieldTags["yaml"] = p.JsonFieldName + stringOrEmpty(omitEmpty, ",omitempty")
}
if p.NeedsFormTag {
fieldTags["form"] = p.JsonFieldName + stringOrEmpty(omitEmpty, ",omitempty")
}
// Support x-go-json-ignore
@ -889,3 +916,14 @@ func generateUnion(outSchema *Schema, elements openapi3.SchemaRefs, discriminato
return nil
}
// setSkipOptionalPointerForContainerType ensures that the "optional pointer" is skipped on container types (such as a slice or a map).
// This is controlled using the `prefer-skip-optional-pointer-on-container-types` Output Option
// NOTE that it is still possible to override this on a per-field basis with `x-go-type-skip-optional-pointer`
func setSkipOptionalPointerForContainerType(outSchema *Schema) {
if !globalState.options.OutputOptions.PreferSkipOptionalPointerOnContainerTypes {
return
}
outSchema.SkipOptionalPointer = true
}

View file

@ -0,0 +1,81 @@
package codegen
import (
"fmt"
"strconv"
"text/template"
"github.com/getkin/kin-openapi/openapi3"
)
const serverURLPrefix = "ServerUrl"
const serverURLSuffixIterations = 10
// ServerObjectDefinition defines the definition of an OpenAPI Server object (https://spec.openapis.org/oas/v3.0.3#server-object) as it is provided to code generation in `oapi-codegen`
type ServerObjectDefinition struct {
// GoName is the name of the variable for this Server URL
GoName string
// OAPISchema is the underlying OpenAPI representation of the Server
OAPISchema *openapi3.Server
}
func GenerateServerURLs(t *template.Template, spec *openapi3.T) (string, error) {
names := make(map[string]*openapi3.Server)
for _, server := range spec.Servers {
suffix := server.Description
if suffix == "" {
suffix = nameNormalizer(server.URL)
}
name := serverURLPrefix + UppercaseFirstCharacter(suffix)
name = nameNormalizer(name)
// if this is the only type with this name, store it
if _, conflict := names[name]; !conflict {
names[name] = server
continue
}
// otherwise, try appending a number to the name
saved := false
// NOTE that we start at 1 on purpose, as
//
// ... ServerURLDevelopmentServer
// ... ServerURLDevelopmentServer1`
//
// reads better than:
//
// ... ServerURLDevelopmentServer
// ... ServerURLDevelopmentServer0
for i := 1; i < 1+serverURLSuffixIterations; i++ {
suffixed := name + strconv.Itoa(i)
// and then store it if there's no conflict
if _, suffixConflict := names[suffixed]; !suffixConflict {
names[suffixed] = server
saved = true
break
}
}
if saved {
continue
}
// otherwise, error
return "", fmt.Errorf("failed to create a unique name for the Server URL (%#v) with description (%#v) after %d iterations", server.URL, server.Description, serverURLSuffixIterations)
}
keys := SortedMapKeys(names)
servers := make([]ServerObjectDefinition, len(keys))
i := 0
for _, k := range keys {
servers[i] = ServerObjectDefinition{
GoName: k,
OAPISchema: names[k],
}
i++
}
return GenerateTemplates([]string{"server-urls.tmpl"}, t, servers)
}

View file

@ -23,6 +23,7 @@ import (
"golang.org/x/text/cases"
"golang.org/x/text/language"
"github.com/getkin/kin-openapi/openapi3"
"github.com/oapi-codegen/oapi-codegen/v2/pkg/util"
)
@ -295,6 +296,26 @@ func stripNewLines(s string) string {
return r.Replace(s)
}
// genServerURLWithVariablesFunctionParams is a template helper method to generate the function parameters for the generated function for a Server object that contains `variables` (https://spec.openapis.org/oas/v3.0.3#server-object)
//
// goTypePrefix is the prefix being used to create underlying types in the template (likely the `ServerObjectDefinition.GoName`)
// variables are this `ServerObjectDefinition`'s variables for the Server object (likely the `ServerObjectDefinition.OAPISchema`)
func genServerURLWithVariablesFunctionParams(goTypePrefix string, variables map[string]*openapi3.ServerVariable) string {
keys := SortedMapKeys(variables)
if len(variables) == 0 {
return ""
}
parts := make([]string, len(variables))
for i := range keys {
k := keys[i]
variableDefinitionPrefix := goTypePrefix + UppercaseFirstCharacter(k) + "Variable"
parts[i] = k + " " + variableDefinitionPrefix
}
return strings.Join(parts, ", ")
}
// TemplateFunctions is passed to the template engine, and we can call each
// function here by keyName from the template code.
var TemplateFunctions = template.FuncMap{
@ -323,4 +344,6 @@ var TemplateFunctions = template.FuncMap{
"stripNewLines": stripNewLines,
"sanitizeGoIdentity": SanitizeGoIdentity,
"toGoComment": StringWithTypeNameToGoComment,
"genServerURLWithVariablesFunctionParams": genServerURLWithVariablesFunctionParams,
}

View file

@ -53,12 +53,12 @@ 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}}
{{if .HasOptionalPointer}}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}}
{{if .HasOptionalPointer}} }{{end}}
{{end}}
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
@ -69,4 +69,4 @@ func (a {{.TypeName}}) MarshalJSON() ([]byte, error) {
return json.Marshal(object)
}
{{end}}
{{end}}
{{end}}

View file

@ -58,7 +58,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
@ -69,7 +69,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}{{if .Required}} else {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
@ -98,7 +98,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
params.{{.GoName}} = {{if .HasOptionalPointer }}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
@ -117,7 +117,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}}
} {{if .Required}}else {
err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found")
@ -135,7 +135,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
if cookie, err = r.Cookie("{{.ParamName}}"); err == nil {
{{- if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value
{{end}}
{{- if .IsJson}}
@ -154,7 +154,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
{{- if .IsStyled}}
@ -164,7 +164,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}

View file

@ -74,6 +74,13 @@ func (r {{genResponseTypeName $opid | ucFirst}}) StatusCode() int {
}
return 0
}
{{ if opts.OutputOptions.ClientResponseBytesFunction }}
// Bytes is a convenience method to retrieve the raw bytes from the HTTP response
func (r {{genResponseTypeName $opid | ucFirst}}) Bytes() []byte {
return r.Body
}
{{end}}
{{end}}

View file

@ -197,12 +197,12 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
if params != nil {
queryValues := queryURL.Query()
{{range $paramIdx, $param := .QueryParams}}
{{if not .Required}} if params.{{.GoName}} != nil { {{end}}
{{if .HasOptionalPointer}} if params.{{.GoName}} != nil { {{end}}
{{if .IsPassThrough}}
queryValues.Add("{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}})
queryValues.Add("{{.ParamName}}", {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}})
{{end}}
{{if .IsJson}}
if queryParamBuf, err := json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}); err != nil {
if queryParamBuf, err := json.Marshal({{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}); err != nil {
return nil, err
} else {
queryValues.Add("{{.ParamName}}", string(queryParamBuf))
@ -210,7 +210,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
{{end}}
{{if .IsStyled}}
if queryFrag, err := runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationQuery, {{if not .Required}}*{{end}}params.{{.GoName}}); err != nil {
if queryFrag, err := runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationQuery, {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}); err != nil {
return nil, err
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
return nil, err
@ -222,7 +222,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
}
}
{{end}}
{{if not .Required}}}{{end}}
{{if .HasOptionalPointer}}}{{end}}
{{end}}
queryURL.RawQuery = queryValues.Encode()
}
@ -236,27 +236,27 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
{{ if .HeaderParams }}
if params != nil {
{{range $paramIdx, $param := .HeaderParams}}
{{if not .Required}} if params.{{.GoName}} != nil { {{end}}
{{if .HasOptionalPointer}} if params.{{.GoName}} != nil { {{end}}
var headerParam{{$paramIdx}} string
{{if .IsPassThrough}}
headerParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}}
headerParam{{$paramIdx}} = {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}
{{end}}
{{if .IsJson}}
var headerParamBuf{{$paramIdx}} []byte
headerParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}})
headerParamBuf{{$paramIdx}}, err = json.Marshal({{if .HasOptionalPointer}}*{{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}})
headerParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}})
if err != nil {
return nil, err
}
{{end}}
req.Header.Set("{{.ParamName}}", headerParam{{$paramIdx}})
{{if not .Required}}}{{end}}
{{if .HasOptionalPointer}}}{{end}}
{{end}}
}
{{- end }}{{/* if .HeaderParams */}}
@ -264,21 +264,21 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
{{ if .CookieParams }}
if params != nil {
{{range $paramIdx, $param := .CookieParams}}
{{if not .Required}} if params.{{.GoName}} != nil { {{end}}
{{if .HasOptionalPointer}} if params.{{.GoName}} != nil { {{end}}
var cookieParam{{$paramIdx}} string
{{if .IsPassThrough}}
cookieParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}}
cookieParam{{$paramIdx}} = {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}}
{{end}}
{{if .IsJson}}
var cookieParamBuf{{$paramIdx}} []byte
cookieParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}})
cookieParamBuf{{$paramIdx}}, err = json.Marshal({{if .HasOptionalPointer}}*{{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}})
cookieParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("simple", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationCookie, {{if .HasOptionalPointer}}*{{end}}params.{{.GoName}})
if err != nil {
return nil, err
}
@ -288,7 +288,7 @@ func New{{$opid}}Request{{if .HasBody}}WithBody{{end}}(server string{{genParamAr
Value:cookieParam{{$paramIdx}},
}
req.AddCookie(cookie{{$paramIdx}})
{{if not .Required}}}{{end}}
{{if .HasOptionalPointer}}}{{end}}
{{ end -}}
}
{{- end }}{{/* if .CookieParams */}}

View file

@ -44,7 +44,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error {
{{else}}
if paramValue := ctx.QueryParam("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
@ -52,7 +52,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error {
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON")
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}{{if .Required}} else {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found"))
@ -70,7 +70,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error {
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]
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}})
@ -84,7 +84,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err))
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}}
} {{if .Required}}else {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Header parameter {{.ParamName}} is required, but not found"))
}{{end}}
@ -94,7 +94,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error {
{{range .CookieParams}}
if cookie, err := ctx.Cookie("{{.ParamName}}"); err == nil {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
@ -107,7 +107,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error {
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Error unmarshaling parameter '{{.ParamName}}' as JSON")
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
{{if .IsStyled}}
var value {{.TypeDef}}
@ -115,7 +115,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error {
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter {{.ParamName}}: %s", err))
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}{{if .Required}} else {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Query argument {{.ParamName}} is required, but not found"))

View file

@ -59,7 +59,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error {
if paramValue := c.Query("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
@ -69,7 +69,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error())
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}{{if .Required}} else {
err = fmt.Errorf("Query argument {{.ParamName}} is required, but not found")
@ -93,7 +93,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error {
var {{.GoName}} {{.TypeDef}}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
{{if .IsJson}}
@ -110,7 +110,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error {
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}}
} {{if .Required}}else {
err = fmt.Errorf("Header parameter {{.ParamName}} is required, but not found: %w", err)
@ -126,7 +126,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error {
if cookie = c.Cookies("{{.ParamName}}"); cookie == "" {
{{- if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie
params.{{.GoName}} = {{if .HasOptionalPointer}}}&{{end}}cookie
{{end}}
{{- if .IsJson}}
@ -142,7 +142,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Error unmarshaling parameter '{{.ParamName}}' as JSON: %w", err).Error())
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
{{- if .IsStyled}}
@ -151,7 +151,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *fiber.Ctx) error {
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
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}

View file

@ -55,7 +55,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) {
if paramValue := c.Query("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
@ -66,7 +66,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) {
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}{{if .Required}} else {
siw.ErrorHandler(c, fmt.Errorf("Query argument {{.ParamName}} is required, but not found"), http.StatusBadRequest)
@ -96,7 +96,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) {
}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
@ -115,7 +115,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) {
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}}
} {{if .Required}}else {
siw.ErrorHandler(c, fmt.Errorf("Header parameter {{.ParamName}} is required, but not found"), http.StatusBadRequest)
@ -132,7 +132,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) {
if cookie, err = c.Cookie("{{.ParamName}}"); err == nil {
{{- if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie
{{end}}
{{- if .IsJson}}
@ -150,7 +150,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) {
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
{{- if .IsStyled}}
@ -160,7 +160,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) {
siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter {{.ParamName}}: %w", err), http.StatusBadRequest)
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}

View file

@ -58,7 +58,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
@ -69,7 +69,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}{{if .Required}} else {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
@ -98,7 +98,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
@ -117,7 +117,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}}
} {{if .Required}}else {
err = fmt.Errorf("Header parameter {{.ParamName}} is required, but not found")
@ -135,7 +135,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
if cookie, err = r.Cookie("{{.ParamName}}"); err == nil {
{{- if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value
{{end}}
{{- if .IsJson}}
@ -154,7 +154,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
{{- if .IsStyled}}
@ -164,7 +164,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}

View file

@ -55,7 +55,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) {
{{else}}
if paramValue := ctx.QueryParam("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
@ -65,7 +65,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) {
ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON")
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}{{if .Required}} else {
ctx.StatusCode(http.StatusBadRequest)
@ -87,7 +87,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) {
return
}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
err = json.Unmarshal([]byte(valueList[0]), &{{.GoName}})
@ -105,7 +105,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) {
return
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}}
} {{if .Required}}else {
ctx.StatusCode(http.StatusBadRequest)
ctx.WriteString("Header {{.ParamName}} is required, but not found")
@ -117,7 +117,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) {
{{range .CookieParams}}
if cookie, err := ctx.Cookie("{{.ParamName}}"); err == nil {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value
{{end}}
{{if .IsJson}}
var value {{.TypeDef}}
@ -134,7 +134,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) {
ctx.WriteString("Error unmarshaling parameter '{{.ParamName}}' as JSON")
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
{{if .IsStyled}}
var value {{.TypeDef}}
@ -144,7 +144,7 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx iris.Context) {
ctx.Writef("Invalid format for parameter {{.ParamName}}: %s", err)
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}{{if .Required}} else {
ctx.StatusCode(http.StatusBadRequest)

View file

@ -0,0 +1,61 @@
{{ range . }}
{{ if eq 0 (len .OAPISchema.Variables) }}
{{/* URLs without variables are straightforward, so we'll create them a constant */}}
// {{ .GoName }} defines the Server URL for {{ .OAPISchema.Description }}
const {{ .GoName}} = "{{ .OAPISchema.URL }}"
{{ else }}
{{/* URLs with variables are not straightforward, as we may need multiple types, and so will model them as a function */}}
{{/* first, we'll start by generating requisite types */}}
{{ $goName := .GoName }}
{{ range $k, $v := .OAPISchema.Variables }}
{{ $prefix := printf "%s%sVariable" $goName ($k | ucFirst) }}
// {{ $prefix }} is the `{{ $k }}` variable for {{ $goName }}
type {{ $prefix }} string
{{ range $v.Enum }}
{{/* TODO this may result in broken generated code if any of the `enum` values are the literal value `default` https://github.com/oapi-codegen/oapi-codegen/issues/2003 */}}
// {{ $prefix }}{{ . | ucFirst }} is one of the accepted values for the `{{ $k }}` variable for {{ $goName }}
const {{ $prefix }}{{ . | ucFirst }} {{ $prefix }} = "{{ . }}"
{{ end }}
{{/* TODO we should introduce a `Valid() error` method to enums https://github.com/oapi-codegen/oapi-codegen/issues/2006 */}}
{{ if $v.Default }}
{{ if gt (len $v.Enum) 0 }}
{{/* if we have an enum, we should use the type defined for it for its default value
and reference the constant we've already defined for the value */}}
{{/* TODO this may result in broken generated code if any of the `enum` values are the literal value `default` https://github.com/oapi-codegen/oapi-codegen/issues/2003 */}}
{{/* TODO this may result in broken generated code if the `default` isn't found in `enum` (which is an issue with the spec) https://github.com/oapi-codegen/oapi-codegen/issues/2007 */}}
// {{ $prefix }}Default is the default choice, for the accepted values for the `{{ $k }}` variable for {{ $goName }}
const {{ $prefix }}Default {{ $prefix }} = {{ $prefix }}{{ $v.Default | ucFirst }}
{{ else }}
// {{ $prefix }}Default is the default value for the `{{ $k }}` variable for {{ $goName }}
const {{ $prefix }}Default = "{{ $v.Default }}"
{{ end }}
{{ end }}
{{ end }}
// New{{ .GoName }} constructs the Server URL for {{ .OAPISchema.Description }}, with the provided variables.
func New{{ .GoName }}({{ genServerURLWithVariablesFunctionParams .GoName .OAPISchema.Variables }}) (string, error) {
u := "{{ .OAPISchema.URL }}"
{{ range $k, $v := .OAPISchema.Variables }}
{{- $placeholder := printf "{%s}" $k -}}
{{- if gt (len $v.Enum) 0 -}}
{{/* TODO https://github.com/oapi-codegen/oapi-codegen/issues/2006 */}}
// TODO in the future, this will validate that the value is part of the {{ printf "%s%sVariable" $goName ($k | ucFirst) }} enum
{{ end -}}
u = strings.ReplaceAll(u, "{{ $placeholder }}", string({{ $k }}))
{{ end }}
if strings.Contains(u, "{") || strings.Contains(u, "}") {
return "", fmt.Errorf("after mapping variables, there were still `{` or `}` characters in the string: %#v", u)
}
return u, nil
}
{{ end }}
{{ end }}

View file

@ -58,7 +58,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}paramValue
{{end}}
{{if .IsJson}}
@ -69,7 +69,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}{{if .Required}} else {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
@ -98,7 +98,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
}
{{if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}valueList[0]
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}valueList[0]
{{end}}
{{if .IsJson}}
@ -117,7 +117,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
}
{{end}}
params.{{.GoName}} = {{if not .Required}}&{{end}}{{.GoName}}
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}{{.GoName}}
} {{if .Required}}else {
err := fmt.Errorf("Header parameter {{.ParamName}} is required, but not found")
@ -135,7 +135,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
if cookie, err = r.Cookie("{{.ParamName}}"); err == nil {
{{- if .IsPassThrough}}
params.{{.GoName}} = {{if not .Required}}&{{end}}cookie.Value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}cookie.Value
{{end}}
{{- if .IsJson}}
@ -154,7 +154,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
{{- if .IsStyled}}
@ -164,7 +164,7 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "{{.ParamName}}", Err: err})
return
}
params.{{.GoName}} = {{if not .Required}}&{{end}}value
params.{{.GoName}} = {{if .HasOptionalPointer}}&{{end}}value
{{end}}
}

View file

@ -54,12 +54,12 @@ func (a {{.TypeName}}) MarshalJSON() ([]byte, error) {
}
}
{{range .Schema.Properties}}
{{if not .Required}}if a.{{.GoFieldName}} != nil { {{end}}
{{if .HasOptionalPointer}}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}}
{{if .HasOptionalPointer}} }{{end}}
{{end}}
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)

View file

@ -102,12 +102,12 @@
}
}
{{range .Schema.Properties}}
{{if not .Required}}if t.{{.GoFieldName}} != nil { {{end}}
{{if .HasOptionalPointer}}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}}
{{if .HasOptionalPointer}} }{{end}}
{{end -}}
b, err = json.Marshal(object)
{{end -}}

View file

@ -283,7 +283,7 @@ func ToCamelCaseWithDigits(s string) string {
func ToCamelCaseWithInitialisms(s string) string {
parts := camelCaseMatchParts.FindAllString(ToCamelCaseWithDigits(s), -1)
for i := range parts {
if v, ok := initialismsMap[strings.ToLower(parts[i])]; ok {
if v, ok := globalState.initialismsMap[strings.ToLower(parts[i])]; ok {
parts[i] = v
}
}
@ -292,19 +292,26 @@ func ToCamelCaseWithInitialisms(s string) string {
var camelCaseMatchParts = regexp.MustCompile(`[\p{Lu}\d]+([\p{Ll}\d]+|$)`)
// initialismsMap stores initialisms as "lower(initialism) -> initialism" map.
// List of initialisms was taken from https://staticcheck.io/docs/configuration/options/#initialisms.
var initialismsMap = makeInitialismsMap([]string{
var initialismsList = []string{
"ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON",
"QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID",
"URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS",
})
}
// targetWordRegex is a regex that matches all initialisms.
var targetWordRegex *regexp.Regexp
func makeInitialismsMap(additionalInitialisms []string) map[string]string {
l := append(initialismsList, additionalInitialisms...)
func makeInitialismsMap(l []string) map[string]string {
m := make(map[string]string, len(l))
for i := range l {
m[strings.ToLower(l[i])] = l[i]
}
// Create a regex to match the initialisms
targetWordRegex = regexp.MustCompile(`(?i)(` + strings.Join(l, "|") + `)`)
return m
}
@ -315,8 +322,6 @@ func ToCamelCaseWithInitialism(str string) string {
func replaceInitialism(s string) string {
// These strings do not apply CamelCase
// Do not do CamelCase when these characters match when the preceding character is lowercase
// ["Acl", "Api", "Ascii", "Cpu", "Css", "Dns", "Eof", "Guid", "Html", "Http", "Https", "Id", "Ip", "Json", "Qps", "Ram", "Rpc", "Sla", "Smtp", "Sql", "Ssh", "Tcp", "Tls", "Ttl", "Udp", "Ui", "Gid", "Uid", "Uuid", "Uri", "Url", "Utf8", "Vm", "Xml", "Xmpp", "Xsrf", "Xss", "Sip", "Rtp", "Amqp", "Db", "Ts"]
targetWordRegex := regexp.MustCompile(`(?i)(Acl|Api|Ascii|Cpu|Css|Dns|Eof|Guid|Html|Http|Https|Id|Ip|Json|Qps|Ram|Rpc|Sla|Smtp|Sql|Ssh|Tcp|Tls|Ttl|Udp|Ui|Gid|Uid|Uuid|Uri|Url|Utf8|Vm|Xml|Xmpp|Xsrf|Xss|Sip|Rtp|Amqp|Db|Ts)`)
return targetWordRegex.ReplaceAllStringFunc(s, func(s string) string {
// If the preceding character is lowercase, do not do CamelCase
if unicode.IsLower(rune(s[0])) {
@ -623,6 +628,12 @@ func SwaggerUriToGorillaUri(uri string) string {
// {?param}
// {?param*}
func SwaggerUriToStdHttpUri(uri string) string {
// https://pkg.go.dev/net/http#hdr-Patterns-ServeMux
// The special wildcard {$} matches only the end of the URL. For example, the pattern "/{$}" matches only the path "/", whereas the pattern "/" matches every path.
if uri == "/" {
return "/{$}"
}
return pathParamRE.ReplaceAllString(uri, "{$1}")
}
@ -805,6 +816,8 @@ func typeNamePrefix(name string) (prefix string) {
prefix += "Caret"
case '%':
prefix += "Percent"
case '_':
prefix += "Underscore"
default:
// Prepend "N" to schemas starting with a number
if prefix == "" && unicode.IsDigit(r) {
@ -868,6 +881,8 @@ func DeprecationComment(reason string) string {
content := "Deprecated:" // The colon is required at the end even without reason
if reason != "" {
content += fmt.Sprintf(" %s", reason)
} else {
content += " this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set"
}
return stringToGoCommentWithPrefix(content, "")
@ -1086,7 +1101,7 @@ func isAdditionalPropertiesExplicitFalse(s *openapi3.Schema) bool {
return false
}
return *s.AdditionalProperties.Has == false //nolint:gosimple
return *s.AdditionalProperties.Has == false //nolint:staticcheck
}
func sliceContains[E comparable](s []E, v E) bool {

View file

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"net/url"
"path/filepath"
"strings"
"github.com/getkin/kin-openapi/openapi3"
@ -65,29 +66,34 @@ func LoadSwaggerWithOverlay(filePath string, opts LoadSwaggerWithOverlayOpts) (s
err = overlay.Validate()
if err != nil {
return nil, fmt.Errorf("The Overlay in %#v was not valid: %v", opts.Path, err)
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- "))
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)
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)
return nil, fmt.Errorf("failed to serialize Overlay'd specification %#v: %v", opts.Path, err)
}
swagger, err = openapi3.NewLoader().LoadFromData(b)
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
swagger, err = loader.LoadFromDataWithPath(b, &url.URL{
Path: filepath.ToSlash(filePath),
})
if err != nil {
return nil, fmt.Errorf("Failed to serialize Overlay'd specification %#v: %v", opts.Path, err)
return nil, fmt.Errorf("failed to serialize Overlay'd specification %#v: %v", opts.Path, err)
}
return swagger, nil

View file

@ -83,12 +83,12 @@ func BindStyledParameterWithOptions(style string, paramName string, value string
// since prior to this refactoring, they always query unescaped.
value, err = url.QueryUnescape(value)
if err != nil {
return fmt.Errorf("error unescaping query parameter '%s': %v", paramName, err)
return fmt.Errorf("error unescaping query parameter '%s': %w", paramName, err)
}
case ParamLocationPath:
value, err = url.PathUnescape(value)
if err != nil {
return fmt.Errorf("error unescaping path parameter '%s': %v", paramName, err)
return fmt.Errorf("error unescaping path parameter '%s': %w", paramName, err)
}
default:
// Headers and cookies aren't escaped.
@ -97,7 +97,7 @@ func BindStyledParameterWithOptions(style string, paramName string, value string
// If the destination implements encoding.TextUnmarshaler we use it for binding
if tu, ok := dest.(encoding.TextUnmarshaler); ok {
if err := tu.UnmarshalText([]byte(value)); err != nil {
return fmt.Errorf("error unmarshaling '%s' text as %T: %s", value, dest, err)
return fmt.Errorf("error unmarshaling '%s' text as %T: %w", value, dest, err)
}
return nil
@ -124,7 +124,7 @@ func BindStyledParameterWithOptions(style string, paramName string, value string
// Chop up the parameter into parts based on its style
parts, err := splitStyledParameter(style, opts.Explode, false, paramName, value)
if err != nil {
return fmt.Errorf("error splitting input '%s' into parts: %s", value, err)
return fmt.Errorf("error splitting input '%s' into parts: %w", value, err)
}
return bindSplitPartsToDestinationArray(parts, dest)
@ -287,7 +287,7 @@ func bindSplitPartsToDestinationStruct(paramName string, parts []string, explode
jsonParam := "{" + strings.Join(fields, ",") + "}"
err := json.Unmarshal([]byte(jsonParam), dest)
if err != nil {
return fmt.Errorf("error binding parameter %s fields: %s", paramName, err)
return fmt.Errorf("error binding parameter %s fields: %w", paramName, err)
}
return nil
}
@ -318,7 +318,11 @@ func BindQueryParameter(style string, explode bool, required bool, paramName str
// inner code will bind the string's value to this interface.
var output interface{}
if required {
// required params are never pointers, but it may happen that optional param
// is not pointer as well if user decides to annotate it with
// x-go-type-skip-optional-pointer
var extraIndirect = !required && v.Kind() == reflect.Pointer
if !extraIndirect {
// If the parameter is required, then the generated code will pass us
// a pointer to it: &int, &object, and so forth. We can directly set
// them.
@ -414,9 +418,10 @@ func BindQueryParameter(style string, explode bool, required bool, paramName str
if err != nil {
return err
}
// If the parameter is required, and we've successfully unmarshaled
// it, this assigns the new object to the pointer pointer.
if !required {
// If the parameter is required (or relies on x-go-type-skip-optional-pointer),
// and we've successfully unmarshaled it, this assigns the new object to the
// pointer pointer.
if extraIndirect {
dv.Set(reflect.ValueOf(output))
}
return nil
@ -456,7 +461,7 @@ func BindQueryParameter(style string, explode bool, required bool, paramName str
if err != nil {
return err
}
if !required {
if extraIndirect {
dv.Set(reflect.ValueOf(output))
}
return nil

View file

@ -100,7 +100,7 @@ func BindStringToObject(src string, dst interface{}) error {
case reflect.Array:
if tu, ok := dst.(encoding.TextUnmarshaler); ok {
if err := tu.UnmarshalText([]byte(src)); err != nil {
return fmt.Errorf("error unmarshaling '%s' text as %T: %s", src, dst, err)
return fmt.Errorf("error unmarshaling '%s' text as %T: %w", src, dst, err)
}
return nil
@ -122,7 +122,7 @@ func BindStringToObject(src string, dst interface{}) error {
if err != nil {
parsedTime, err = time.Parse(types.DateFormat, src)
if err != nil {
return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %s", src, err)
return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %w", src, err)
}
}
// So, assigning this gets a little fun. We have a value to the
@ -145,7 +145,7 @@ func BindStringToObject(src string, dst interface{}) error {
}
parsedTime, err := time.Parse(types.DateFormat, src)
if err != nil {
return fmt.Errorf("error parsing '%s' as date: %s", src, err)
return fmt.Errorf("error parsing '%s' as date: %w", src, err)
}
parsedDate := types.Date{Time: parsedTime}

View file

@ -258,7 +258,7 @@ func assignPathValues(dst interface{}, pathValues fieldOrValue) error {
// TODO: why is this marked as an ineffassign?
tm, err = time.Parse(types.DateFormat, pathValues.value) //nolint:ineffassign,staticcheck
if err != nil {
return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %s", pathValues.value, err)
return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %w", pathValues.value, err)
}
return fmt.Errorf("invalid date format: %w", err)
}

View file

@ -26,8 +26,9 @@ import (
"strings"
"time"
"github.com/oapi-codegen/runtime/types"
"github.com/google/uuid"
"github.com/oapi-codegen/runtime/types"
)
// Parameter escaping works differently based on where a header is found
@ -79,7 +80,7 @@ func StyleParamWithLocation(style string, explode bool, paramName string, paramL
if !convertableToTime && !convertableToDate {
b, err := tu.MarshalText()
if err != nil {
return "", fmt.Errorf("error marshaling '%s' as text: %s", value, err)
return "", fmt.Errorf("error marshaling '%s' as text: %w", value, err)
}
return stylePrimitive(style, explode, paramName, paramLocation, string(b))
@ -165,7 +166,7 @@ func styleSlice(style string, explode bool, paramName string, paramLocation Para
part = escapeParameterString(part, paramLocation)
parts[i] = part
if err != nil {
return "", fmt.Errorf("error formatting '%s': %s", paramName, err)
return "", fmt.Errorf("error formatting '%s': %w", paramName, err)
}
}
return prefix + strings.Join(parts, separator), nil
@ -273,7 +274,7 @@ func styleStruct(style string, explode bool, paramName string, paramLocation Par
}
str, err := primitiveToString(f.Interface())
if err != nil {
return "", fmt.Errorf("error formatting '%s': %s", paramName, err)
return "", fmt.Errorf("error formatting '%s': %w", paramName, err)
}
fieldDict[fieldName] = str
}
@ -288,19 +289,15 @@ func styleMap(style string, explode bool, paramName string, paramLocation ParamL
}
return MarshalDeepObject(value, paramName)
}
dict, ok := value.(map[string]interface{})
if !ok {
return "", errors.New("map not of type map[string]interface{}")
}
v := reflect.ValueOf(value)
fieldDict := make(map[string]string)
for fieldName, value := range dict {
str, err := primitiveToString(value)
for _, fieldName := range v.MapKeys() {
str, err := primitiveToString(v.MapIndex(fieldName).Interface())
if err != nil {
return "", fmt.Errorf("error formatting '%s': %s", paramName, err)
return "", fmt.Errorf("error formatting '%s': %w", paramName, err)
}
fieldDict[fieldName] = str
fieldDict[fieldName.String()] = str
}
return processFieldDict(style, explode, paramName, paramLocation, fieldDict)
}