diff --git a/cmd/openapi/commands/overlay/README.md b/cmd/openapi/commands/overlay/README.md index ba0dd95..ded9867 100644 --- a/cmd/openapi/commands/overlay/README.md +++ b/cmd/openapi/commands/overlay/README.md @@ -30,13 +30,18 @@ OpenAPI Overlays provide a way to modify OpenAPI and Arazzo specifications witho Apply an overlay to an OpenAPI specification. ```bash -# Apply overlay to a specification +# Apply overlay to a specification (positional arguments) +openapi overlay apply overlay.yaml spec.yaml + +# Apply overlay to a specification (flags) openapi overlay apply --overlay overlay.yaml --schema spec.yaml # Apply overlay with output to file openapi overlay apply --overlay overlay.yaml --schema spec.yaml --out modified-spec.yaml # Apply overlay when overlay has extends key set +openapi overlay apply overlay.yaml +# or openapi overlay apply --overlay overlay.yaml ``` @@ -46,17 +51,21 @@ Features: - Supports all OpenAPI Overlay Specification operations - Handles complex nested modifications - Preserves original document structure where not modified +- Supports both positional arguments and explicit flags ### `validate` Validate an overlay file for compliance with the OpenAPI Overlay Specification. ```bash -# Validate an overlay file +# Validate an overlay file (positional argument) +openapi overlay validate overlay.yaml + +# Validate an overlay file (flag) openapi overlay validate --overlay overlay.yaml # Validate with verbose output -openapi overlay validate -v --overlay overlay.yaml +openapi overlay validate -v overlay.yaml ``` This command checks for: @@ -73,11 +82,14 @@ Note: This validates the overlay file structure itself, not whether it will appl Generate an OpenAPI Overlay specification from two input files. ```bash -# Generate overlay from two specifications -openapi overlay compare --before spec1.yaml --after spec2.yaml --out overlay.yaml +# Generate overlay from two specifications (positional arguments) +openapi overlay compare spec1.yaml spec2.yaml -# Generate overlay with console output +# Generate overlay from two specifications (flags) openapi overlay compare --before spec1.yaml --after spec2.yaml + +# Generate overlay with output to file +openapi overlay compare --before spec1.yaml --after spec2.yaml --out overlay.yaml ``` Features: @@ -86,6 +98,7 @@ Features: - Generates overlay operations for all changes - Provides diagnostic output showing detected changes - Creates overlay files that can recreate the transformation +- Supports both positional arguments and explicit flags ### `upgrade` @@ -172,9 +185,24 @@ All commands support these common options: - `-h, --help`: Show help for the command - `-v, --verbose`: Enable verbose output (global flag) -- `--overlay`: Path to the overlay file -- `--schema`: Path to the OpenAPI specification (for apply command) -- `--out`: Output file path (optional, defaults to stdout) + +Command-specific flags: + +**apply command:** + +- `--overlay`: Path to the overlay file (alternative to positional argument) +- `--schema`: Path to the OpenAPI specification (alternative to positional argument) +- `-o, --out`: Output file path (optional, defaults to stdout) + +**validate command:** + +- `--overlay`: Path to the overlay file (alternative to positional argument) + +**compare command:** + +- `--before`: Path to the first (before) specification file (alternative to positional argument) +- `--after`: Path to the second (after) specification file (alternative to positional argument) +- `-o, --out`: Output file path (optional, defaults to stdout) ## Output Formats @@ -185,24 +213,33 @@ All commands work with both YAML and JSON input files, but always output YAML at ### Basic Workflow ```bash -# Create an overlay by comparing two specs +# Create an overlay by comparing two specs (using flags) openapi overlay compare --before original.yaml --after modified.yaml --out changes.overlay.yaml +# Or using positional arguments +openapi overlay compare original.yaml modified.yaml --out changes.overlay.yaml + # Validate the generated overlay -openapi overlay validate --overlay changes.overlay.yaml +openapi overlay validate changes.overlay.yaml -# Apply the overlay to the original spec +# Apply the overlay to the original spec (using flags) openapi overlay apply --overlay changes.overlay.yaml --schema original.yaml --out final.yaml + +# Or using positional arguments +openapi overlay apply changes.overlay.yaml original.yaml --out final.yaml ``` ### Environment-Specific Modifications ```bash -# Apply production overlay +# Apply production overlay (using flags) openapi overlay apply --overlay prod.overlay.yaml --schema base-spec.yaml --out prod-spec.yaml +# Or using positional arguments +openapi overlay apply prod.overlay.yaml base-spec.yaml --out prod-spec.yaml + # Apply development overlay -openapi overlay apply --overlay dev.overlay.yaml --schema base-spec.yaml --out dev-spec.yaml +openapi overlay apply dev.overlay.yaml base-spec.yaml --out dev-spec.yaml ``` ### Integration with Other Commands @@ -210,5 +247,5 @@ openapi overlay apply --overlay dev.overlay.yaml --schema base-spec.yaml --out d ```bash # Validate base spec, apply overlay, then validate result openapi spec validate ./base-spec.yaml -openapi overlay apply --overlay ./modifications.yaml --schema ./base-spec.yaml --out ./modified-spec.yaml +openapi overlay apply ./modifications.yaml ./base-spec.yaml --out ./modified-spec.yaml openapi spec validate ./modified-spec.yaml diff --git a/cmd/openapi/commands/overlay/apply.go b/cmd/openapi/commands/overlay/apply.go index 7995412..f65abd2 100644 --- a/cmd/openapi/commands/overlay/apply.go +++ b/cmd/openapi/commands/overlay/apply.go @@ -8,25 +8,60 @@ import ( "gopkg.in/yaml.v3" ) +var ( + applyOverlayFlag string + applySchemaFlag string + applyOutFlag string +) + var applyCmd = &cobra.Command{ Use: "apply [ ]", Short: "Given an overlay, it will apply it to the spec. If omitted, spec will be loaded via extends (only from local file system).", - Args: cobra.RangeArgs(1, 2), + Args: cobra.RangeArgs(0, 2), Run: RunApply, + Example: ` # Apply overlay using positional arguments + openapi overlay apply overlay.yaml spec.yaml + + # Apply overlay using flags + openapi overlay apply --overlay overlay.yaml --schema spec.yaml + + # Apply overlay with output to file + openapi overlay apply --overlay overlay.yaml --schema spec.yaml --out modified-spec.yaml + + # Apply overlay when overlay has extends key set + openapi overlay apply overlay.yaml`, +} + +func init() { + applyCmd.Flags().StringVar(&applyOverlayFlag, "overlay", "", "Path to the overlay file") + applyCmd.Flags().StringVar(&applySchemaFlag, "schema", "", "Path to the OpenAPI specification file") + applyCmd.Flags().StringVarP(&applyOutFlag, "out", "o", "", "Output file path (defaults to stdout)") } func RunApply(cmd *cobra.Command, args []string) { - overlayFile := args[0] + // Determine overlay file path from flag or positional argument + var overlayFile string + if applyOverlayFlag != "" { + overlayFile = applyOverlayFlag + } else if len(args) > 0 { + overlayFile = args[0] + } else { + Dief("overlay file is required (use --overlay flag or provide as first argument)") + } o, err := loader.LoadOverlay(overlayFile) if err != nil { Die(err) } + // Determine spec file path from flag or positional argument var specFile string - if len(args) > 1 { + if applySchemaFlag != "" { + specFile = applySchemaFlag + } else if len(args) > 1 { specFile = args[1] } + ys, specFile, err := loader.LoadEitherSpecification(specFile, o) if err != nil { Die(err) @@ -37,7 +72,18 @@ func RunApply(cmd *cobra.Command, args []string) { Dief("Failed to apply overlay to spec file %q: %v", specFile, err) } - err = yaml.NewEncoder(os.Stdout).Encode(ys) + // Write to output file if specified, otherwise stdout + out := os.Stdout + if applyOutFlag != "" { + f, err := os.Create(applyOutFlag) + if err != nil { + Dief("Failed to create output file %q: %v", applyOutFlag, err) + } + defer f.Close() + out = f + } + + err = yaml.NewEncoder(out).Encode(ys) if err != nil { Dief("Failed to encode spec file %q: %v", specFile, err) } diff --git a/cmd/openapi/commands/overlay/compare.go b/cmd/openapi/commands/overlay/compare.go index b1ced44..f40090e 100644 --- a/cmd/openapi/commands/overlay/compare.go +++ b/cmd/openapi/commands/overlay/compare.go @@ -9,32 +9,83 @@ import ( "github.com/spf13/cobra" ) +var ( + compareBeforeFlag string + compareAfterFlag string + compareOutFlag string +) + var compareCmd = &cobra.Command{ Use: "compare ", Short: "Given two specs, it will output an overlay that describes the differences between them", - Args: cobra.ExactArgs(2), + Args: cobra.RangeArgs(0, 2), Run: RunCompare, + Example: ` # Compare specs using positional arguments + openapi overlay compare spec1.yaml spec2.yaml + + # Compare specs using flags + openapi overlay compare --before spec1.yaml --after spec2.yaml + + # Compare specs with output to file + openapi overlay compare --before spec1.yaml --after spec2.yaml --out overlay.yaml`, +} + +func init() { + compareCmd.Flags().StringVar(&compareBeforeFlag, "before", "", "Path to the first (before) specification file") + compareCmd.Flags().StringVar(&compareAfterFlag, "after", "", "Path to the second (after) specification file") + compareCmd.Flags().StringVarP(&compareOutFlag, "out", "o", "", "Output file path (defaults to stdout)") } func RunCompare(cmd *cobra.Command, args []string) { - y1, err := loader.LoadSpecification(args[0]) + // Determine first spec file path from flag or positional argument + var spec1 string + if compareBeforeFlag != "" { + spec1 = compareBeforeFlag + } else if len(args) > 0 { + spec1 = args[0] + } else { + Dief("first specification file is required (use --before flag or provide as first argument)") + } + + // Determine second spec file path from flag or positional argument + var spec2 string + if compareAfterFlag != "" { + spec2 = compareAfterFlag + } else if len(args) > 1 { + spec2 = args[1] + } else { + Dief("second specification file is required (use --after flag or provide as second argument)") + } + + y1, err := loader.LoadSpecification(spec1) if err != nil { - Dief("Failed to load %q: %v", args[0], err) + Dief("Failed to load %q: %v", spec1, err) } - y2, err := loader.LoadSpecification(args[1]) + y2, err := loader.LoadSpecification(spec2) if err != nil { - Dief("Failed to load %q: %v", args[1], err) + Dief("Failed to load %q: %v", spec2, err) } - title := fmt.Sprintf("Overlay %s => %s", args[0], args[1]) + title := fmt.Sprintf("Overlay %s => %s", spec1, spec2) o, err := overlay.Compare(title, y1, *y2) if err != nil { - Dief("Failed to compare spec files %q and %q: %v", args[0], args[1], err) + Dief("Failed to compare spec files %q and %q: %v", spec1, spec2, err) + } + + // Write to output file if specified, otherwise stdout + out := os.Stdout + if compareOutFlag != "" { + f, err := os.Create(compareOutFlag) + if err != nil { + Dief("Failed to create output file %q: %v", compareOutFlag, err) + } + defer f.Close() + out = f } - err = o.Format(os.Stdout) + err = o.Format(out) if err != nil { Dief("Failed to format overlay: %v", err) } diff --git a/cmd/openapi/commands/overlay/validate.go b/cmd/openapi/commands/overlay/validate.go index f96160c..0d614cf 100644 --- a/cmd/openapi/commands/overlay/validate.go +++ b/cmd/openapi/commands/overlay/validate.go @@ -7,23 +7,44 @@ import ( "github.com/spf13/cobra" ) +var validateOverlayFlag string + var validateCmd = &cobra.Command{ Use: "validate ", Short: "Given an overlay, it will state whether it appears to be valid or describe the problems found", - Args: cobra.ExactArgs(1), + Args: cobra.RangeArgs(0, 1), Run: RunValidateOverlay, + Example: ` # Validate overlay using positional argument + openapi overlay validate overlay.yaml + + # Validate overlay using flag + openapi overlay validate --overlay overlay.yaml`, +} + +func init() { + validateCmd.Flags().StringVar(&validateOverlayFlag, "overlay", "", "Path to the overlay file") } func RunValidateOverlay(cmd *cobra.Command, args []string) { - o, err := loader.LoadOverlay(args[0]) + // Determine overlay file path from flag or positional argument + var overlayFile string + if validateOverlayFlag != "" { + overlayFile = validateOverlayFlag + } else if len(args) > 0 { + overlayFile = args[0] + } else { + Dief("overlay file is required (use --overlay flag or provide as first argument)") + } + + o, err := loader.LoadOverlay(overlayFile) if err != nil { Die(err) } err = o.Validate() if err != nil { - Dief("Overlay file %q failed validation:\n%v", args[0], err) + Dief("Overlay file %q failed validation:\n%v", overlayFile, err) } - fmt.Printf("Overlay file %q is valid.\n", args[0]) + fmt.Printf("Overlay file %q is valid.\n", overlayFile) } diff --git a/mise-tasks/test-cli b/mise-tasks/test-cli index 39bb731..5ddcd56 100755 --- a/mise-tasks/test-cli +++ b/mise-tasks/test-cli @@ -220,6 +220,19 @@ $CLI overlay compare overlay/testdata/openapi.yaml overlay/testdata/openapi-over echo " ✓ Validating generated overlay..." $CLI overlay validate dist/test/test-overlay-generated.yaml > /dev/null +# Test overlay commands with flags +echo " ✓ Testing overlay validate with --overlay flag..." +$CLI overlay validate --overlay overlay/testdata/overlay.yaml > /dev/null + +echo " ✓ Testing overlay apply with --overlay and --schema flags..." +$CLI overlay apply --overlay overlay/testdata/overlay.yaml --schema overlay/testdata/openapi.yaml > dist/test/test-overlayed-flags.yaml + +echo " ✓ Testing overlay compare with --before and --after flags..." +$CLI overlay compare --before overlay/testdata/openapi.yaml --after overlay/testdata/openapi-overlayed.yaml > dist/test/test-overlay-generated-flags.yaml + +echo " ✓ Validating overlay generated with flags..." +$CLI overlay validate --overlay dist/test/test-overlay-generated-flags.yaml > /dev/null + # Test error cases - commands that should fail echo " ✓ Testing error cases..."