* update code.gitea.io/sdk/gitea v0.13.1 -> v0.13.2 * update github.com/go-swagger/go-swagger v0.25.0 -> v0.26.0 * update github.com/google/uuid v1.1.2 -> v1.2.0 * update github.com/klauspost/compress v1.11.3 -> v1.11.7 * update github.com/lib/pq 083382b7e6fc -> v1.9.0 * update github.com/markbates/goth v1.65.0 -> v1.66.1 * update github.com/mattn/go-sqlite3 v1.14.4 -> v1.14.6 * update github.com/mgechev/revive 246eac737dc7 -> v1.0.3 * update github.com/minio/minio-go/v7 v7.0.6 -> v7.0.7 * update github.com/niklasfasching/go-org v1.3.2 -> v1.4.0 * update github.com/olivere/elastic/v7 v7.0.21 -> v7.0.22 * update github.com/pquerna/otp v1.2.0 -> v1.3.0 * update github.com/xanzy/go-gitlab v0.39.0 -> v0.42.0 * update github.com/yuin/goldmark v1.2.1 -> v1.3.1
1290 lines
44 KiB
Go
Vendored
1290 lines
44 KiB
Go
Vendored
// Copyright 2015 go-swagger maintainers
|
|
//
|
|
// 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 generator
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/go-openapi/analysis"
|
|
"github.com/go-openapi/loads"
|
|
"github.com/go-openapi/runtime"
|
|
"github.com/go-openapi/spec"
|
|
"github.com/go-openapi/swag"
|
|
)
|
|
|
|
type respSort struct {
|
|
Code int
|
|
Response spec.Response
|
|
}
|
|
|
|
type responses []respSort
|
|
|
|
func (s responses) Len() int { return len(s) }
|
|
func (s responses) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
func (s responses) Less(i, j int) bool { return s[i].Code < s[j].Code }
|
|
|
|
// sortedResponses produces a sorted list of responses.
|
|
// TODO: this is redundant with the definition given in struct.go
|
|
func sortedResponses(input map[int]spec.Response) responses {
|
|
var res responses
|
|
for k, v := range input {
|
|
if k > 0 {
|
|
res = append(res, respSort{k, v})
|
|
}
|
|
}
|
|
sort.Sort(res)
|
|
return res
|
|
}
|
|
|
|
// GenerateServerOperation generates a parameter model, parameter validator, http handler implementations for a given operation.
|
|
//
|
|
// It also generates an operation handler interface that uses the parameter model for handling a valid request.
|
|
// Allows for specifying a list of tags to include only certain tags for the generation
|
|
func GenerateServerOperation(operationNames []string, opts *GenOpts) error {
|
|
if err := opts.CheckOpts(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := opts.setTemplates(); err != nil {
|
|
return err
|
|
}
|
|
|
|
specDoc, analyzed, err := opts.analyzeSpec()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ops := gatherOperations(analyzed, operationNames)
|
|
|
|
if len(ops) == 0 {
|
|
return errors.New("no operations were selected")
|
|
}
|
|
|
|
for operationName, opRef := range ops {
|
|
method, path, operation := opRef.Method, opRef.Path, opRef.Op
|
|
|
|
serverPackage := opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget)
|
|
generator := operationGenerator{
|
|
Name: operationName,
|
|
Method: method,
|
|
Path: path,
|
|
BasePath: specDoc.BasePath(),
|
|
APIPackage: opts.LanguageOpts.ManglePackagePath(opts.APIPackage, defaultOperationsTarget),
|
|
ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, defaultModelsTarget),
|
|
ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, defaultClientTarget),
|
|
ServerPackage: serverPackage,
|
|
Operation: *operation,
|
|
SecurityRequirements: analyzed.SecurityRequirementsFor(operation),
|
|
SecurityDefinitions: analyzed.SecurityDefinitionsFor(operation),
|
|
Principal: opts.PrincipalAlias(),
|
|
Target: filepath.Join(opts.Target, filepath.FromSlash(serverPackage)),
|
|
Base: opts.Target,
|
|
Tags: opts.Tags,
|
|
IncludeHandler: opts.IncludeHandler,
|
|
IncludeParameters: opts.IncludeParameters,
|
|
IncludeResponses: opts.IncludeResponses,
|
|
IncludeValidator: opts.IncludeValidator,
|
|
DumpData: opts.DumpData,
|
|
DefaultScheme: opts.DefaultScheme,
|
|
DefaultProduces: opts.DefaultProduces,
|
|
DefaultConsumes: opts.DefaultConsumes,
|
|
Doc: specDoc,
|
|
Analyzed: analyzed,
|
|
GenOpts: opts,
|
|
}
|
|
if err := generator.Generate(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type operationGenerator struct {
|
|
Authorized bool
|
|
IncludeHandler bool
|
|
IncludeParameters bool
|
|
IncludeResponses bool
|
|
IncludeValidator bool
|
|
DumpData bool
|
|
|
|
Principal string
|
|
Target string
|
|
Base string
|
|
Name string
|
|
Method string
|
|
Path string
|
|
BasePath string
|
|
APIPackage string
|
|
ModelsPackage string
|
|
ServerPackage string
|
|
ClientPackage string
|
|
Operation spec.Operation
|
|
SecurityRequirements [][]analysis.SecurityRequirement
|
|
SecurityDefinitions map[string]spec.SecurityScheme
|
|
Tags []string
|
|
DefaultScheme string
|
|
DefaultProduces string
|
|
DefaultConsumes string
|
|
Doc *loads.Document
|
|
Analyzed *analysis.Spec
|
|
GenOpts *GenOpts
|
|
}
|
|
|
|
// Generate a single operation
|
|
func (o *operationGenerator) Generate() error {
|
|
|
|
defaultImports := o.GenOpts.defaultImports()
|
|
|
|
apiPackage := o.GenOpts.LanguageOpts.ManglePackagePath(o.GenOpts.APIPackage, defaultOperationsTarget)
|
|
imports := o.GenOpts.initImports(
|
|
filepath.Join(o.GenOpts.LanguageOpts.ManglePackagePath(o.GenOpts.ServerPackage, defaultServerTarget), apiPackage))
|
|
|
|
bldr := codeGenOpBuilder{
|
|
ModelsPackage: o.ModelsPackage,
|
|
Principal: o.GenOpts.PrincipalAlias(),
|
|
Target: o.Target,
|
|
DefaultImports: defaultImports,
|
|
Imports: imports,
|
|
DefaultScheme: o.DefaultScheme,
|
|
Doc: o.Doc,
|
|
Analyzed: o.Analyzed,
|
|
BasePath: o.BasePath,
|
|
GenOpts: o.GenOpts,
|
|
Name: o.Name,
|
|
Operation: o.Operation,
|
|
Method: o.Method,
|
|
Path: o.Path,
|
|
IncludeValidator: o.IncludeValidator,
|
|
APIPackage: o.APIPackage, // defaults to main operations package
|
|
DefaultProduces: o.DefaultProduces,
|
|
DefaultConsumes: o.DefaultConsumes,
|
|
Authed: len(o.Analyzed.SecurityRequirementsFor(&o.Operation)) > 0,
|
|
Security: o.Analyzed.SecurityRequirementsFor(&o.Operation),
|
|
SecurityDefinitions: o.Analyzed.SecurityDefinitionsFor(&o.Operation),
|
|
RootAPIPackage: o.GenOpts.LanguageOpts.ManglePackageName(o.ServerPackage, defaultServerTarget),
|
|
}
|
|
|
|
_, tags, _ := bldr.analyzeTags()
|
|
|
|
op, err := bldr.MakeOperation()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
op.Tags = tags
|
|
operations := make(GenOperations, 0, 1)
|
|
operations = append(operations, op)
|
|
sort.Sort(operations)
|
|
|
|
for _, pp := range operations {
|
|
op := pp
|
|
if o.GenOpts.DumpData {
|
|
_ = dumpData(swag.ToDynamicJSON(op))
|
|
continue
|
|
}
|
|
if err := o.GenOpts.renderOperation(&op); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type codeGenOpBuilder struct {
|
|
Authed bool
|
|
IncludeValidator bool
|
|
|
|
Name string
|
|
Method string
|
|
Path string
|
|
BasePath string
|
|
APIPackage string
|
|
APIPackageAlias string
|
|
RootAPIPackage string
|
|
ModelsPackage string
|
|
Principal string
|
|
Target string
|
|
Operation spec.Operation
|
|
Doc *loads.Document
|
|
PristineDoc *loads.Document
|
|
Analyzed *analysis.Spec
|
|
DefaultImports map[string]string
|
|
Imports map[string]string
|
|
DefaultScheme string
|
|
DefaultProduces string
|
|
DefaultConsumes string
|
|
Security [][]analysis.SecurityRequirement
|
|
SecurityDefinitions map[string]spec.SecurityScheme
|
|
ExtraSchemas map[string]GenSchema
|
|
GenOpts *GenOpts
|
|
}
|
|
|
|
// paramMappings yields a map of safe parameter names for an operation
|
|
func paramMappings(params map[string]spec.Parameter) (map[string]map[string]string, string) {
|
|
idMapping := map[string]map[string]string{
|
|
"query": make(map[string]string, len(params)),
|
|
"path": make(map[string]string, len(params)),
|
|
"formData": make(map[string]string, len(params)),
|
|
"header": make(map[string]string, len(params)),
|
|
"body": make(map[string]string, len(params)),
|
|
}
|
|
|
|
// In order to avoid unstable generation, adopt same naming convention
|
|
// for all parameters with same name across locations.
|
|
seenIds := make(map[string]interface{}, len(params))
|
|
for id, p := range params {
|
|
if val, ok := seenIds[p.Name]; ok {
|
|
previous := val.(struct{ id, in string })
|
|
idMapping[p.In][p.Name] = swag.ToGoName(id)
|
|
// rewrite the previously found one
|
|
idMapping[previous.in][p.Name] = swag.ToGoName(previous.id)
|
|
} else {
|
|
idMapping[p.In][p.Name] = swag.ToGoName(p.Name)
|
|
}
|
|
seenIds[strings.ToLower(idMapping[p.In][p.Name])] = struct{ id, in string }{id: id, in: p.In}
|
|
}
|
|
|
|
// pick a deconflicted private name for timeout for this operation
|
|
timeoutName := renameTimeout(seenIds, "timeout")
|
|
|
|
return idMapping, timeoutName
|
|
}
|
|
|
|
// renameTimeout renames the variable in use by client template to avoid conflicting
|
|
// with param names.
|
|
//
|
|
// NOTE: this merely protects the timeout field in the client parameter struct,
|
|
// fields "Context" and "HTTPClient" remain exposed to name conflicts.
|
|
func renameTimeout(seenIds map[string]interface{}, timeoutName string) string {
|
|
if seenIds == nil {
|
|
return timeoutName
|
|
}
|
|
current := strings.ToLower(timeoutName)
|
|
if _, ok := seenIds[current]; !ok {
|
|
return timeoutName
|
|
}
|
|
var next string
|
|
switch current {
|
|
case "timeout":
|
|
next = "requestTimeout"
|
|
case "requesttimeout":
|
|
next = "httpRequestTimeout"
|
|
case "httprequesttimeout":
|
|
next = "swaggerTimeout"
|
|
case "swaggertimeout":
|
|
next = "operationTimeout"
|
|
case "operationtimeout":
|
|
next = "opTimeout"
|
|
case "optimeout":
|
|
next = "operTimeout"
|
|
default:
|
|
next = timeoutName + "1"
|
|
}
|
|
return renameTimeout(seenIds, next)
|
|
}
|
|
|
|
func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
|
|
debugLog("[%s %s] parsing operation (id: %q)", b.Method, b.Path, b.Operation.ID)
|
|
// NOTE: we assume flatten is enabled by default (i.e. complex constructs are resolved from the models package),
|
|
// but do not assume the spec is necessarily fully flattened (i.e. all schemas moved to definitions).
|
|
//
|
|
// Fully flattened means that all complex constructs are present as
|
|
// definitions and models produced accordingly in ModelsPackage,
|
|
// whereas minimal flatten simply ensures that there are no weird $ref's in the spec.
|
|
//
|
|
// When some complex anonymous constructs are specified, extra schemas are produced in the operations package.
|
|
//
|
|
// In all cases, resetting definitions to the _original_ (untransformed) spec is not an option:
|
|
// we take from there the spec possibly already transformed by the GenDefinitions stage.
|
|
resolver := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(b.ModelsPackage, defaultModelsTarget), b.DefaultImports[b.ModelsPackage], b.Doc)
|
|
receiver := "o"
|
|
|
|
operation := b.Operation
|
|
var params, qp, pp, hp, fp GenParameters
|
|
var hasQueryParams, hasPathParams, hasHeaderParams, hasFormParams, hasFileParams, hasFormValueParams, hasBodyParams bool
|
|
paramsForOperation := b.Analyzed.ParamsFor(b.Method, b.Path)
|
|
|
|
idMapping, timeoutName := paramMappings(paramsForOperation)
|
|
|
|
for _, p := range paramsForOperation {
|
|
cp, err := b.MakeParameter(receiver, resolver, p, idMapping)
|
|
|
|
if err != nil {
|
|
return GenOperation{}, err
|
|
}
|
|
if cp.IsQueryParam() {
|
|
hasQueryParams = true
|
|
qp = append(qp, cp)
|
|
}
|
|
if cp.IsFormParam() {
|
|
if p.Type == file {
|
|
hasFileParams = true
|
|
}
|
|
if p.Type != file {
|
|
hasFormValueParams = true
|
|
}
|
|
hasFormParams = true
|
|
fp = append(fp, cp)
|
|
}
|
|
if cp.IsPathParam() {
|
|
hasPathParams = true
|
|
pp = append(pp, cp)
|
|
}
|
|
if cp.IsHeaderParam() {
|
|
hasHeaderParams = true
|
|
hp = append(hp, cp)
|
|
}
|
|
if cp.IsBodyParam() {
|
|
hasBodyParams = true
|
|
}
|
|
params = append(params, cp)
|
|
}
|
|
sort.Sort(params)
|
|
sort.Sort(qp)
|
|
sort.Sort(pp)
|
|
sort.Sort(hp)
|
|
sort.Sort(fp)
|
|
|
|
var srs responses
|
|
if operation.Responses != nil {
|
|
srs = sortedResponses(operation.Responses.StatusCodeResponses)
|
|
}
|
|
responses := make([]GenResponse, 0, len(srs))
|
|
var defaultResponse *GenResponse
|
|
var successResponses []GenResponse
|
|
if operation.Responses != nil {
|
|
for _, v := range srs {
|
|
name, ok := v.Response.Extensions.GetString(xGoName)
|
|
if !ok {
|
|
// look for name of well-known codes
|
|
name = runtime.Statuses[v.Code]
|
|
if name == "" {
|
|
// non-standard codes deserve some name
|
|
name = fmt.Sprintf("Status %d", v.Code)
|
|
}
|
|
}
|
|
name = swag.ToJSONName(b.Name + " " + name)
|
|
isSuccess := v.Code/100 == 2
|
|
gr, err := b.MakeResponse(receiver, name, isSuccess, resolver, v.Code, v.Response)
|
|
if err != nil {
|
|
return GenOperation{}, err
|
|
}
|
|
if isSuccess {
|
|
successResponses = append(successResponses, gr)
|
|
}
|
|
responses = append(responses, gr)
|
|
}
|
|
|
|
if operation.Responses.Default != nil {
|
|
gr, err := b.MakeResponse(receiver, b.Name+" default", false, resolver, -1, *operation.Responses.Default)
|
|
if err != nil {
|
|
return GenOperation{}, err
|
|
}
|
|
defaultResponse = &gr
|
|
}
|
|
}
|
|
|
|
// Always render a default response, even when no responses were defined
|
|
if operation.Responses == nil || (operation.Responses.Default == nil && len(srs) == 0) {
|
|
gr, err := b.MakeResponse(receiver, b.Name+" default", false, resolver, -1, spec.Response{})
|
|
if err != nil {
|
|
return GenOperation{}, err
|
|
}
|
|
defaultResponse = &gr
|
|
}
|
|
|
|
swsp := resolver.Doc.Spec()
|
|
|
|
schemes, extraSchemes := gatherURISchemes(swsp, operation)
|
|
originalSchemes := operation.Schemes
|
|
originalExtraSchemes := getExtraSchemes(operation.Extensions)
|
|
|
|
produces := producesOrDefault(operation.Produces, swsp.Produces, b.DefaultProduces)
|
|
sort.Strings(produces)
|
|
|
|
consumes := producesOrDefault(operation.Consumes, swsp.Consumes, b.DefaultConsumes)
|
|
sort.Strings(consumes)
|
|
|
|
var successResponse *GenResponse
|
|
for _, resp := range successResponses {
|
|
sr := resp
|
|
if sr.IsSuccess {
|
|
successResponse = &sr
|
|
break
|
|
}
|
|
}
|
|
|
|
var hasStreamingResponse bool
|
|
if defaultResponse != nil && defaultResponse.Schema != nil && defaultResponse.Schema.IsStream {
|
|
hasStreamingResponse = true
|
|
}
|
|
|
|
if !hasStreamingResponse {
|
|
for _, sr := range successResponses {
|
|
if !hasStreamingResponse && sr.Schema != nil && sr.Schema.IsStream {
|
|
hasStreamingResponse = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if !hasStreamingResponse {
|
|
for _, r := range responses {
|
|
if r.Schema != nil && r.Schema.IsStream {
|
|
hasStreamingResponse = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return GenOperation{
|
|
GenCommon: GenCommon{
|
|
Copyright: b.GenOpts.Copyright,
|
|
TargetImportPath: b.GenOpts.LanguageOpts.baseImport(b.GenOpts.Target),
|
|
},
|
|
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
|
|
PackageAlias: b.APIPackageAlias,
|
|
RootPackage: b.RootAPIPackage,
|
|
Name: b.Name,
|
|
Method: b.Method,
|
|
Path: b.Path,
|
|
BasePath: b.BasePath,
|
|
Tags: operation.Tags,
|
|
UseTags: len(operation.Tags) > 0 && !b.GenOpts.SkipTagPackages,
|
|
Description: trimBOM(operation.Description),
|
|
ReceiverName: receiver,
|
|
DefaultImports: b.DefaultImports,
|
|
Imports: b.Imports,
|
|
Params: params,
|
|
Summary: trimBOM(operation.Summary),
|
|
QueryParams: qp,
|
|
PathParams: pp,
|
|
HeaderParams: hp,
|
|
FormParams: fp,
|
|
HasQueryParams: hasQueryParams,
|
|
HasPathParams: hasPathParams,
|
|
HasHeaderParams: hasHeaderParams,
|
|
HasFormParams: hasFormParams,
|
|
HasFormValueParams: hasFormValueParams,
|
|
HasFileParams: hasFileParams,
|
|
HasBodyParams: hasBodyParams,
|
|
HasStreamingResponse: hasStreamingResponse,
|
|
Authorized: b.Authed,
|
|
Security: b.makeSecurityRequirements(receiver), // resolved security requirements, for codegen
|
|
SecurityDefinitions: b.makeSecuritySchemes(receiver),
|
|
SecurityRequirements: securityRequirements(operation.Security), // raw security requirements, for doc
|
|
Principal: b.Principal,
|
|
Responses: responses,
|
|
DefaultResponse: defaultResponse,
|
|
SuccessResponse: successResponse,
|
|
SuccessResponses: successResponses,
|
|
ExtraSchemas: gatherExtraSchemas(b.ExtraSchemas),
|
|
Schemes: schemeOrDefault(schemes, b.DefaultScheme),
|
|
SchemeOverrides: originalSchemes, // raw operation schemes, for doc
|
|
ProducesMediaTypes: produces, // resolved produces, for codegen
|
|
ConsumesMediaTypes: consumes, // resolved consumes, for codegen
|
|
Produces: operation.Produces, // for doc
|
|
Consumes: operation.Consumes, // for doc
|
|
ExtraSchemes: extraSchemes, // resolved schemes, for codegen
|
|
ExtraSchemeOverrides: originalExtraSchemes, // raw operation extra schemes, for doc
|
|
TimeoutName: timeoutName,
|
|
Extensions: operation.Extensions,
|
|
StrictResponders: b.GenOpts.StrictResponders,
|
|
|
|
PrincipalIsNullable: b.GenOpts.PrincipalIsNullable(),
|
|
ExternalDocs: trimExternalDoc(operation.ExternalDocs),
|
|
}, nil
|
|
}
|
|
|
|
func producesOrDefault(produces []string, fallback []string, defaultProduces string) []string {
|
|
if len(produces) > 0 {
|
|
return produces
|
|
}
|
|
if len(fallback) > 0 {
|
|
return fallback
|
|
}
|
|
return []string{defaultProduces}
|
|
}
|
|
|
|
func schemeOrDefault(schemes []string, defaultScheme string) []string {
|
|
if len(schemes) == 0 {
|
|
return []string{defaultScheme}
|
|
}
|
|
return schemes
|
|
}
|
|
|
|
func (b *codeGenOpBuilder) MakeResponse(receiver, name string, isSuccess bool, resolver *typeResolver, code int, resp spec.Response) (GenResponse, error) {
|
|
debugLog("[%s %s] making id %q", b.Method, b.Path, b.Operation.ID)
|
|
|
|
// assume minimal flattening has been carried on, so there is not $ref in response (but some may remain in response schema)
|
|
examples := make(GenResponseExamples, 0, len(resp.Examples))
|
|
for k, v := range resp.Examples {
|
|
examples = append(examples, GenResponseExample{MediaType: k, Example: v})
|
|
}
|
|
sort.Sort(examples)
|
|
|
|
res := GenResponse{
|
|
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
|
|
ModelsPackage: b.ModelsPackage,
|
|
ReceiverName: receiver,
|
|
Name: name,
|
|
Description: trimBOM(resp.Description),
|
|
DefaultImports: b.DefaultImports,
|
|
Imports: b.Imports,
|
|
IsSuccess: isSuccess,
|
|
Code: code,
|
|
Method: b.Method,
|
|
Path: b.Path,
|
|
Extensions: resp.Extensions,
|
|
StrictResponders: b.GenOpts.StrictResponders,
|
|
OperationName: b.Name,
|
|
Examples: examples,
|
|
}
|
|
|
|
// prepare response headers
|
|
for hName, header := range resp.Headers {
|
|
hdr, err := b.MakeHeader(receiver, hName, header)
|
|
if err != nil {
|
|
return GenResponse{}, err
|
|
}
|
|
res.Headers = append(res.Headers, hdr)
|
|
}
|
|
sort.Sort(res.Headers)
|
|
|
|
if resp.Schema != nil {
|
|
// resolve schema model
|
|
schema, ers := b.buildOperationSchema(fmt.Sprintf("%q", name), name+"Body", swag.ToGoName(name+"Body"), receiver, "i", resp.Schema, resolver)
|
|
if ers != nil {
|
|
return GenResponse{}, ers
|
|
}
|
|
res.Schema = &schema
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (b *codeGenOpBuilder) MakeHeader(receiver, name string, hdr spec.Header) (GenHeader, error) {
|
|
tpe := simpleResolvedType(hdr.Type, hdr.Format, hdr.Items, &hdr.CommonValidations)
|
|
|
|
id := swag.ToGoName(name)
|
|
res := GenHeader{
|
|
sharedValidations: sharedValidations{
|
|
Required: true,
|
|
SchemaValidations: hdr.Validations(), // NOTE: Required is not defined by the Swagger schema for header. Set arbitrarily to true for convenience in templates.
|
|
},
|
|
resolvedType: tpe,
|
|
Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
|
|
ReceiverName: receiver,
|
|
ID: id,
|
|
Name: name,
|
|
Path: fmt.Sprintf("%q", name),
|
|
ValueExpression: fmt.Sprintf("%s.%s", receiver, id),
|
|
Description: trimBOM(hdr.Description),
|
|
Default: hdr.Default,
|
|
HasDefault: hdr.Default != nil,
|
|
Converter: stringConverters[tpe.GoType],
|
|
Formatter: stringFormatters[tpe.GoType],
|
|
ZeroValue: tpe.Zero(),
|
|
CollectionFormat: hdr.CollectionFormat,
|
|
IndexVar: "i",
|
|
}
|
|
res.HasValidations, res.HasSliceValidations = b.HasValidations(hdr.CommonValidations, res.resolvedType)
|
|
|
|
hasChildValidations := false
|
|
if hdr.Items != nil {
|
|
pi, err := b.MakeHeaderItem(receiver, name+" "+res.IndexVar, res.IndexVar+"i", "fmt.Sprintf(\"%s.%v\", \"header\", "+res.IndexVar+")", res.Name+"I", hdr.Items, nil)
|
|
if err != nil {
|
|
return GenHeader{}, err
|
|
}
|
|
res.Child = &pi
|
|
hasChildValidations = pi.HasValidations
|
|
}
|
|
// we feed the GenHeader structure the same way as we do for
|
|
// GenParameter, even though there is currently no actual validation
|
|
// for response headers.
|
|
res.HasValidations = res.HasValidations || hasChildValidations
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (b *codeGenOpBuilder) MakeHeaderItem(receiver, paramName, indexVar, path, valueExpression string, items, parent *spec.Items) (GenItems, error) {
|
|
var res GenItems
|
|
res.resolvedType = simpleResolvedType(items.Type, items.Format, items.Items, &items.CommonValidations)
|
|
|
|
res.sharedValidations = sharedValidations{
|
|
Required: false,
|
|
SchemaValidations: items.Validations(),
|
|
}
|
|
res.Name = paramName
|
|
res.Path = path
|
|
res.Location = "header"
|
|
res.ValueExpression = swag.ToVarName(valueExpression)
|
|
res.CollectionFormat = items.CollectionFormat
|
|
res.Converter = stringConverters[res.GoType]
|
|
res.Formatter = stringFormatters[res.GoType]
|
|
res.IndexVar = indexVar
|
|
res.HasValidations, res.HasSliceValidations = b.HasValidations(items.CommonValidations, res.resolvedType)
|
|
res.IsEnumCI = b.GenOpts.AllowEnumCI || hasEnumCI(items.Extensions)
|
|
|
|
if items.Items != nil {
|
|
// Recursively follows nested arrays
|
|
// IMPORTANT! transmitting a ValueExpression consistent with the parent's one
|
|
hi, err := b.MakeHeaderItem(receiver, paramName+" "+indexVar, indexVar+"i", "fmt.Sprintf(\"%s.%v\", \"header\", "+indexVar+")", res.ValueExpression+"I", items.Items, items)
|
|
if err != nil {
|
|
return GenItems{}, err
|
|
}
|
|
res.Child = &hi
|
|
hi.Parent = &res
|
|
// Propagates HasValidations flag to outer Items definition (currently not in use: done to remain consistent with parameters)
|
|
res.HasValidations = res.HasValidations || hi.HasValidations
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// HasValidations resolves the validation status for simple schema objects
|
|
func (b *codeGenOpBuilder) HasValidations(sh spec.CommonValidations, rt resolvedType) (hasValidations bool, hasSliceValidations bool) {
|
|
hasSliceValidations = sh.HasArrayValidations() || sh.HasEnum()
|
|
hasValidations = sh.HasNumberValidations() || sh.HasStringValidations() || hasSliceValidations || hasFormatValidation(rt)
|
|
return
|
|
}
|
|
|
|
func (b *codeGenOpBuilder) MakeParameterItem(receiver, paramName, indexVar, path, valueExpression, location string, resolver *typeResolver, items, parent *spec.Items) (GenItems, error) {
|
|
debugLog("making parameter item recv=%s param=%s index=%s valueExpr=%s path=%s location=%s", receiver, paramName, indexVar, valueExpression, path, location)
|
|
var res GenItems
|
|
res.resolvedType = simpleResolvedType(items.Type, items.Format, items.Items, &items.CommonValidations)
|
|
|
|
res.sharedValidations = sharedValidations{
|
|
Required: false,
|
|
SchemaValidations: items.Validations(),
|
|
}
|
|
res.Name = paramName
|
|
res.Path = path
|
|
res.Location = location
|
|
res.ValueExpression = swag.ToVarName(valueExpression)
|
|
res.CollectionFormat = items.CollectionFormat
|
|
res.Converter = stringConverters[res.GoType]
|
|
res.Formatter = stringFormatters[res.GoType]
|
|
res.IndexVar = indexVar
|
|
|
|
res.HasValidations, res.HasSliceValidations = b.HasValidations(items.CommonValidations, res.resolvedType)
|
|
res.IsEnumCI = b.GenOpts.AllowEnumCI || hasEnumCI(items.Extensions)
|
|
res.NeedsIndex = res.HasValidations || res.Converter != "" || (res.IsCustomFormatter && !res.SkipParse)
|
|
|
|
if items.Items != nil {
|
|
// Recursively follows nested arrays
|
|
// IMPORTANT! transmitting a ValueExpression consistent with the parent's one
|
|
pi, err := b.MakeParameterItem(receiver, paramName+" "+indexVar, indexVar+"i", "fmt.Sprintf(\"%s.%v\", "+path+", "+indexVar+")", res.ValueExpression+"I", location, resolver, items.Items, items)
|
|
if err != nil {
|
|
return GenItems{}, err
|
|
}
|
|
res.Child = &pi
|
|
pi.Parent = &res
|
|
// Propagates HasValidations flag to outer Items definition
|
|
res.HasValidations = res.HasValidations || pi.HasValidations
|
|
res.NeedsIndex = res.NeedsIndex || pi.NeedsIndex
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (b *codeGenOpBuilder) MakeParameter(receiver string, resolver *typeResolver, param spec.Parameter, idMapping map[string]map[string]string) (GenParameter, error) {
|
|
debugLog("[%s %s] making parameter %q", b.Method, b.Path, param.Name)
|
|
|
|
// assume minimal flattening has been carried on, so there is not $ref in response (but some may remain in response schema)
|
|
|
|
var child *GenItems
|
|
id := swag.ToGoName(param.Name)
|
|
if goName, ok := param.Extensions["x-go-name"]; ok {
|
|
id, ok = goName.(string)
|
|
if !ok {
|
|
return GenParameter{}, fmt.Errorf(`%s %s, parameter %q: "x-go-name" field must be a string, not a %T`,
|
|
b.Method, b.Path, param.Name, goName)
|
|
}
|
|
} else if len(idMapping) > 0 {
|
|
id = idMapping[param.In][param.Name]
|
|
}
|
|
|
|
res := GenParameter{
|
|
ID: id,
|
|
Name: param.Name,
|
|
ModelsPackage: b.ModelsPackage,
|
|
Path: fmt.Sprintf("%q", param.Name),
|
|
ValueExpression: fmt.Sprintf("%s.%s", receiver, id),
|
|
IndexVar: "i",
|
|
Default: param.Default,
|
|
HasDefault: param.Default != nil,
|
|
Description: trimBOM(param.Description),
|
|
ReceiverName: receiver,
|
|
CollectionFormat: param.CollectionFormat,
|
|
Child: child,
|
|
Location: param.In,
|
|
AllowEmptyValue: (param.In == "query" || param.In == "formData") && param.AllowEmptyValue,
|
|
Extensions: param.Extensions,
|
|
}
|
|
|
|
if param.In == "body" {
|
|
// Process parameters declared in body (i.e. have a Schema)
|
|
res.Required = param.Required
|
|
if err := b.MakeBodyParameter(&res, resolver, param.Schema); err != nil {
|
|
return GenParameter{}, err
|
|
}
|
|
} else {
|
|
// Process parameters declared in other inputs: path, query, header (SimpleSchema)
|
|
res.resolvedType = simpleResolvedType(param.Type, param.Format, param.Items, ¶m.CommonValidations)
|
|
res.sharedValidations = sharedValidations{
|
|
Required: param.Required,
|
|
SchemaValidations: param.Validations(),
|
|
}
|
|
|
|
res.ZeroValue = res.resolvedType.Zero()
|
|
|
|
hasChildValidations := false
|
|
if param.Items != nil {
|
|
// Follow Items definition for array parameters
|
|
pi, err := b.MakeParameterItem(receiver, param.Name+" "+res.IndexVar, res.IndexVar+"i", "fmt.Sprintf(\"%s.%v\", "+res.Path+", "+res.IndexVar+")", res.Name+"I", param.In, resolver, param.Items, nil)
|
|
if err != nil {
|
|
return GenParameter{}, err
|
|
}
|
|
res.Child = &pi
|
|
// Propagates HasValidations from from child array
|
|
hasChildValidations = pi.HasValidations
|
|
}
|
|
res.IsNullable = !param.Required && !param.AllowEmptyValue
|
|
res.HasValidations, res.HasSliceValidations = b.HasValidations(param.CommonValidations, res.resolvedType)
|
|
res.HasValidations = res.HasValidations || hasChildValidations
|
|
res.IsEnumCI = b.GenOpts.AllowEnumCI || hasEnumCI(param.Extensions)
|
|
}
|
|
|
|
// Select codegen strategy for body param validation
|
|
res.Converter = stringConverters[res.GoType]
|
|
res.Formatter = stringFormatters[res.GoType]
|
|
b.setBodyParamValidation(&res)
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// MakeBodyParameter constructs a body parameter schema
|
|
func (b *codeGenOpBuilder) MakeBodyParameter(res *GenParameter, resolver *typeResolver, sch *spec.Schema) error {
|
|
// resolve schema model
|
|
schema, ers := b.buildOperationSchema(res.Path, b.Operation.ID+"ParamsBody", swag.ToGoName(b.Operation.ID+" Body"), res.ReceiverName, res.IndexVar, sch, resolver)
|
|
if ers != nil {
|
|
return ers
|
|
}
|
|
res.Schema = &schema
|
|
res.Schema.Required = res.Required // Required in body is managed independently from validations
|
|
|
|
// build Child items for nested slices and maps
|
|
var items *GenItems
|
|
res.KeyVar = "k"
|
|
res.Schema.KeyVar = "k"
|
|
switch {
|
|
case schema.IsMap && !schema.IsInterface:
|
|
items = b.MakeBodyParameterItemsAndMaps(res, res.Schema.AdditionalProperties)
|
|
case schema.IsArray:
|
|
items = b.MakeBodyParameterItemsAndMaps(res, res.Schema.Items)
|
|
default:
|
|
items = new(GenItems)
|
|
}
|
|
|
|
// templates assume at least one .Child != nil
|
|
res.Child = items
|
|
schema.HasValidations = schema.HasValidations || items.HasValidations
|
|
|
|
res.resolvedType = schema.resolvedType
|
|
|
|
// simple and schema views share the same validations
|
|
res.sharedValidations = schema.sharedValidations
|
|
res.ZeroValue = schema.Zero()
|
|
return nil
|
|
}
|
|
|
|
// MakeBodyParameterItemsAndMaps clones the .Items schema structure (resp. .AdditionalProperties) as a .GenItems structure
|
|
// for compatibility with simple param templates.
|
|
//
|
|
// Constructed children assume simple structures: any complex object is assumed to be resolved by a model or extra schema definition
|
|
func (b *codeGenOpBuilder) MakeBodyParameterItemsAndMaps(res *GenParameter, it *GenSchema) *GenItems {
|
|
items := new(GenItems)
|
|
if it != nil {
|
|
var prev *GenItems
|
|
next := items
|
|
if res.Schema.IsArray {
|
|
next.Path = "fmt.Sprintf(\"%s.%v\", " + res.Path + ", " + res.IndexVar + ")"
|
|
} else if res.Schema.IsMap {
|
|
next.Path = "fmt.Sprintf(\"%s.%v\", " + res.Path + ", " + res.KeyVar + ")"
|
|
}
|
|
next.Name = res.Name + " " + res.Schema.IndexVar
|
|
next.IndexVar = res.Schema.IndexVar + "i"
|
|
next.KeyVar = res.Schema.KeyVar + "k"
|
|
next.ValueExpression = swag.ToVarName(res.Name + "I")
|
|
next.Location = "body"
|
|
for it != nil {
|
|
next.resolvedType = it.resolvedType
|
|
next.sharedValidations = it.sharedValidations
|
|
next.Formatter = stringFormatters[it.SwaggerFormat]
|
|
next.Converter = stringConverters[res.GoType]
|
|
next.Parent = prev
|
|
_, next.IsCustomFormatter = customFormatters[it.GoType]
|
|
next.IsCustomFormatter = next.IsCustomFormatter && !it.IsStream
|
|
|
|
// special instruction to avoid using CollectionFormat for body params
|
|
next.SkipParse = true
|
|
|
|
if prev != nil {
|
|
if prev.IsArray {
|
|
next.Path = "fmt.Sprintf(\"%s.%v\", " + prev.Path + ", " + prev.IndexVar + ")"
|
|
} else if prev.IsMap {
|
|
next.Path = "fmt.Sprintf(\"%s.%v\", " + prev.Path + ", " + prev.KeyVar + ")"
|
|
}
|
|
next.Name = prev.Name + prev.IndexVar
|
|
next.IndexVar = prev.IndexVar + "i"
|
|
next.KeyVar = prev.KeyVar + "k"
|
|
next.ValueExpression = swag.ToVarName(prev.ValueExpression + "I")
|
|
prev.Child = next
|
|
}
|
|
|
|
// found a complex or aliased thing
|
|
// hide details from the aliased type and stop recursing
|
|
if next.IsAliased || next.IsComplexObject {
|
|
next.IsArray = false
|
|
next.IsMap = false
|
|
next.IsCustomFormatter = false
|
|
next.IsComplexObject = true
|
|
next.IsAliased = true
|
|
break
|
|
}
|
|
if next.IsInterface || next.IsStream || next.IsBase64 {
|
|
next.HasValidations = false
|
|
}
|
|
next.NeedsIndex = next.HasValidations || next.Converter != "" || (next.IsCustomFormatter && !next.SkipParse)
|
|
prev = next
|
|
next = new(GenItems)
|
|
|
|
switch {
|
|
case it.Items != nil:
|
|
it = it.Items
|
|
case it.AdditionalProperties != nil:
|
|
it = it.AdditionalProperties
|
|
default:
|
|
it = nil
|
|
}
|
|
}
|
|
// propagate HasValidations
|
|
var propag func(child *GenItems) (bool, bool)
|
|
propag = func(child *GenItems) (bool, bool) {
|
|
if child == nil {
|
|
return false, false
|
|
}
|
|
cValidations, cIndex := propag(child.Child)
|
|
child.HasValidations = child.HasValidations || cValidations
|
|
child.NeedsIndex = child.HasValidations || child.Converter != "" || (child.IsCustomFormatter && !child.SkipParse) || cIndex
|
|
return child.HasValidations, child.NeedsIndex
|
|
}
|
|
items.HasValidations, items.NeedsIndex = propag(items)
|
|
|
|
// resolve nullability conflicts when declaring body as a map of array of an anonymous complex object
|
|
// (e.g. refer to an extra schema type, which is nullable, but not rendered as a pointer in arrays or maps)
|
|
// Rule: outer type rules (with IsMapNullOverride), inner types are fixed
|
|
var fixNullable func(child *GenItems) string
|
|
fixNullable = func(child *GenItems) string {
|
|
if !child.IsArray && !child.IsMap {
|
|
if child.IsComplexObject {
|
|
return child.GoType
|
|
}
|
|
return ""
|
|
}
|
|
if innerType := fixNullable(child.Child); innerType != "" {
|
|
if child.IsMapNullOverride && child.IsArray {
|
|
child.GoType = "[]" + innerType
|
|
return child.GoType
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
fixNullable(items)
|
|
}
|
|
return items
|
|
}
|
|
|
|
func (b *codeGenOpBuilder) setBodyParamValidation(p *GenParameter) {
|
|
// Determine validation strategy for body param.
|
|
//
|
|
// Here are the distinct strategies:
|
|
// - the body parameter is a model object => delegates
|
|
// - the body parameter is an array of model objects => carry on slice validations, then iterate and delegate
|
|
// - the body parameter is a map of model objects => iterate and delegate
|
|
// - the body parameter is an array of simple objects (including maps)
|
|
// - the body parameter is a map of simple objects (including arrays)
|
|
if p.IsBodyParam() {
|
|
var hasSimpleBodyParams, hasSimpleBodyItems, hasSimpleBodyMap, hasModelBodyParams, hasModelBodyItems, hasModelBodyMap bool
|
|
s := p.Schema
|
|
if s != nil {
|
|
doNot := s.IsInterface || s.IsStream || s.IsBase64
|
|
// composition of primitive fields must be properly identified: hack this through
|
|
_, isPrimitive := primitives[s.GoType]
|
|
_, isFormatter := customFormatters[s.GoType]
|
|
isComposedPrimitive := s.IsPrimitive && !(isPrimitive || isFormatter)
|
|
|
|
hasSimpleBodyParams = !s.IsComplexObject && !s.IsAliased && !isComposedPrimitive && !doNot
|
|
hasModelBodyParams = (s.IsComplexObject || s.IsAliased || isComposedPrimitive) && !doNot
|
|
|
|
if s.IsArray && s.Items != nil {
|
|
it := s.Items
|
|
doNot = it.IsInterface || it.IsStream || it.IsBase64
|
|
hasSimpleBodyItems = !it.IsComplexObject && !(it.IsAliased || doNot)
|
|
hasModelBodyItems = (it.IsComplexObject || it.IsAliased) && !doNot
|
|
}
|
|
if s.IsMap && s.AdditionalProperties != nil {
|
|
it := s.AdditionalProperties
|
|
hasSimpleBodyMap = !it.IsComplexObject && !(it.IsAliased || doNot)
|
|
hasModelBodyMap = !hasSimpleBodyMap && !doNot
|
|
}
|
|
}
|
|
// set validation strategy for body param
|
|
p.HasSimpleBodyParams = hasSimpleBodyParams
|
|
p.HasSimpleBodyItems = hasSimpleBodyItems
|
|
p.HasModelBodyParams = hasModelBodyParams
|
|
p.HasModelBodyItems = hasModelBodyItems
|
|
p.HasModelBodyMap = hasModelBodyMap
|
|
p.HasSimpleBodyMap = hasSimpleBodyMap
|
|
}
|
|
|
|
}
|
|
|
|
// makeSecuritySchemes produces a sorted list of security schemes for this operation
|
|
func (b *codeGenOpBuilder) makeSecuritySchemes(receiver string) GenSecuritySchemes {
|
|
return gatherSecuritySchemes(b.SecurityDefinitions, b.Name, b.Principal, receiver, b.GenOpts.PrincipalIsNullable())
|
|
}
|
|
|
|
// makeSecurityRequirements produces a sorted list of security requirements for this operation.
|
|
// As for current, these requirements are not used by codegen (sec. requirement is determined at runtime).
|
|
// We keep the order of the slice from the original spec, but sort the inner slice which comes from a map,
|
|
// as well as the map of scopes.
|
|
func (b *codeGenOpBuilder) makeSecurityRequirements(receiver string) []GenSecurityRequirements {
|
|
if b.Security == nil {
|
|
// nil (default requirement) is different than [] (no requirement)
|
|
return nil
|
|
}
|
|
|
|
securityRequirements := make([]GenSecurityRequirements, 0, len(b.Security))
|
|
for _, req := range b.Security {
|
|
jointReq := make(GenSecurityRequirements, 0, len(req))
|
|
for _, j := range req {
|
|
scopes := j.Scopes
|
|
sort.Strings(scopes)
|
|
jointReq = append(jointReq, GenSecurityRequirement{
|
|
Name: j.Name,
|
|
Scopes: scopes,
|
|
})
|
|
}
|
|
// sort joint requirements (come from a map in spec)
|
|
sort.Sort(jointReq)
|
|
securityRequirements = append(securityRequirements, jointReq)
|
|
}
|
|
return securityRequirements
|
|
}
|
|
|
|
// cloneSchema returns a deep copy of a schema
|
|
func (b *codeGenOpBuilder) cloneSchema(schema *spec.Schema) *spec.Schema {
|
|
savedSchema := &spec.Schema{}
|
|
schemaRep, _ := json.Marshal(schema)
|
|
_ = json.Unmarshal(schemaRep, savedSchema)
|
|
return savedSchema
|
|
}
|
|
|
|
// saveResolveContext keeps a copy of known definitions and schema to properly roll back on a makeGenSchema() call
|
|
// This uses a deep clone the spec document to construct a type resolver which knows about definitions when the making of this operation started,
|
|
// and only these definitions. We are not interested in the "original spec", but in the already transformed spec.
|
|
func (b *codeGenOpBuilder) saveResolveContext(resolver *typeResolver, schema *spec.Schema) (*typeResolver, *spec.Schema) {
|
|
if b.PristineDoc == nil {
|
|
b.PristineDoc = b.Doc.Pristine()
|
|
}
|
|
rslv := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget), b.DefaultImports[b.ModelsPackage], b.PristineDoc)
|
|
|
|
return rslv, b.cloneSchema(schema)
|
|
}
|
|
|
|
// liftExtraSchemas constructs the schema for an anonymous construct with some ExtraSchemas.
|
|
//
|
|
// When some ExtraSchemas are produced from something else than a definition,
|
|
// this indicates we are not running in fully flattened mode and we need to render
|
|
// these ExtraSchemas in the operation's package.
|
|
// We need to rebuild the schema with a new type resolver to reflect this change in the
|
|
// models package.
|
|
func (b *codeGenOpBuilder) liftExtraSchemas(resolver, rslv *typeResolver, bs *spec.Schema, sc *schemaGenContext) (schema *GenSchema, err error) {
|
|
// restore resolving state before previous call to makeGenSchema()
|
|
sc.Schema = *bs
|
|
|
|
pg := sc.shallowClone()
|
|
pkg := b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget)
|
|
|
|
// make a resolver for current package (i.e. operations)
|
|
pg.TypeResolver = newTypeResolver("", b.DefaultImports[b.APIPackage], rslv.Doc).withKeepDefinitionsPackage(pkg)
|
|
pg.ExtraSchemas = make(map[string]GenSchema, len(sc.ExtraSchemas))
|
|
pg.UseContainerInName = true
|
|
|
|
// rebuild schema within local package
|
|
if err = pg.makeGenSchema(); err != nil {
|
|
return
|
|
}
|
|
|
|
// lift nested extra schemas (inlined types)
|
|
if b.ExtraSchemas == nil {
|
|
b.ExtraSchemas = make(map[string]GenSchema, len(pg.ExtraSchemas))
|
|
}
|
|
for _, v := range pg.ExtraSchemas {
|
|
vv := v
|
|
if !v.IsStream {
|
|
b.ExtraSchemas[vv.Name] = vv
|
|
}
|
|
}
|
|
schema = &pg.GenSchema
|
|
return
|
|
}
|
|
|
|
// buildOperationSchema constructs a schema for an operation (for body params or responses).
|
|
// It determines if the schema is readily available from the models package,
|
|
// or if a schema has to be generated in the operations package (i.e. is anonymous).
|
|
// Whenever an anonymous schema needs some extra schemas, we also determine if these extras are
|
|
// available from models or must be generated alongside the schema in the operations package.
|
|
//
|
|
// Duplicate extra schemas are pruned later on, when operations grouping in packages (e.g. from tags) takes place.
|
|
func (b *codeGenOpBuilder) buildOperationSchema(schemaPath, containerName, schemaName, receiverName, indexVar string, sch *spec.Schema, resolver *typeResolver) (GenSchema, error) {
|
|
var schema GenSchema
|
|
|
|
if sch == nil {
|
|
sch = &spec.Schema{}
|
|
}
|
|
shallowClonedResolver := *resolver
|
|
shallowClonedResolver.ModelsFullPkg = b.DefaultImports[b.ModelsPackage]
|
|
rslv := &shallowClonedResolver
|
|
|
|
sc := schemaGenContext{
|
|
Path: schemaPath,
|
|
Name: containerName,
|
|
Receiver: receiverName,
|
|
ValueExpr: receiverName,
|
|
IndexVar: indexVar,
|
|
Schema: *sch,
|
|
Required: false,
|
|
TypeResolver: rslv,
|
|
Named: false,
|
|
IncludeModel: true,
|
|
IncludeValidator: b.GenOpts.IncludeValidator,
|
|
StrictAdditionalProperties: b.GenOpts.StrictAdditionalProperties,
|
|
ExtraSchemas: make(map[string]GenSchema),
|
|
StructTags: b.GenOpts.StructTags,
|
|
}
|
|
|
|
var (
|
|
br *typeResolver
|
|
bs *spec.Schema
|
|
)
|
|
|
|
if sch.Ref.String() == "" {
|
|
// backup the type resolver context
|
|
// (not needed when the schema has a name)
|
|
br, bs = b.saveResolveContext(rslv, sch)
|
|
}
|
|
|
|
if err := sc.makeGenSchema(); err != nil {
|
|
return GenSchema{}, err
|
|
}
|
|
for alias, pkg := range findImports(&sc.GenSchema) {
|
|
b.Imports[alias] = pkg
|
|
}
|
|
|
|
if sch.Ref.String() == "" && len(sc.ExtraSchemas) > 0 {
|
|
newSchema, err := b.liftExtraSchemas(resolver, br, bs, &sc)
|
|
if err != nil {
|
|
return GenSchema{}, err
|
|
}
|
|
if newSchema != nil {
|
|
schema = *newSchema
|
|
}
|
|
} else {
|
|
schema = sc.GenSchema
|
|
}
|
|
|
|
if schema.IsAnonymous {
|
|
// a generated name for anonymous schema
|
|
// TODO: support x-go-name
|
|
hasProperties := len(schema.Properties) > 0
|
|
isAllOf := len(schema.AllOf) > 0
|
|
isInterface := schema.IsInterface
|
|
hasValidations := schema.HasValidations
|
|
|
|
// for complex anonymous objects, produce an extra schema
|
|
if hasProperties || isAllOf {
|
|
if b.ExtraSchemas == nil {
|
|
b.ExtraSchemas = make(map[string]GenSchema)
|
|
}
|
|
schema.Name = schemaName
|
|
schema.GoType = schemaName
|
|
schema.IsAnonymous = false
|
|
b.ExtraSchemas[schemaName] = schema
|
|
|
|
// constructs new schema to refer to the newly created type
|
|
schema = GenSchema{}
|
|
schema.IsAnonymous = false
|
|
schema.IsComplexObject = true
|
|
schema.SwaggerType = schemaName
|
|
schema.HasValidations = hasValidations
|
|
schema.GoType = schemaName
|
|
} else if isInterface {
|
|
schema = GenSchema{}
|
|
schema.IsAnonymous = false
|
|
schema.IsComplexObject = false
|
|
schema.IsInterface = true
|
|
schema.HasValidations = false
|
|
schema.GoType = iface
|
|
}
|
|
}
|
|
return schema, nil
|
|
}
|
|
|
|
func intersectTags(left, right []string) []string {
|
|
// dedupe
|
|
uniqueTags := make(map[string]struct{}, maxInt(len(left), len(right)))
|
|
for _, l := range left {
|
|
if len(right) == 0 || swag.ContainsStrings(right, l) {
|
|
uniqueTags[l] = struct{}{}
|
|
}
|
|
}
|
|
filtered := make([]string, 0, len(uniqueTags))
|
|
// stable output across generations, preserving original order
|
|
for _, k := range left {
|
|
if _, ok := uniqueTags[k]; !ok {
|
|
continue
|
|
}
|
|
filtered = append(filtered, k)
|
|
delete(uniqueTags, k)
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
// analyze tags for an operation
|
|
func (b *codeGenOpBuilder) analyzeTags() (string, []string, bool) {
|
|
var (
|
|
filter []string
|
|
tag string
|
|
hasTagOverride bool
|
|
)
|
|
if b.GenOpts != nil {
|
|
filter = b.GenOpts.Tags
|
|
}
|
|
intersected := intersectTags(pruneEmpty(b.Operation.Tags), filter)
|
|
if !b.GenOpts.SkipTagPackages && len(intersected) > 0 {
|
|
// override generation with: x-go-operation-tag
|
|
tag, hasTagOverride = b.Operation.Extensions.GetString(xGoOperationTag)
|
|
if !hasTagOverride {
|
|
// TODO(fred): this part should be delegated to some new TagsFor(operation) in go-openapi/analysis
|
|
tag = intersected[0]
|
|
gtags := b.Doc.Spec().Tags
|
|
for _, gtag := range gtags {
|
|
if gtag.Name != tag {
|
|
continue
|
|
}
|
|
// honor x-go-name in tag
|
|
if name, hasGoName := gtag.Extensions.GetString(xGoName); hasGoName {
|
|
tag = name
|
|
break
|
|
}
|
|
// honor x-go-operation-tag in tag
|
|
if name, hasOpName := gtag.Extensions.GetString(xGoOperationTag); hasOpName {
|
|
tag = name
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if tag == b.APIPackage {
|
|
// conflict with "operations" package is handled separately
|
|
tag = renameOperationPackage(intersected, tag)
|
|
}
|
|
b.APIPackage = b.GenOpts.LanguageOpts.ManglePackageName(tag, b.APIPackage) // actual package name
|
|
b.APIPackageAlias = deconflictTag(intersected, b.APIPackage) // deconflicted import alias
|
|
return tag, intersected, len(filter) == 0 || len(filter) > 0 && len(intersected) > 0
|
|
}
|
|
|
|
func maxInt(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
// deconflictTag ensures generated packages for operations based on tags do not conflict
|
|
// with other imports
|
|
func deconflictTag(seenTags []string, pkg string) string {
|
|
return deconflictPkg(pkg, func(pkg string) string { return renameOperationPackage(seenTags, pkg) })
|
|
}
|
|
|
|
// deconflictPrincipal ensures that whenever an external principal package is added, it doesn't conflict
|
|
// with standard inports
|
|
func deconflictPrincipal(pkg string) string {
|
|
switch pkg {
|
|
case "principal":
|
|
return renamePrincipalPackage(pkg)
|
|
default:
|
|
return deconflictPkg(pkg, renamePrincipalPackage)
|
|
}
|
|
}
|
|
|
|
// deconflictPkg renames package names which conflict with standard imports
|
|
func deconflictPkg(pkg string, renamer func(string) string) string {
|
|
switch pkg {
|
|
// package conflict with variables
|
|
case "api", "httptransport", "formats", "server":
|
|
fallthrough
|
|
// package conflict with go-openapi imports
|
|
case "errors", "runtime", "middleware", "security", "spec", "strfmt", "loads", "swag", "validate":
|
|
fallthrough
|
|
// package conflict with stdlib/other lib imports
|
|
case "tls", "http", "fmt", "strings", "log", "flags", "pflag", "json", "time":
|
|
return renamer(pkg)
|
|
}
|
|
return pkg
|
|
}
|
|
|
|
func renameOperationPackage(seenTags []string, pkg string) string {
|
|
current := strings.ToLower(pkg) + "ops"
|
|
if len(seenTags) == 0 {
|
|
return current
|
|
}
|
|
for swag.ContainsStringsCI(seenTags, current) {
|
|
current += "1"
|
|
}
|
|
return current
|
|
}
|
|
|
|
func renamePrincipalPackage(pkg string) string {
|
|
// favors readability over perfect deconfliction
|
|
return "auth"
|
|
}
|
|
|
|
func renameServerPackage(pkg string) string {
|
|
// favors readability over perfect deconfliction
|
|
return "swagger" + pkg + "srv"
|
|
}
|
|
|
|
func renameAPIPackage(pkg string) string {
|
|
// favors readability over perfect deconfliction
|
|
return "swagger" + pkg
|
|
}
|