github.com/jamescostian/go-swagger@v0.30.4-0.20221130163922-68364d6b567b/scan/classifier.go (about)

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