github.com/jamescostian/go-swagger@v0.30.4-0.20221130163922-68364d6b567b/scan/scanner.go (about)

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