github.com/w3security/vervet/v5@v5.3.1-0.20230618081846-5bd9b5d799dc/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/w3security/vervet/v5" 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 }