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