github.com/kaisawind/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  }