github.com/snyk/vervet/v4@v4.27.2/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/v4"
    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(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  func removeOrphanedComponents(t *openapi3.T) error {
    78  	ix, err := vervet.NewRefIndex(t)
    79  	if err != nil {
    80  		return err
    81  	}
    82  	if t.Components.Schemas != nil {
    83  		var remove []string
    84  		for key := range t.Components.Schemas {
    85  			if !ix.HasRef("#/components/schemas/" + key) {
    86  				remove = append(remove, key)
    87  			}
    88  		}
    89  		for i := range remove {
    90  			delete(t.Components.Schemas, remove[i])
    91  		}
    92  	}
    93  	if t.Components.Parameters != nil {
    94  		var remove []string
    95  		for key := range t.Components.Parameters {
    96  			if !ix.HasRef("#/components/parameters/" + key) {
    97  				remove = append(remove, key)
    98  			}
    99  		}
   100  		for i := range remove {
   101  			delete(t.Components.Parameters, remove[i])
   102  		}
   103  	}
   104  	if t.Components.Headers != nil {
   105  		var remove []string
   106  		for key := range t.Components.Headers {
   107  			if !ix.HasRef("#/components/headers/" + key) {
   108  				remove = append(remove, key)
   109  			}
   110  		}
   111  		for i := range remove {
   112  			delete(t.Components.Headers, remove[i])
   113  		}
   114  	}
   115  	if t.Components.RequestBodies != nil {
   116  		var remove []string
   117  		for key := range t.Components.RequestBodies {
   118  			if !ix.HasRef("#/components/requestbodies/" + key) {
   119  				remove = append(remove, key)
   120  			}
   121  		}
   122  		for i := range remove {
   123  			delete(t.Components.RequestBodies, remove[i])
   124  		}
   125  	}
   126  	if t.Components.Responses != nil {
   127  		var remove []string
   128  		for key := range t.Components.Responses {
   129  			if !ix.HasRef("#/components/responses/" + key) {
   130  				remove = append(remove, key)
   131  			}
   132  		}
   133  		for i := range remove {
   134  			delete(t.Components.Responses, remove[i])
   135  		}
   136  	}
   137  	if t.Components.SecuritySchemes != nil {
   138  		var remove []string
   139  		for key := range t.Components.SecuritySchemes {
   140  			if !ix.HasRef("#/components/securityschemes/" + key) {
   141  				remove = append(remove, key)
   142  			}
   143  		}
   144  		for i := range remove {
   145  			delete(t.Components.SecuritySchemes, remove[i])
   146  		}
   147  	}
   148  	if t.Components.Examples != nil {
   149  		var remove []string
   150  		for key := range t.Components.Examples {
   151  			if !ix.HasRef("#/components/examples/" + key) {
   152  				remove = append(remove, key)
   153  			}
   154  		}
   155  		for i := range remove {
   156  			delete(t.Components.Examples, remove[i])
   157  		}
   158  	}
   159  	if t.Components.Links != nil {
   160  		var remove []string
   161  		for key := range t.Components.Links {
   162  			if !ix.HasRef("#/components/links/" + key) {
   163  				remove = append(remove, key)
   164  			}
   165  		}
   166  		for i := range remove {
   167  			delete(t.Components.Links, remove[i])
   168  		}
   169  	}
   170  	if t.Components.Callbacks != nil {
   171  		var remove []string
   172  		for key := range t.Components.Callbacks {
   173  			if !ix.HasRef("#/components/callbacks/" + key) {
   174  				remove = append(remove, key)
   175  			}
   176  		}
   177  		for i := range remove {
   178  			delete(t.Components.Callbacks, remove[i])
   179  		}
   180  	}
   181  	return nil
   182  }