Skip to content

Commit

Permalink
Adds the capability to alias resource names and aliases line_items ->…
Browse files Browse the repository at this point in the history
… invoice_line_items (#1270)

* Adds basic functions for managing a list of aliases for commands to rename them while leaving the original resource hidden (but functional for backward compatibility)

* Refactors resource command generation to create duplicate resources with aliased names when an alias is defined. Also removes some unnecessary pointer returns

* Hides principle resources when they have been aliased and teaches help text to omit hidden resources

* Generates resource commands with line_items aliased to invoice_line_items

* Adds tests to ensure aliases are hidden in the resource list and that aliased and principle resources hit the same underlying APIs
  • Loading branch information
fhats-stripe authored Nov 4, 2024
1 parent 25dfc23 commit 7ffd100
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 24 deletions.
39 changes: 39 additions & 0 deletions pkg/cmd/resource/alias.go
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}
2 changes: 2 additions & 0 deletions pkg/cmd/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,7 @@ func PostProcessResourceCommands(rootCmd *cobra.Command, cfg *config.Config) err
return err
}

HideAliasedCommands(rootCmd)

return nil
}
2 changes: 1 addition & 1 deletion pkg/cmd/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
25 changes: 25 additions & 0 deletions pkg/cmd/resources_cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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",
Expand Down
37 changes: 37 additions & 0 deletions pkg/cmd/resources_test.go
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions pkg/cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
77 changes: 54 additions & 23 deletions pkg/gen/gen_resources_cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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),
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -255,7 +286,7 @@ func addToTemplateData(data *TemplateData, nsName, resName, subResName string, s
}
}

return data, nil
return nil
}

func parseSchemaName(name string) (string, string) {
Expand Down

0 comments on commit 7ffd100

Please sign in to comment.