github.com/emreu/go-swagger@v0.22.1/generator/shared.go (about)

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package generator
    16  
    17  import (
    18  	"bytes"
    19  	"errors"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"log"
    23  	"os"
    24  	"path"
    25  	"path/filepath"
    26  	"reflect"
    27  	"regexp"
    28  	"sort"
    29  	"strings"
    30  	"text/template"
    31  	"unicode"
    32  
    33  	swaggererrors "github.com/go-openapi/errors"
    34  
    35  	"github.com/go-openapi/analysis"
    36  	"github.com/go-openapi/loads"
    37  	"github.com/go-openapi/spec"
    38  	"github.com/go-openapi/strfmt"
    39  	"github.com/go-openapi/swag"
    40  	"github.com/go-openapi/validate"
    41  	"golang.org/x/tools/imports"
    42  )
    43  
    44  //go:generate go-bindata -mode 420 -modtime 1482416923 -pkg=generator -ignore=.*\.sw? -ignore=.*\.md ./templates/...
    45  
    46  // LanguageOpts to describe a language to the code generator
    47  type LanguageOpts struct {
    48  	ReservedWords    []string
    49  	BaseImportFunc   func(string) string `json:"-"`
    50  	reservedWordsSet map[string]struct{}
    51  	initialized      bool
    52  	formatFunc       func(string, []byte) ([]byte, error)
    53  	fileNameFunc     func(string) string
    54  }
    55  
    56  // Init the language option
    57  func (l *LanguageOpts) Init() {
    58  	if !l.initialized {
    59  		l.initialized = true
    60  		l.reservedWordsSet = make(map[string]struct{})
    61  		for _, rw := range l.ReservedWords {
    62  			l.reservedWordsSet[rw] = struct{}{}
    63  		}
    64  	}
    65  }
    66  
    67  // MangleName makes sure a reserved word gets a safe name
    68  func (l *LanguageOpts) MangleName(name, suffix string) string {
    69  	if _, ok := l.reservedWordsSet[swag.ToFileName(name)]; !ok {
    70  		return name
    71  	}
    72  	return strings.Join([]string{name, suffix}, "_")
    73  }
    74  
    75  // MangleVarName makes sure a reserved word gets a safe name
    76  func (l *LanguageOpts) MangleVarName(name string) string {
    77  	nm := swag.ToVarName(name)
    78  	if _, ok := l.reservedWordsSet[nm]; !ok {
    79  		return nm
    80  	}
    81  	return nm + "Var"
    82  }
    83  
    84  // MangleFileName makes sure a file name gets a safe name
    85  func (l *LanguageOpts) MangleFileName(name string) string {
    86  	if l.fileNameFunc != nil {
    87  		return l.fileNameFunc(name)
    88  	}
    89  	return swag.ToFileName(name)
    90  }
    91  
    92  // ManglePackageName makes sure a package gets a safe name.
    93  // In case of a file system path (e.g. name contains "/" or "\" on Windows), this return only the last element.
    94  func (l *LanguageOpts) ManglePackageName(name, suffix string) string {
    95  	if name == "" {
    96  		return suffix
    97  	}
    98  	pth := filepath.ToSlash(filepath.Clean(name)) // preserve path
    99  	_, pkg := path.Split(pth)                     // drop path
   100  	return l.MangleName(swag.ToFileName(pkg), suffix)
   101  }
   102  
   103  // ManglePackagePath makes sure a full package path gets a safe name.
   104  // Only the last part of the path is altered.
   105  func (l *LanguageOpts) ManglePackagePath(name string, suffix string) string {
   106  	if name == "" {
   107  		return suffix
   108  	}
   109  	target := filepath.ToSlash(filepath.Clean(name)) // preserve path
   110  	parts := strings.Split(target, "/")
   111  	parts[len(parts)-1] = l.ManglePackageName(parts[len(parts)-1], suffix)
   112  	return strings.Join(parts, "/")
   113  }
   114  
   115  // FormatContent formats a file with a language specific formatter
   116  func (l *LanguageOpts) FormatContent(name string, content []byte) ([]byte, error) {
   117  	if l.formatFunc != nil {
   118  		return l.formatFunc(name, content)
   119  	}
   120  	return content, nil
   121  }
   122  
   123  func (l *LanguageOpts) baseImport(tgt string) string {
   124  	if l.BaseImportFunc != nil {
   125  		return l.BaseImportFunc(tgt)
   126  	}
   127  	return ""
   128  }
   129  
   130  var golang = GoLangOpts()
   131  
   132  // GoLangOpts for rendering items as golang code
   133  func GoLangOpts() *LanguageOpts {
   134  	var goOtherReservedSuffixes = map[string]bool{
   135  		// see:
   136  		// https://golang.org/src/go/build/syslist.go
   137  		// https://golang.org/doc/install/source#environment
   138  
   139  		// goos
   140  		"android":   true,
   141  		"darwin":    true,
   142  		"dragonfly": true,
   143  		"freebsd":   true,
   144  		"js":        true,
   145  		"linux":     true,
   146  		"nacl":      true,
   147  		"netbsd":    true,
   148  		"openbsd":   true,
   149  		"plan9":     true,
   150  		"solaris":   true,
   151  		"windows":   true,
   152  		"zos":       true,
   153  
   154  		// arch
   155  		"386":         true,
   156  		"amd64":       true,
   157  		"amd64p32":    true,
   158  		"arm":         true,
   159  		"armbe":       true,
   160  		"arm64":       true,
   161  		"arm64be":     true,
   162  		"mips":        true,
   163  		"mipsle":      true,
   164  		"mips64":      true,
   165  		"mips64le":    true,
   166  		"mips64p32":   true,
   167  		"mips64p32le": true,
   168  		"ppc":         true,
   169  		"ppc64":       true,
   170  		"ppc64le":     true,
   171  		"riscv":       true,
   172  		"riscv64":     true,
   173  		"s390":        true,
   174  		"s390x":       true,
   175  		"sparc":       true,
   176  		"sparc64":     true,
   177  		"wasm":        true,
   178  
   179  		// other reserved suffixes
   180  		"test": true,
   181  	}
   182  
   183  	opts := new(LanguageOpts)
   184  	opts.ReservedWords = []string{
   185  		"break", "default", "func", "interface", "select",
   186  		"case", "defer", "go", "map", "struct",
   187  		"chan", "else", "goto", "package", "switch",
   188  		"const", "fallthrough", "if", "range", "type",
   189  		"continue", "for", "import", "return", "var",
   190  	}
   191  	opts.formatFunc = func(ffn string, content []byte) ([]byte, error) {
   192  		opts := new(imports.Options)
   193  		opts.TabIndent = true
   194  		opts.TabWidth = 2
   195  		opts.Fragment = true
   196  		opts.Comments = true
   197  		return imports.Process(ffn, content, opts)
   198  	}
   199  	opts.fileNameFunc = func(name string) string {
   200  		// whenever a generated file name ends with a suffix
   201  		// that is meaningful to go build, adds a "swagger"
   202  		// suffix
   203  		parts := strings.Split(swag.ToFileName(name), "_")
   204  		if goOtherReservedSuffixes[parts[len(parts)-1]] {
   205  			// file name ending with a reserved arch or os name
   206  			// are appended an innocuous suffix "swagger"
   207  			parts = append(parts, "swagger")
   208  		}
   209  		return strings.Join(parts, "_")
   210  	}
   211  
   212  	opts.BaseImportFunc = func(tgt string) string {
   213  		tgt = filepath.Clean(tgt)
   214  		// On Windows, filepath.Abs("") behaves differently than on Unix.
   215  		// Windows: yields an error, since Abs() does not know the volume.
   216  		// UNIX: returns current working directory
   217  		if tgt == "" {
   218  			tgt = "."
   219  		}
   220  		tgtAbsPath, err := filepath.Abs(tgt)
   221  		if err != nil {
   222  			log.Fatalf("could not evaluate base import path with target \"%s\": %v", tgt, err)
   223  		}
   224  
   225  		var tgtAbsPathExtended string
   226  		tgtAbsPathExtended, err = filepath.EvalSymlinks(tgtAbsPath)
   227  		if err != nil {
   228  			log.Fatalf("could not evaluate base import path with target \"%s\" (with symlink resolution): %v", tgtAbsPath, err)
   229  		}
   230  
   231  		gopath := os.Getenv("GOPATH")
   232  		if gopath == "" {
   233  			gopath = filepath.Join(os.Getenv("HOME"), "go")
   234  		}
   235  
   236  		var pth string
   237  		for _, gp := range filepath.SplitList(gopath) {
   238  			// EvalSymLinks also calls the Clean
   239  			gopathExtended, er := filepath.EvalSymlinks(gp)
   240  			if er != nil {
   241  				log.Fatalln(er)
   242  			}
   243  			gopathExtended = filepath.Join(gopathExtended, "src")
   244  			gp = filepath.Join(gp, "src")
   245  
   246  			// At this stage we have expanded and unexpanded target path. GOPATH is fully expanded.
   247  			// Expanded means symlink free.
   248  			// We compare both types of targetpath<s> with gopath.
   249  			// If any one of them coincides with gopath , it is imperative that
   250  			// target path lies inside gopath. How?
   251  			// 		- Case 1: Irrespective of symlinks paths coincide. Both non-expanded paths.
   252  			// 		- Case 2: Symlink in target path points to location inside GOPATH. (Expanded Target Path)
   253  			//    - Case 3: Symlink in target path points to directory outside GOPATH (Unexpanded target path)
   254  
   255  			// Case 1: - Do nothing case. If non-expanded paths match just generate base import path as if
   256  			//				   there are no symlinks.
   257  
   258  			// Case 2: - Symlink in target path points to location inside GOPATH. (Expanded Target Path)
   259  			//					 First if will fail. Second if will succeed.
   260  
   261  			// Case 3: - Symlink in target path points to directory outside GOPATH (Unexpanded target path)
   262  			// 					 First if will succeed and break.
   263  
   264  			//compares non expanded path for both
   265  			if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPath, gp); ok {
   266  				pth = relativepath
   267  				break
   268  			}
   269  
   270  			// Compares non-expanded target path
   271  			if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPath, gopathExtended); ok {
   272  				pth = relativepath
   273  				break
   274  			}
   275  
   276  			// Compares expanded target path.
   277  			if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPathExtended, gopathExtended); ok {
   278  				pth = relativepath
   279  				break
   280  			}
   281  
   282  		}
   283  
   284  		mod, goModuleAbsPath, err := tryResolveModule(tgtAbsPath)
   285  		switch {
   286  		case err != nil:
   287  			log.Fatalf("Failed to resolve module using go.mod file: %s", err)
   288  		case mod != "":
   289  			relTgt := relPathToRelGoPath(goModuleAbsPath, tgtAbsPath)
   290  			if !strings.HasSuffix(mod, relTgt) {
   291  				return mod + relTgt
   292  			}
   293  			return mod
   294  		}
   295  
   296  		if pth == "" {
   297  			log.Fatalln("target must reside inside a location in the $GOPATH/src or be a module")
   298  		}
   299  		return pth
   300  	}
   301  	opts.Init()
   302  	return opts
   303  }
   304  
   305  var moduleRe = regexp.MustCompile(`module[ \t]+([^\s]+)`)
   306  
   307  // resolveGoModFile walks up the directory tree starting from 'dir' until it
   308  // finds a go.mod file. If go.mod is found it will return the related file
   309  // object. If no go.mod file is found it will return an error.
   310  func resolveGoModFile(dir string) (*os.File, string, error) {
   311  	goModPath := filepath.Join(dir, "go.mod")
   312  	f, err := os.Open(goModPath)
   313  	if err != nil {
   314  		if os.IsNotExist(err) && dir != filepath.Dir(dir) {
   315  			return resolveGoModFile(filepath.Dir(dir))
   316  		}
   317  		return nil, "", err
   318  	}
   319  	return f, dir, nil
   320  }
   321  
   322  // relPathToRelGoPath takes a relative os path and returns the relative go
   323  // package path. For unix nothing will change but for windows \ will be
   324  // converted to /.
   325  func relPathToRelGoPath(modAbsPath, absPath string) string {
   326  	if absPath == "." {
   327  		return ""
   328  	}
   329  
   330  	path := strings.TrimPrefix(absPath, modAbsPath)
   331  	pathItems := strings.Split(path, string(filepath.Separator))
   332  	return strings.Join(pathItems, "/")
   333  }
   334  
   335  func tryResolveModule(baseTargetPath string) (string, string, error) {
   336  	f, goModAbsPath, err := resolveGoModFile(baseTargetPath)
   337  	switch {
   338  	case os.IsNotExist(err):
   339  		return "", "", nil
   340  	case err != nil:
   341  		return "", "", err
   342  	}
   343  
   344  	src, err := ioutil.ReadAll(f)
   345  	if err != nil {
   346  		return "", "", err
   347  	}
   348  
   349  	match := moduleRe.FindSubmatch(src)
   350  	if len(match) != 2 {
   351  		return "", "", nil
   352  	}
   353  
   354  	return string(match[1]), goModAbsPath, nil
   355  }
   356  
   357  func findSwaggerSpec(nm string) (string, error) {
   358  	specs := []string{"swagger.json", "swagger.yml", "swagger.yaml"}
   359  	if nm != "" {
   360  		specs = []string{nm}
   361  	}
   362  	var name string
   363  	for _, nn := range specs {
   364  		f, err := os.Stat(nn)
   365  		if err != nil && !os.IsNotExist(err) {
   366  			return "", err
   367  		}
   368  		if err != nil && os.IsNotExist(err) {
   369  			continue
   370  		}
   371  		if f.IsDir() {
   372  			return "", fmt.Errorf("%s is a directory", nn)
   373  		}
   374  		name = nn
   375  		break
   376  	}
   377  	if name == "" {
   378  		return "", errors.New("couldn't find a swagger spec")
   379  	}
   380  	return name, nil
   381  }
   382  
   383  // DefaultSectionOpts for a given opts, this is used when no config file is passed
   384  // and uses the embedded templates when no local override can be found
   385  func DefaultSectionOpts(gen *GenOpts) {
   386  	sec := gen.Sections
   387  	if len(sec.Models) == 0 {
   388  		sec.Models = []TemplateOpts{
   389  			{
   390  				Name:     "definition",
   391  				Source:   "asset:model",
   392  				Target:   "{{ joinFilePath .Target (toPackagePath .ModelPackage) }}",
   393  				FileName: "{{ (snakize (pascalize .Name)) }}.go",
   394  			},
   395  		}
   396  	}
   397  
   398  	if len(sec.Operations) == 0 {
   399  		if gen.IsClient {
   400  			sec.Operations = []TemplateOpts{
   401  				{
   402  					Name:     "parameters",
   403  					Source:   "asset:clientParameter",
   404  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}",
   405  					FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go",
   406  				},
   407  				{
   408  					Name:     "responses",
   409  					Source:   "asset:clientResponse",
   410  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}",
   411  					FileName: "{{ (snakize (pascalize .Name)) }}_responses.go",
   412  				},
   413  			}
   414  
   415  		} else {
   416  			ops := []TemplateOpts{}
   417  			if gen.IncludeParameters {
   418  				ops = append(ops, TemplateOpts{
   419  					Name:     "parameters",
   420  					Source:   "asset:serverParameter",
   421  					Target:   "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package)  }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
   422  					FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go",
   423  				})
   424  			}
   425  			if gen.IncludeURLBuilder {
   426  				ops = append(ops, TemplateOpts{
   427  					Name:     "urlbuilder",
   428  					Source:   "asset:serverUrlbuilder",
   429  					Target:   "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
   430  					FileName: "{{ (snakize (pascalize .Name)) }}_urlbuilder.go",
   431  				})
   432  			}
   433  			if gen.IncludeResponses {
   434  				ops = append(ops, TemplateOpts{
   435  					Name:     "responses",
   436  					Source:   "asset:serverResponses",
   437  					Target:   "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
   438  					FileName: "{{ (snakize (pascalize .Name)) }}_responses.go",
   439  				})
   440  			}
   441  			if gen.IncludeHandler {
   442  				ops = append(ops, TemplateOpts{
   443  					Name:     "handler",
   444  					Source:   "asset:serverOperation",
   445  					Target:   "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
   446  					FileName: "{{ (snakize (pascalize .Name)) }}.go",
   447  				})
   448  			}
   449  			sec.Operations = ops
   450  		}
   451  	}
   452  
   453  	if len(sec.OperationGroups) == 0 {
   454  		if gen.IsClient {
   455  			sec.OperationGroups = []TemplateOpts{
   456  				{
   457  					Name:     "client",
   458  					Source:   "asset:clientClient",
   459  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Name)}}",
   460  					FileName: "{{ (snakize (pascalize .Name)) }}_client.go",
   461  				},
   462  			}
   463  		} else {
   464  			sec.OperationGroups = []TemplateOpts{}
   465  		}
   466  	}
   467  
   468  	if len(sec.Application) == 0 {
   469  		if gen.IsClient {
   470  			sec.Application = []TemplateOpts{
   471  				{
   472  					Name:     "facade",
   473  					Source:   "asset:clientFacade",
   474  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) }}",
   475  					FileName: "{{ snakize .Name }}Client.go",
   476  				},
   477  			}
   478  		} else {
   479  			sec.Application = []TemplateOpts{
   480  				{
   481  					Name:       "configure",
   482  					Source:     "asset:serverConfigureapi",
   483  					Target:     "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   484  					FileName:   "configure_{{ (snakize (pascalize .Name)) }}.go",
   485  					SkipExists: !gen.RegenerateConfigureAPI,
   486  				},
   487  				{
   488  					Name:     "main",
   489  					Source:   "asset:serverMain",
   490  					Target:   "{{ joinFilePath .Target \"cmd\" (dasherize (pascalize .Name)) }}-server",
   491  					FileName: "main.go",
   492  				},
   493  				{
   494  					Name:     "embedded_spec",
   495  					Source:   "asset:swaggerJsonEmbed",
   496  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   497  					FileName: "embedded_spec.go",
   498  				},
   499  				{
   500  					Name:     "server",
   501  					Source:   "asset:serverServer",
   502  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   503  					FileName: "server.go",
   504  				},
   505  				{
   506  					Name:     "builder",
   507  					Source:   "asset:serverBuilder",
   508  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) }}",
   509  					FileName: "{{ snakize (pascalize .Name) }}_api.go",
   510  				},
   511  				{
   512  					Name:     "doc",
   513  					Source:   "asset:serverDoc",
   514  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   515  					FileName: "doc.go",
   516  				},
   517  			}
   518  		}
   519  	}
   520  	gen.Sections = sec
   521  
   522  }
   523  
   524  // TemplateOpts allows
   525  type TemplateOpts struct {
   526  	Name       string `mapstructure:"name"`
   527  	Source     string `mapstructure:"source"`
   528  	Target     string `mapstructure:"target"`
   529  	FileName   string `mapstructure:"file_name"`
   530  	SkipExists bool   `mapstructure:"skip_exists"`
   531  	SkipFormat bool   `mapstructure:"skip_format"`
   532  }
   533  
   534  // SectionOpts allows for specifying options to customize the templates used for generation
   535  type SectionOpts struct {
   536  	Application     []TemplateOpts `mapstructure:"application"`
   537  	Operations      []TemplateOpts `mapstructure:"operations"`
   538  	OperationGroups []TemplateOpts `mapstructure:"operation_groups"`
   539  	Models          []TemplateOpts `mapstructure:"models"`
   540  }
   541  
   542  // GenOpts the options for the generator
   543  type GenOpts struct {
   544  	IncludeModel               bool
   545  	IncludeValidator           bool
   546  	IncludeHandler             bool
   547  	IncludeParameters          bool
   548  	IncludeResponses           bool
   549  	IncludeURLBuilder          bool
   550  	IncludeMain                bool
   551  	IncludeSupport             bool
   552  	ExcludeSpec                bool
   553  	DumpData                   bool
   554  	ValidateSpec               bool
   555  	FlattenOpts                *analysis.FlattenOpts
   556  	IsClient                   bool
   557  	defaultsEnsured            bool
   558  	PropertiesSpecOrder        bool
   559  	StrictAdditionalProperties bool
   560  	AllowTemplateOverride      bool
   561  
   562  	Spec                   string
   563  	APIPackage             string
   564  	ModelPackage           string
   565  	ServerPackage          string
   566  	ClientPackage          string
   567  	Principal              string
   568  	Target                 string
   569  	Sections               SectionOpts
   570  	LanguageOpts           *LanguageOpts
   571  	TypeMapping            map[string]string
   572  	Imports                map[string]string
   573  	DefaultScheme          string
   574  	DefaultProduces        string
   575  	DefaultConsumes        string
   576  	TemplateDir            string
   577  	Template               string
   578  	RegenerateConfigureAPI bool
   579  	Operations             []string
   580  	Models                 []string
   581  	Tags                   []string
   582  	Name                   string
   583  	FlagStrategy           string
   584  	CompatibilityMode      string
   585  	ExistingModels         string
   586  	Copyright              string
   587  }
   588  
   589  // CheckOpts carries out some global consistency checks on options.
   590  //
   591  // At the moment, these checks simply protect TargetPath() and SpecPath()
   592  // functions. More checks may be added here.
   593  func (g *GenOpts) CheckOpts() error {
   594  	if !filepath.IsAbs(g.Target) {
   595  		if _, err := filepath.Abs(g.Target); err != nil {
   596  			return fmt.Errorf("could not locate target %s: %v", g.Target, err)
   597  		}
   598  	}
   599  	if filepath.IsAbs(g.ServerPackage) {
   600  		return fmt.Errorf("you shouldn't specify an absolute path in --server-package: %s", g.ServerPackage)
   601  	}
   602  	if !filepath.IsAbs(g.Spec) && !strings.HasPrefix(g.Spec, "http://") && !strings.HasPrefix(g.Spec, "https://") {
   603  		if _, err := filepath.Abs(g.Spec); err != nil {
   604  			return fmt.Errorf("could not locate spec: %s", g.Spec)
   605  		}
   606  	}
   607  	return nil
   608  }
   609  
   610  // TargetPath returns the target generation path relative to the server package.
   611  // This method is used by templates, e.g. with {{ .TargetPath }}
   612  //
   613  // Errors cases are prevented by calling CheckOpts beforehand.
   614  //
   615  // Example:
   616  // Target: ${PWD}/tmp
   617  // ServerPackage: abc/efg
   618  //
   619  // Server is generated in ${PWD}/tmp/abc/efg
   620  // relative TargetPath returned: ../../../tmp
   621  //
   622  func (g *GenOpts) TargetPath() string {
   623  	var tgt string
   624  	if g.Target == "" {
   625  		tgt = "." // That's for windows
   626  	} else {
   627  		tgt = g.Target
   628  	}
   629  	tgtAbs, _ := filepath.Abs(tgt)
   630  	srvPkg := filepath.FromSlash(g.LanguageOpts.ManglePackagePath(g.ServerPackage, "server"))
   631  	srvrAbs := filepath.Join(tgtAbs, srvPkg)
   632  	tgtRel, _ := filepath.Rel(srvrAbs, filepath.Dir(tgtAbs))
   633  	tgtRel = filepath.Join(tgtRel, filepath.Base(tgtAbs))
   634  	return tgtRel
   635  }
   636  
   637  // SpecPath returns the path to the spec relative to the server package.
   638  // If the spec is remote keep this absolute location.
   639  //
   640  // If spec is not relative to server (e.g. lives on a different drive on windows),
   641  // then the resolved path is absolute.
   642  //
   643  // This method is used by templates, e.g. with {{ .SpecPath }}
   644  //
   645  // Errors cases are prevented by calling CheckOpts beforehand.
   646  func (g *GenOpts) SpecPath() string {
   647  	if strings.HasPrefix(g.Spec, "http://") || strings.HasPrefix(g.Spec, "https://") {
   648  		return g.Spec
   649  	}
   650  	// Local specifications
   651  	specAbs, _ := filepath.Abs(g.Spec)
   652  	var tgt string
   653  	if g.Target == "" {
   654  		tgt = "." // That's for windows
   655  	} else {
   656  		tgt = g.Target
   657  	}
   658  	tgtAbs, _ := filepath.Abs(tgt)
   659  	srvPkg := filepath.FromSlash(g.LanguageOpts.ManglePackagePath(g.ServerPackage, "server"))
   660  	srvAbs := filepath.Join(tgtAbs, srvPkg)
   661  	specRel, err := filepath.Rel(srvAbs, specAbs)
   662  	if err != nil {
   663  		return specAbs
   664  	}
   665  	return specRel
   666  }
   667  
   668  // EnsureDefaults for these gen opts
   669  func (g *GenOpts) EnsureDefaults() error {
   670  	if g.defaultsEnsured {
   671  		return nil
   672  	}
   673  	DefaultSectionOpts(g)
   674  	if g.LanguageOpts == nil {
   675  		g.LanguageOpts = GoLangOpts()
   676  	}
   677  	// set defaults for flattening options
   678  	g.FlattenOpts = &analysis.FlattenOpts{
   679  		Minimal:      true,
   680  		Verbose:      true,
   681  		RemoveUnused: false,
   682  		Expand:       false,
   683  	}
   684  	g.defaultsEnsured = true
   685  	return nil
   686  }
   687  
   688  func (g *GenOpts) location(t *TemplateOpts, data interface{}) (string, string, error) {
   689  	v := reflect.Indirect(reflect.ValueOf(data))
   690  	fld := v.FieldByName("Name")
   691  	var name string
   692  	if fld.IsValid() {
   693  		log.Println("name field", fld.String())
   694  		name = fld.String()
   695  	}
   696  
   697  	fldpack := v.FieldByName("Package")
   698  	pkg := g.APIPackage
   699  	if fldpack.IsValid() {
   700  		log.Println("package field", fldpack.String())
   701  		pkg = fldpack.String()
   702  	}
   703  
   704  	var tags []string
   705  	tagsF := v.FieldByName("Tags")
   706  	if tagsF.IsValid() {
   707  		tags = tagsF.Interface().([]string)
   708  	}
   709  
   710  	pthTpl, err := template.New(t.Name + "-target").Funcs(FuncMap).Parse(t.Target)
   711  	if err != nil {
   712  		return "", "", err
   713  	}
   714  
   715  	fNameTpl, err := template.New(t.Name + "-filename").Funcs(FuncMap).Parse(t.FileName)
   716  	if err != nil {
   717  		return "", "", err
   718  	}
   719  
   720  	d := struct {
   721  		Name, Package, APIPackage, ServerPackage, ClientPackage, ModelPackage, Target string
   722  		Tags                                                                          []string
   723  	}{
   724  		Name:          name,
   725  		Package:       pkg,
   726  		APIPackage:    g.APIPackage,
   727  		ServerPackage: g.ServerPackage,
   728  		ClientPackage: g.ClientPackage,
   729  		ModelPackage:  g.ModelPackage,
   730  		Target:        g.Target,
   731  		Tags:          tags,
   732  	}
   733  
   734  	// pretty.Println(data)
   735  	var pthBuf bytes.Buffer
   736  	if e := pthTpl.Execute(&pthBuf, d); e != nil {
   737  		return "", "", e
   738  	}
   739  
   740  	var fNameBuf bytes.Buffer
   741  	if e := fNameTpl.Execute(&fNameBuf, d); e != nil {
   742  		return "", "", e
   743  	}
   744  	return pthBuf.String(), fileName(fNameBuf.String()), nil
   745  }
   746  
   747  func (g *GenOpts) render(t *TemplateOpts, data interface{}) ([]byte, error) {
   748  	var templ *template.Template
   749  
   750  	if strings.HasPrefix(strings.ToLower(t.Source), "asset:") {
   751  		tt, err := templates.Get(strings.TrimPrefix(t.Source, "asset:"))
   752  		if err != nil {
   753  			return nil, err
   754  		}
   755  		templ = tt
   756  	}
   757  
   758  	if templ == nil {
   759  		// try to load from repository (and enable dependencies)
   760  		name := swag.ToJSONName(strings.TrimSuffix(t.Source, ".gotmpl"))
   761  		tt, err := templates.Get(name)
   762  		if err == nil {
   763  			templ = tt
   764  		}
   765  	}
   766  
   767  	if templ == nil {
   768  		// try to load template from disk, in TemplateDir if specified
   769  		// (dependencies resolution is limited to preloaded assets)
   770  		var templateFile string
   771  		if g.TemplateDir != "" {
   772  			templateFile = filepath.Join(g.TemplateDir, t.Source)
   773  		} else {
   774  			templateFile = t.Source
   775  		}
   776  		content, err := ioutil.ReadFile(templateFile)
   777  		if err != nil {
   778  			return nil, fmt.Errorf("error while opening %s template file: %v", templateFile, err)
   779  		}
   780  		tt, err := template.New(t.Source).Funcs(FuncMap).Parse(string(content))
   781  		if err != nil {
   782  			return nil, fmt.Errorf("template parsing failed on template %s: %v", t.Name, err)
   783  		}
   784  		templ = tt
   785  	}
   786  
   787  	if templ == nil {
   788  		return nil, fmt.Errorf("template %q not found", t.Source)
   789  	}
   790  
   791  	var tBuf bytes.Buffer
   792  	if err := templ.Execute(&tBuf, data); err != nil {
   793  		return nil, fmt.Errorf("template execution failed for template %s: %v", t.Name, err)
   794  	}
   795  	log.Printf("executed template %s", t.Source)
   796  
   797  	return tBuf.Bytes(), nil
   798  }
   799  
   800  // Render template and write generated source code
   801  // generated code is reformatted ("linted"), which gives an
   802  // additional level of checking. If this step fails, the generated
   803  // code is still dumped, for template debugging purposes.
   804  func (g *GenOpts) write(t *TemplateOpts, data interface{}) error {
   805  	dir, fname, err := g.location(t, data)
   806  	if err != nil {
   807  		return fmt.Errorf("failed to resolve template location for template %s: %v", t.Name, err)
   808  	}
   809  
   810  	if t.SkipExists && fileExists(dir, fname) {
   811  		debugLog("skipping generation of %s because it already exists and skip_exist directive is set for %s",
   812  			filepath.Join(dir, fname), t.Name)
   813  		return nil
   814  	}
   815  
   816  	log.Printf("creating generated file %q in %q as %s", fname, dir, t.Name)
   817  	content, err := g.render(t, data)
   818  	if err != nil {
   819  		return fmt.Errorf("failed rendering template data for %s: %v", t.Name, err)
   820  	}
   821  
   822  	if dir != "" {
   823  		_, exists := os.Stat(dir)
   824  		if os.IsNotExist(exists) {
   825  			debugLog("creating directory %q for \"%s\"", dir, t.Name)
   826  			// Directory settings consistent with file privileges.
   827  			// Environment's umask may alter this setup
   828  			if e := os.MkdirAll(dir, 0755); e != nil {
   829  				return e
   830  			}
   831  		}
   832  	}
   833  
   834  	// Conditionally format the code, unless the user wants to skip
   835  	formatted := content
   836  	var writeerr error
   837  
   838  	if !t.SkipFormat {
   839  		formatted, err = g.LanguageOpts.FormatContent(fname, content)
   840  		if err != nil {
   841  			log.Printf("source formatting failed on template-generated source (%q for %s). Check that your template produces valid code", filepath.Join(dir, fname), t.Name)
   842  			writeerr = ioutil.WriteFile(filepath.Join(dir, fname), content, 0644)
   843  			if writeerr != nil {
   844  				return fmt.Errorf("failed to write (unformatted) file %q in %q: %v", fname, dir, writeerr)
   845  			}
   846  			log.Printf("unformatted generated source %q has been dumped for template debugging purposes. DO NOT build on this source!", fname)
   847  			return fmt.Errorf("source formatting on generated source %q failed: %v", t.Name, err)
   848  		}
   849  	}
   850  
   851  	writeerr = ioutil.WriteFile(filepath.Join(dir, fname), formatted, 0644)
   852  	if writeerr != nil {
   853  		return fmt.Errorf("failed to write file %q in %q: %v", fname, dir, writeerr)
   854  	}
   855  	return err
   856  }
   857  
   858  func fileName(in string) string {
   859  	ext := filepath.Ext(in)
   860  	return swag.ToFileName(strings.TrimSuffix(in, ext)) + ext
   861  }
   862  
   863  func (g *GenOpts) shouldRenderApp(t *TemplateOpts, app *GenApp) bool {
   864  	switch swag.ToFileName(swag.ToGoName(t.Name)) {
   865  	case "main":
   866  		return g.IncludeMain
   867  	case "embedded_spec":
   868  		return !g.ExcludeSpec
   869  	default:
   870  		return true
   871  	}
   872  }
   873  
   874  func (g *GenOpts) shouldRenderOperations() bool {
   875  	return g.IncludeHandler || g.IncludeParameters || g.IncludeResponses
   876  }
   877  
   878  func (g *GenOpts) renderApplication(app *GenApp) error {
   879  	log.Printf("rendering %d templates for application %s", len(g.Sections.Application), app.Name)
   880  	for _, templ := range g.Sections.Application {
   881  		if !g.shouldRenderApp(&templ, app) {
   882  			continue
   883  		}
   884  		if err := g.write(&templ, app); err != nil {
   885  			return err
   886  		}
   887  	}
   888  	return nil
   889  }
   890  
   891  func (g *GenOpts) renderOperationGroup(gg *GenOperationGroup) error {
   892  	log.Printf("rendering %d templates for operation group %s", len(g.Sections.OperationGroups), g.Name)
   893  	for _, templ := range g.Sections.OperationGroups {
   894  		if !g.shouldRenderOperations() {
   895  			continue
   896  		}
   897  
   898  		if err := g.write(&templ, gg); err != nil {
   899  			return err
   900  		}
   901  	}
   902  	return nil
   903  }
   904  
   905  func (g *GenOpts) renderOperation(gg *GenOperation) error {
   906  	log.Printf("rendering %d templates for operation %s", len(g.Sections.Operations), g.Name)
   907  	for _, templ := range g.Sections.Operations {
   908  		if !g.shouldRenderOperations() {
   909  			continue
   910  		}
   911  
   912  		if err := g.write(&templ, gg); err != nil {
   913  			return err
   914  		}
   915  	}
   916  	return nil
   917  }
   918  
   919  func (g *GenOpts) renderDefinition(gg *GenDefinition) error {
   920  	log.Printf("rendering %d templates for model %s", len(g.Sections.Models), gg.Name)
   921  	for _, templ := range g.Sections.Models {
   922  		if !g.IncludeModel {
   923  			continue
   924  		}
   925  
   926  		if err := g.write(&templ, gg); err != nil {
   927  			return err
   928  		}
   929  	}
   930  	return nil
   931  }
   932  
   933  func validateSpec(path string, doc *loads.Document) (err error) {
   934  	if doc == nil {
   935  		if path, doc, err = loadSpec(path); err != nil {
   936  			return err
   937  		}
   938  	}
   939  
   940  	result := validate.Spec(doc, strfmt.Default)
   941  	if result == nil {
   942  		return nil
   943  	}
   944  
   945  	str := fmt.Sprintf("The swagger spec at %q is invalid against swagger specification %s. see errors :\n", path, doc.Version())
   946  	for _, desc := range result.(*swaggererrors.CompositeError).Errors {
   947  		str += fmt.Sprintf("- %s\n", desc)
   948  	}
   949  	return errors.New(str)
   950  }
   951  
   952  func loadSpec(specFile string) (string, *loads.Document, error) {
   953  	// find swagger spec document, verify it exists
   954  	specPath := specFile
   955  	var err error
   956  	if !strings.HasPrefix(specPath, "http") {
   957  		specPath, err = findSwaggerSpec(specFile)
   958  		if err != nil {
   959  			return "", nil, err
   960  		}
   961  	}
   962  
   963  	// load swagger spec
   964  	specDoc, err := loads.Spec(specPath)
   965  	if err != nil {
   966  		return "", nil, err
   967  	}
   968  	return specPath, specDoc, nil
   969  }
   970  
   971  func fileExists(target, name string) bool {
   972  	_, err := os.Stat(filepath.Join(target, name))
   973  	return !os.IsNotExist(err)
   974  }
   975  
   976  func gatherModels(specDoc *loads.Document, modelNames []string) (map[string]spec.Schema, error) {
   977  	models, mnc := make(map[string]spec.Schema), len(modelNames)
   978  	defs := specDoc.Spec().Definitions
   979  
   980  	if mnc > 0 {
   981  		var unknownModels []string
   982  		for _, m := range modelNames {
   983  			_, ok := defs[m]
   984  			if !ok {
   985  				unknownModels = append(unknownModels, m)
   986  			}
   987  		}
   988  		if len(unknownModels) != 0 {
   989  			return nil, fmt.Errorf("unknown models: %s", strings.Join(unknownModels, ", "))
   990  		}
   991  	}
   992  	for k, v := range defs {
   993  		if mnc == 0 {
   994  			models[k] = v
   995  		}
   996  		for _, nm := range modelNames {
   997  			if k == nm {
   998  				models[k] = v
   999  			}
  1000  		}
  1001  	}
  1002  	return models, nil
  1003  }
  1004  
  1005  func appNameOrDefault(specDoc *loads.Document, name, defaultName string) string {
  1006  	if strings.TrimSpace(name) == "" {
  1007  		if specDoc.Spec().Info != nil && strings.TrimSpace(specDoc.Spec().Info.Title) != "" {
  1008  			name = specDoc.Spec().Info.Title
  1009  		} else {
  1010  			name = defaultName
  1011  		}
  1012  	}
  1013  	return strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(swag.ToGoName(name), "Test"), "API"), "Test")
  1014  }
  1015  
  1016  func containsString(names []string, name string) bool {
  1017  	for _, nm := range names {
  1018  		if nm == name {
  1019  			return true
  1020  		}
  1021  	}
  1022  	return false
  1023  }
  1024  
  1025  type opRef struct {
  1026  	Method string
  1027  	Path   string
  1028  	Key    string
  1029  	ID     string
  1030  	Op     *spec.Operation
  1031  }
  1032  
  1033  type opRefs []opRef
  1034  
  1035  func (o opRefs) Len() int           { return len(o) }
  1036  func (o opRefs) Swap(i, j int)      { o[i], o[j] = o[j], o[i] }
  1037  func (o opRefs) Less(i, j int) bool { return o[i].Key < o[j].Key }
  1038  
  1039  func gatherOperations(specDoc *analysis.Spec, operationIDs []string) map[string]opRef {
  1040  	var oprefs opRefs
  1041  
  1042  	for method, pathItem := range specDoc.Operations() {
  1043  		for path, operation := range pathItem {
  1044  			// nm := ensureUniqueName(operation.ID, method, path, operations)
  1045  			vv := *operation
  1046  			oprefs = append(oprefs, opRef{
  1047  				Key:    swag.ToGoName(strings.ToLower(method) + " " + path),
  1048  				Method: method,
  1049  				Path:   path,
  1050  				ID:     vv.ID,
  1051  				Op:     &vv,
  1052  			})
  1053  		}
  1054  	}
  1055  
  1056  	sort.Sort(oprefs)
  1057  
  1058  	operations := make(map[string]opRef)
  1059  	for _, opr := range oprefs {
  1060  		nm := opr.ID
  1061  		if nm == "" {
  1062  			nm = opr.Key
  1063  		}
  1064  
  1065  		oo, found := operations[nm]
  1066  		if found && oo.Method != opr.Method && oo.Path != opr.Path {
  1067  			nm = opr.Key
  1068  		}
  1069  		if len(operationIDs) == 0 || containsString(operationIDs, opr.ID) || containsString(operationIDs, nm) {
  1070  			opr.ID = nm
  1071  			opr.Op.ID = nm
  1072  			operations[nm] = opr
  1073  		}
  1074  	}
  1075  
  1076  	return operations
  1077  }
  1078  
  1079  func pascalize(arg string) string {
  1080  	runes := []rune(arg)
  1081  	switch len(runes) {
  1082  	case 0:
  1083  		return ""
  1084  	case 1: // handle special case when we have a single rune that is not handled by swag.ToGoName
  1085  		switch runes[0] {
  1086  		case '+', '-', '#', '_': // those cases are handled differently than swag utility
  1087  			return prefixForName(arg)
  1088  		}
  1089  	}
  1090  	return swag.ToGoName(swag.ToGoName(arg)) // want to remove spaces
  1091  }
  1092  
  1093  func prefixForName(arg string) string {
  1094  	first := []rune(arg)[0]
  1095  	if len(arg) == 0 || unicode.IsLetter(first) {
  1096  		return ""
  1097  	}
  1098  	switch first {
  1099  	case '+':
  1100  		return "Plus"
  1101  	case '-':
  1102  		return "Minus"
  1103  	case '#':
  1104  		return "HashTag"
  1105  		// other cases ($,@ etc..) handled by swag.ToGoName
  1106  	}
  1107  	return "Nr"
  1108  }
  1109  
  1110  func init() {
  1111  	// this makes the ToGoName func behave with the special
  1112  	// prefixing rule above
  1113  	swag.GoNamePrefixFunc = prefixForName
  1114  }
  1115  
  1116  func pruneEmpty(in []string) (out []string) {
  1117  	for _, v := range in {
  1118  		if v != "" {
  1119  			out = append(out, v)
  1120  		}
  1121  	}
  1122  	return
  1123  }
  1124  
  1125  func trimBOM(in string) string {
  1126  	return strings.Trim(in, "\xef\xbb\xbf")
  1127  }
  1128  
  1129  func validateAndFlattenSpec(opts *GenOpts, specDoc *loads.Document) (*loads.Document, error) {
  1130  
  1131  	var err error
  1132  
  1133  	// Validate if needed
  1134  	if opts.ValidateSpec {
  1135  		log.Printf("validating spec %v", opts.Spec)
  1136  		if erv := validateSpec(opts.Spec, specDoc); erv != nil {
  1137  			return specDoc, erv
  1138  		}
  1139  	}
  1140  
  1141  	// Restore spec to original
  1142  	opts.Spec, specDoc, err = loadSpec(opts.Spec)
  1143  	if err != nil {
  1144  		return nil, err
  1145  	}
  1146  
  1147  	absBasePath := specDoc.SpecFilePath()
  1148  	if !filepath.IsAbs(absBasePath) {
  1149  		cwd, _ := os.Getwd()
  1150  		absBasePath = filepath.Join(cwd, absBasePath)
  1151  	}
  1152  
  1153  	// Some preprocessing is required before codegen
  1154  	//
  1155  	// This ensures at least that $ref's in the spec document are canonical,
  1156  	// i.e all $ref are local to this file and point to some uniquely named definition.
  1157  	//
  1158  	// Default option is to ensure minimal flattening of $ref, bundling remote $refs and relocating arbitrary JSON
  1159  	// pointers as definitions.
  1160  	// This preprocessing may introduce duplicate names (e.g. remote $ref with same name). In this case, a definition
  1161  	// suffixed with "OAIGen" is produced.
  1162  	//
  1163  	// Full flattening option farther transforms the spec by moving every complex object (e.g. with some properties)
  1164  	// as a standalone definition.
  1165  	//
  1166  	// Eventually, an "expand spec" option is available. It is essentially useful for testing purposes.
  1167  	//
  1168  	// NOTE(fredbi): spec expansion may produce some unsupported constructs and is not yet protected against the
  1169  	// following cases:
  1170  	//  - polymorphic types generation may fail with expansion (expand destructs the reuse intent of the $ref in allOf)
  1171  	//  - name duplicates may occur and result in compilation failures
  1172  	// The right place to fix these shortcomings is go-openapi/analysis.
  1173  
  1174  	opts.FlattenOpts.BasePath = absBasePath // BasePath must be absolute
  1175  	opts.FlattenOpts.Spec = analysis.New(specDoc.Spec())
  1176  
  1177  	var preprocessingOption string
  1178  	switch {
  1179  	case opts.FlattenOpts.Expand:
  1180  		preprocessingOption = "expand"
  1181  	case opts.FlattenOpts.Minimal:
  1182  		preprocessingOption = "minimal flattening"
  1183  	default:
  1184  		preprocessingOption = "full flattening"
  1185  	}
  1186  	log.Printf("preprocessing spec with option:  %s", preprocessingOption)
  1187  
  1188  	if err = analysis.Flatten(*opts.FlattenOpts); err != nil {
  1189  		return nil, err
  1190  	}
  1191  
  1192  	// yields the preprocessed spec document
  1193  	return specDoc, nil
  1194  }
  1195  
  1196  // gatherSecuritySchemes produces a sorted representation from a map of spec security schemes
  1197  func gatherSecuritySchemes(securitySchemes map[string]spec.SecurityScheme, appName, principal, receiver string) (security GenSecuritySchemes) {
  1198  	for scheme, req := range securitySchemes {
  1199  		isOAuth2 := strings.ToLower(req.Type) == "oauth2"
  1200  		var scopes []string
  1201  		if isOAuth2 {
  1202  			for k := range req.Scopes {
  1203  				scopes = append(scopes, k)
  1204  			}
  1205  		}
  1206  		sort.Strings(scopes)
  1207  
  1208  		security = append(security, GenSecurityScheme{
  1209  			AppName:      appName,
  1210  			ID:           scheme,
  1211  			ReceiverName: receiver,
  1212  			Name:         req.Name,
  1213  			IsBasicAuth:  strings.ToLower(req.Type) == "basic",
  1214  			IsAPIKeyAuth: strings.ToLower(req.Type) == "apikey",
  1215  			IsOAuth2:     isOAuth2,
  1216  			Scopes:       scopes,
  1217  			Principal:    principal,
  1218  			Source:       req.In,
  1219  			// from original spec
  1220  			Description:      req.Description,
  1221  			Type:             strings.ToLower(req.Type),
  1222  			In:               req.In,
  1223  			Flow:             req.Flow,
  1224  			AuthorizationURL: req.AuthorizationURL,
  1225  			TokenURL:         req.TokenURL,
  1226  			Extensions:       req.Extensions,
  1227  		})
  1228  	}
  1229  	sort.Sort(security)
  1230  	return
  1231  }
  1232  
  1233  // gatherExtraSchemas produces a sorted list of extra schemas.
  1234  //
  1235  // ExtraSchemas are inlined types rendered in the same model file.
  1236  func gatherExtraSchemas(extraMap map[string]GenSchema) (extras GenSchemaList) {
  1237  	var extraKeys []string
  1238  	for k := range extraMap {
  1239  		extraKeys = append(extraKeys, k)
  1240  	}
  1241  	sort.Strings(extraKeys)
  1242  	for _, k := range extraKeys {
  1243  		// figure out if top level validations are needed
  1244  		p := extraMap[k]
  1245  		p.HasValidations = shallowValidationLookup(p)
  1246  		extras = append(extras, p)
  1247  	}
  1248  	return
  1249  }
  1250  
  1251  func sharedValidationsFromSimple(v spec.CommonValidations, isRequired bool) (sh sharedValidations) {
  1252  	sh = sharedValidations{
  1253  		Required:         isRequired,
  1254  		Maximum:          v.Maximum,
  1255  		ExclusiveMaximum: v.ExclusiveMaximum,
  1256  		Minimum:          v.Minimum,
  1257  		ExclusiveMinimum: v.ExclusiveMinimum,
  1258  		MaxLength:        v.MaxLength,
  1259  		MinLength:        v.MinLength,
  1260  		Pattern:          v.Pattern,
  1261  		MaxItems:         v.MaxItems,
  1262  		MinItems:         v.MinItems,
  1263  		UniqueItems:      v.UniqueItems,
  1264  		MultipleOf:       v.MultipleOf,
  1265  		Enum:             v.Enum,
  1266  	}
  1267  	return
  1268  }
  1269  
  1270  func sharedValidationsFromSchema(v spec.Schema, isRequired bool) (sh sharedValidations) {
  1271  	sh = sharedValidations{
  1272  		Required:         isRequired,
  1273  		Maximum:          v.Maximum,
  1274  		ExclusiveMaximum: v.ExclusiveMaximum,
  1275  		Minimum:          v.Minimum,
  1276  		ExclusiveMinimum: v.ExclusiveMinimum,
  1277  		MaxLength:        v.MaxLength,
  1278  		MinLength:        v.MinLength,
  1279  		Pattern:          v.Pattern,
  1280  		MaxItems:         v.MaxItems,
  1281  		MinItems:         v.MinItems,
  1282  		UniqueItems:      v.UniqueItems,
  1283  		MultipleOf:       v.MultipleOf,
  1284  		Enum:             v.Enum,
  1285  	}
  1286  	return
  1287  }