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