github.com/circl-dev/go-swagger@v0.31.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/circl-dev/loads/fmts" 12 "github.com/circl-dev/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 }