@@ -13,9 +13,11 @@ import (
1313)
1414
1515var (
16- snipWriteInPlace bool
17- snipOperationIDs []string
18- snipOperations []string
16+ snipWriteInPlace bool
17+ snipOperationIDs []string
18+ snipOperations []string
19+ snipKeepOperationIDs []string
20+ snipKeepOperations []string
1921)
2022
2123var snipCmd = & cobra.Command {
@@ -73,6 +75,9 @@ func init() {
7375 snipCmd .Flags ().BoolVarP (& snipWriteInPlace , "write" , "w" , false , "write result in-place to input file" )
7476 snipCmd .Flags ().StringSliceVar (& snipOperationIDs , "operationId" , nil , "operation ID to remove (can be comma-separated or repeated)" )
7577 snipCmd .Flags ().StringSliceVar (& snipOperations , "operation" , nil , "operation as path:method to remove (can be comma-separated or repeated)" )
78+ // Keep-mode flags (mutually exclusive with remove-mode flags)
79+ snipCmd .Flags ().StringSliceVar (& snipKeepOperationIDs , "keepOperationId" , nil , "operation ID to keep (can be comma-separated or repeated)" )
80+ snipCmd .Flags ().StringSliceVar (& snipKeepOperations , "keepOperation" , nil , "operation as path:method to keep (can be comma-separated or repeated)" )
7681}
7782
7883func runSnip (cmd * cobra.Command , args []string ) error {
@@ -84,20 +89,29 @@ func runSnip(cmd *cobra.Command, args []string) error {
8489 outputFile = args [1 ]
8590 }
8691
87- // Check if any operations were specified via flags
88- hasOperationFlags := len (snipOperationIDs ) > 0 || len (snipOperations ) > 0
92+ // Check which flag sets were specified
93+ hasRemoveFlags := len (snipOperationIDs ) > 0 || len (snipOperations ) > 0
94+ hasKeepFlags := len (snipKeepOperationIDs ) > 0 || len (snipKeepOperations ) > 0
8995
90- // If -w is specified without operation flags, error
91- if snipWriteInPlace && ! hasOperationFlags {
92- return fmt .Errorf ("--write flag requires specifying operations via --operationId or --operation flags " )
96+ // If -w is specified without any operation selection flags, error
97+ if snipWriteInPlace && ! ( hasRemoveFlags || hasKeepFlags ) {
98+ return fmt .Errorf ("--write flag requires specifying operations via --operationId/--operation or --keepOperationId/--keepOperation " )
9399 }
94100
95- if ! hasOperationFlags {
96- // No flags - interactive mode
101+ // Interactive mode when no flags provided
102+ if ! hasRemoveFlags && ! hasKeepFlags {
97103 return runSnipInteractive (ctx , inputFile , outputFile )
98104 }
99105
100- // Flags specified - CLI mode
106+ // Disallow mixing keep + remove flags; ambiguous intent
107+ if hasRemoveFlags && hasKeepFlags {
108+ return fmt .Errorf ("cannot combine keep and remove flags; use either --operationId/--operation or --keepOperationId/--keepOperation" )
109+ }
110+
111+ // CLI mode
112+ if hasKeepFlags {
113+ return runSnipCLIKeep (ctx , inputFile , outputFile )
114+ }
101115 return runSnipCLI (ctx , inputFile , outputFile )
102116}
103117
@@ -139,6 +153,87 @@ func runSnipCLI(ctx context.Context, inputFile, outputFile string) error {
139153 return processor .WriteDocument (ctx , doc )
140154}
141155
156+ func runSnipCLIKeep (ctx context.Context , inputFile , outputFile string ) error {
157+ // Create processor
158+ processor , err := NewOpenAPIProcessor (inputFile , outputFile , snipWriteInPlace )
159+ if err != nil {
160+ return err
161+ }
162+
163+ // Load document
164+ doc , validationErrors , err := processor .LoadDocument (ctx )
165+ if err != nil {
166+ return err
167+ }
168+
169+ // Report validation errors (if any)
170+ processor .ReportValidationErrors (validationErrors )
171+
172+ // Parse keep flags
173+ keepOps , err := parseKeepOperationFlags ()
174+ if err != nil {
175+ return err
176+ }
177+ if len (keepOps ) == 0 {
178+ return fmt .Errorf ("no operations specified to keep" )
179+ }
180+
181+ // Collect all operations from the document
182+ allOps , err := explore .CollectOperations (ctx , doc )
183+ if err != nil {
184+ return fmt .Errorf ("failed to collect operations: %w" , err )
185+ }
186+ if len (allOps ) == 0 {
187+ return fmt .Errorf ("no operations found in the OpenAPI document" )
188+ }
189+
190+ // Build lookup sets for keep filters
191+ keepByID := map [string ]bool {}
192+ keepByPathMethod := map [string ]bool {}
193+ for _ , k := range keepOps {
194+ if k .OperationID != "" {
195+ keepByID [k .OperationID ] = true
196+ }
197+ if k .Path != "" && k .Method != "" {
198+ key := strings .ToUpper (k .Method ) + " " + k .Path
199+ keepByPathMethod [key ] = true
200+ }
201+ }
202+
203+ // Compute removal list = all - keep
204+ var operationsToRemove []openapi.OperationIdentifier
205+ for _ , op := range allOps {
206+ if op .OperationID != "" && keepByID [op .OperationID ] {
207+ continue
208+ }
209+ key := strings .ToUpper (op .Method ) + " " + op .Path
210+ if keepByPathMethod [key ] {
211+ continue
212+ }
213+ operationsToRemove = append (operationsToRemove , openapi.OperationIdentifier {
214+ Path : op .Path ,
215+ Method : strings .ToUpper (op .Method ),
216+ })
217+ }
218+
219+ // If nothing to remove, write as-is
220+ if len (operationsToRemove ) == 0 {
221+ processor .PrintSuccess ("No operations to remove based on keep filters; writing document unchanged" )
222+ return processor .WriteDocument (ctx , doc )
223+ }
224+
225+ // Perform the snip
226+ removed , err := openapi .Snip (ctx , doc , operationsToRemove )
227+ if err != nil {
228+ return fmt .Errorf ("failed to snip operations: %w" , err )
229+ }
230+
231+ processor .PrintSuccess (fmt .Sprintf ("Successfully kept %d operation(s) and removed %d operation(s) with cleanup" , len (allOps )- removed , removed ))
232+
233+ // Write the snipped document
234+ return processor .WriteDocument (ctx , doc )
235+ }
236+
142237func runSnipInteractive (ctx context.Context , inputFile , outputFile string ) error {
143238 // Load the OpenAPI document
144239 doc , err := loadOpenAPIDocument (ctx , inputFile )
@@ -306,6 +401,47 @@ func parseOperationFlags() ([]openapi.OperationIdentifier, error) {
306401 return operations , nil
307402}
308403
404+ // parseKeepOperationFlags parses the keep flags into operation identifiers
405+ // Handles both repeated flags and comma-separated values
406+ func parseKeepOperationFlags () ([]openapi.OperationIdentifier , error ) {
407+ var operations []openapi.OperationIdentifier
408+
409+ // Parse keep operation IDs
410+ for _ , opID := range snipKeepOperationIDs {
411+ if opID != "" {
412+ operations = append (operations , openapi.OperationIdentifier {
413+ OperationID : opID ,
414+ })
415+ }
416+ }
417+
418+ // Parse keep path:method operations
419+ for _ , op := range snipKeepOperations {
420+ if op == "" {
421+ continue
422+ }
423+
424+ parts := strings .SplitN (op , ":" , 2 )
425+ if len (parts ) != 2 {
426+ return nil , fmt .Errorf ("invalid keep operation format: %s (expected path:METHOD format, e.g., /users:GET)" , op )
427+ }
428+
429+ path := parts [0 ]
430+ method := strings .ToUpper (parts [1 ])
431+
432+ if path == "" || method == "" {
433+ return nil , fmt .Errorf ("invalid keep operation format: %s (path and method cannot be empty)" , op )
434+ }
435+
436+ operations = append (operations , openapi.OperationIdentifier {
437+ Path : path ,
438+ Method : method ,
439+ })
440+ }
441+
442+ return operations , nil
443+ }
444+
309445// GetSnipCommand returns the snip command for external use
310446func GetSnipCommand () * cobra.Command {
311447 return snipCmd
0 commit comments