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