github.com/rzurga/go-swagger@v0.28.1-0.20211109195225-5d1f453ffa3a/scan/classifier.go (about)

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