github.com/yilecjw/go-swagger@v0.19.0/scan/classifier.go (about) 1 // Copyright 2015 go-swagger maintainers 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package scan 16 17 import ( 18 "fmt" 19 "go/ast" 20 "log" 21 "regexp" 22 23 "golang.org/x/tools/go/loader" 24 ) 25 26 type packageFilter struct { 27 Name string 28 } 29 30 func (pf *packageFilter) Matches(path string) bool { 31 matched, err := regexp.MatchString(pf.Name, path) 32 if err != nil { 33 log.Fatal(err) 34 } 35 return matched 36 } 37 38 type packageFilters []packageFilter 39 40 func (pf packageFilters) HasFilters() bool { 41 return len(pf) > 0 42 } 43 44 func (pf packageFilters) Matches(path string) bool { 45 for _, mod := range pf { 46 if mod.Matches(path) { 47 return true 48 } 49 } 50 return false 51 } 52 53 type classifiedProgram struct { 54 Meta []*ast.File 55 Models []*ast.File 56 Routes []*ast.File 57 Operations []*ast.File 58 Parameters []*ast.File 59 Responses []*ast.File 60 } 61 62 // programClassifier classifies the files of a program into buckets 63 // for processing by a swagger spec generator. This buckets files in 64 // 3 groups: Meta, Models and Operations. 65 // 66 // Each of these buckets is then processed with an appropriate parsing strategy 67 // 68 // When there are Include or Exclude filters provide they are used to limit the 69 // candidates prior to parsing. 70 // The include filters take precedence over the excludes. So when something appears 71 // in both filters it will be included. 72 type programClassifier struct { 73 Includes packageFilters 74 Excludes packageFilters 75 } 76 77 func (pc *programClassifier) Classify(prog *loader.Program) (*classifiedProgram, error) { 78 var cp classifiedProgram 79 for pkg, pkgInfo := range prog.AllPackages { 80 if Debug { 81 log.Printf("analyzing: %s\n", pkg.Path()) 82 } 83 if pc.Includes.HasFilters() { 84 if !pc.Includes.Matches(pkg.Path()) { 85 continue 86 } 87 } else if pc.Excludes.HasFilters() { 88 if pc.Excludes.Matches(pkg.Path()) { 89 continue 90 } 91 } 92 93 for _, file := range pkgInfo.Files { 94 var ro, op, mt, pm, rs, mm bool // only add a particular file once 95 for _, comments := range file.Comments { 96 var seenStruct string 97 for _, cline := range comments.List { 98 if cline != nil { 99 matches := rxSwaggerAnnotation.FindStringSubmatch(cline.Text) 100 if len(matches) > 1 { 101 switch matches[1] { 102 case "route": 103 if !ro { 104 cp.Routes = append(cp.Routes, file) 105 ro = true 106 } 107 case "operation": 108 if !op { 109 cp.Operations = append(cp.Operations, file) 110 op = true 111 } 112 case "model": 113 if !mm { 114 cp.Models = append(cp.Models, file) 115 mm = true 116 } 117 if seenStruct == "" || seenStruct == matches[1] { 118 seenStruct = matches[1] 119 } else { 120 return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1]) 121 } 122 case "meta": 123 if !mt { 124 cp.Meta = append(cp.Meta, file) 125 mt = true 126 } 127 case "parameters": 128 if !pm { 129 cp.Parameters = append(cp.Parameters, file) 130 pm = true 131 } 132 if seenStruct == "" || seenStruct == matches[1] { 133 seenStruct = matches[1] 134 } else { 135 return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1]) 136 } 137 case "response": 138 if !rs { 139 cp.Responses = append(cp.Responses, file) 140 rs = true 141 } 142 if seenStruct == "" || seenStruct == matches[1] { 143 seenStruct = matches[1] 144 } else { 145 return nil, fmt.Errorf("classifier: already annotated as %s, can't also be %q", seenStruct, matches[1]) 146 } 147 case "strfmt", "name", "discriminated", "file", "enum", "default", "alias", "type": 148 // TODO: perhaps collect these and pass along to avoid lookups later on 149 case "allOf": 150 case "ignore": 151 default: 152 return nil, fmt.Errorf("classifier: unknown swagger annotation %q", matches[1]) 153 } 154 } 155 156 } 157 } 158 } 159 } 160 } 161 162 return &cp, nil 163 }