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 }