github.com/AngusLu/go-swagger@v0.28.0/codescan/parser.go (about)

     1  package codescan
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"go/ast"
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/go-openapi/loads/fmts"
    12  	"github.com/go-openapi/spec"
    13  	"github.com/pkg/errors"
    14  	"gopkg.in/yaml.v2"
    15  )
    16  
    17  func shouldAcceptTag(tags []string, includeTags map[string]bool, excludeTags map[string]bool) bool {
    18  	for _, tag := range tags {
    19  		if len(includeTags) > 0 {
    20  			if includeTags[tag] {
    21  				return true
    22  			}
    23  		} else if len(excludeTags) > 0 {
    24  			if excludeTags[tag] {
    25  				return false
    26  			}
    27  		}
    28  	}
    29  	return len(includeTags) == 0
    30  }
    31  
    32  func shouldAcceptPkg(path string, includePkgs, excludePkgs []string) bool {
    33  	if len(includePkgs) == 0 && len(excludePkgs) == 0 {
    34  		return true
    35  	}
    36  	for _, pkgName := range includePkgs {
    37  		matched, _ := regexp.MatchString(pkgName, path)
    38  		if matched {
    39  			return true
    40  		}
    41  	}
    42  	for _, pkgName := range excludePkgs {
    43  		matched, _ := regexp.MatchString(pkgName, path)
    44  		if matched {
    45  			return false
    46  		}
    47  	}
    48  	return len(includePkgs) == 0
    49  }
    50  
    51  // Many thanks go to https://github.com/yvasiyarov/swagger
    52  // this is loosely based on that implementation but for swagger 2.0
    53  
    54  func joinDropLast(lines []string) string {
    55  	l := len(lines)
    56  	lns := lines
    57  	if l > 0 && len(strings.TrimSpace(lines[l-1])) == 0 {
    58  		lns = lines[:l-1]
    59  	}
    60  	return strings.Join(lns, "\n")
    61  }
    62  
    63  func removeEmptyLines(lines []string) (notEmpty []string) {
    64  	for _, l := range lines {
    65  		if len(strings.TrimSpace(l)) > 0 {
    66  			notEmpty = append(notEmpty, l)
    67  		}
    68  	}
    69  	return
    70  }
    71  
    72  func rxf(rxp, ar string) *regexp.Regexp {
    73  	return regexp.MustCompile(fmt.Sprintf(rxp, ar))
    74  }
    75  
    76  func allOfMember(comments *ast.CommentGroup) bool {
    77  	if comments != nil {
    78  		for _, cmt := range comments.List {
    79  			for _, ln := range strings.Split(cmt.Text, "\n") {
    80  				if rxAllOf.MatchString(ln) {
    81  					return true
    82  				}
    83  			}
    84  		}
    85  	}
    86  	return false
    87  }
    88  
    89  func fileParam(comments *ast.CommentGroup) bool {
    90  	if comments != nil {
    91  		for _, cmt := range comments.List {
    92  			for _, ln := range strings.Split(cmt.Text, "\n") {
    93  				if rxFileUpload.MatchString(ln) {
    94  					return true
    95  				}
    96  			}
    97  		}
    98  	}
    99  	return false
   100  }
   101  
   102  func strfmtName(comments *ast.CommentGroup) (string, bool) {
   103  	if comments != nil {
   104  		for _, cmt := range comments.List {
   105  			for _, ln := range strings.Split(cmt.Text, "\n") {
   106  				matches := rxStrFmt.FindStringSubmatch(ln)
   107  				if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
   108  					return strings.TrimSpace(matches[1]), true
   109  				}
   110  			}
   111  		}
   112  	}
   113  	return "", false
   114  }
   115  
   116  func ignored(comments *ast.CommentGroup) bool {
   117  	if comments != nil {
   118  		for _, cmt := range comments.List {
   119  			for _, ln := range strings.Split(cmt.Text, "\n") {
   120  				if rxIgnoreOverride.MatchString(ln) {
   121  					return true
   122  				}
   123  			}
   124  		}
   125  	}
   126  	return false
   127  }
   128  
   129  func enumName(comments *ast.CommentGroup) (string, bool) {
   130  	if comments != nil {
   131  		for _, cmt := range comments.List {
   132  			for _, ln := range strings.Split(cmt.Text, "\n") {
   133  				matches := rxEnum.FindStringSubmatch(ln)
   134  				if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
   135  					return strings.TrimSpace(matches[1]), true
   136  				}
   137  			}
   138  		}
   139  	}
   140  	return "", false
   141  }
   142  
   143  func aliasParam(comments *ast.CommentGroup) bool {
   144  	if comments != nil {
   145  		for _, cmt := range comments.List {
   146  			for _, ln := range strings.Split(cmt.Text, "\n") {
   147  				if rxAlias.MatchString(ln) {
   148  					return true
   149  				}
   150  			}
   151  		}
   152  	}
   153  	return false
   154  }
   155  
   156  func isAliasParam(prop swaggerTypable) bool {
   157  	var isParam bool
   158  	if param, ok := prop.(paramTypable); ok {
   159  		isParam = param.param.In == "query" ||
   160  			param.param.In == "path" ||
   161  			param.param.In == "formData"
   162  	}
   163  	return isParam
   164  }
   165  
   166  func defaultName(comments *ast.CommentGroup) (string, bool) {
   167  	if comments != nil {
   168  		for _, cmt := range comments.List {
   169  			for _, ln := range strings.Split(cmt.Text, "\n") {
   170  				matches := rxDefault.FindStringSubmatch(ln)
   171  				if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
   172  					return strings.TrimSpace(matches[1]), true
   173  				}
   174  			}
   175  		}
   176  	}
   177  	return "", false
   178  }
   179  
   180  func typeName(comments *ast.CommentGroup) (string, bool) {
   181  	var typ string
   182  	if comments != nil {
   183  		for _, cmt := range comments.List {
   184  			for _, ln := range strings.Split(cmt.Text, "\n") {
   185  				matches := rxType.FindStringSubmatch(ln)
   186  				if len(matches) > 1 && len(strings.TrimSpace(matches[1])) > 0 {
   187  					typ = strings.TrimSpace(matches[1])
   188  					return typ, true
   189  				}
   190  			}
   191  		}
   192  	}
   193  	return "", false
   194  }
   195  
   196  type swaggerTypable interface {
   197  	Typed(string, string)
   198  	SetRef(spec.Ref)
   199  	Items() swaggerTypable
   200  	Schema() *spec.Schema
   201  	Level() int
   202  	AddExtension(key string, value interface{})
   203  	WithEnum(...interface{})
   204  	WithEnumDescription(desc string)
   205  }
   206  
   207  // Map all Go builtin types that have Json representation to Swagger/Json types.
   208  // See https://golang.org/pkg/builtin/ and http://swagger.io/specification/
   209  func swaggerSchemaForType(typeName string, prop swaggerTypable) error {
   210  	switch typeName {
   211  	case "bool":
   212  		prop.Typed("boolean", "")
   213  	case "byte":
   214  		prop.Typed("integer", "uint8")
   215  	case "complex128", "complex64":
   216  		return fmt.Errorf("unsupported builtin %q (no JSON marshaller)", typeName)
   217  	case "error":
   218  		// TODO: error is often marshalled into a string but not always (e.g. errors package creates
   219  		// errors that are marshalled into an empty object), this could be handled the same way
   220  		// custom JSON marshallers are handled (in future)
   221  		prop.Typed("string", "")
   222  	case "float32":
   223  		prop.Typed("number", "float")
   224  	case "float64":
   225  		prop.Typed("number", "double")
   226  	case "int":
   227  		prop.Typed("integer", "int64")
   228  	case "int16":
   229  		prop.Typed("integer", "int16")
   230  	case "int32":
   231  		prop.Typed("integer", "int32")
   232  	case "int64":
   233  		prop.Typed("integer", "int64")
   234  	case "int8":
   235  		prop.Typed("integer", "int8")
   236  	case "rune":
   237  		prop.Typed("integer", "int32")
   238  	case "string":
   239  		prop.Typed("string", "")
   240  	case "uint":
   241  		prop.Typed("integer", "uint64")
   242  	case "uint16":
   243  		prop.Typed("integer", "uint16")
   244  	case "uint32":
   245  		prop.Typed("integer", "uint32")
   246  	case "uint64":
   247  		prop.Typed("integer", "uint64")
   248  	case "uint8":
   249  		prop.Typed("integer", "uint8")
   250  	case "uintptr":
   251  		prop.Typed("integer", "uint64")
   252  	default:
   253  		return fmt.Errorf("unsupported type %q", typeName)
   254  	}
   255  	return nil
   256  }
   257  
   258  func newMultiLineTagParser(name string, parser valueParser, skipCleanUp bool) tagParser {
   259  	return tagParser{
   260  		Name:        name,
   261  		MultiLine:   true,
   262  		SkipCleanUp: skipCleanUp,
   263  		Parser:      parser,
   264  	}
   265  }
   266  
   267  func newSingleLineTagParser(name string, parser valueParser) tagParser {
   268  	return tagParser{
   269  		Name:        name,
   270  		MultiLine:   false,
   271  		SkipCleanUp: false,
   272  		Parser:      parser,
   273  	}
   274  }
   275  
   276  type tagParser struct {
   277  	Name        string
   278  	MultiLine   bool
   279  	SkipCleanUp bool
   280  	Lines       []string
   281  	Parser      valueParser
   282  }
   283  
   284  func (st *tagParser) Matches(line string) bool {
   285  	return st.Parser.Matches(line)
   286  }
   287  
   288  func (st *tagParser) Parse(lines []string) error {
   289  	return st.Parser.Parse(lines)
   290  }
   291  
   292  func newYamlParser(rx *regexp.Regexp, setter func(json.RawMessage) error) valueParser {
   293  	return &yamlParser{
   294  		set: setter,
   295  		rx:  rx,
   296  	}
   297  }
   298  
   299  type yamlParser struct {
   300  	set func(json.RawMessage) error
   301  	rx  *regexp.Regexp
   302  }
   303  
   304  func (y *yamlParser) Parse(lines []string) error {
   305  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   306  		return nil
   307  	}
   308  
   309  	var uncommented []string
   310  	uncommented = append(uncommented, removeYamlIndent(lines)...)
   311  
   312  	yamlContent := strings.Join(uncommented, "\n")
   313  	var yamlValue interface{}
   314  	err := yaml.Unmarshal([]byte(yamlContent), &yamlValue)
   315  	if err != nil {
   316  		return err
   317  	}
   318  
   319  	var jsonValue json.RawMessage
   320  	jsonValue, err = fmts.YAMLToJSON(yamlValue)
   321  	if err != nil {
   322  		return err
   323  	}
   324  
   325  	return y.set(jsonValue)
   326  }
   327  
   328  func (y *yamlParser) Matches(line string) bool {
   329  	return y.rx.MatchString(line)
   330  }
   331  
   332  // aggregates lines in header until it sees `---`,
   333  // the beginning of a YAML spec
   334  type yamlSpecScanner struct {
   335  	header         []string
   336  	yamlSpec       []string
   337  	setTitle       func([]string)
   338  	setDescription func([]string)
   339  	workedOutTitle bool
   340  	title          []string
   341  	skipHeader     bool
   342  }
   343  
   344  func cleanupScannerLines(lines []string, ur *regexp.Regexp, yamlBlock *regexp.Regexp) []string {
   345  	// bail early when there is nothing to parse
   346  	if len(lines) == 0 {
   347  		return lines
   348  	}
   349  	seenLine := -1
   350  	var lastContent int
   351  	var uncommented []string
   352  	var startBlock bool
   353  	var yamlLines []string
   354  	for i, v := range lines {
   355  		if yamlBlock != nil && yamlBlock.MatchString(v) && !startBlock {
   356  			startBlock = true
   357  			if seenLine < 0 {
   358  				seenLine = i
   359  			}
   360  			continue
   361  		}
   362  		if startBlock {
   363  			if yamlBlock != nil && yamlBlock.MatchString(v) {
   364  				startBlock = false
   365  				uncommented = append(uncommented, removeIndent(yamlLines)...)
   366  				continue
   367  			}
   368  			yamlLines = append(yamlLines, v)
   369  			if v != "" {
   370  				if seenLine < 0 {
   371  					seenLine = i
   372  				}
   373  				lastContent = i
   374  			}
   375  			continue
   376  		}
   377  		str := ur.ReplaceAllString(v, "")
   378  		uncommented = append(uncommented, str)
   379  		if str != "" {
   380  			if seenLine < 0 {
   381  				seenLine = i
   382  			}
   383  			lastContent = i
   384  		}
   385  	}
   386  
   387  	// fixes issue #50
   388  	if seenLine == -1 {
   389  		return nil
   390  	}
   391  	return uncommented[seenLine : lastContent+1]
   392  }
   393  
   394  // a shared function that can be used to split given headers
   395  // into a title and description
   396  func collectScannerTitleDescription(headers []string) (title, desc []string) {
   397  	hdrs := cleanupScannerLines(headers, rxUncommentHeaders, nil)
   398  
   399  	idx := -1
   400  	for i, line := range hdrs {
   401  		if strings.TrimSpace(line) == "" {
   402  			idx = i
   403  			break
   404  		}
   405  	}
   406  
   407  	if idx > -1 {
   408  		title = hdrs[:idx]
   409  		if len(hdrs) > idx+1 {
   410  			desc = hdrs[idx+1:]
   411  		} else {
   412  			desc = nil
   413  		}
   414  		return
   415  	}
   416  
   417  	if len(hdrs) > 0 {
   418  		line := hdrs[0]
   419  		if rxPunctuationEnd.MatchString(line) {
   420  			title = []string{line}
   421  			desc = hdrs[1:]
   422  		} else {
   423  			desc = hdrs
   424  		}
   425  	}
   426  
   427  	return
   428  }
   429  
   430  func (sp *yamlSpecScanner) collectTitleDescription() {
   431  	if sp.workedOutTitle {
   432  		return
   433  	}
   434  	if sp.setTitle == nil {
   435  		sp.header = cleanupScannerLines(sp.header, rxUncommentHeaders, nil)
   436  		return
   437  	}
   438  
   439  	sp.workedOutTitle = true
   440  	sp.title, sp.header = collectScannerTitleDescription(sp.header)
   441  }
   442  
   443  func (sp *yamlSpecScanner) Title() []string {
   444  	sp.collectTitleDescription()
   445  	return sp.title
   446  }
   447  
   448  func (sp *yamlSpecScanner) Description() []string {
   449  	sp.collectTitleDescription()
   450  	return sp.header
   451  }
   452  
   453  func (sp *yamlSpecScanner) Parse(doc *ast.CommentGroup) error {
   454  	if doc == nil {
   455  		return nil
   456  	}
   457  	var startedYAMLSpec bool
   458  COMMENTS:
   459  	for _, c := range doc.List {
   460  		for _, line := range strings.Split(c.Text, "\n") {
   461  			if rxSwaggerAnnotation.MatchString(line) {
   462  				break COMMENTS // a new swagger: annotation terminates this parser
   463  			}
   464  
   465  			if !startedYAMLSpec {
   466  				if rxBeginYAMLSpec.MatchString(line) {
   467  					startedYAMLSpec = true
   468  					sp.yamlSpec = append(sp.yamlSpec, line)
   469  					continue
   470  				}
   471  
   472  				if !sp.skipHeader {
   473  					sp.header = append(sp.header, line)
   474  				}
   475  
   476  				// no YAML spec yet, moving on
   477  				continue
   478  			}
   479  
   480  			sp.yamlSpec = append(sp.yamlSpec, line)
   481  		}
   482  	}
   483  	if sp.setTitle != nil {
   484  		sp.setTitle(sp.Title())
   485  	}
   486  	if sp.setDescription != nil {
   487  		sp.setDescription(sp.Description())
   488  	}
   489  	return nil
   490  }
   491  
   492  func (sp *yamlSpecScanner) UnmarshalSpec(u func([]byte) error) (err error) {
   493  	specYaml := cleanupScannerLines(sp.yamlSpec, rxUncommentYAML, nil)
   494  	if len(specYaml) == 0 {
   495  		return errors.New("no spec available to unmarshal")
   496  	}
   497  
   498  	if !strings.Contains(specYaml[0], "---") {
   499  		return errors.New("yaml spec has to start with `---`")
   500  	}
   501  
   502  	// remove indentation
   503  	specYaml = removeIndent(specYaml)
   504  
   505  	// 1. parse yaml lines
   506  	yamlValue := make(map[interface{}]interface{})
   507  
   508  	yamlContent := strings.Join(specYaml, "\n")
   509  	err = yaml.Unmarshal([]byte(yamlContent), &yamlValue)
   510  	if err != nil {
   511  		return
   512  	}
   513  
   514  	// 2. convert to json
   515  	var jsonValue json.RawMessage
   516  	jsonValue, err = fmts.YAMLToJSON(yamlValue)
   517  	if err != nil {
   518  		return
   519  	}
   520  
   521  	// 3. unmarshal the json into an interface
   522  	var data []byte
   523  	data, err = jsonValue.MarshalJSON()
   524  	if err != nil {
   525  		return
   526  	}
   527  	err = u(data)
   528  	if err != nil {
   529  		return
   530  	}
   531  
   532  	// all parsed, returning...
   533  	sp.yamlSpec = nil // spec is now consumed, so let's erase the parsed lines
   534  	return
   535  }
   536  
   537  // removes indent base on the first line
   538  func removeIndent(spec []string) []string {
   539  	loc := rxIndent.FindStringIndex(spec[0])
   540  	if loc[1] == 0 {
   541  		return spec
   542  	}
   543  	for i := range spec {
   544  		if len(spec[i]) >= loc[1] {
   545  			spec[i] = spec[i][loc[1]-1:]
   546  		}
   547  	}
   548  	return spec
   549  }
   550  
   551  // removes indent base on the first line
   552  func removeYamlIndent(spec []string) []string {
   553  	loc := rxIndent.FindStringIndex(spec[0])
   554  	if loc[1] == 0 {
   555  		return nil
   556  	}
   557  	var s []string
   558  	for i := range spec {
   559  		if len(spec[i]) >= loc[1] {
   560  			s = append(s, spec[i][loc[1]-1:])
   561  		}
   562  	}
   563  	return s
   564  }
   565  
   566  // aggregates lines in header until it sees a tag.
   567  type sectionedParser struct {
   568  	header     []string
   569  	matched    map[string]tagParser
   570  	annotation valueParser
   571  
   572  	seenTag        bool
   573  	skipHeader     bool
   574  	setTitle       func([]string)
   575  	setDescription func([]string)
   576  	workedOutTitle bool
   577  	taggers        []tagParser
   578  	currentTagger  *tagParser
   579  	title          []string
   580  	ignored        bool
   581  }
   582  
   583  func (st *sectionedParser) collectTitleDescription() {
   584  	if st.workedOutTitle {
   585  		return
   586  	}
   587  	if st.setTitle == nil {
   588  		st.header = cleanupScannerLines(st.header, rxUncommentHeaders, nil)
   589  		return
   590  	}
   591  
   592  	st.workedOutTitle = true
   593  	st.title, st.header = collectScannerTitleDescription(st.header)
   594  }
   595  
   596  func (st *sectionedParser) Title() []string {
   597  	st.collectTitleDescription()
   598  	return st.title
   599  }
   600  
   601  func (st *sectionedParser) Description() []string {
   602  	st.collectTitleDescription()
   603  	return st.header
   604  }
   605  
   606  func (st *sectionedParser) Parse(doc *ast.CommentGroup) error {
   607  	if doc == nil {
   608  		return nil
   609  	}
   610  COMMENTS:
   611  	for _, c := range doc.List {
   612  		for _, line := range strings.Split(c.Text, "\n") {
   613  			if rxSwaggerAnnotation.MatchString(line) {
   614  				if rxIgnoreOverride.MatchString(line) {
   615  					st.ignored = true
   616  					break COMMENTS // an explicit ignore terminates this parser
   617  				}
   618  				if st.annotation == nil || !st.annotation.Matches(line) {
   619  					break COMMENTS // a new swagger: annotation terminates this parser
   620  				}
   621  
   622  				_ = st.annotation.Parse([]string{line})
   623  				if len(st.header) > 0 {
   624  					st.seenTag = true
   625  				}
   626  				continue
   627  			}
   628  
   629  			var matched bool
   630  			for _, tg := range st.taggers {
   631  				tagger := tg
   632  				if tagger.Matches(line) {
   633  					st.seenTag = true
   634  					st.currentTagger = &tagger
   635  					matched = true
   636  					break
   637  				}
   638  			}
   639  
   640  			if st.currentTagger == nil {
   641  				if !st.skipHeader && !st.seenTag {
   642  					st.header = append(st.header, line)
   643  				}
   644  				// didn't match a tag, moving on
   645  				continue
   646  			}
   647  
   648  			if st.currentTagger.MultiLine && matched {
   649  				// the first line of a multiline tagger doesn't count
   650  				continue
   651  			}
   652  
   653  			ts, ok := st.matched[st.currentTagger.Name]
   654  			if !ok {
   655  				ts = *st.currentTagger
   656  			}
   657  			ts.Lines = append(ts.Lines, line)
   658  			if st.matched == nil {
   659  				st.matched = make(map[string]tagParser)
   660  			}
   661  			st.matched[st.currentTagger.Name] = ts
   662  
   663  			if !st.currentTagger.MultiLine {
   664  				st.currentTagger = nil
   665  			}
   666  		}
   667  	}
   668  	if st.setTitle != nil {
   669  		st.setTitle(st.Title())
   670  	}
   671  	if st.setDescription != nil {
   672  		st.setDescription(st.Description())
   673  	}
   674  	for _, mt := range st.matched {
   675  		if !mt.SkipCleanUp {
   676  			mt.Lines = cleanupScannerLines(mt.Lines, rxUncommentHeaders, nil)
   677  		}
   678  		if err := mt.Parse(mt.Lines); err != nil {
   679  			return err
   680  		}
   681  	}
   682  	return nil
   683  }
   684  
   685  type validationBuilder interface {
   686  	SetMaximum(float64, bool)
   687  	SetMinimum(float64, bool)
   688  	SetMultipleOf(float64)
   689  
   690  	SetMinItems(int64)
   691  	SetMaxItems(int64)
   692  
   693  	SetMinLength(int64)
   694  	SetMaxLength(int64)
   695  	SetPattern(string)
   696  
   697  	SetUnique(bool)
   698  	SetEnum(string)
   699  	SetDefault(interface{})
   700  	SetExample(interface{})
   701  }
   702  
   703  type valueParser interface {
   704  	Parse([]string) error
   705  	Matches(string) bool
   706  }
   707  
   708  type operationValidationBuilder interface {
   709  	validationBuilder
   710  	SetCollectionFormat(string)
   711  }
   712  
   713  type setMaximum struct {
   714  	builder validationBuilder
   715  	rx      *regexp.Regexp
   716  }
   717  
   718  func (sm *setMaximum) Parse(lines []string) error {
   719  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   720  		return nil
   721  	}
   722  	matches := sm.rx.FindStringSubmatch(lines[0])
   723  	if len(matches) > 2 && len(matches[2]) > 0 {
   724  		max, err := strconv.ParseFloat(matches[2], 64)
   725  		if err != nil {
   726  			return err
   727  		}
   728  		sm.builder.SetMaximum(max, matches[1] == "<")
   729  	}
   730  	return nil
   731  }
   732  
   733  func (sm *setMaximum) Matches(line string) bool {
   734  	return sm.rx.MatchString(line)
   735  }
   736  
   737  type setMinimum struct {
   738  	builder validationBuilder
   739  	rx      *regexp.Regexp
   740  }
   741  
   742  func (sm *setMinimum) Matches(line string) bool {
   743  	return sm.rx.MatchString(line)
   744  }
   745  
   746  func (sm *setMinimum) Parse(lines []string) error {
   747  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   748  		return nil
   749  	}
   750  	matches := sm.rx.FindStringSubmatch(lines[0])
   751  	if len(matches) > 2 && len(matches[2]) > 0 {
   752  		min, err := strconv.ParseFloat(matches[2], 64)
   753  		if err != nil {
   754  			return err
   755  		}
   756  		sm.builder.SetMinimum(min, matches[1] == ">")
   757  	}
   758  	return nil
   759  }
   760  
   761  type setMultipleOf struct {
   762  	builder validationBuilder
   763  	rx      *regexp.Regexp
   764  }
   765  
   766  func (sm *setMultipleOf) Matches(line string) bool {
   767  	return sm.rx.MatchString(line)
   768  }
   769  
   770  func (sm *setMultipleOf) Parse(lines []string) error {
   771  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   772  		return nil
   773  	}
   774  	matches := sm.rx.FindStringSubmatch(lines[0])
   775  	if len(matches) > 2 && len(matches[1]) > 0 {
   776  		multipleOf, err := strconv.ParseFloat(matches[1], 64)
   777  		if err != nil {
   778  			return err
   779  		}
   780  		sm.builder.SetMultipleOf(multipleOf)
   781  	}
   782  	return nil
   783  }
   784  
   785  type setMaxItems struct {
   786  	builder validationBuilder
   787  	rx      *regexp.Regexp
   788  }
   789  
   790  func (sm *setMaxItems) Matches(line string) bool {
   791  	return sm.rx.MatchString(line)
   792  }
   793  
   794  func (sm *setMaxItems) Parse(lines []string) error {
   795  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   796  		return nil
   797  	}
   798  	matches := sm.rx.FindStringSubmatch(lines[0])
   799  	if len(matches) > 1 && len(matches[1]) > 0 {
   800  		maxItems, err := strconv.ParseInt(matches[1], 10, 64)
   801  		if err != nil {
   802  			return err
   803  		}
   804  		sm.builder.SetMaxItems(maxItems)
   805  	}
   806  	return nil
   807  }
   808  
   809  type setMinItems struct {
   810  	builder validationBuilder
   811  	rx      *regexp.Regexp
   812  }
   813  
   814  func (sm *setMinItems) Matches(line string) bool {
   815  	return sm.rx.MatchString(line)
   816  }
   817  
   818  func (sm *setMinItems) Parse(lines []string) error {
   819  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   820  		return nil
   821  	}
   822  	matches := sm.rx.FindStringSubmatch(lines[0])
   823  	if len(matches) > 1 && len(matches[1]) > 0 {
   824  		minItems, err := strconv.ParseInt(matches[1], 10, 64)
   825  		if err != nil {
   826  			return err
   827  		}
   828  		sm.builder.SetMinItems(minItems)
   829  	}
   830  	return nil
   831  }
   832  
   833  type setMaxLength struct {
   834  	builder validationBuilder
   835  	rx      *regexp.Regexp
   836  }
   837  
   838  func (sm *setMaxLength) Parse(lines []string) error {
   839  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   840  		return nil
   841  	}
   842  	matches := sm.rx.FindStringSubmatch(lines[0])
   843  	if len(matches) > 1 && len(matches[1]) > 0 {
   844  		maxLength, err := strconv.ParseInt(matches[1], 10, 64)
   845  		if err != nil {
   846  			return err
   847  		}
   848  		sm.builder.SetMaxLength(maxLength)
   849  	}
   850  	return nil
   851  }
   852  
   853  func (sm *setMaxLength) Matches(line string) bool {
   854  	return sm.rx.MatchString(line)
   855  }
   856  
   857  type setMinLength struct {
   858  	builder validationBuilder
   859  	rx      *regexp.Regexp
   860  }
   861  
   862  func (sm *setMinLength) Parse(lines []string) error {
   863  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   864  		return nil
   865  	}
   866  	matches := sm.rx.FindStringSubmatch(lines[0])
   867  	if len(matches) > 1 && len(matches[1]) > 0 {
   868  		minLength, err := strconv.ParseInt(matches[1], 10, 64)
   869  		if err != nil {
   870  			return err
   871  		}
   872  		sm.builder.SetMinLength(minLength)
   873  	}
   874  	return nil
   875  }
   876  
   877  func (sm *setMinLength) Matches(line string) bool {
   878  	return sm.rx.MatchString(line)
   879  }
   880  
   881  type setPattern struct {
   882  	builder validationBuilder
   883  	rx      *regexp.Regexp
   884  }
   885  
   886  func (sm *setPattern) Parse(lines []string) error {
   887  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   888  		return nil
   889  	}
   890  	matches := sm.rx.FindStringSubmatch(lines[0])
   891  	if len(matches) > 1 && len(matches[1]) > 0 {
   892  		sm.builder.SetPattern(matches[1])
   893  	}
   894  	return nil
   895  }
   896  
   897  func (sm *setPattern) Matches(line string) bool {
   898  	return sm.rx.MatchString(line)
   899  }
   900  
   901  type setCollectionFormat struct {
   902  	builder operationValidationBuilder
   903  	rx      *regexp.Regexp
   904  }
   905  
   906  func (sm *setCollectionFormat) Parse(lines []string) error {
   907  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   908  		return nil
   909  	}
   910  	matches := sm.rx.FindStringSubmatch(lines[0])
   911  	if len(matches) > 1 && len(matches[1]) > 0 {
   912  		sm.builder.SetCollectionFormat(matches[1])
   913  	}
   914  	return nil
   915  }
   916  
   917  func (sm *setCollectionFormat) Matches(line string) bool {
   918  	return sm.rx.MatchString(line)
   919  }
   920  
   921  type setUnique struct {
   922  	builder validationBuilder
   923  	rx      *regexp.Regexp
   924  }
   925  
   926  func (su *setUnique) Matches(line string) bool {
   927  	return su.rx.MatchString(line)
   928  }
   929  
   930  func (su *setUnique) Parse(lines []string) error {
   931  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   932  		return nil
   933  	}
   934  	matches := su.rx.FindStringSubmatch(lines[0])
   935  	if len(matches) > 1 && len(matches[1]) > 0 {
   936  		req, err := strconv.ParseBool(matches[1])
   937  		if err != nil {
   938  			return err
   939  		}
   940  		su.builder.SetUnique(req)
   941  	}
   942  	return nil
   943  }
   944  
   945  type setEnum struct {
   946  	builder validationBuilder
   947  	rx      *regexp.Regexp
   948  }
   949  
   950  func (se *setEnum) Matches(line string) bool {
   951  	return se.rx.MatchString(line)
   952  }
   953  
   954  func (se *setEnum) Parse(lines []string) error {
   955  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
   956  		return nil
   957  	}
   958  	matches := se.rx.FindStringSubmatch(lines[0])
   959  	if len(matches) > 1 && len(matches[1]) > 0 {
   960  		se.builder.SetEnum(matches[1])
   961  	}
   962  	return nil
   963  }
   964  
   965  func parseValueFromSchema(s string, schema *spec.SimpleSchema) (interface{}, error) {
   966  	if schema != nil {
   967  		switch strings.Trim(schema.TypeName(), "\"") {
   968  		case "integer", "int", "int64", "int32", "int16":
   969  			return strconv.Atoi(s)
   970  		case "bool", "boolean":
   971  			return strconv.ParseBool(s)
   972  		case "number", "float64", "float32":
   973  			return strconv.ParseFloat(s, 64)
   974  		case "object":
   975  			var obj map[string]interface{}
   976  			if err := json.Unmarshal([]byte(s), &obj); err != nil {
   977  				// If we can't parse it, just return the string.
   978  				return s, nil
   979  			}
   980  			return obj, nil
   981  		case "array":
   982  			var slice []interface{}
   983  			if err := json.Unmarshal([]byte(s), &slice); err != nil {
   984  				// If we can't parse it, just return the string.
   985  				return s, nil
   986  			}
   987  			return slice, nil
   988  		default:
   989  			return s, nil
   990  		}
   991  	} else {
   992  		return s, nil
   993  	}
   994  }
   995  
   996  type setDefault struct {
   997  	scheme  *spec.SimpleSchema
   998  	builder validationBuilder
   999  	rx      *regexp.Regexp
  1000  }
  1001  
  1002  func (sd *setDefault) Matches(line string) bool {
  1003  	return sd.rx.MatchString(line)
  1004  }
  1005  
  1006  func (sd *setDefault) Parse(lines []string) error {
  1007  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
  1008  		return nil
  1009  	}
  1010  	matches := sd.rx.FindStringSubmatch(lines[0])
  1011  	if len(matches) > 1 && len(matches[1]) > 0 {
  1012  		d, err := parseValueFromSchema(matches[1], sd.scheme)
  1013  		if err != nil {
  1014  			return err
  1015  		}
  1016  		sd.builder.SetDefault(d)
  1017  	}
  1018  	return nil
  1019  }
  1020  
  1021  type setExample struct {
  1022  	scheme  *spec.SimpleSchema
  1023  	builder validationBuilder
  1024  	rx      *regexp.Regexp
  1025  }
  1026  
  1027  func (se *setExample) Matches(line string) bool {
  1028  	return se.rx.MatchString(line)
  1029  }
  1030  
  1031  func (se *setExample) Parse(lines []string) error {
  1032  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
  1033  		return nil
  1034  	}
  1035  	matches := se.rx.FindStringSubmatch(lines[0])
  1036  	if len(matches) > 1 && len(matches[1]) > 0 {
  1037  		d, err := parseValueFromSchema(matches[1], se.scheme)
  1038  		if err != nil {
  1039  			return err
  1040  		}
  1041  		se.builder.SetExample(d)
  1042  	}
  1043  	return nil
  1044  }
  1045  
  1046  type matchOnlyParam struct {
  1047  	tgt *spec.Parameter
  1048  	rx  *regexp.Regexp
  1049  }
  1050  
  1051  func (mo *matchOnlyParam) Matches(line string) bool {
  1052  	return mo.rx.MatchString(line)
  1053  }
  1054  
  1055  func (mo *matchOnlyParam) Parse(lines []string) error {
  1056  	return nil
  1057  }
  1058  
  1059  type setRequiredParam struct {
  1060  	tgt *spec.Parameter
  1061  }
  1062  
  1063  func (su *setRequiredParam) Matches(line string) bool {
  1064  	return rxRequired.MatchString(line)
  1065  }
  1066  
  1067  func (su *setRequiredParam) Parse(lines []string) error {
  1068  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
  1069  		return nil
  1070  	}
  1071  	matches := rxRequired.FindStringSubmatch(lines[0])
  1072  	if len(matches) > 1 && len(matches[1]) > 0 {
  1073  		req, err := strconv.ParseBool(matches[1])
  1074  		if err != nil {
  1075  			return err
  1076  		}
  1077  		su.tgt.Required = req
  1078  	}
  1079  	return nil
  1080  }
  1081  
  1082  type setReadOnlySchema struct {
  1083  	tgt *spec.Schema
  1084  }
  1085  
  1086  func (su *setReadOnlySchema) Matches(line string) bool {
  1087  	return rxReadOnly.MatchString(line)
  1088  }
  1089  
  1090  func (su *setReadOnlySchema) Parse(lines []string) error {
  1091  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
  1092  		return nil
  1093  	}
  1094  	matches := rxReadOnly.FindStringSubmatch(lines[0])
  1095  	if len(matches) > 1 && len(matches[1]) > 0 {
  1096  		req, err := strconv.ParseBool(matches[1])
  1097  		if err != nil {
  1098  			return err
  1099  		}
  1100  		su.tgt.ReadOnly = req
  1101  	}
  1102  	return nil
  1103  }
  1104  
  1105  type setDeprecatedOp struct {
  1106  	tgt *spec.Operation
  1107  }
  1108  
  1109  func (su *setDeprecatedOp) Matches(line string) bool {
  1110  	return rxDeprecated.MatchString(line)
  1111  }
  1112  
  1113  func (su *setDeprecatedOp) Parse(lines []string) error {
  1114  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
  1115  		return nil
  1116  	}
  1117  	matches := rxDeprecated.FindStringSubmatch(lines[0])
  1118  	if len(matches) > 1 && len(matches[1]) > 0 {
  1119  		req, err := strconv.ParseBool(matches[1])
  1120  		if err != nil {
  1121  			return err
  1122  		}
  1123  		su.tgt.Deprecated = req
  1124  	}
  1125  	return nil
  1126  }
  1127  
  1128  type setDiscriminator struct {
  1129  	schema *spec.Schema
  1130  	field  string
  1131  }
  1132  
  1133  func (su *setDiscriminator) Matches(line string) bool {
  1134  	return rxDiscriminator.MatchString(line)
  1135  }
  1136  
  1137  func (su *setDiscriminator) Parse(lines []string) error {
  1138  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
  1139  		return nil
  1140  	}
  1141  	matches := rxDiscriminator.FindStringSubmatch(lines[0])
  1142  	if len(matches) > 1 && len(matches[1]) > 0 {
  1143  		req, err := strconv.ParseBool(matches[1])
  1144  		if err != nil {
  1145  			return err
  1146  		}
  1147  		if req {
  1148  			su.schema.Discriminator = su.field
  1149  		} else if su.schema.Discriminator == su.field {
  1150  			su.schema.Discriminator = ""
  1151  		}
  1152  	}
  1153  	return nil
  1154  }
  1155  
  1156  type setRequiredSchema struct {
  1157  	schema *spec.Schema
  1158  	field  string
  1159  }
  1160  
  1161  func (su *setRequiredSchema) Matches(line string) bool {
  1162  	return rxRequired.MatchString(line)
  1163  }
  1164  
  1165  func (su *setRequiredSchema) Parse(lines []string) error {
  1166  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
  1167  		return nil
  1168  	}
  1169  	matches := rxRequired.FindStringSubmatch(lines[0])
  1170  	if len(matches) > 1 && len(matches[1]) > 0 {
  1171  		req, err := strconv.ParseBool(matches[1])
  1172  		if err != nil {
  1173  			return err
  1174  		}
  1175  		midx := -1
  1176  		for i, nm := range su.schema.Required {
  1177  			if nm == su.field {
  1178  				midx = i
  1179  				break
  1180  			}
  1181  		}
  1182  		if req {
  1183  			if midx < 0 {
  1184  				su.schema.Required = append(su.schema.Required, su.field)
  1185  			}
  1186  		} else if midx >= 0 {
  1187  			su.schema.Required = append(su.schema.Required[:midx], su.schema.Required[midx+1:]...)
  1188  		}
  1189  	}
  1190  	return nil
  1191  }
  1192  
  1193  func newMultilineDropEmptyParser(rx *regexp.Regexp, set func([]string)) *multiLineDropEmptyParser {
  1194  	return &multiLineDropEmptyParser{
  1195  		rx:  rx,
  1196  		set: set,
  1197  	}
  1198  }
  1199  
  1200  type multiLineDropEmptyParser struct {
  1201  	set func([]string)
  1202  	rx  *regexp.Regexp
  1203  }
  1204  
  1205  func (m *multiLineDropEmptyParser) Matches(line string) bool {
  1206  	return m.rx.MatchString(line)
  1207  }
  1208  
  1209  func (m *multiLineDropEmptyParser) Parse(lines []string) error {
  1210  	m.set(removeEmptyLines(lines))
  1211  	return nil
  1212  }
  1213  
  1214  func newSetSchemes(set func([]string)) *setSchemes {
  1215  	return &setSchemes{
  1216  		set: set,
  1217  		rx:  rxSchemes,
  1218  	}
  1219  }
  1220  
  1221  type setSchemes struct {
  1222  	set func([]string)
  1223  	rx  *regexp.Regexp
  1224  }
  1225  
  1226  func (ss *setSchemes) Matches(line string) bool {
  1227  	return ss.rx.MatchString(line)
  1228  }
  1229  
  1230  func (ss *setSchemes) Parse(lines []string) error {
  1231  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
  1232  		return nil
  1233  	}
  1234  	matches := ss.rx.FindStringSubmatch(lines[0])
  1235  	if len(matches) > 1 && len(matches[1]) > 0 {
  1236  		sch := strings.Split(matches[1], ", ")
  1237  
  1238  		schemes := []string{}
  1239  		for _, s := range sch {
  1240  			ts := strings.TrimSpace(s)
  1241  			if ts != "" {
  1242  				schemes = append(schemes, ts)
  1243  			}
  1244  		}
  1245  		ss.set(schemes)
  1246  	}
  1247  	return nil
  1248  }
  1249  
  1250  func newSetSecurity(rx *regexp.Regexp, setter func([]map[string][]string)) *setSecurity {
  1251  	return &setSecurity{
  1252  		set: setter,
  1253  		rx:  rx,
  1254  	}
  1255  }
  1256  
  1257  type setSecurity struct {
  1258  	set func([]map[string][]string)
  1259  	rx  *regexp.Regexp
  1260  }
  1261  
  1262  func (ss *setSecurity) Matches(line string) bool {
  1263  	return ss.rx.MatchString(line)
  1264  }
  1265  
  1266  func (ss *setSecurity) Parse(lines []string) error {
  1267  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
  1268  		return nil
  1269  	}
  1270  
  1271  	var result []map[string][]string
  1272  	for _, line := range lines {
  1273  		kv := strings.SplitN(line, ":", 2)
  1274  		scopes := []string{}
  1275  		var key string
  1276  
  1277  		if len(kv) > 1 {
  1278  			scs := strings.Split(kv[1], ",")
  1279  			for _, scope := range scs {
  1280  				tr := strings.TrimSpace(scope)
  1281  				if tr != "" {
  1282  					tr = strings.SplitAfter(tr, " ")[0]
  1283  					scopes = append(scopes, strings.TrimSpace(tr))
  1284  				}
  1285  			}
  1286  
  1287  			key = strings.TrimSpace(kv[0])
  1288  
  1289  			result = append(result, map[string][]string{key: scopes})
  1290  		}
  1291  	}
  1292  	ss.set(result)
  1293  	return nil
  1294  }
  1295  
  1296  func newSetResponses(definitions map[string]spec.Schema, responses map[string]spec.Response, setter func(*spec.Response, map[int]spec.Response)) *setOpResponses {
  1297  	return &setOpResponses{
  1298  		set:         setter,
  1299  		rx:          rxResponses,
  1300  		definitions: definitions,
  1301  		responses:   responses,
  1302  	}
  1303  }
  1304  
  1305  type setOpResponses struct {
  1306  	set         func(*spec.Response, map[int]spec.Response)
  1307  	rx          *regexp.Regexp
  1308  	definitions map[string]spec.Schema
  1309  	responses   map[string]spec.Response
  1310  }
  1311  
  1312  func (ss *setOpResponses) Matches(line string) bool {
  1313  	return ss.rx.MatchString(line)
  1314  }
  1315  
  1316  // ResponseTag used when specifying a response to point to a defined swagger:response
  1317  const ResponseTag = "response"
  1318  
  1319  // BodyTag used when specifying a response to point to a model/schema
  1320  const BodyTag = "body"
  1321  
  1322  // DescriptionTag used when specifying a response that gives a description of the response
  1323  const DescriptionTag = "description"
  1324  
  1325  func parseTags(line string) (modelOrResponse string, arrays int, isDefinitionRef bool, description string, err error) {
  1326  	tags := strings.Split(line, " ")
  1327  	parsedModelOrResponse := false
  1328  
  1329  	for i, tagAndValue := range tags {
  1330  		tagValList := strings.SplitN(tagAndValue, ":", 2)
  1331  		var tag, value string
  1332  		if len(tagValList) > 1 {
  1333  			tag = tagValList[0]
  1334  			value = tagValList[1]
  1335  		} else {
  1336  			// TODO: Print a warning, and in the long term, do not support not tagged values
  1337  			// Add a default tag if none is supplied
  1338  			if i == 0 {
  1339  				tag = ResponseTag
  1340  			} else {
  1341  				tag = DescriptionTag
  1342  			}
  1343  			value = tagValList[0]
  1344  		}
  1345  
  1346  		foundModelOrResponse := false
  1347  		if !parsedModelOrResponse {
  1348  			if tag == BodyTag {
  1349  				foundModelOrResponse = true
  1350  				isDefinitionRef = true
  1351  			}
  1352  			if tag == ResponseTag {
  1353  				foundModelOrResponse = true
  1354  				isDefinitionRef = false
  1355  			}
  1356  		}
  1357  		if foundModelOrResponse {
  1358  			// Read the model or response tag
  1359  			parsedModelOrResponse = true
  1360  			// Check for nested arrays
  1361  			arrays = 0
  1362  			for strings.HasPrefix(value, "[]") {
  1363  				arrays++
  1364  				value = value[2:]
  1365  			}
  1366  			// What's left over is the model name
  1367  			modelOrResponse = value
  1368  		} else {
  1369  			foundDescription := false
  1370  			if tag == DescriptionTag {
  1371  				foundDescription = true
  1372  			}
  1373  			if foundDescription {
  1374  				// Descriptions are special, they make they read the rest of the line
  1375  				descriptionWords := []string{value}
  1376  				if i < len(tags)-1 {
  1377  					descriptionWords = append(descriptionWords, tags[i+1:]...)
  1378  				}
  1379  				description = strings.Join(descriptionWords, " ")
  1380  				break
  1381  			} else {
  1382  				if tag == ResponseTag || tag == BodyTag || tag == DescriptionTag {
  1383  					err = fmt.Errorf("valid tag %s, but not in a valid position", tag)
  1384  				} else {
  1385  					err = fmt.Errorf("invalid tag: %s", tag)
  1386  				}
  1387  				// return error
  1388  				return
  1389  			}
  1390  		}
  1391  	}
  1392  
  1393  	// TODO: Maybe do, if !parsedModelOrResponse {return some error}
  1394  	return
  1395  }
  1396  
  1397  func (ss *setOpResponses) Parse(lines []string) error {
  1398  	if len(lines) == 0 || (len(lines) == 1 && len(lines[0]) == 0) {
  1399  		return nil
  1400  	}
  1401  
  1402  	var def *spec.Response
  1403  	var scr map[int]spec.Response
  1404  
  1405  	for _, line := range lines {
  1406  		kv := strings.SplitN(line, ":", 2)
  1407  		var key, value string
  1408  
  1409  		if len(kv) > 1 {
  1410  			key = strings.TrimSpace(kv[0])
  1411  			if key == "" {
  1412  				// this must be some weird empty line
  1413  				continue
  1414  			}
  1415  			value = strings.TrimSpace(kv[1])
  1416  			if value == "" {
  1417  				var resp spec.Response
  1418  				if strings.EqualFold("default", key) {
  1419  					if def == nil {
  1420  						def = &resp
  1421  					}
  1422  				} else {
  1423  					if sc, err := strconv.Atoi(key); err == nil {
  1424  						if scr == nil {
  1425  							scr = make(map[int]spec.Response)
  1426  						}
  1427  						scr[sc] = resp
  1428  					}
  1429  				}
  1430  				continue
  1431  			}
  1432  			refTarget, arrays, isDefinitionRef, description, err := parseTags(value)
  1433  			if err != nil {
  1434  				return err
  1435  			}
  1436  			// A possible exception for having a definition
  1437  			if _, ok := ss.responses[refTarget]; !ok {
  1438  				if _, ok := ss.definitions[refTarget]; ok {
  1439  					isDefinitionRef = true
  1440  				}
  1441  			}
  1442  
  1443  			var ref spec.Ref
  1444  			if isDefinitionRef {
  1445  				if description == "" {
  1446  					description = refTarget
  1447  				}
  1448  				ref, err = spec.NewRef("#/definitions/" + refTarget)
  1449  			} else {
  1450  				ref, err = spec.NewRef("#/responses/" + refTarget)
  1451  			}
  1452  			if err != nil {
  1453  				return err
  1454  			}
  1455  
  1456  			// description should used on anyway.
  1457  			resp := spec.Response{ResponseProps: spec.ResponseProps{Description: description}}
  1458  
  1459  			if isDefinitionRef {
  1460  				resp.Schema = new(spec.Schema)
  1461  				resp.Description = description
  1462  				if arrays == 0 {
  1463  					resp.Schema.Ref = ref
  1464  				} else {
  1465  					cs := resp.Schema
  1466  					for i := 0; i < arrays; i++ {
  1467  						cs.Typed("array", "")
  1468  						cs.Items = new(spec.SchemaOrArray)
  1469  						cs.Items.Schema = new(spec.Schema)
  1470  						cs = cs.Items.Schema
  1471  					}
  1472  					cs.Ref = ref
  1473  				}
  1474  				// ref. could be empty while use description tag
  1475  			} else if len(refTarget) > 0 {
  1476  				resp.Ref = ref
  1477  			}
  1478  
  1479  			if strings.EqualFold("default", key) {
  1480  				if def == nil {
  1481  					def = &resp
  1482  				}
  1483  			} else {
  1484  				if sc, err := strconv.Atoi(key); err == nil {
  1485  					if scr == nil {
  1486  						scr = make(map[int]spec.Response)
  1487  					}
  1488  					scr[sc] = resp
  1489  				}
  1490  			}
  1491  		}
  1492  	}
  1493  	ss.set(def, scr)
  1494  	return nil
  1495  }
  1496  
  1497  func parseEnum(val string, s *spec.SimpleSchema) []interface{} {
  1498  	list := strings.Split(val, ",")
  1499  	interfaceSlice := make([]interface{}, len(list))
  1500  	for i, d := range list {
  1501  		v, err := parseValueFromSchema(d, s)
  1502  		if err != nil {
  1503  			interfaceSlice[i] = d
  1504  			continue
  1505  		}
  1506  
  1507  		interfaceSlice[i] = v
  1508  	}
  1509  	return interfaceSlice
  1510  }