diff --git a/pkg/cmd/resource/alias.go b/pkg/cmd/resource/alias.go new file mode 100644 index 00000000..dd86ab1c --- /dev/null +++ b/pkg/cmd/resource/alias.go @@ -0,0 +1,39 @@ +package resource + +import ( + "github.com/spf13/cobra" +) + +// Aliases are mapped from resource name in the OpenAPI spec -> friendly name in the CLI +// Each alias causes a second resource command (the target / friendly name) to be generated, and uses post-processing +// to hide the principle resource name (OpenAPI spec) +var aliasedCmds = map[string]string{ + "line_item": "invoice_line_item", +} + +// GetCmdAlias retrieves the alias for a given resource, if one is present; otherwise returns "" +func GetCmdAlias(principle string) string { + alias, ok := aliasedCmds[principle] + if !ok { + return "" + } + return alias +} + +// GetAliases retrieves the entire alias map, useful for testing +func GetAliases() map[string]string { + return aliasedCmds +} + +// HideAliasedCommands performs the post-processing on the command tree to hide +// resources that have an alias +func HideAliasedCommands(rootCmd *cobra.Command) { + for _, cmd := range rootCmd.Commands() { + for principle := range aliasedCmds { + formattedPrinciple := GetResourceCmdName(principle) + if cmd.Use == formattedPrinciple { + cmd.Hidden = true + } + } + } +} diff --git a/pkg/cmd/resource/resource.go b/pkg/cmd/resource/resource.go index 3e54260a..eea16461 100644 --- a/pkg/cmd/resource/resource.go +++ b/pkg/cmd/resource/resource.go @@ -133,5 +133,7 @@ func PostProcessResourceCommands(rootCmd *cobra.Command, cfg *config.Config) err return err } + HideAliasedCommands(rootCmd) + return nil } diff --git a/pkg/cmd/resources.go b/pkg/cmd/resources.go index 57e94457..b31706e2 100644 --- a/pkg/cmd/resources.go +++ b/pkg/cmd/resources.go @@ -28,7 +28,7 @@ func newResourcesCmd() *resourcesCmd { func getResourcesHelpTemplate() string { // This template uses `.Parent` to access subcommands on the root command. - return fmt.Sprintf(`%s{{range $index, $cmd := .Parent.Commands}}{{if (or (eq (index $.Parent.Annotations $cmd.Name) "resource") (eq (index $.Parent.Annotations $cmd.Name) "namespace"))}} + return fmt.Sprintf(`%s{{range $index, $cmd := .Parent.Commands}}{{if (and (not $cmd.Hidden) (or (eq (index $.Parent.Annotations $cmd.Name) "resource") (eq (index $.Parent.Annotations $cmd.Name) "namespace")))}} {{rpad $cmd.Name $cmd.NamePadding }} {{$cmd.Short}}{{end}}{{end}} Use "stripe [command] --help" for more information about a command. diff --git a/pkg/cmd/resources_cmds.go b/pkg/cmd/resources_cmds.go index de7f45bf..23732893 100644 --- a/pkg/cmd/resources_cmds.go +++ b/pkg/cmd/resources_cmds.go @@ -62,6 +62,7 @@ func addAllResourcesCmds(rootCmd *cobra.Command) { rFeeRefundsCmd := resource.NewResourceCmd(rootCmd, "fee_refunds") rFileLinksCmd := resource.NewResourceCmd(rootCmd, "file_links") rFilesCmd := resource.NewResourceCmd(rootCmd, "files") + rInvoiceLineItemsCmd := resource.NewResourceCmd(rootCmd, "invoice_line_items") rInvoiceRenderingTemplatesCmd := resource.NewResourceCmd(rootCmd, "invoice_rendering_templates") rInvoiceitemsCmd := resource.NewResourceCmd(rootCmd, "invoiceitems") rInvoicesCmd := resource.NewResourceCmd(rootCmd, "invoices") @@ -1189,6 +1190,30 @@ func addAllResourcesCmds(rootCmd *cobra.Command) { "starting_after": "string", }, &Config) resource.NewOperationCmd(rFilesCmd.Cmd, "retrieve", "/v1/files/{file}", http.MethodGet, map[string]string{}, &Config) + resource.NewOperationCmd(rInvoiceLineItemsCmd.Cmd, "list", "/v1/invoices/{invoice}/lines", http.MethodGet, map[string]string{ + "ending_before": "string", + "limit": "integer", + "starting_after": "string", + }, &Config) + resource.NewOperationCmd(rInvoiceLineItemsCmd.Cmd, "update", "/v1/invoices/{invoice}/lines/{line_item_id}", http.MethodPost, map[string]string{ + "amount": "integer", + "description": "string", + "discountable": "boolean", + "period.end": "integer", + "period.start": "integer", + "price": "string", + "price_data.currency": "string", + "price_data.product": "string", + "price_data.product_data.description": "string", + "price_data.product_data.images": "array", + "price_data.product_data.name": "string", + "price_data.product_data.tax_code": "string", + "price_data.tax_behavior": "string", + "price_data.unit_amount": "integer", + "price_data.unit_amount_decimal": "string", + "quantity": "integer", + "tax_rates": "array", + }, &Config) resource.NewOperationCmd(rInvoiceRenderingTemplatesCmd.Cmd, "archive", "/v1/invoice_rendering_templates/{template}/archive", http.MethodPost, map[string]string{}, &Config) resource.NewOperationCmd(rInvoiceRenderingTemplatesCmd.Cmd, "list", "/v1/invoice_rendering_templates", http.MethodGet, map[string]string{ "ending_before": "string", diff --git a/pkg/cmd/resources_test.go b/pkg/cmd/resources_test.go index 780f4a7c..491e9ab6 100644 --- a/pkg/cmd/resources_test.go +++ b/pkg/cmd/resources_test.go @@ -1,10 +1,17 @@ package cmd import ( + "fmt" "io" "net/http" + "net/http/httptest" + "regexp" "testing" + "github.com/stretchr/testify/assert" + + "github.com/stripe/stripe-cli/pkg/cmd/resource" + "github.com/BurntSushi/toml" "github.com/stretchr/testify/require" @@ -22,6 +29,36 @@ func TestResources(t *testing.T) { require.NoError(t, err) } +func TestResourcesListAliasedName(t *testing.T) { + output, err := executeCommand(rootCmd, "resources") + require.NoError(t, err) + + assert.Contains(t, output, "Available commands:") + + aliases := resource.GetAliases() + for principle, alias := range aliases { + aliasRegexp := fmt.Sprintf("\n\\s+%s(s?)\\s+\n", resource.GetResourceCmdName(alias)) + principleRegexp := fmt.Sprintf("\n\\s+%s(s?)\\s+\n", resource.GetResourceCmdName(principle)) + assert.Regexp(t, regexp.MustCompile(aliasRegexp), output) + assert.NotRegexp(t, regexp.MustCompile(principleRegexp), output) + } +} + +func TestAliasedResourcesCallPrincipleAPI(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.URL.Path, "/v1/invoices/in_123/lines") + })) + defer ts.Close() + + apiBase := fmt.Sprintf("--api-base=%s", ts.URL) + apiKey := "--api-key=rk_test_1234567890" + + _, err := executeCommand(rootCmd, apiBase, apiKey, "invoice_line_items", "list", "in_123") + require.NoError(t, err) + _, err = executeCommand(rootCmd, apiBase, apiKey, "line_items", "list", "in_123") + require.NoError(t, err) +} + func TestConflictWithPluginCommand(t *testing.T) { // directly downloading the manifest can only be done within this unit test // plugins.GetPluginList should be used under normal circumstances diff --git a/pkg/cmd/root_test.go b/pkg/cmd/root_test.go index 01fc7d14..86cec9d7 100644 --- a/pkg/cmd/root_test.go +++ b/pkg/cmd/root_test.go @@ -25,6 +25,9 @@ func executeCommandC(root *cobra.Command, args ...string) (c *cobra.Command, out c, err = root.ExecuteC() + // Resets args for the next test run to avoid arguments for flags being carried over + root.SetArgs([]string{}) + return c, buf.String(), err } diff --git a/pkg/gen/gen_resources_cmds.go b/pkg/gen/gen_resources_cmds.go index 593b4661..891768cf 100644 --- a/pkg/gen/gen_resources_cmds.go +++ b/pkg/gen/gen_resources_cmds.go @@ -111,39 +111,69 @@ func getTemplateData() (*TemplateData, error) { continue } - origNsName, origResName := parseSchemaName(name) + err := genCmdTemplate(name, name, data, stripeAPI) + if err != nil { + return nil, err + } + + alias := resource.GetCmdAlias(name) - // Iterate over every operation for the resource - for _, op := range *schema.XStripeOperations { - // We're only implementing "service" operations - if op.MethodOn != "service" { - continue + if alias != "" { + // Aliased commands write a second entry into the resource commands, and use post-processing to hide the + // command from the index (e.g. when running `stripe resources`) + err := genCmdTemplate(name, alias, data, stripeAPI) + if err != nil { + return nil, err } + } + } - nsName := origNsName - resName := origResName - subResName := "" + return data, nil +} - if strings.Contains(op.Path, test_helpers_path) && test_helpers_path != nsName { - // create entry in the test_helpers namespace - if nsName != "" { - data, err = addToTemplateData(data, test_helpers_path, nsName, resName, stripeAPI, op) - } else { - data, err = addToTemplateData(data, test_helpers_path, resName, "", stripeAPI, op) - } +func genCmdTemplate(schemaName string, cmdName string, data *TemplateData, stripeAPI *spec.Spec) error { + origNsName, origResName := parseSchemaName(cmdName) + schema := stripeAPI.Components.Schemas[schemaName] + + // Iterate over every operation for the resource + for _, op := range *schema.XStripeOperations { + // We're only implementing "service" operations + if op.MethodOn != "service" { + continue + } + + nsName := origNsName + resName := origResName + subResName := "" - // add test_helpers as a sub-resource to the current namespace-resource entry - subResName = test_helpers_path + if strings.Contains(op.Path, test_helpers_path) && test_helpers_path != nsName { + // create entry in the test_helpers namespace + if nsName != "" { + err := addToTemplateData(data, test_helpers_path, nsName, resName, stripeAPI, op) + if err != nil { + return err + } + } else { + err := addToTemplateData(data, test_helpers_path, resName, "", stripeAPI, op) + if err != nil { + return err + } } - data, err = addToTemplateData(data, nsName, resName, subResName, stripeAPI, op) + // add test_helpers as a sub-resource to the current namespace-resource entry + subResName = test_helpers_path + } + + err := addToTemplateData(data, nsName, resName, subResName, stripeAPI, op) + if err != nil { + return err } } - return data, nil + return nil } -func addToTemplateData(data *TemplateData, nsName, resName, subResName string, stripeAPI *spec.Spec, op spec.StripeOperation) (*TemplateData, error) { +func addToTemplateData(data *TemplateData, nsName, resName, subResName string, stripeAPI *spec.Spec, op spec.StripeOperation) error { hasSubResources := subResName != "" if _, ok := data.Namespaces[nsName]; !ok { @@ -156,6 +186,7 @@ func addToTemplateData(data *TemplateData, nsName, resName, subResName string, s resCmdName := resource.GetResourceCmdName(resName) if _, ok := data.Namespaces[nsName].Resources[resCmdName]; !ok { data.Namespaces[nsName].Resources[resCmdName] = &ResourceData{ + Operations: make(map[string]*OperationData), SubResources: make(map[string]*ResourceData), } @@ -187,7 +218,7 @@ func addToTemplateData(data *TemplateData, nsName, resName, subResName string, s // Skip deprecated methods if specOp.Deprecated != nil && *specOp.Deprecated == true { - return data, nil + return nil } if strings.ToUpper(httpString) == http.MethodPost { @@ -255,7 +286,7 @@ func addToTemplateData(data *TemplateData, nsName, resName, subResName string, s } } - return data, nil + return nil } func parseSchemaName(name string) (string, string) {