github.com/rzurga/go-swagger@v0.28.1-0.20211109195225-5d1f453ffa3a/scan/scanner.go (about)

     1  // +build !go1.11
     2  
     3  // Copyright 2015 go-swagger maintainers
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //    http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package scan
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"go/ast"
    24  	"go/build"
    25  	goparser "go/parser"
    26  	"go/types"
    27  	"log"
    28  	"os"
    29  	"regexp"
    30  	"strings"
    31  
    32  	"github.com/go-openapi/loads/fmts"
    33  	"github.com/go-openapi/spec"
    34  	"github.com/go-openapi/swag"
    35  	"golang.org/x/tools/go/loader"
    36  	yaml "gopkg.in/yaml.v2"
    37  )
    38  
    39  const (
    40  	rxMethod = "(\\p{L}+)"
    41  	rxPath   = "((?:/[\\p{L}\\p{N}\\p{Pd}\\p{Pc}{}\\-\\.\\?_~%!$&'()*+,;=:@/]*)+/?)"
    42  	rxOpTags = "(\\p{L}[\\p{L}\\p{N}\\p{Pd}\\.\\p{Pc}\\p{Zs}]+)"
    43  	rxOpID   = "((?:\\p{L}[\\p{L}\\p{N}\\p{Pd}\\p{Pc}]+)+)"
    44  
    45  	rxMaximumFmt    = "%s[Mm]ax(?:imum)?\\p{Zs}*:\\p{Zs}*([\\<=])?\\p{Zs}*([\\+-]?(?:\\p{N}+\\.)?\\p{N}+)$"
    46  	rxMinimumFmt    = "%s[Mm]in(?:imum)?\\p{Zs}*:\\p{Zs}*([\\>=])?\\p{Zs}*([\\+-]?(?:\\p{N}+\\.)?\\p{N}+)$"
    47  	rxMultipleOfFmt = "%s[Mm]ultiple\\p{Zs}*[Oo]f\\p{Zs}*:\\p{Zs}*([\\+-]?(?:\\p{N}+\\.)?\\p{N}+)$"
    48  
    49  	rxMaxLengthFmt        = "%s[Mm]ax(?:imum)?(?:\\p{Zs}*[\\p{Pd}\\p{Pc}]?[Ll]en(?:gth)?)\\p{Zs}*:\\p{Zs}*(\\p{N}+)$"
    50  	rxMinLengthFmt        = "%s[Mm]in(?:imum)?(?:\\p{Zs}*[\\p{Pd}\\p{Pc}]?[Ll]en(?:gth)?)\\p{Zs}*:\\p{Zs}*(\\p{N}+)$"
    51  	rxPatternFmt          = "%s[Pp]attern\\p{Zs}*:\\p{Zs}*(.*)$"
    52  	rxCollectionFormatFmt = "%s[Cc]ollection(?:\\p{Zs}*[\\p{Pd}\\p{Pc}]?[Ff]ormat)\\p{Zs}*:\\p{Zs}*(.*)$"
    53  	rxEnumFmt             = "%s[Ee]num\\p{Zs}*:\\p{Zs}*(.*)$"
    54  	rxDefaultFmt          = "%s[Dd]efault\\p{Zs}*:\\p{Zs}*(.*)$"
    55  	rxExampleFmt          = "%s[Ee]xample\\p{Zs}*:\\p{Zs}*(.*)$"
    56  
    57  	rxMaxItemsFmt = "%s[Mm]ax(?:imum)?(?:\\p{Zs}*|[\\p{Pd}\\p{Pc}]|\\.)?[Ii]tems\\p{Zs}*:\\p{Zs}*(\\p{N}+)$"
    58  	rxMinItemsFmt = "%s[Mm]in(?:imum)?(?:\\p{Zs}*|[\\p{Pd}\\p{Pc}]|\\.)?[Ii]tems\\p{Zs}*:\\p{Zs}*(\\p{N}+)$"
    59  	rxUniqueFmt   = "%s[Uu]nique\\p{Zs}*:\\p{Zs}*(true|false)$"
    60  
    61  	rxItemsPrefixFmt = "(?:[Ii]tems[\\.\\p{Zs}]*){%d}"
    62  )
    63  
    64  var (
    65  	rxSwaggerAnnotation  = regexp.MustCompile(`swagger:([\p{L}\p{N}\p{Pd}\p{Pc}]+)`)
    66  	rxFileUpload         = regexp.MustCompile(`swagger:file`)
    67  	rxStrFmt             = regexp.MustCompile(`swagger:strfmt\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)$`)
    68  	rxAlias              = regexp.MustCompile(`swagger:alias`)
    69  	rxName               = regexp.MustCompile(`swagger:name\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}\.]+)$`)
    70  	rxAllOf              = regexp.MustCompile(`swagger:allOf\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}\.]+)?$`)
    71  	rxModelOverride      = regexp.MustCompile(`swagger:model\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)?$`)
    72  	rxResponseOverride   = regexp.MustCompile(`swagger:response\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)?$`)
    73  	rxParametersOverride = regexp.MustCompile(`swagger:parameters\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}\p{Zs}]+)$`)
    74  	rxEnum               = regexp.MustCompile(`swagger:enum\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)$`)
    75  	rxIgnoreOverride     = regexp.MustCompile(`swagger:ignore\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)?$`)
    76  	rxDefault            = regexp.MustCompile(`swagger:default\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)$`)
    77  	rxType               = regexp.MustCompile(`swagger:type\p{Zs}*(\p{L}[\p{L}\p{N}\p{Pd}\p{Pc}]+)$`)
    78  	rxRoute              = regexp.MustCompile(
    79  		"swagger:route\\p{Zs}*" +
    80  			rxMethod +
    81  			"\\p{Zs}*" +
    82  			rxPath +
    83  			"(?:\\p{Zs}+" +
    84  			rxOpTags +
    85  			")?\\p{Zs}+" +
    86  			rxOpID + "\\p{Zs}*$")
    87  	rxBeginYAMLSpec    = regexp.MustCompile(`---\p{Zs}*$`)
    88  	rxUncommentHeaders = regexp.MustCompile(`^[\p{Zs}\t/\*-]*\|?`)
    89  	rxUncommentYAML    = regexp.MustCompile(`^[\p{Zs}\t]*/*`)
    90  	rxOperation        = regexp.MustCompile(
    91  		"swagger:operation\\p{Zs}*" +
    92  			rxMethod +
    93  			"\\p{Zs}*" +
    94  			rxPath +
    95  			"(?:\\p{Zs}+" +
    96  			rxOpTags +
    97  			")?\\p{Zs}+" +
    98  			rxOpID + "\\p{Zs}*$")
    99  
   100  	rxSpace              = regexp.MustCompile(`\p{Zs}+`)
   101  	rxIndent             = regexp.MustCompile(`\p{Zs}*/*\p{Zs}*[^\p{Zs}]`)
   102  	rxPunctuationEnd     = regexp.MustCompile(`\p{Po}$`)
   103  	rxStripComments      = regexp.MustCompile(`^[^\p{L}\p{N}\p{Pd}\p{Pc}\+]*`)
   104  	rxStripTitleComments = regexp.MustCompile(`^[^\p{L}]*[Pp]ackage\p{Zs}+[^\p{Zs}]+\p{Zs}*`)
   105  	rxAllowedExtensions  = regexp.MustCompile(`^[Xx]-`)
   106  
   107  	rxIn              = regexp.MustCompile(`[Ii]n\p{Zs}*:\p{Zs}*(query|path|header|body|formData)$`)
   108  	rxRequired        = regexp.MustCompile(`[Rr]equired\p{Zs}*:\p{Zs}*(true|false)$`)
   109  	rxDiscriminator   = regexp.MustCompile(`[Dd]iscriminator\p{Zs}*:\p{Zs}*(true|false)$`)
   110  	rxReadOnly        = regexp.MustCompile(`[Rr]ead(?:\p{Zs}*|[\p{Pd}\p{Pc}])?[Oo]nly\p{Zs}*:\p{Zs}*(true|false)$`)
   111  	rxConsumes        = regexp.MustCompile(`[Cc]onsumes\p{Zs}*:`)
   112  	rxProduces        = regexp.MustCompile(`[Pp]roduces\p{Zs}*:`)
   113  	rxSecuritySchemes = regexp.MustCompile(`[Ss]ecurity\p{Zs}*:`)
   114  	rxSecurity        = regexp.MustCompile(`[Ss]ecurity\p{Zs}*[Dd]efinitions:`)
   115  	rxResponses       = regexp.MustCompile(`[Rr]esponses\p{Zs}*:`)
   116  	rxParameters      = regexp.MustCompile(`[Pp]arameters\p{Zs}*:`)
   117  	rxSchemes         = regexp.MustCompile(`[Ss]chemes\p{Zs}*:\p{Zs}*((?:(?:https?|HTTPS?|wss?|WSS?)[\p{Zs},]*)+)$`)
   118  	rxVersion         = regexp.MustCompile(`[Vv]ersion\p{Zs}*:\p{Zs}*(.+)$`)
   119  	rxHost            = regexp.MustCompile(`[Hh]ost\p{Zs}*:\p{Zs}*(.+)$`)
   120  	rxBasePath        = regexp.MustCompile(`[Bb]ase\p{Zs}*-*[Pp]ath\p{Zs}*:\p{Zs}*` + rxPath + "$")
   121  	rxLicense         = regexp.MustCompile(`[Ll]icense\p{Zs}*:\p{Zs}*(.+)$`)
   122  	rxContact         = regexp.MustCompile(`[Cc]ontact\p{Zs}*-?(?:[Ii]info\p{Zs}*)?:\p{Zs}*(.+)$`)
   123  	rxTOS             = regexp.MustCompile(`[Tt](:?erms)?\p{Zs}*-?[Oo]f?\p{Zs}*-?[Ss](?:ervice)?\p{Zs}*:`)
   124  	rxExtensions      = regexp.MustCompile(`[Ee]xtensions\p{Zs}*:`)
   125  	rxInfoExtensions  = regexp.MustCompile(`[In]nfo\p{Zs}*[Ee]xtensions:`)
   126  	// currently unused: rxExample         = regexp.MustCompile(`[Ex]ample\p{Zs}*:\p{Zs}*(.*)$`)
   127  )
   128  
   129  // Many thanks go to https://github.com/yvasiyarov/swagger
   130  // this is loosely based on that implementation but for swagger 2.0
   131  
   132  func joinDropLast(lines []string) string {
   133  	l := len(lines)
   134  	lns := lines
   135  	if l > 0 && len(strings.TrimSpace(lines[l-1])) == 0 {
   136  		lns = lines[:l-1]
   137  	}
   138  	return strings.Join(lns, "\n")
   139  }
   140  
   141  func removeEmptyLines(lines []string) (notEmpty []string) {
   142  	for _, l := range lines {
   143  		if len(strings.TrimSpace(l)) > 0 {
   144  			notEmpty = append(notEmpty, l)
   145  		}
   146  	}
   147  	return
   148  }
   149  
   150  func rxf(rxp, ar string) *regexp.Regexp {
   151  	return regexp.MustCompile(fmt.Sprintf(rxp, ar))
   152  }
   153  
   154  // The Opts for the application scanner.
   155  type Opts struct {
   156  	BasePath    string
   157  	Input       *spec.Swagger
   158  	ScanModels  bool
   159  	BuildTags   string
   160  	Include     []string
   161  	Exclude     []string
   162  	IncludeTags []string
   163  	ExcludeTags []string
   164  }
   165  
   166  func safeConvert(str string) bool {
   167  	b, err := swag.ConvertBool(str)
   168  	if err != nil {
   169  		return false
   170  	}
   171  	return b
   172  }
   173  
   174  // Debug is true when process is run with DEBUG=1 env var
   175  var Debug = safeConvert(os.Getenv("DEBUG"))
   176  
   177  // Application scans the application and builds a swagger spec based on the information from the code files.
   178  // When there are includes provided, only those files are considered for the initial discovery.
   179  // Similarly the excludes will exclude an item from initial discovery through scanning for annotations.
   180  // When something in the discovered items requires a type that is contained in the includes or excludes it will still be
   181  // in the spec.
   182  func Application(opts Opts) (*spec.Swagger, error) {
   183  	parser, err := newAppScanner(&opts)
   184  
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	return parser.Parse()
   189  }
   190  
   191  // appScanner the global context for scanning a go application
   192  // into a swagger specification
   193  type appScanner struct {
   194  	loader      *loader.Config
   195  	prog        *loader.Program
   196  	classifier  *programClassifier
   197  	discovered  []schemaDecl
   198  	input       *spec.Swagger
   199  	definitions map[string]spec.Schema
   200  	responses   map[string]spec.Response
   201  	operations  map[string]*spec.Operation
   202  	scanModels  bool
   203  	includeTags map[string]bool
   204  	excludeTas  map[string]bool
   205  
   206  	// MainPackage the path to find the main class in
   207  	MainPackage string
   208  }
   209  
   210  // newAppScanner creates a new api parser
   211  func newAppScanner(opts *Opts) (*appScanner, error) {
   212  	if Debug {
   213  		log.Println("scanning packages discovered through entrypoint @ ", opts.BasePath)
   214  	}
   215  	var ldr loader.Config
   216  	ldr.ParserMode = goparser.ParseComments
   217  	ldr.Import(opts.BasePath)
   218  	if opts.BuildTags != "" {
   219  		ldr.Build = &build.Default
   220  		ldr.Build.BuildTags = strings.Split(opts.BuildTags, ",")
   221  	}
   222  	ldr.TypeChecker = types.Config{FakeImportC: true}
   223  	prog, err := ldr.Load()
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  
   228  	var includes, excludes packageFilters
   229  	if len(opts.Include) > 0 {
   230  		for _, include := range opts.Include {
   231  			includes = append(includes, packageFilter{Name: include})
   232  		}
   233  	}
   234  	if len(opts.Exclude) > 0 {
   235  		for _, exclude := range opts.Exclude {
   236  			excludes = append(excludes, packageFilter{Name: exclude})
   237  		}
   238  	}
   239  	includeTags := make(map[string]bool)
   240  	for _, includeTag := range opts.IncludeTags {
   241  		includeTags[includeTag] = true
   242  	}
   243  	excludeTags := make(map[string]bool)
   244  	for _, excludeTag := range opts.ExcludeTags {
   245  		excludeTags[excludeTag] = true
   246  	}
   247  
   248  	input := opts.Input
   249  	if input == nil {
   250  		input = new(spec.Swagger)
   251  		input.Swagger = "2.0"
   252  	}
   253  
   254  	if input.Paths == nil {
   255  		input.Paths = new(spec.Paths)
   256  	}
   257  	if input.Definitions == nil {
   258  		input.Definitions = make(map[string]spec.Schema)
   259  	}
   260  	if input.Responses == nil {
   261  		input.Responses = make(map[string]spec.Response)
   262  	}
   263  	if input.Extensions == nil {
   264  		input.Extensions = make(spec.Extensions)
   265  	}
   266  
   267  	return &appScanner{
   268  		MainPackage: opts.BasePath,
   269  		prog:        prog,
   270  		input:       input,
   271  		loader:      &ldr,
   272  		operations:  collectOperationsFromInput(input),
   273  		definitions: input.Definitions,
   274  		responses:   input.Responses,
   275  		scanModels:  opts.ScanModels,
   276  		classifier: &programClassifier{
   277  			Includes: includes,
   278  			Excludes: excludes,
   279  		},
   280  		includeTags: includeTags,
   281  		excludeTas:  excludeTags,
   282  	}, nil
   283  }
   284  
   285  func collectOperationsFromInput(input *spec.Swagger) map[string]*spec.Operation {
   286  	operations := make(map[string]*spec.Operation)
   287  	if input != nil && input.Paths != nil {
   288  		for _, pth := range input.Paths.Paths {
   289  			if pth.Get != nil {
   290  				operations[pth.Get.ID] = pth.Get
   291  			}
   292  			if pth.Post != nil {
   293  				operations[pth.Post.ID] = pth.Post
   294  			}
   295  			if pth.Put != nil {
   296  				operations[pth.Put.ID] = pth.Put
   297  			}
   298  			if pth.Patch != nil {
   299  				operations[pth.Patch.ID] = pth.Patch
   300  			}
   301  			if pth.Delete != nil {
   302  				operations[pth.Delete.ID] = pth.Delete
   303  			}
   304  			if pth.Head != nil {
   305  				operations[pth.Head.ID] = pth.Head
   306  			}
   307  			if pth.Options != nil {
   308  				operations[pth.Options.ID] = pth.Options
   309  			}
   310  		}
   311  	}
   312  	return operations
   313  }
   314  
   315  // Parse produces a swagger object for an application
   316  func (a *appScanner) Parse() (*spec.Swagger, error) {
   317  	// classification still includes files that are completely commented out
   318  	cp, err := a.classifier.Classify(a.prog)
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  
   323  	// build models dictionary
   324  	if a.scanModels {
   325  		for _, modelsFile := range cp.Models {
   326  			if err := a.parseSchema(modelsFile); err != nil {
   327  				return nil, err
   328  			}
   329  		}
   330  	}
   331  
   332  	// build parameters dictionary
   333  	for _, paramsFile := range cp.Parameters {
   334  		if err := a.parseParameters(paramsFile); err != nil {
   335  			return nil, err
   336  		}
   337  	}
   338  
   339  	// build responses dictionary
   340  	for _, responseFile := range cp.Responses {
   341  		if err := a.parseResponses(responseFile); err != nil {
   342  			return nil, err
   343  		}
   344  	}
   345  
   346  	// build definitions dictionary
   347  	if err := a.processDiscovered(); err != nil {
   348  		return nil, err
   349  	}
   350  
   351  	// build paths dictionary
   352  	for _, routeFile := range cp.Routes {
   353  		if err := a.parseRoutes(routeFile); err != nil {
   354  			return nil, err
   355  		}
   356  	}
   357  	for _, operationFile := range cp.Operations {
   358  		if err := a.parseOperations(operationFile); err != nil {
   359  			return nil, err
   360  		}
   361  	}
   362  
   363  	// build swagger object
   364  	for _, metaFile := range cp.Meta {
   365  		if err := a.parseMeta(metaFile); err != nil {
   366  			return nil, err
   367  		}
   368  	}
   369  
   370  	if a.input.Swagger == "" {
   371  		a.input.Swagger = "2.0"
   372  	}
   373  
   374  	return a.input, nil
   375  }
   376  
   377  func (a *appScanner) processDiscovered() error {
   378  	// loop over discovered until all the items are in definitions
   379  	keepGoing := len(a.discovered) > 0
   380  	for keepGoing {
   381  		var queue []schemaDecl
   382  		for _, d := range a.discovered {
   383  			if _, ok := a.definitions[d.Name]; !ok {
   384  				queue = append(queue, d)
   385  			}
   386  		}
   387  		a.discovered = nil
   388  		for _, sd := range queue {
   389  			if err := a.parseDiscoveredSchema(sd); err != nil {
   390  				return err
   391  			}
   392  		}
   393  		keepGoing = len(a.discovered) > 0
   394  	}
   395  
   396  	return nil
   397  }
   398  
   399  func (a *appScanner) parseSchema(file *ast.File) error {
   400  	sp := newSchemaParser(a.prog)
   401  	if err := sp.Parse(file, a.definitions); err != nil {
   402  		return err
   403  	}
   404  	a.discovered = append(a.discovered, sp.postDecls...)
   405  	return nil
   406  }
   407  
   408  func (a *appScanner) parseDiscoveredSchema(sd schemaDecl) error {
   409  	sp := newSchemaParser(a.prog)
   410  	sp.discovered = &sd
   411  
   412  	if err := sp.Parse(sd.File, a.definitions); err != nil {
   413  		return err
   414  	}
   415  	a.discovered = append(a.discovered, sp.postDecls...)
   416  	return nil
   417  }
   418  
   419  func (a *appScanner) parseRoutes(file *ast.File) error {
   420  	rp := newRoutesParser(a.prog)
   421  	rp.operations = a.operations
   422  	rp.definitions = a.definitions
   423  	rp.responses = a.responses
   424  
   425  	return rp.Parse(file, a.input.Paths, a.includeTags, a.excludeTas)
   426  }
   427  
   428  func (a *appScanner) parseOperations(file *ast.File) error {
   429  	op := newOperationsParser(a.prog)
   430  	op.operations = a.operations
   431  	op.definitions = a.definitions
   432  	op.responses = a.responses
   433  	return op.Parse(file, a.input.Paths, a.includeTags, a.excludeTas)
   434  }
   435  
   436  func (a *appScanner) parseParameters(file *ast.File) error {
   437  	rp := newParameterParser(a.prog)
   438  	if err := rp.Parse(file, a.operations); err != nil {
   439  		return err
   440  	}
   441  	a.discovered = append(a.discovered, rp.postDecls...)
   442  	a.discovered = append(a.discovered, rp.scp.postDecls...)
   443  	return nil
   444  }
   445  
   446  func (a *appScanner) parseResponses(file *ast.File) error {
   447  	rp := newResponseParser(a.prog)
   448  	if err := rp.Parse(file, a.responses); err != nil {
   449  		return err
   450  	}
   451  	a.discovered = append(a.discovered, rp.postDecls...)
   452  	a.discovered = append(a.discovered, rp.scp.postDecls...)
   453  	return nil
   454  }
   455  
   456  func (a *appScanner) parseMeta(file *ast.File) error {
   457  	return newMetaParser(a.input).Parse(file.Doc)
   458  }
   459  
   460  // MustExpandPackagePath gets the real package path on disk
   461  func (a *appScanner) MustExpandPackagePath(packagePath string) string {
   462  	pkgRealpath := swag.FindInGoSearchPath(packagePath)
   463  	if pkgRealpath == "" {
   464  		log.Fatalf("Can't find package %s \n", packagePath)
   465  	}
   466  
   467  	return pkgRealpath
   468  }
   469  
   470  type swaggerTypable interface {
   471  	Typed(string, string)
   472  	SetRef(spec.Ref)
   473  	Items() swaggerTypable
   474  	WithEnum(...interface{})
   475  	Schema() *spec.Schema
   476  	Level() int
   477  }
   478  
   479  // Map all Go builtin types that have Json representation to Swagger/Json types.
   480  // See https://golang.org/pkg/builtin/ and http://swagger.io/specification/
   481  func swaggerSchemaForType(typeName string, prop swaggerTypable) error {
   482  	switch typeName {
   483  	case "bool":
   484  		prop.Typed("boolean", "")
   485  	case "byte":
   486  		prop.Typed("integer", "uint8")
   487  	case "complex128", "complex64":
   488  		return fmt.Errorf("unsupported builtin %q (no JSON marshaller)", typeName)
   489  	case "error":
   490  		// TODO: error is often marshalled into a string but not always (e.g. errors package creates
   491  		// errors that are marshalled into an empty object), this could be handled the same way
   492  		// custom JSON marshallers are handled (in future)
   493  		prop.Typed("string", "")
   494  	case "float32":
   495  		prop.Typed("number", "float")
   496  	case "float64":
   497  		prop.Typed("number", "double")
   498  	case "int":
   499  		prop.Typed("integer", "int64")
   500  	case "int16":
   501  		prop.Typed("integer", "int16")
   502  	case "int32":
   503  		prop.Typed("integer", "int32")
   504  	case "int64":
   505  		prop.Typed("integer", "int64")
   506  	case "int8":
   507  		prop.Typed("integer", "int8")
   508  	case "rune":
   509  		prop.Typed("integer", "int32")
   510  	case "string":
   511  		prop.Typed("string", "")
   512  	case "uint":
   513  		prop.Typed("integer", "uint64")
   514  	case "uint16":
   515  		prop.Typed("integer", "uint16")
   516  	case "uint32":
   517  		prop.Typed("integer", "uint32")
   518  	case "uint64":
   519  		prop.Typed("integer", "uint64")
   520  	case "uint8":
   521  		prop.Typed("integer", "uint8")
   522  	case "uintptr":
   523  		prop.Typed("integer", "uint64")
   524  	default:
   525  		return fmt.Errorf("unsupported type %q", typeName)
   526  	}
   527  	return nil
   528  }
   529  
   530  func newMultiLineTagParser(name string, parser valueParser, skipCleanUp bool) tagParser {
   531  	return tagParser{
   532  		Name:        name,
   533  		MultiLine:   true,
   534  		SkipCleanUp: skipCleanUp,
   535  		Parser:      parser,
   536  	}
   537  }
   538  
   539  func newSingleLineTagParser(name string, parser valueParser) tagParser {
   540  	return tagParser{
   541  		Name:        name,
   542  		MultiLine:   false,
   543  		SkipCleanUp: false,
   544  		Parser:      parser,
   545  	}
   546  }
   547  
   548  type tagParser struct {
   549  	Name        string
   550  	MultiLine   bool
   551  	SkipCleanUp bool
   552  	Lines       []string
   553  	Parser      valueParser
   554  }
   555  
   556  func (st *tagParser) Matches(line string) bool {
   557  	return st.Parser.Matches(line)
   558  }
   559  
   560  func (st *tagParser) Parse(lines []string) error {
   561  	return st.Parser.Parse(lines)
   562  }
   563  
   564  func newYamlParser(rx *regexp.Regexp, setter func(json.RawMessage) error) valueParser {
   565  	return &yamlParser{
   566  		set: setter,
   567  		rx:  rx,
   568  	}
   569  }
   570  
   571  type yamlParser struct {
   572  	set func(json.RawMessage) error
   573  	rx  *regexp.Regexp
   574  }
   575  
   576  func (y *yamlParser) Parse(lines []string) error {
   577  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   578  		return nil
   579  	}
   580  
   581  	var uncommented []string
   582  	uncommented = append(uncommented, removeYamlIndent(lines)...)
   583  
   584  	yamlContent := strings.Join(uncommented, "\n")
   585  	var yamlValue interface{}
   586  	err := yaml.Unmarshal([]byte(yamlContent), &yamlValue)
   587  	if err != nil {
   588  		return err
   589  	}
   590  
   591  	var jsonValue json.RawMessage
   592  	jsonValue, err = fmts.YAMLToJSON(yamlValue)
   593  	if err != nil {
   594  		return err
   595  	}
   596  
   597  	return y.set(jsonValue)
   598  }
   599  
   600  func (y *yamlParser) Matches(line string) bool {
   601  	return y.rx.MatchString(line)
   602  }
   603  
   604  // aggregates lines in header until it sees `---`,
   605  // the beginning of a YAML spec
   606  type yamlSpecScanner struct {
   607  	header         []string
   608  	yamlSpec       []string
   609  	setTitle       func([]string)
   610  	setDescription func([]string)
   611  	workedOutTitle bool
   612  	title          []string
   613  	skipHeader     bool
   614  }
   615  
   616  func cleanupScannerLines(lines []string, ur *regexp.Regexp, yamlBlock *regexp.Regexp) []string {
   617  	// bail early when there is nothing to parse
   618  	if len(lines) == 0 {
   619  		return lines
   620  	}
   621  	seenLine := -1
   622  	var lastContent int
   623  	var uncommented []string
   624  	var startBlock bool
   625  	var yaml []string
   626  	for i, v := range lines {
   627  		if yamlBlock != nil && yamlBlock.MatchString(v) && !startBlock {
   628  			startBlock = true
   629  			if seenLine < 0 {
   630  				seenLine = i
   631  			}
   632  			continue
   633  		}
   634  		if startBlock {
   635  			if yamlBlock.MatchString(v) {
   636  				startBlock = false
   637  				uncommented = append(uncommented, removeIndent(yaml)...)
   638  				continue
   639  			}
   640  			yaml = append(yaml, v)
   641  			if v != "" {
   642  				if seenLine < 0 {
   643  					seenLine = i
   644  				}
   645  				lastContent = i
   646  			}
   647  			continue
   648  		}
   649  		str := ur.ReplaceAllString(v, "")
   650  		uncommented = append(uncommented, str)
   651  		if str != "" {
   652  			if seenLine < 0 {
   653  				seenLine = i
   654  			}
   655  			lastContent = i
   656  		}
   657  	}
   658  
   659  	// fixes issue #50
   660  	if seenLine == -1 {
   661  		return nil
   662  	}
   663  	return uncommented[seenLine : lastContent+1]
   664  }
   665  
   666  // a shared function that can be used to split given headers
   667  // into a title and description
   668  func collectScannerTitleDescription(headers []string) (title, desc []string) {
   669  	hdrs := cleanupScannerLines(headers, rxUncommentHeaders, nil)
   670  
   671  	idx := -1
   672  	for i, line := range hdrs {
   673  		if strings.TrimSpace(line) == "" {
   674  			idx = i
   675  			break
   676  		}
   677  	}
   678  
   679  	if idx > -1 {
   680  		title = hdrs[:idx]
   681  		if len(hdrs) > idx+1 {
   682  			desc = hdrs[idx+1:]
   683  		} else {
   684  			desc = nil
   685  		}
   686  		return
   687  	}
   688  
   689  	if len(hdrs) > 0 {
   690  		line := hdrs[0]
   691  		if rxPunctuationEnd.MatchString(line) {
   692  			title = []string{line}
   693  			desc = hdrs[1:]
   694  		} else {
   695  			desc = hdrs
   696  		}
   697  	}
   698  
   699  	return
   700  }
   701  
   702  func (sp *yamlSpecScanner) collectTitleDescription() {
   703  	if sp.workedOutTitle {
   704  		return
   705  	}
   706  	if sp.setTitle == nil {
   707  		sp.header = cleanupScannerLines(sp.header, rxUncommentHeaders, nil)
   708  		return
   709  	}
   710  
   711  	sp.workedOutTitle = true
   712  	sp.title, sp.header = collectScannerTitleDescription(sp.header)
   713  }
   714  
   715  func (sp *yamlSpecScanner) Title() []string {
   716  	sp.collectTitleDescription()
   717  	return sp.title
   718  }
   719  
   720  func (sp *yamlSpecScanner) Description() []string {
   721  	sp.collectTitleDescription()
   722  	return sp.header
   723  }
   724  
   725  func (sp *yamlSpecScanner) Parse(doc *ast.CommentGroup) error {
   726  	if doc == nil {
   727  		return nil
   728  	}
   729  	var startedYAMLSpec bool
   730  COMMENTS:
   731  	for _, c := range doc.List {
   732  		for _, line := range strings.Split(c.Text, "\n") {
   733  			if rxSwaggerAnnotation.MatchString(line) {
   734  				break COMMENTS // a new swagger: annotation terminates this parser
   735  			}
   736  
   737  			if !startedYAMLSpec {
   738  				if rxBeginYAMLSpec.MatchString(line) {
   739  					startedYAMLSpec = true
   740  					sp.yamlSpec = append(sp.yamlSpec, line)
   741  					continue
   742  				}
   743  
   744  				if !sp.skipHeader {
   745  					sp.header = append(sp.header, line)
   746  				}
   747  
   748  				// no YAML spec yet, moving on
   749  				continue
   750  			}
   751  
   752  			sp.yamlSpec = append(sp.yamlSpec, line)
   753  		}
   754  	}
   755  	if sp.setTitle != nil {
   756  		sp.setTitle(sp.Title())
   757  	}
   758  	if sp.setDescription != nil {
   759  		sp.setDescription(sp.Description())
   760  	}
   761  	return nil
   762  }
   763  
   764  func (sp *yamlSpecScanner) UnmarshalSpec(u func([]byte) error) (err error) {
   765  	spec := cleanupScannerLines(sp.yamlSpec, rxUncommentYAML, nil)
   766  	if len(spec) == 0 {
   767  		return errors.New("no spec available to unmarshal")
   768  	}
   769  
   770  	if !strings.Contains(spec[0], "---") {
   771  		return errors.New("yaml spec has to start with `---`")
   772  	}
   773  
   774  	// remove indentation
   775  	spec = removeIndent(spec)
   776  
   777  	// 1. parse yaml lines
   778  	yamlValue := make(map[interface{}]interface{})
   779  
   780  	yamlContent := strings.Join(spec, "\n")
   781  	err = yaml.Unmarshal([]byte(yamlContent), &yamlValue)
   782  	if err != nil {
   783  		return
   784  	}
   785  
   786  	// 2. convert to json
   787  	var jsonValue json.RawMessage
   788  	jsonValue, err = fmts.YAMLToJSON(yamlValue)
   789  	if err != nil {
   790  		return
   791  	}
   792  
   793  	// 3. unmarshal the json into an interface
   794  	var data []byte
   795  	data, err = jsonValue.MarshalJSON()
   796  	if err != nil {
   797  		return
   798  	}
   799  	err = u(data)
   800  	if err != nil {
   801  		return
   802  	}
   803  
   804  	// all parsed, returning...
   805  	sp.yamlSpec = nil // spec is now consumed, so let's erase the parsed lines
   806  	return
   807  }
   808  
   809  // removes indent base on the first line
   810  func removeIndent(spec []string) []string {
   811  	loc := rxIndent.FindStringIndex(spec[0])
   812  	if loc[1] > 0 {
   813  		for i := range spec {
   814  			if len(spec[i]) >= loc[1] {
   815  				spec[i] = spec[i][loc[1]-1:]
   816  			}
   817  		}
   818  	}
   819  	return spec
   820  }
   821  
   822  // removes indent base on the first line
   823  func removeYamlIndent(spec []string) []string {
   824  	loc := rxIndent.FindStringIndex(spec[0])
   825  	var s []string
   826  	if loc[1] > 0 {
   827  		for i := range spec {
   828  			if len(spec[i]) >= loc[1] {
   829  				s = append(s, spec[i][loc[1]-1:])
   830  			}
   831  		}
   832  	}
   833  	return s
   834  }
   835  
   836  // aggregates lines in header until it sees a tag.
   837  type sectionedParser struct {
   838  	header     []string
   839  	matched    map[string]tagParser
   840  	annotation valueParser
   841  
   842  	seenTag        bool
   843  	skipHeader     bool
   844  	setTitle       func([]string)
   845  	setDescription func([]string)
   846  	workedOutTitle bool
   847  	taggers        []tagParser
   848  	currentTagger  *tagParser
   849  	title          []string
   850  	ignored        bool
   851  }
   852  
   853  func (st *sectionedParser) collectTitleDescription() {
   854  	if st.workedOutTitle {
   855  		return
   856  	}
   857  	if st.setTitle == nil {
   858  		st.header = cleanupScannerLines(st.header, rxUncommentHeaders, nil)
   859  		return
   860  	}
   861  
   862  	st.workedOutTitle = true
   863  	st.title, st.header = collectScannerTitleDescription(st.header)
   864  }
   865  
   866  func (st *sectionedParser) Title() []string {
   867  	st.collectTitleDescription()
   868  	return st.title
   869  }
   870  
   871  func (st *sectionedParser) Description() []string {
   872  	st.collectTitleDescription()
   873  	return st.header
   874  }
   875  
   876  func (st *sectionedParser) Parse(doc *ast.CommentGroup) error {
   877  	if doc == nil {
   878  		return nil
   879  	}
   880  COMMENTS:
   881  	for _, c := range doc.List {
   882  		for _, line := range strings.Split(c.Text, "\n") {
   883  			if rxSwaggerAnnotation.MatchString(line) {
   884  				if rxIgnoreOverride.MatchString(line) {
   885  					st.ignored = true
   886  					break COMMENTS // an explicit ignore terminates this parser
   887  				}
   888  				if st.annotation == nil || !st.annotation.Matches(line) {
   889  					break COMMENTS // a new swagger: annotation terminates this parser
   890  				}
   891  
   892  				_ = st.annotation.Parse([]string{line})
   893  				if len(st.header) > 0 {
   894  					st.seenTag = true
   895  				}
   896  				continue
   897  			}
   898  
   899  			var matched bool
   900  			for _, tagger := range st.taggers {
   901  				if tagger.Matches(line) {
   902  					st.seenTag = true
   903  					st.currentTagger = &tagger
   904  					matched = true
   905  					break
   906  				}
   907  			}
   908  
   909  			if st.currentTagger == nil {
   910  				if !st.skipHeader && !st.seenTag {
   911  					st.header = append(st.header, line)
   912  				}
   913  				// didn't match a tag, moving on
   914  				continue
   915  			}
   916  
   917  			if st.currentTagger.MultiLine && matched {
   918  				// the first line of a multiline tagger doesn't count
   919  				continue
   920  			}
   921  
   922  			ts, ok := st.matched[st.currentTagger.Name]
   923  			if !ok {
   924  				ts = *st.currentTagger
   925  			}
   926  			ts.Lines = append(ts.Lines, line)
   927  			if st.matched == nil {
   928  				st.matched = make(map[string]tagParser)
   929  			}
   930  			st.matched[st.currentTagger.Name] = ts
   931  
   932  			if !st.currentTagger.MultiLine {
   933  				st.currentTagger = nil
   934  			}
   935  		}
   936  	}
   937  	if st.setTitle != nil {
   938  		st.setTitle(st.Title())
   939  	}
   940  	if st.setDescription != nil {
   941  		st.setDescription(st.Description())
   942  	}
   943  	for _, mt := range st.matched {
   944  		if !mt.SkipCleanUp {
   945  			mt.Lines = cleanupScannerLines(mt.Lines, rxUncommentHeaders, nil)
   946  		}
   947  		if err := mt.Parse(mt.Lines); err != nil {
   948  			return err
   949  		}
   950  	}
   951  	return nil
   952  }