2019-09-04 21:53:54 +02:00
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package generator
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
"text/template"
"unicode"
swaggererrors "github.com/go-openapi/errors"
"github.com/go-openapi/analysis"
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
"golang.org/x/tools/imports"
)
//go:generate go-bindata -mode 420 -modtime 1482416923 -pkg=generator -ignore=.*\.sw? -ignore=.*\.md ./templates/...
// LanguageOpts to describe a language to the code generator
type LanguageOpts struct {
ReservedWords [ ] string
BaseImportFunc func ( string ) string ` json:"-" `
reservedWordsSet map [ string ] struct { }
initialized bool
formatFunc func ( string , [ ] byte ) ( [ ] byte , error )
fileNameFunc func ( string ) string
}
// Init the language option
func ( l * LanguageOpts ) Init ( ) {
if ! l . initialized {
l . initialized = true
l . reservedWordsSet = make ( map [ string ] struct { } )
for _ , rw := range l . ReservedWords {
l . reservedWordsSet [ rw ] = struct { } { }
}
}
}
// MangleName makes sure a reserved word gets a safe name
func ( l * LanguageOpts ) MangleName ( name , suffix string ) string {
if _ , ok := l . reservedWordsSet [ swag . ToFileName ( name ) ] ; ! ok {
return name
}
return strings . Join ( [ ] string { name , suffix } , "_" )
}
// MangleVarName makes sure a reserved word gets a safe name
func ( l * LanguageOpts ) MangleVarName ( name string ) string {
nm := swag . ToVarName ( name )
if _ , ok := l . reservedWordsSet [ nm ] ; ! ok {
return nm
}
return nm + "Var"
}
// MangleFileName makes sure a file name gets a safe name
func ( l * LanguageOpts ) MangleFileName ( name string ) string {
if l . fileNameFunc != nil {
return l . fileNameFunc ( name )
}
return swag . ToFileName ( name )
}
// ManglePackageName makes sure a package gets a safe name.
// In case of a file system path (e.g. name contains "/" or "\" on Windows), this return only the last element.
func ( l * LanguageOpts ) ManglePackageName ( name , suffix string ) string {
if name == "" {
return suffix
}
pth := filepath . ToSlash ( filepath . Clean ( name ) ) // preserve path
_ , pkg := path . Split ( pth ) // drop path
return l . MangleName ( swag . ToFileName ( pkg ) , suffix )
}
// ManglePackagePath makes sure a full package path gets a safe name.
// Only the last part of the path is altered.
func ( l * LanguageOpts ) ManglePackagePath ( name string , suffix string ) string {
if name == "" {
return suffix
}
target := filepath . ToSlash ( filepath . Clean ( name ) ) // preserve path
parts := strings . Split ( target , "/" )
parts [ len ( parts ) - 1 ] = l . ManglePackageName ( parts [ len ( parts ) - 1 ] , suffix )
return strings . Join ( parts , "/" )
}
// FormatContent formats a file with a language specific formatter
func ( l * LanguageOpts ) FormatContent ( name string , content [ ] byte ) ( [ ] byte , error ) {
if l . formatFunc != nil {
return l . formatFunc ( name , content )
}
return content , nil
}
func ( l * LanguageOpts ) baseImport ( tgt string ) string {
if l . BaseImportFunc != nil {
return l . BaseImportFunc ( tgt )
}
return ""
}
var golang = GoLangOpts ( )
// GoLangOpts for rendering items as golang code
func GoLangOpts ( ) * LanguageOpts {
var goOtherReservedSuffixes = map [ string ] bool {
// see:
// https://golang.org/src/go/build/syslist.go
// https://golang.org/doc/install/source#environment
// goos
"android" : true ,
"darwin" : true ,
"dragonfly" : true ,
"freebsd" : true ,
"js" : true ,
"linux" : true ,
"nacl" : true ,
"netbsd" : true ,
"openbsd" : true ,
"plan9" : true ,
"solaris" : true ,
"windows" : true ,
"zos" : true ,
// arch
"386" : true ,
"amd64" : true ,
"amd64p32" : true ,
"arm" : true ,
"armbe" : true ,
"arm64" : true ,
"arm64be" : true ,
"mips" : true ,
"mipsle" : true ,
"mips64" : true ,
"mips64le" : true ,
"mips64p32" : true ,
"mips64p32le" : true ,
"ppc" : true ,
"ppc64" : true ,
"ppc64le" : true ,
"riscv" : true ,
"riscv64" : true ,
"s390" : true ,
"s390x" : true ,
"sparc" : true ,
"sparc64" : true ,
"wasm" : true ,
// other reserved suffixes
"test" : true ,
}
opts := new ( LanguageOpts )
opts . ReservedWords = [ ] string {
"break" , "default" , "func" , "interface" , "select" ,
"case" , "defer" , "go" , "map" , "struct" ,
"chan" , "else" , "goto" , "package" , "switch" ,
"const" , "fallthrough" , "if" , "range" , "type" ,
"continue" , "for" , "import" , "return" , "var" ,
}
opts . formatFunc = func ( ffn string , content [ ] byte ) ( [ ] byte , error ) {
opts := new ( imports . Options )
opts . TabIndent = true
opts . TabWidth = 2
opts . Fragment = true
opts . Comments = true
return imports . Process ( ffn , content , opts )
}
opts . fileNameFunc = func ( name string ) string {
// whenever a generated file name ends with a suffix
// that is meaningful to go build, adds a "swagger"
// suffix
parts := strings . Split ( swag . ToFileName ( name ) , "_" )
if goOtherReservedSuffixes [ parts [ len ( parts ) - 1 ] ] {
// file name ending with a reserved arch or os name
// are appended an innocuous suffix "swagger"
parts = append ( parts , "swagger" )
}
return strings . Join ( parts , "_" )
}
opts . BaseImportFunc = func ( tgt string ) string {
tgt = filepath . Clean ( tgt )
// On Windows, filepath.Abs("") behaves differently than on Unix.
// Windows: yields an error, since Abs() does not know the volume.
// UNIX: returns current working directory
if tgt == "" {
tgt = "."
}
tgtAbsPath , err := filepath . Abs ( tgt )
if err != nil {
log . Fatalf ( "could not evaluate base import path with target \"%s\": %v" , tgt , err )
}
var tgtAbsPathExtended string
tgtAbsPathExtended , err = filepath . EvalSymlinks ( tgtAbsPath )
if err != nil {
log . Fatalf ( "could not evaluate base import path with target \"%s\" (with symlink resolution): %v" , tgtAbsPath , err )
}
gopath := os . Getenv ( "GOPATH" )
if gopath == "" {
gopath = filepath . Join ( os . Getenv ( "HOME" ) , "go" )
}
var pth string
for _ , gp := range filepath . SplitList ( gopath ) {
// EvalSymLinks also calls the Clean
gopathExtended , er := filepath . EvalSymlinks ( gp )
if er != nil {
log . Fatalln ( er )
}
gopathExtended = filepath . Join ( gopathExtended , "src" )
gp = filepath . Join ( gp , "src" )
// At this stage we have expanded and unexpanded target path. GOPATH is fully expanded.
// Expanded means symlink free.
// We compare both types of targetpath<s> with gopath.
// If any one of them coincides with gopath , it is imperative that
// target path lies inside gopath. How?
// - Case 1: Irrespective of symlinks paths coincide. Both non-expanded paths.
// - Case 2: Symlink in target path points to location inside GOPATH. (Expanded Target Path)
// - Case 3: Symlink in target path points to directory outside GOPATH (Unexpanded target path)
// Case 1: - Do nothing case. If non-expanded paths match just generate base import path as if
// there are no symlinks.
// Case 2: - Symlink in target path points to location inside GOPATH. (Expanded Target Path)
// First if will fail. Second if will succeed.
// Case 3: - Symlink in target path points to directory outside GOPATH (Unexpanded target path)
// First if will succeed and break.
//compares non expanded path for both
if ok , relativepath := checkPrefixAndFetchRelativePath ( tgtAbsPath , gp ) ; ok {
pth = relativepath
break
}
// Compares non-expanded target path
if ok , relativepath := checkPrefixAndFetchRelativePath ( tgtAbsPath , gopathExtended ) ; ok {
pth = relativepath
break
}
// Compares expanded target path.
if ok , relativepath := checkPrefixAndFetchRelativePath ( tgtAbsPathExtended , gopathExtended ) ; ok {
pth = relativepath
break
}
}
mod , goModuleAbsPath , err := tryResolveModule ( tgtAbsPath )
switch {
case err != nil :
log . Fatalf ( "Failed to resolve module using go.mod file: %s" , err )
case mod != "" :
relTgt := relPathToRelGoPath ( goModuleAbsPath , tgtAbsPath )
if ! strings . HasSuffix ( mod , relTgt ) {
return mod + relTgt
}
return mod
}
if pth == "" {
log . Fatalln ( "target must reside inside a location in the $GOPATH/src or be a module" )
}
return pth
}
opts . Init ( )
return opts
}
var moduleRe = regexp . MustCompile ( ` module[ \t]+([^\s]+) ` )
// resolveGoModFile walks up the directory tree starting from 'dir' until it
// finds a go.mod file. If go.mod is found it will return the related file
// object. If no go.mod file is found it will return an error.
func resolveGoModFile ( dir string ) ( * os . File , string , error ) {
goModPath := filepath . Join ( dir , "go.mod" )
f , err := os . Open ( goModPath )
if err != nil {
if os . IsNotExist ( err ) && dir != filepath . Dir ( dir ) {
return resolveGoModFile ( filepath . Dir ( dir ) )
}
return nil , "" , err
}
return f , dir , nil
}
// relPathToRelGoPath takes a relative os path and returns the relative go
// package path. For unix nothing will change but for windows \ will be
// converted to /.
func relPathToRelGoPath ( modAbsPath , absPath string ) string {
if absPath == "." {
return ""
}
path := strings . TrimPrefix ( absPath , modAbsPath )
pathItems := strings . Split ( path , string ( filepath . Separator ) )
return strings . Join ( pathItems , "/" )
}
func tryResolveModule ( baseTargetPath string ) ( string , string , error ) {
f , goModAbsPath , err := resolveGoModFile ( baseTargetPath )
switch {
case os . IsNotExist ( err ) :
return "" , "" , nil
case err != nil :
return "" , "" , err
}
src , err := ioutil . ReadAll ( f )
if err != nil {
return "" , "" , err
}
match := moduleRe . FindSubmatch ( src )
if len ( match ) != 2 {
return "" , "" , nil
}
return string ( match [ 1 ] ) , goModAbsPath , nil
}
func findSwaggerSpec ( nm string ) ( string , error ) {
specs := [ ] string { "swagger.json" , "swagger.yml" , "swagger.yaml" }
if nm != "" {
specs = [ ] string { nm }
}
var name string
for _ , nn := range specs {
f , err := os . Stat ( nn )
if err != nil && ! os . IsNotExist ( err ) {
return "" , err
}
if err != nil && os . IsNotExist ( err ) {
continue
}
if f . IsDir ( ) {
return "" , fmt . Errorf ( "%s is a directory" , nn )
}
name = nn
break
}
if name == "" {
return "" , errors . New ( "couldn't find a swagger spec" )
}
return name , nil
}
// DefaultSectionOpts for a given opts, this is used when no config file is passed
// and uses the embedded templates when no local override can be found
func DefaultSectionOpts ( gen * GenOpts ) {
sec := gen . Sections
if len ( sec . Models ) == 0 {
sec . Models = [ ] TemplateOpts {
{
Name : "definition" ,
Source : "asset:model" ,
Target : "{{ joinFilePath .Target (toPackagePath .ModelPackage) }}" ,
FileName : "{{ (snakize (pascalize .Name)) }}.go" ,
} ,
}
}
if len ( sec . Operations ) == 0 {
if gen . IsClient {
sec . Operations = [ ] TemplateOpts {
{
Name : "parameters" ,
Source : "asset:clientParameter" ,
Target : "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}" ,
FileName : "{{ (snakize (pascalize .Name)) }}_parameters.go" ,
} ,
{
Name : "responses" ,
Source : "asset:clientResponse" ,
Target : "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}" ,
FileName : "{{ (snakize (pascalize .Name)) }}_responses.go" ,
} ,
}
} else {
ops := [ ] TemplateOpts { }
if gen . IncludeParameters {
ops = append ( ops , TemplateOpts {
Name : "parameters" ,
Source : "asset:serverParameter" ,
Target : "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}" ,
FileName : "{{ (snakize (pascalize .Name)) }}_parameters.go" ,
} )
}
if gen . IncludeURLBuilder {
ops = append ( ops , TemplateOpts {
Name : "urlbuilder" ,
Source : "asset:serverUrlbuilder" ,
Target : "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}" ,
FileName : "{{ (snakize (pascalize .Name)) }}_urlbuilder.go" ,
} )
}
if gen . IncludeResponses {
ops = append ( ops , TemplateOpts {
Name : "responses" ,
Source : "asset:serverResponses" ,
Target : "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}" ,
FileName : "{{ (snakize (pascalize .Name)) }}_responses.go" ,
} )
}
if gen . IncludeHandler {
ops = append ( ops , TemplateOpts {
Name : "handler" ,
Source : "asset:serverOperation" ,
Target : "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}" ,
FileName : "{{ (snakize (pascalize .Name)) }}.go" ,
} )
}
sec . Operations = ops
}
}
if len ( sec . OperationGroups ) == 0 {
if gen . IsClient {
sec . OperationGroups = [ ] TemplateOpts {
{
Name : "client" ,
Source : "asset:clientClient" ,
Target : "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Name)}}" ,
FileName : "{{ (snakize (pascalize .Name)) }}_client.go" ,
} ,
}
} else {
sec . OperationGroups = [ ] TemplateOpts { }
}
}
if len ( sec . Application ) == 0 {
if gen . IsClient {
sec . Application = [ ] TemplateOpts {
{
Name : "facade" ,
Source : "asset:clientFacade" ,
Target : "{{ joinFilePath .Target (toPackagePath .ClientPackage) }}" ,
FileName : "{{ snakize .Name }}Client.go" ,
} ,
}
} else {
sec . Application = [ ] TemplateOpts {
{
Name : "configure" ,
Source : "asset:serverConfigureapi" ,
Target : "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}" ,
FileName : "configure_{{ (snakize (pascalize .Name)) }}.go" ,
SkipExists : ! gen . RegenerateConfigureAPI ,
} ,
{
Name : "main" ,
Source : "asset:serverMain" ,
Target : "{{ joinFilePath .Target \"cmd\" (dasherize (pascalize .Name)) }}-server" ,
FileName : "main.go" ,
} ,
{
Name : "embedded_spec" ,
Source : "asset:swaggerJsonEmbed" ,
Target : "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}" ,
FileName : "embedded_spec.go" ,
} ,
{
Name : "server" ,
Source : "asset:serverServer" ,
Target : "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}" ,
FileName : "server.go" ,
} ,
{
Name : "builder" ,
Source : "asset:serverBuilder" ,
Target : "{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) }}" ,
FileName : "{{ snakize (pascalize .Name) }}_api.go" ,
} ,
{
Name : "doc" ,
Source : "asset:serverDoc" ,
Target : "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}" ,
FileName : "doc.go" ,
} ,
}
}
}
gen . Sections = sec
}
// TemplateOpts allows
type TemplateOpts struct {
Name string ` mapstructure:"name" `
Source string ` mapstructure:"source" `
Target string ` mapstructure:"target" `
FileName string ` mapstructure:"file_name" `
SkipExists bool ` mapstructure:"skip_exists" `
SkipFormat bool ` mapstructure:"skip_format" `
}
// SectionOpts allows for specifying options to customize the templates used for generation
type SectionOpts struct {
Application [ ] TemplateOpts ` mapstructure:"application" `
Operations [ ] TemplateOpts ` mapstructure:"operations" `
OperationGroups [ ] TemplateOpts ` mapstructure:"operation_groups" `
Models [ ] TemplateOpts ` mapstructure:"models" `
}
// GenOpts the options for the generator
type GenOpts struct {
IncludeModel bool
IncludeValidator bool
IncludeHandler bool
IncludeParameters bool
IncludeResponses bool
IncludeURLBuilder bool
IncludeMain bool
IncludeSupport bool
ExcludeSpec bool
DumpData bool
ValidateSpec bool
FlattenOpts * analysis . FlattenOpts
IsClient bool
defaultsEnsured bool
PropertiesSpecOrder bool
StrictAdditionalProperties bool
2019-12-18 16:05:30 +01:00
AllowTemplateOverride bool
2019-09-04 21:53:54 +02:00
Spec string
APIPackage string
ModelPackage string
ServerPackage string
ClientPackage string
Principal string
Target string
Sections SectionOpts
LanguageOpts * LanguageOpts
TypeMapping map [ string ] string
Imports map [ string ] string
DefaultScheme string
DefaultProduces string
DefaultConsumes string
TemplateDir string
Template string
RegenerateConfigureAPI bool
Operations [ ] string
Models [ ] string
Tags [ ] string
Name string
FlagStrategy string
CompatibilityMode string
ExistingModels string
Copyright string
}
// CheckOpts carries out some global consistency checks on options.
//
// At the moment, these checks simply protect TargetPath() and SpecPath()
// functions. More checks may be added here.
func ( g * GenOpts ) CheckOpts ( ) error {
if ! filepath . IsAbs ( g . Target ) {
if _ , err := filepath . Abs ( g . Target ) ; err != nil {
return fmt . Errorf ( "could not locate target %s: %v" , g . Target , err )
}
}
if filepath . IsAbs ( g . ServerPackage ) {
return fmt . Errorf ( "you shouldn't specify an absolute path in --server-package: %s" , g . ServerPackage )
}
if ! filepath . IsAbs ( g . Spec ) && ! strings . HasPrefix ( g . Spec , "http://" ) && ! strings . HasPrefix ( g . Spec , "https://" ) {
if _ , err := filepath . Abs ( g . Spec ) ; err != nil {
return fmt . Errorf ( "could not locate spec: %s" , g . Spec )
}
}
return nil
}
// TargetPath returns the target generation path relative to the server package.
// This method is used by templates, e.g. with {{ .TargetPath }}
//
// Errors cases are prevented by calling CheckOpts beforehand.
//
// Example:
// Target: ${PWD}/tmp
// ServerPackage: abc/efg
//
// Server is generated in ${PWD}/tmp/abc/efg
// relative TargetPath returned: ../../../tmp
//
func ( g * GenOpts ) TargetPath ( ) string {
var tgt string
if g . Target == "" {
tgt = "." // That's for windows
} else {
tgt = g . Target
}
tgtAbs , _ := filepath . Abs ( tgt )
srvPkg := filepath . FromSlash ( g . LanguageOpts . ManglePackagePath ( g . ServerPackage , "server" ) )
srvrAbs := filepath . Join ( tgtAbs , srvPkg )
tgtRel , _ := filepath . Rel ( srvrAbs , filepath . Dir ( tgtAbs ) )
tgtRel = filepath . Join ( tgtRel , filepath . Base ( tgtAbs ) )
return tgtRel
}
// SpecPath returns the path to the spec relative to the server package.
// If the spec is remote keep this absolute location.
//
// If spec is not relative to server (e.g. lives on a different drive on windows),
// then the resolved path is absolute.
//
// This method is used by templates, e.g. with {{ .SpecPath }}
//
// Errors cases are prevented by calling CheckOpts beforehand.
func ( g * GenOpts ) SpecPath ( ) string {
if strings . HasPrefix ( g . Spec , "http://" ) || strings . HasPrefix ( g . Spec , "https://" ) {
return g . Spec
}
// Local specifications
specAbs , _ := filepath . Abs ( g . Spec )
var tgt string
if g . Target == "" {
tgt = "." // That's for windows
} else {
tgt = g . Target
}
tgtAbs , _ := filepath . Abs ( tgt )
srvPkg := filepath . FromSlash ( g . LanguageOpts . ManglePackagePath ( g . ServerPackage , "server" ) )
srvAbs := filepath . Join ( tgtAbs , srvPkg )
specRel , err := filepath . Rel ( srvAbs , specAbs )
if err != nil {
return specAbs
}
return specRel
}
// EnsureDefaults for these gen opts
func ( g * GenOpts ) EnsureDefaults ( ) error {
if g . defaultsEnsured {
return nil
}
DefaultSectionOpts ( g )
if g . LanguageOpts == nil {
g . LanguageOpts = GoLangOpts ( )
}
// set defaults for flattening options
g . FlattenOpts = & analysis . FlattenOpts {
Minimal : true ,
Verbose : true ,
RemoveUnused : false ,
Expand : false ,
}
g . defaultsEnsured = true
return nil
}
func ( g * GenOpts ) location ( t * TemplateOpts , data interface { } ) ( string , string , error ) {
v := reflect . Indirect ( reflect . ValueOf ( data ) )
fld := v . FieldByName ( "Name" )
var name string
if fld . IsValid ( ) {
log . Println ( "name field" , fld . String ( ) )
name = fld . String ( )
}
fldpack := v . FieldByName ( "Package" )
pkg := g . APIPackage
if fldpack . IsValid ( ) {
log . Println ( "package field" , fldpack . String ( ) )
pkg = fldpack . String ( )
}
var tags [ ] string
tagsF := v . FieldByName ( "Tags" )
if tagsF . IsValid ( ) {
tags = tagsF . Interface ( ) . ( [ ] string )
}
pthTpl , err := template . New ( t . Name + "-target" ) . Funcs ( FuncMap ) . Parse ( t . Target )
if err != nil {
return "" , "" , err
}
fNameTpl , err := template . New ( t . Name + "-filename" ) . Funcs ( FuncMap ) . Parse ( t . FileName )
if err != nil {
return "" , "" , err
}
d := struct {
Name , Package , APIPackage , ServerPackage , ClientPackage , ModelPackage , Target string
Tags [ ] string
} {
Name : name ,
Package : pkg ,
APIPackage : g . APIPackage ,
ServerPackage : g . ServerPackage ,
ClientPackage : g . ClientPackage ,
ModelPackage : g . ModelPackage ,
Target : g . Target ,
Tags : tags ,
}
// pretty.Println(data)
var pthBuf bytes . Buffer
if e := pthTpl . Execute ( & pthBuf , d ) ; e != nil {
return "" , "" , e
}
var fNameBuf bytes . Buffer
if e := fNameTpl . Execute ( & fNameBuf , d ) ; e != nil {
return "" , "" , e
}
return pthBuf . String ( ) , fileName ( fNameBuf . String ( ) ) , nil
}
func ( g * GenOpts ) render ( t * TemplateOpts , data interface { } ) ( [ ] byte , error ) {
var templ * template . Template
if strings . HasPrefix ( strings . ToLower ( t . Source ) , "asset:" ) {
tt , err := templates . Get ( strings . TrimPrefix ( t . Source , "asset:" ) )
if err != nil {
return nil , err
}
templ = tt
}
if templ == nil {
// try to load from repository (and enable dependencies)
name := swag . ToJSONName ( strings . TrimSuffix ( t . Source , ".gotmpl" ) )
tt , err := templates . Get ( name )
if err == nil {
templ = tt
}
}
if templ == nil {
// try to load template from disk, in TemplateDir if specified
// (dependencies resolution is limited to preloaded assets)
var templateFile string
if g . TemplateDir != "" {
templateFile = filepath . Join ( g . TemplateDir , t . Source )
} else {
templateFile = t . Source
}
content , err := ioutil . ReadFile ( templateFile )
if err != nil {
return nil , fmt . Errorf ( "error while opening %s template file: %v" , templateFile , err )
}
tt , err := template . New ( t . Source ) . Funcs ( FuncMap ) . Parse ( string ( content ) )
if err != nil {
return nil , fmt . Errorf ( "template parsing failed on template %s: %v" , t . Name , err )
}
templ = tt
}
if templ == nil {
return nil , fmt . Errorf ( "template %q not found" , t . Source )
}
var tBuf bytes . Buffer
if err := templ . Execute ( & tBuf , data ) ; err != nil {
return nil , fmt . Errorf ( "template execution failed for template %s: %v" , t . Name , err )
}
log . Printf ( "executed template %s" , t . Source )
return tBuf . Bytes ( ) , nil
}
// Render template and write generated source code
// generated code is reformatted ("linted"), which gives an
// additional level of checking. If this step fails, the generated
// code is still dumped, for template debugging purposes.
func ( g * GenOpts ) write ( t * TemplateOpts , data interface { } ) error {
dir , fname , err := g . location ( t , data )
if err != nil {
return fmt . Errorf ( "failed to resolve template location for template %s: %v" , t . Name , err )
}
if t . SkipExists && fileExists ( dir , fname ) {
debugLog ( "skipping generation of %s because it already exists and skip_exist directive is set for %s" ,
filepath . Join ( dir , fname ) , t . Name )
return nil
}
log . Printf ( "creating generated file %q in %q as %s" , fname , dir , t . Name )
content , err := g . render ( t , data )
if err != nil {
return fmt . Errorf ( "failed rendering template data for %s: %v" , t . Name , err )
}
if dir != "" {
_ , exists := os . Stat ( dir )
if os . IsNotExist ( exists ) {
debugLog ( "creating directory %q for \"%s\"" , dir , t . Name )
// Directory settings consistent with file privileges.
// Environment's umask may alter this setup
if e := os . MkdirAll ( dir , 0755 ) ; e != nil {
return e
}
}
}
// Conditionally format the code, unless the user wants to skip
formatted := content
var writeerr error
if ! t . SkipFormat {
formatted , err = g . LanguageOpts . FormatContent ( fname , content )
if err != nil {
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 )
writeerr = ioutil . WriteFile ( filepath . Join ( dir , fname ) , content , 0644 )
if writeerr != nil {
return fmt . Errorf ( "failed to write (unformatted) file %q in %q: %v" , fname , dir , writeerr )
}
log . Printf ( "unformatted generated source %q has been dumped for template debugging purposes. DO NOT build on this source!" , fname )
return fmt . Errorf ( "source formatting on generated source %q failed: %v" , t . Name , err )
}
}
writeerr = ioutil . WriteFile ( filepath . Join ( dir , fname ) , formatted , 0644 )
if writeerr != nil {
return fmt . Errorf ( "failed to write file %q in %q: %v" , fname , dir , writeerr )
}
return err
}
func fileName ( in string ) string {
ext := filepath . Ext ( in )
return swag . ToFileName ( strings . TrimSuffix ( in , ext ) ) + ext
}
func ( g * GenOpts ) shouldRenderApp ( t * TemplateOpts , app * GenApp ) bool {
switch swag . ToFileName ( swag . ToGoName ( t . Name ) ) {
case "main" :
return g . IncludeMain
case "embedded_spec" :
return ! g . ExcludeSpec
default :
return true
}
}
func ( g * GenOpts ) shouldRenderOperations ( ) bool {
return g . IncludeHandler || g . IncludeParameters || g . IncludeResponses
}
func ( g * GenOpts ) renderApplication ( app * GenApp ) error {
log . Printf ( "rendering %d templates for application %s" , len ( g . Sections . Application ) , app . Name )
for _ , templ := range g . Sections . Application {
if ! g . shouldRenderApp ( & templ , app ) {
continue
}
if err := g . write ( & templ , app ) ; err != nil {
return err
}
}
return nil
}
func ( g * GenOpts ) renderOperationGroup ( gg * GenOperationGroup ) error {
log . Printf ( "rendering %d templates for operation group %s" , len ( g . Sections . OperationGroups ) , g . Name )
for _ , templ := range g . Sections . OperationGroups {
if ! g . shouldRenderOperations ( ) {
continue
}
if err := g . write ( & templ , gg ) ; err != nil {
return err
}
}
return nil
}
func ( g * GenOpts ) renderOperation ( gg * GenOperation ) error {
log . Printf ( "rendering %d templates for operation %s" , len ( g . Sections . Operations ) , g . Name )
for _ , templ := range g . Sections . Operations {
if ! g . shouldRenderOperations ( ) {
continue
}
if err := g . write ( & templ , gg ) ; err != nil {
return err
}
}
return nil
}
func ( g * GenOpts ) renderDefinition ( gg * GenDefinition ) error {
log . Printf ( "rendering %d templates for model %s" , len ( g . Sections . Models ) , gg . Name )
for _ , templ := range g . Sections . Models {
if ! g . IncludeModel {
continue
}
if err := g . write ( & templ , gg ) ; err != nil {
return err
}
}
return nil
}
func validateSpec ( path string , doc * loads . Document ) ( err error ) {
if doc == nil {
if path , doc , err = loadSpec ( path ) ; err != nil {
return err
}
}
result := validate . Spec ( doc , strfmt . Default )
if result == nil {
return nil
}
str := fmt . Sprintf ( "The swagger spec at %q is invalid against swagger specification %s. see errors :\n" , path , doc . Version ( ) )
for _ , desc := range result . ( * swaggererrors . CompositeError ) . Errors {
str += fmt . Sprintf ( "- %s\n" , desc )
}
return errors . New ( str )
}
func loadSpec ( specFile string ) ( string , * loads . Document , error ) {
// find swagger spec document, verify it exists
specPath := specFile
var err error
if ! strings . HasPrefix ( specPath , "http" ) {
specPath , err = findSwaggerSpec ( specFile )
if err != nil {
return "" , nil , err
}
}
// load swagger spec
specDoc , err := loads . Spec ( specPath )
if err != nil {
return "" , nil , err
}
return specPath , specDoc , nil
}
func fileExists ( target , name string ) bool {
_ , err := os . Stat ( filepath . Join ( target , name ) )
return ! os . IsNotExist ( err )
}
func gatherModels ( specDoc * loads . Document , modelNames [ ] string ) ( map [ string ] spec . Schema , error ) {
models , mnc := make ( map [ string ] spec . Schema ) , len ( modelNames )
defs := specDoc . Spec ( ) . Definitions
if mnc > 0 {
var unknownModels [ ] string
for _ , m := range modelNames {
_ , ok := defs [ m ]
if ! ok {
unknownModels = append ( unknownModels , m )
}
}
if len ( unknownModels ) != 0 {
return nil , fmt . Errorf ( "unknown models: %s" , strings . Join ( unknownModels , ", " ) )
}
}
for k , v := range defs {
if mnc == 0 {
models [ k ] = v
}
for _ , nm := range modelNames {
if k == nm {
models [ k ] = v
}
}
}
return models , nil
}
func appNameOrDefault ( specDoc * loads . Document , name , defaultName string ) string {
if strings . TrimSpace ( name ) == "" {
if specDoc . Spec ( ) . Info != nil && strings . TrimSpace ( specDoc . Spec ( ) . Info . Title ) != "" {
name = specDoc . Spec ( ) . Info . Title
} else {
name = defaultName
}
}
return strings . TrimSuffix ( strings . TrimSuffix ( strings . TrimSuffix ( swag . ToGoName ( name ) , "Test" ) , "API" ) , "Test" )
}
func containsString ( names [ ] string , name string ) bool {
for _ , nm := range names {
if nm == name {
return true
}
}
return false
}
type opRef struct {
Method string
Path string
Key string
ID string
Op * spec . Operation
}
type opRefs [ ] opRef
func ( o opRefs ) Len ( ) int { return len ( o ) }
func ( o opRefs ) Swap ( i , j int ) { o [ i ] , o [ j ] = o [ j ] , o [ i ] }
func ( o opRefs ) Less ( i , j int ) bool { return o [ i ] . Key < o [ j ] . Key }
func gatherOperations ( specDoc * analysis . Spec , operationIDs [ ] string ) map [ string ] opRef {
var oprefs opRefs
for method , pathItem := range specDoc . Operations ( ) {
for path , operation := range pathItem {
// nm := ensureUniqueName(operation.ID, method, path, operations)
vv := * operation
oprefs = append ( oprefs , opRef {
Key : swag . ToGoName ( strings . ToLower ( method ) + " " + path ) ,
Method : method ,
Path : path ,
ID : vv . ID ,
Op : & vv ,
} )
}
}
sort . Sort ( oprefs )
operations := make ( map [ string ] opRef )
for _ , opr := range oprefs {
nm := opr . ID
if nm == "" {
nm = opr . Key
}
oo , found := operations [ nm ]
if found && oo . Method != opr . Method && oo . Path != opr . Path {
nm = opr . Key
}
if len ( operationIDs ) == 0 || containsString ( operationIDs , opr . ID ) || containsString ( operationIDs , nm ) {
opr . ID = nm
opr . Op . ID = nm
operations [ nm ] = opr
}
}
return operations
}
func pascalize ( arg string ) string {
runes := [ ] rune ( arg )
switch len ( runes ) {
case 0 :
return ""
case 1 : // handle special case when we have a single rune that is not handled by swag.ToGoName
switch runes [ 0 ] {
case '+' , '-' , '#' , '_' : // those cases are handled differently than swag utility
return prefixForName ( arg )
}
}
return swag . ToGoName ( swag . ToGoName ( arg ) ) // want to remove spaces
}
func prefixForName ( arg string ) string {
first := [ ] rune ( arg ) [ 0 ]
if len ( arg ) == 0 || unicode . IsLetter ( first ) {
return ""
}
switch first {
case '+' :
return "Plus"
case '-' :
return "Minus"
case '#' :
return "HashTag"
// other cases ($,@ etc..) handled by swag.ToGoName
}
return "Nr"
}
func init ( ) {
// this makes the ToGoName func behave with the special
// prefixing rule above
swag . GoNamePrefixFunc = prefixForName
}
func pruneEmpty ( in [ ] string ) ( out [ ] string ) {
for _ , v := range in {
if v != "" {
out = append ( out , v )
}
}
return
}
func trimBOM ( in string ) string {
return strings . Trim ( in , "\xef\xbb\xbf" )
}
func validateAndFlattenSpec ( opts * GenOpts , specDoc * loads . Document ) ( * loads . Document , error ) {
var err error
// Validate if needed
if opts . ValidateSpec {
log . Printf ( "validating spec %v" , opts . Spec )
if erv := validateSpec ( opts . Spec , specDoc ) ; erv != nil {
return specDoc , erv
}
}
// Restore spec to original
opts . Spec , specDoc , err = loadSpec ( opts . Spec )
if err != nil {
return nil , err
}
absBasePath := specDoc . SpecFilePath ( )
if ! filepath . IsAbs ( absBasePath ) {
cwd , _ := os . Getwd ( )
absBasePath = filepath . Join ( cwd , absBasePath )
}
// Some preprocessing is required before codegen
//
// This ensures at least that $ref's in the spec document are canonical,
// i.e all $ref are local to this file and point to some uniquely named definition.
//
// Default option is to ensure minimal flattening of $ref, bundling remote $refs and relocating arbitrary JSON
// pointers as definitions.
// This preprocessing may introduce duplicate names (e.g. remote $ref with same name). In this case, a definition
// suffixed with "OAIGen" is produced.
//
// Full flattening option farther transforms the spec by moving every complex object (e.g. with some properties)
// as a standalone definition.
//
// Eventually, an "expand spec" option is available. It is essentially useful for testing purposes.
//
// NOTE(fredbi): spec expansion may produce some unsupported constructs and is not yet protected against the
// following cases:
// - polymorphic types generation may fail with expansion (expand destructs the reuse intent of the $ref in allOf)
// - name duplicates may occur and result in compilation failures
// The right place to fix these shortcomings is go-openapi/analysis.
opts . FlattenOpts . BasePath = absBasePath // BasePath must be absolute
opts . FlattenOpts . Spec = analysis . New ( specDoc . Spec ( ) )
var preprocessingOption string
switch {
case opts . FlattenOpts . Expand :
preprocessingOption = "expand"
case opts . FlattenOpts . Minimal :
preprocessingOption = "minimal flattening"
default :
preprocessingOption = "full flattening"
}
log . Printf ( "preprocessing spec with option: %s" , preprocessingOption )
if err = analysis . Flatten ( * opts . FlattenOpts ) ; err != nil {
return nil , err
}
// yields the preprocessed spec document
return specDoc , nil
}
// gatherSecuritySchemes produces a sorted representation from a map of spec security schemes
func gatherSecuritySchemes ( securitySchemes map [ string ] spec . SecurityScheme , appName , principal , receiver string ) ( security GenSecuritySchemes ) {
for scheme , req := range securitySchemes {
isOAuth2 := strings . ToLower ( req . Type ) == "oauth2"
var scopes [ ] string
if isOAuth2 {
for k := range req . Scopes {
scopes = append ( scopes , k )
}
}
sort . Strings ( scopes )
security = append ( security , GenSecurityScheme {
AppName : appName ,
ID : scheme ,
ReceiverName : receiver ,
Name : req . Name ,
IsBasicAuth : strings . ToLower ( req . Type ) == "basic" ,
IsAPIKeyAuth : strings . ToLower ( req . Type ) == "apikey" ,
IsOAuth2 : isOAuth2 ,
Scopes : scopes ,
Principal : principal ,
Source : req . In ,
// from original spec
Description : req . Description ,
Type : strings . ToLower ( req . Type ) ,
In : req . In ,
Flow : req . Flow ,
AuthorizationURL : req . AuthorizationURL ,
TokenURL : req . TokenURL ,
Extensions : req . Extensions ,
} )
}
sort . Sort ( security )
return
}
// gatherExtraSchemas produces a sorted list of extra schemas.
//
// ExtraSchemas are inlined types rendered in the same model file.
func gatherExtraSchemas ( extraMap map [ string ] GenSchema ) ( extras GenSchemaList ) {
var extraKeys [ ] string
for k := range extraMap {
extraKeys = append ( extraKeys , k )
}
sort . Strings ( extraKeys )
for _ , k := range extraKeys {
// figure out if top level validations are needed
p := extraMap [ k ]
p . HasValidations = shallowValidationLookup ( p )
extras = append ( extras , p )
}
return
}
func sharedValidationsFromSimple ( v spec . CommonValidations , isRequired bool ) ( sh sharedValidations ) {
sh = sharedValidations {
Required : isRequired ,
Maximum : v . Maximum ,
ExclusiveMaximum : v . ExclusiveMaximum ,
Minimum : v . Minimum ,
ExclusiveMinimum : v . ExclusiveMinimum ,
MaxLength : v . MaxLength ,
MinLength : v . MinLength ,
Pattern : v . Pattern ,
MaxItems : v . MaxItems ,
MinItems : v . MinItems ,
UniqueItems : v . UniqueItems ,
MultipleOf : v . MultipleOf ,
Enum : v . Enum ,
}
return
}
func sharedValidationsFromSchema ( v spec . Schema , isRequired bool ) ( sh sharedValidations ) {
sh = sharedValidations {
Required : isRequired ,
Maximum : v . Maximum ,
ExclusiveMaximum : v . ExclusiveMaximum ,
Minimum : v . Minimum ,
ExclusiveMinimum : v . ExclusiveMinimum ,
MaxLength : v . MaxLength ,
MinLength : v . MinLength ,
Pattern : v . Pattern ,
MaxItems : v . MaxItems ,
MinItems : v . MinItems ,
UniqueItems : v . UniqueItems ,
MultipleOf : v . MultipleOf ,
Enum : v . Enum ,
}
return
}