github.com/snyk/vervet/v6@v6.2.4/internal/cmd/filter.go (about)

     1  package cmd
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/getkin/kin-openapi/openapi3"
     7  	"github.com/urfave/cli/v2"
     8  
     9  	"github.com/snyk/vervet/v6"
    10  )
    11  
    12  // FilterCommand is the `vervet filter` subcommand.
    13  var FilterCommand = cli.Command{
    14  	Name:      "filter",
    15  	Usage:     "Filter an OpenAPI document",
    16  	ArgsUsage: "[spec.yaml file]",
    17  	Flags: []cli.Flag{
    18  		&cli.StringSliceFlag{Name: "include-paths", Aliases: []string{"I"}},
    19  		&cli.StringSliceFlag{Name: "exclude-paths", Aliases: []string{"X"}},
    20  	},
    21  	Action: Filter,
    22  }
    23  
    24  // Filter an OpenAPI spec file.
    25  func Filter(ctx *cli.Context) error {
    26  	if ctx.Args().Len() < 1 {
    27  		return fmt.Errorf("missing spec.yaml file")
    28  	}
    29  	specFile, err := absPath(ctx.Args().Get(0))
    30  	if err != nil {
    31  		return fmt.Errorf("failed to resolve %q", ctx.Args().Get(0))
    32  	}
    33  	doc, err := vervet.NewDocumentFile(specFile)
    34  	if err != nil {
    35  		return fmt.Errorf("failed to load spec from %q: %v", specFile, err)
    36  	}
    37  
    38  	// Localize all references, so we emit a completely self-contained OpenAPI document.
    39  	err = vervet.Localize(ctx.Context, doc)
    40  	if err != nil {
    41  		return fmt.Errorf("failed to localize refs: %w", err)
    42  	}
    43  
    44  	if excludePaths := ctx.StringSlice("exclude-paths"); len(excludePaths) > 0 {
    45  		for _, excludePath := range excludePaths {
    46  			delete(doc.Paths, excludePath)
    47  		}
    48  	}
    49  	if includePaths := ctx.StringSlice("include-paths"); len(includePaths) > 0 {
    50  		newPaths := openapi3.Paths{}
    51  		for _, includePath := range includePaths {
    52  			if pathInfo, ok := doc.Paths[includePath]; ok {
    53  				newPaths[includePath] = pathInfo
    54  			}
    55  		}
    56  		doc.Paths = newPaths
    57  	}
    58  
    59  	err = removeOrphanedComponents(doc.T)
    60  	if err != nil {
    61  		return fmt.Errorf("failed to remove orphaned components: %W", err)
    62  	}
    63  
    64  	yamlBuf, err := vervet.ToSpecYAML(doc)
    65  	if err != nil {
    66  		return fmt.Errorf("failed to convert JSON to YAML: %w", err)
    67  	}
    68  	fmt.Println(string(yamlBuf))
    69  
    70  	err = doc.Validate(ctx.Context)
    71  	if err != nil {
    72  		return fmt.Errorf("error: spec validation failed: %w", err)
    73  	}
    74  	return nil
    75  }
    76  
    77  // TODO: refactor to reduce cyclomatic complexity.
    78  func removeOrphanedComponents(t *openapi3.T) error { //nolint:gocyclo // acked
    79  	ix, err := vervet.NewRefIndex(t)
    80  	if err != nil {
    81  		return err
    82  	}
    83  	if t.Components.Schemas != nil {
    84  		var remove []string
    85  		for key := range t.Components.Schemas {
    86  			if !ix.HasRef("#/components/schemas/" + key) {
    87  				remove = append(remove, key)
    88  			}
    89  		}
    90  		for i := range remove {
    91  			delete(t.Components.Schemas, remove[i])
    92  		}
    93  	}
    94  	if t.Components.Parameters != nil {
    95  		var remove []string
    96  		for key := range t.Components.Parameters {
    97  			if !ix.HasRef("#/components/parameters/" + key) {
    98  				remove = append(remove, key)
    99  			}
   100  		}
   101  		for i := range remove {
   102  			delete(t.Components.Parameters, remove[i])
   103  		}
   104  	}
   105  	if t.Components.Headers != nil {
   106  		var remove []string
   107  		for key := range t.Components.Headers {
   108  			if !ix.HasRef("#/components/headers/" + key) {
   109  				remove = append(remove, key)
   110  			}
   111  		}
   112  		for i := range remove {
   113  			delete(t.Components.Headers, remove[i])
   114  		}
   115  	}
   116  	if t.Components.RequestBodies != nil {
   117  		var remove []string
   118  		for key := range t.Components.RequestBodies {
   119  			if !ix.HasRef("#/components/requestbodies/" + key) {
   120  				remove = append(remove, key)
   121  			}
   122  		}
   123  		for i := range remove {
   124  			delete(t.Components.RequestBodies, remove[i])
   125  		}
   126  	}
   127  	if t.Components.Responses != nil {
   128  		var remove []string
   129  		for key := range t.Components.Responses {
   130  			if !ix.HasRef("#/components/responses/" + key) {
   131  				remove = append(remove, key)
   132  			}
   133  		}
   134  		for i := range remove {
   135  			delete(t.Components.Responses, remove[i])
   136  		}
   137  	}
   138  	if t.Components.SecuritySchemes != nil {
   139  		var remove []string
   140  		for key := range t.Components.SecuritySchemes {
   141  			if !ix.HasRef("#/components/securityschemes/" + key) {
   142  				remove = append(remove, key)
   143  			}
   144  		}
   145  		for i := range remove {
   146  			delete(t.Components.SecuritySchemes, remove[i])
   147  		}
   148  	}
   149  	if t.Components.Examples != nil {
   150  		var remove []string
   151  		for key := range t.Components.Examples {
   152  			if !ix.HasRef("#/components/examples/" + key) {
   153  				remove = append(remove, key)
   154  			}
   155  		}
   156  		for i := range remove {
   157  			delete(t.Components.Examples, remove[i])
   158  		}
   159  	}
   160  	if t.Components.Links != nil {
   161  		var remove []string
   162  		for key := range t.Components.Links {
   163  			if !ix.HasRef("#/components/links/" + key) {
   164  				remove = append(remove, key)
   165  			}
   166  		}
   167  		for i := range remove {
   168  			delete(t.Components.Links, remove[i])
   169  		}
   170  	}
   171  	if t.Components.Callbacks != nil {
   172  		var remove []string
   173  		for key := range t.Components.Callbacks {
   174  			if !ix.HasRef("#/components/callbacks/" + key) {
   175  				remove = append(remove, key)
   176  			}
   177  		}
   178  		for i := range remove {
   179  			delete(t.Components.Callbacks, remove[i])
   180  		}
   181  	}
   182  	return nil
   183  }