From bee9be5501a7bdd4a41006ec15ed66ad47aa2151 Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Thu, 2 May 2024 16:44:20 -0700 Subject: [PATCH 1/5] create and generate template from template data --- cmd/resources/gen_resources.go | 37 +++- cmd/resources/resource_cmds.go | 185 ++++++++++++++++-- cmd/resources/resource_cmds.tmpl | 42 ++++ cmd/resources/resources.go | 121 ++++++------ go.mod | 1 + go.sum | 2 + .../github.com/iancoleman/strcase/.travis.yml | 10 + vendor/github.com/iancoleman/strcase/LICENSE | 22 +++ .../github.com/iancoleman/strcase/README.md | 59 ++++++ .../github.com/iancoleman/strcase/acronyms.go | 13 ++ vendor/github.com/iancoleman/strcase/camel.go | 87 ++++++++ vendor/github.com/iancoleman/strcase/doc.go | 12 ++ vendor/github.com/iancoleman/strcase/snake.go | 115 +++++++++++ vendor/modules.txt | 3 + 14 files changed, 635 insertions(+), 74 deletions(-) create mode 100644 cmd/resources/resource_cmds.tmpl create mode 100644 vendor/github.com/iancoleman/strcase/.travis.yml create mode 100644 vendor/github.com/iancoleman/strcase/LICENSE create mode 100644 vendor/github.com/iancoleman/strcase/README.md create mode 100644 vendor/github.com/iancoleman/strcase/acronyms.go create mode 100644 vendor/github.com/iancoleman/strcase/camel.go create mode 100644 vendor/github.com/iancoleman/strcase/doc.go create mode 100644 vendor/github.com/iancoleman/strcase/snake.go diff --git a/cmd/resources/gen_resources.go b/cmd/resources/gen_resources.go index d986d99a..d3385c57 100644 --- a/cmd/resources/gen_resources.go +++ b/cmd/resources/gen_resources.go @@ -4,16 +4,49 @@ package main import ( + "bytes" + "go/format" + "io/ioutil" "log" + "text/template" "ldcli/cmd/resources" ) -const pathSpecFile = "../ld-teams-openapi.json" +const ( + pathSpecFile = "../ld-teams-openapi.json" + pathTemplate = "resources/resource_cmds.tmpl" + templateName = "resource_cmds.tmpl" + pathOutput = "resources/resource_cmds.go" +) func main() { log.Println("Generating resources...") - _, err := resources.GetTemplateData(pathSpecFile) + templateData, err := resources.GetTemplateData(pathSpecFile) + if err != nil { + panic(err) + } + + tmpl, err := template.New(templateName).ParseFiles(pathTemplate) + if err != nil { + panic(err) + } + + var result bytes.Buffer + err = tmpl.Execute(&result, templateData) + if err != nil { + panic(err) + } + + // Format the output of the template execution + formatted, err := format.Source(result.Bytes()) + if err != nil { + panic(err) + } + + // Write the formatted source code to disk + log.Printf("writing %s\n", pathOutput) + err = ioutil.WriteFile(pathOutput, formatted, 0644) if err != nil { panic(err) } diff --git a/cmd/resources/resource_cmds.go b/cmd/resources/resource_cmds.go index f253287f..b25b4146 100644 --- a/cmd/resources/resource_cmds.go +++ b/cmd/resources/resource_cmds.go @@ -1,4 +1,4 @@ -// this file WILL be generated (sc-241153) +// This file is generated by gen_resources.go; DO NOT EDIT. package resources @@ -16,38 +16,37 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti analyticsTracker, "teams", "Make requests (list, create, etc.) on teams", - "A team is a group of members in your LaunchDarkly account. A team can have maintainers who are able to add and remove team members. It also can have custom roles assigned to it that allows shared access to those roles for all team members. To learn more, read [Teams](https://docs.launchdarkly.com/home/teams).\n\nThe Teams API allows you to create, read, update, and delete a team.\n\nSeveral of the endpoints in the Teams API require one or more member IDs. The member ID is returned as part of the [List account members](/tag/Account-members#operation/getMembers) response. It is the `_id` field of each element in the `items` array.", + "\u003e ### Teams is an Enterprise feature\n\u003e\n\u003e Teams is available to customers on an Enterprise plan. To learn more, [read about our pricing](https://launchdarkly.com/pricing/). To upgrade your plan, [contact Sales](https://launchdarkly.com/contact-sales/).\n\nA team is a group of members in your LaunchDarkly account. A team can have maintainers who are able to add and remove team members. It also can have custom roles assigned to it that allows shared access to those roles for all team members. To learn more, read [Teams](https://docs.launchdarkly.com/home/teams).\n\nThe Teams API allows you to create, read, update, and delete a team.\n\nSeveral of the endpoints in the Teams API require one or more member IDs. The member ID is returned as part of the [List account members](/tag/Account-members#operation/getMembers) response. It is the `_id` field of each element in the `items` array.\n", ) // Operation commands NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ - Short: "Create team", - Long: "Create a team. To learn more, read [Creating a team](https://docs.launchdarkly.com/home/teams/creating).\n\n### Expanding the teams response\nLaunchDarkly supports four fields for expanding the \"Create team\" response. By default, these fields are **not** included in the response.\n\nTo expand the response, append the `expand` query parameter and add a comma-separated list with any of the following fields:\n\n* `members` includes the total count of members that belong to the team.\n* `roles` includes a paginated list of the custom roles that you have assigned to the team.\n* `projects` includes a paginated list of the projects that the team has any write access to.\n* `maintainers` includes a paginated list of the maintainers that you have assigned to the team.\n\nFor example, `expand=members,roles` includes the `members` and `roles` fields in the response.\n", - Use: "create", // TODO: translate post -> create - + Short: "Delete team", + Long: "Delete a team by key. To learn more, read [Deleting a team](https://docs.launchdarkly.com/home/teams/managing#deleting-a-team).", + Use: "delete-team", Params: []Param{ { - Name: "expand", - In: "query", - Description: "A comma-separated list of properties that can reveal additional information in the response. Supported fields are explained above.", + Name: "team-key", + In: "path", + Description: "The team key", Type: "string", }, }, - HTTPMethod: "post", - RequiresBody: true, - Path: "/api/v2/teams", + HTTPMethod: "DELETE", + RequiresBody: false, + Path: "/api/v2/teams/{teamKey}", }) + NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ Short: "Get team", Long: "Fetch a team by key.\n\n### Expanding the teams response\nLaunchDarkly supports four fields for expanding the \"Get team\" response. By default, these fields are **not** included in the response.\n\nTo expand the response, append the `expand` query parameter and add a comma-separated list with any of the following fields:\n\n* `members` includes the total count of members that belong to the team.\n* `roles` includes a paginated list of the custom roles that you have assigned to the team.\n* `projects` includes a paginated list of the projects that the team has any write access to.\n* `maintainers` includes a paginated list of the maintainers that you have assigned to the team.\n\nFor example, `expand=members,roles` includes the `members` and `roles` fields in the response.\n", - Use: "get", + Use: "get-team", Params: []Param{ { - Name: "teamKey", // TODO: kebab case/trim key? to be consistent with our existing flags (e.g. projectKey = project) + Name: "team-key", In: "path", Description: "The team key.", Type: "string", - Required: true, }, { Name: "expand", @@ -56,7 +55,159 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti Type: "string", }, }, - HTTPMethod: "get", - Path: "/api/v2/teams/{teamKey}", + HTTPMethod: "GET", + RequiresBody: false, + Path: "/api/v2/teams/{teamKey}", + }) + + NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ + Short: "Get team maintainers", + Long: "Fetch the maintainers that have been assigned to the team. To learn more, read [Managing team maintainers](https://docs.launchdarkly.com/home/teams/managing#managing-team-maintainers).", + Use: "get-team-maintainers", + Params: []Param{ + { + Name: "team-key", + In: "path", + Description: "The team key", + Type: "string", + }, + { + Name: "limit", + In: "query", + Description: "The number of maintainers to return in the response. Defaults to 20.", + Type: "integer", + }, + { + Name: "offset", + In: "query", + Description: "Where to start in the list. This is for use with pagination. For example, an offset of 10 skips the first ten items and then returns the next items in the list, up to the query `limit`.", + Type: "integer", + }, + }, + HTTPMethod: "GET", + RequiresBody: false, + Path: "/api/v2/teams/{teamKey}/maintainers", + }) + + NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ + Short: "Get team custom roles", + Long: "Fetch the custom roles that have been assigned to the team. To learn more, read [Managing team permissions](https://docs.launchdarkly.com/home/teams/managing#managing-team-permissions).", + Use: "get-team-roles", + Params: []Param{ + { + Name: "team-key", + In: "path", + Description: "The team key", + Type: "string", + }, + { + Name: "limit", + In: "query", + Description: "The number of roles to return in the response. Defaults to 20.", + Type: "integer", + }, + { + Name: "offset", + In: "query", + Description: "Where to start in the list. This is for use with pagination. For example, an offset of 10 skips the first ten items and then returns the next items in the list, up to the query `limit`.", + Type: "integer", + }, + }, + HTTPMethod: "GET", + RequiresBody: false, + Path: "/api/v2/teams/{teamKey}/roles", + }) + + NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ + Short: "List teams", + Long: "Return a list of teams.\n\nBy default, this returns the first 20 teams. Page through this list with the `limit` parameter and by following the `first`, `prev`, `next`, and `last` links in the `_links` field that returns. If those links do not appear, the pages they refer to don't exist. For example, the `first` and `prev` links will be missing from the response on the first page, because there is no previous page and you cannot return to the first page when you are already on the first page.\n\n### Filtering teams\n\nLaunchDarkly supports the following fields for filters:\n\n- `query` is a string that matches against the teams' names and keys. It is not case-sensitive.\n - A request with `query:abc` returns teams with the string `abc` in their name or key.\n- `nomembers` is a boolean that filters the list of teams who have 0 members\n - A request with `nomembers:true` returns teams that have 0 members\n - A request with `nomembers:false` returns teams that have 1 or more members\n\n### Expanding the teams response\nLaunchDarkly supports four fields for expanding the \"List teams\" response. By default, these fields are **not** included in the response.\n\nTo expand the response, append the `expand` query parameter and add a comma-separated list with any of the following fields:\n\n* `members` includes the total count of members that belong to the team.\n* `roles` includes a paginated list of the custom roles that you have assigned to the team.\n* `projects` includes a paginated list of the projects that the team has any write access to.\n* `maintainers` includes a paginated list of the maintainers that you have assigned to the team.\n\nFor example, `expand=members,roles` includes the `members` and `roles` fields in the response.\n", + Use: "get-teams", + Params: []Param{ + { + Name: "limit", + In: "query", + Description: "The number of teams to return in the response. Defaults to 20.", + Type: "integer", + }, + { + Name: "offset", + In: "query", + Description: "Where to start in the list. Use this with pagination. For example, an offset of 10 skips the first ten items and returns the next `limit` items.", + Type: "integer", + }, + { + Name: "filter", + In: "query", + Description: "A comma-separated list of filters. Each filter is constructed as `field:value`.", + Type: "string", + }, + { + Name: "expand", + In: "query", + Description: "A comma-separated list of properties that can reveal additional information in the response.", + Type: "string", + }, + }, + HTTPMethod: "GET", + RequiresBody: false, + Path: "/api/v2/teams", + }) + + NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ + Short: "Update team", + Long: "Perform a partial update to a team. Updating a team uses the semantic patch format.\n\nTo make a semantic patch request, you must append `domain-model=launchdarkly.semanticpatch` to your `Content-Type` header. To learn more, read [Updates using semantic patch](/reference#updates-using-semantic-patch).\n\n### Instructions\n\nSemantic patch requests support the following `kind` instructions for updating teams.\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to expand instructions for \u003cstrong\u003eupdating teams\u003c/strong\u003e\u003c/summary\u003e\n\n#### addCustomRoles\n\nAdds custom roles to the team. Team members will have these custom roles granted to them.\n\n##### Parameters\n\n- `values`: List of custom role keys.\n\nHere's an example:\n\n```json\n{\n \"instructions\": [{\n \"kind\": \"addCustomRoles\",\n \"values\": [ \"example-custom-role\" ]\n }]\n}\n```\n\n#### removeCustomRoles\n\nRemoves custom roles from the team. The app will no longer grant these custom roles to the team members.\n\n##### Parameters\n\n- `values`: List of custom role keys.\n\nHere's an example:\n\n```json\n{\n \"instructions\": [{\n \"kind\": \"removeCustomRoles\",\n \"values\": [ \"example-custom-role\" ]\n }]\n}\n```\n\n#### addMembers\n\nAdds members to the team.\n\n##### Parameters\n\n- `values`: List of member IDs to add.\n\nHere's an example:\n\n```json\n{\n \"instructions\": [{\n \"kind\": \"addMembers\",\n \"values\": [ \"1234a56b7c89d012345e678f\", \"507f1f77bcf86cd799439011\" ]\n }]\n}\n```\n\n#### removeMembers\n\nRemoves members from the team.\n\n##### Parameters\n\n- `values`: List of member IDs to remove.\n\nHere's an example:\n\n```json\n{\n \"instructions\": [{\n \"kind\": \"removeMembers\",\n \"values\": [ \"1234a56b7c89d012345e678f\", \"507f1f77bcf86cd799439011\" ]\n }]\n}\n```\n\n#### replaceMembers\n\nReplaces the existing members of the team with the new members.\n\n##### Parameters\n\n- `values`: List of member IDs of the new members.\n\nHere's an example:\n\n```json\n{\n \"instructions\": [{\n \"kind\": \"replaceMembers\",\n \"values\": [ \"1234a56b7c89d012345e678f\", \"507f1f77bcf86cd799439011\" ]\n }]\n}\n```\n\n#### addPermissionGrants\n\nAdds permission grants to members for the team. For example, a permission grant could allow a member to act as a team maintainer. A permission grant may have either an `actionSet` or a list of `actions` but not both at the same time. The members do not have to be team members to have a permission grant for the team.\n\n##### Parameters\n\n- `actionSet`: Name of the action set.\n- `actions`: List of actions.\n- `memberIDs`: List of member IDs.\n\nHere's an example:\n\n```json\n{\n \"instructions\": [{\n \"kind\": \"addPermissionGrants\",\n \"actions\": [ \"updateTeamName\", \"updateTeamDescription\" ],\n \"memberIDs\": [ \"1234a56b7c89d012345e678f\", \"507f1f77bcf86cd799439011\" ]\n }]\n}\n```\n\n#### removePermissionGrants\n\nRemoves permission grants from members for the team. A permission grant may have either an `actionSet` or a list of `actions` but not both at the same time. The `actionSet` and `actions` must match an existing permission grant.\n\n##### Parameters\n\n- `actionSet`: Name of the action set.\n- `actions`: List of actions.\n- `memberIDs`: List of member IDs.\n\nHere's an example:\n\n```json\n{\n \"instructions\": [{\n \"kind\": \"removePermissionGrants\",\n \"actions\": [ \"updateTeamName\", \"updateTeamDescription\" ],\n \"memberIDs\": [ \"1234a56b7c89d012345e678f\", \"507f1f77bcf86cd799439011\" ]\n }]\n}\n```\n\n#### updateDescription\n\nUpdates the description of the team.\n\n##### Parameters\n\n- `value`: The new description.\n\nHere's an example:\n\n```json\n{\n \"instructions\": [{\n \"kind\": \"updateDescription\",\n \"value\": \"Updated team description\"\n }]\n}\n```\n\n#### updateName\n\nUpdates the name of the team.\n\n##### Parameters\n\n- `value`: The new name.\n\nHere's an example:\n\n```json\n{\n \"instructions\": [{\n \"kind\": \"updateName\",\n \"value\": \"Updated team name\"\n }]\n}\n```\n\n\u003c/details\u003e\n\n### Expanding the teams response\nLaunchDarkly supports four fields for expanding the \"Update team\" response. By default, these fields are **not** included in the response.\n\nTo expand the response, append the `expand` query parameter and add a comma-separated list with any of the following fields:\n\n* `members` includes the total count of members that belong to the team.\n* `roles` includes a paginated list of the custom roles that you have assigned to the team.\n* `projects` includes a paginated list of the projects that the team has any write access to.\n* `maintainers` includes a paginated list of the maintainers that you have assigned to the team.\n\nFor example, `expand=members,roles` includes the `members` and `roles` fields in the response.\n", + Use: "patch-team", + Params: []Param{ + { + Name: "team-key", + In: "path", + Description: "The team key", + Type: "string", + }, + { + Name: "expand", + In: "query", + Description: "A comma-separated list of properties that can reveal additional information in the response. Supported fields are explained above.", + Type: "string", + }, + }, + HTTPMethod: "PATCH", + RequiresBody: true, + Path: "/api/v2/teams/{teamKey}", + }) + + NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ + Short: "Create team", + Long: "Create a team. To learn more, read [Creating a team](https://docs.launchdarkly.com/home/teams/creating).\n\n### Expanding the teams response\nLaunchDarkly supports four fields for expanding the \"Create team\" response. By default, these fields are **not** included in the response.\n\nTo expand the response, append the `expand` query parameter and add a comma-separated list with any of the following fields:\n\n* `members` includes the total count of members that belong to the team.\n* `roles` includes a paginated list of the custom roles that you have assigned to the team.\n* `projects` includes a paginated list of the projects that the team has any write access to.\n* `maintainers` includes a paginated list of the maintainers that you have assigned to the team.\n\nFor example, `expand=members,roles` includes the `members` and `roles` fields in the response.\n", + Use: "post-team", + Params: []Param{ + { + Name: "expand", + In: "query", + Description: "A comma-separated list of properties that can reveal additional information in the response. Supported fields are explained above.", + Type: "string", + }, + }, + HTTPMethod: "POST", + RequiresBody: true, + Path: "/api/v2/teams", }) + + NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ + Short: "Add multiple members to team", + Long: "Add multiple members to an existing team by uploading a CSV file of member email addresses. Your CSV file must include email addresses in the first column. You can include data in additional columns, but LaunchDarkly ignores all data outside the first column. Headers are optional. To learn more, read [Managing team members](https://docs.launchdarkly.com/home/teams/managing#managing-team-members).\n\n**Members are only added on a `201` response.** A `207` indicates the CSV file contains a combination of valid and invalid entries. A `207` results in no members being added to the team.\n\nOn a `207` response, if an entry contains bad input, the `message` field contains the row number as well as the reason for the error. The `message` field is omitted if the entry is valid.\n\nExample `207` response:\n```json\n{\n \"items\": [\n {\n \"status\": \"success\",\n \"value\": \"new-team-member@acme.com\"\n },\n {\n \"message\": \"Line 2: empty row\",\n \"status\": \"error\",\n \"value\": \"\"\n },\n {\n \"message\": \"Line 3: email already exists in the specified team\",\n \"status\": \"error\",\n \"value\": \"existing-team-member@acme.com\"\n },\n {\n \"message\": \"Line 4: invalid email formatting\",\n \"status\": \"error\",\n \"value\": \"invalid email format\"\n }\n ]\n}\n```\n\nMessage | Resolution\n--- | ---\nEmpty row | This line is blank. Add an email address and try again.\nDuplicate entry | This email address appears in the file twice. Remove the email from the file and try again.\nEmail already exists in the specified team | This member is already on your team. Remove the email from the file and try again.\nInvalid formatting | This email address is not formatted correctly. Fix the formatting and try again.\nEmail does not belong to a LaunchDarkly member | The email address doesn't belong to a LaunchDarkly account member. Invite them to LaunchDarkly, then re-add them to the team.\n\nOn a `400` response, the `message` field may contain errors specific to this endpoint.\n\nExample `400` response:\n```json\n{\n \"code\": \"invalid_request\",\n \"message\": \"Unable to process file\"\n}\n```\n\nMessage | Resolution\n--- | ---\nUnable to process file | LaunchDarkly could not process the file for an unspecified reason. Review your file for errors and try again.\nFile exceeds 25mb | Break up your file into multiple files of less than 25mbs each.\nAll emails have invalid formatting | None of the email addresses in the file are in the correct format. Fix the formatting and try again.\nAll emails belong to existing team members | All listed members are already on this team. Populate the file with member emails that do not belong to the team and try again.\nFile is empty | The CSV file does not contain any email addresses. Populate the file and try again.\nNo emails belong to members of your LaunchDarkly organization | None of the email addresses belong to members of your LaunchDarkly account. Invite these members to LaunchDarkly, then re-add them to the team.\n", + Use: "post-team-members", + Params: []Param{ + { + Name: "team-key", + In: "path", + Description: "The team key", + Type: "string", + }, + }, + HTTPMethod: "POST", + RequiresBody: true, + Path: "/api/v2/teams/{teamKey}/members", + }) + } diff --git a/cmd/resources/resource_cmds.tmpl b/cmd/resources/resource_cmds.tmpl new file mode 100644 index 00000000..763c29ad --- /dev/null +++ b/cmd/resources/resource_cmds.tmpl @@ -0,0 +1,42 @@ +// This file is generated by gen_resources.go; DO NOT EDIT. + +package resources + +import ( + "github.com/spf13/cobra" + + "ldcli/internal/analytics" + "ldcli/internal/resources" +) + +func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyticsTracker analytics.Tracker) { + // Resource commands{{ range $resName, $resData := .Resources }} + gen_{{ $resName }}ResourceCmd := NewResourceCmd( + rootCmd, + analyticsTracker, + "{{ $resData.Name }}", + "Make requests (list, create, etc.) on {{ $resData.DisplayName }}", + {{ $resData.Description }}, + ) + {{ end }} + + // Operation commands{{ range $resName, $resData := .Resources }}{{ range $opName, $opData := $resData.Operations }} + NewOperationCmd(gen_{{ $resName }}ResourceCmd, client, OperationData{ + Short: {{ $opData.Short }}, + Long: {{ $opData.Long }}, + Use: "{{ $opData.Use }}", + Params: []Param{ {{ range $param := $opData.Params }} + { + Name: "{{ $param.Name }}", + In: "{{ $param.In }}", + Description: {{ $param.Description }}, + Type: "{{ $param.Type }}", + }, {{ end }} + }, + HTTPMethod: "{{ $opData.HTTPMethod }}", + RequiresBody: {{ $opData.RequiresBody }}, + Path: "{{ $opData.Path }}", + }) + + {{ end }}{{ end }} +} \ No newline at end of file diff --git a/cmd/resources/resources.go b/cmd/resources/resources.go index 7899f3f9..f044975b 100644 --- a/cmd/resources/resources.go +++ b/cmd/resources/resources.go @@ -7,10 +7,10 @@ import ( "net/url" "os" "regexp" - "strconv" "strings" "github.com/getkin/kin-openapi/openapi3" + "github.com/iancoleman/strcase" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -29,6 +29,7 @@ type TemplateData struct { type ResourceData struct { Name string + DisplayName string Description string Operations map[string]OperationData } @@ -52,6 +53,11 @@ type Param struct { Required bool } +func jsonString(s string) string { + bs, _ := json.Marshal(s) + return string(bs) +} + func GetTemplateData(fileName string) (TemplateData, error) { rawFile, err := os.ReadFile(fileName) if err != nil { @@ -66,16 +72,25 @@ func GetTemplateData(fileName string) (TemplateData, error) { resources := make(map[string]ResourceData) for _, r := range spec.Tags { - resources[r.Name] = ResourceData{ - Name: r.Name, - Description: r.Description, + if strings.Contains(r.Name, "(beta)") { + // skip beta resources for now + continue + } + resources[strcase.ToCamel(r.Name)] = ResourceData{ + DisplayName: strings.ToLower(r.Name), + Name: strcase.ToKebab(strings.ToLower(r.Name)), + Description: jsonString(r.Description), Operations: make(map[string]OperationData, 0), } } for path, pathItem := range spec.Paths.Map() { for method, op := range pathItem.Operations() { - tag := op.Tags[0] // TODO: confirm each op only has one tag + tag := op.Tags[0] // each op only has one tag + if strings.Contains(tag, "(beta)") { + // skip beta resources for now + continue + } resource, ok := resources[tag] if !ok { log.Printf("Matching resource not found for %s operation's tag: %s", op.OperationID, tag) @@ -85,8 +100,8 @@ func GetTemplateData(fileName string) (TemplateData, error) { use := getCmdUse(method, op, spec) operation := OperationData{ - Short: op.Summary, - Long: op.Description, + Short: jsonString(op.Summary), + Long: jsonString(op.Description), Use: use, Params: make([]Param, 0), HTTPMethod: method, @@ -99,9 +114,9 @@ func GetTemplateData(fileName string) (TemplateData, error) { // TODO: confirm if we only have one type per param b/c somehow this is a slice types := *p.Value.Schema.Value.Type param := Param{ - Name: p.Value.Name, + Name: strcase.ToKebab(p.Value.Name), In: p.Value.In, - Description: p.Value.Description, + Description: jsonString(p.Value.Description), Type: types[0], Required: p.Value.Required, } @@ -117,39 +132,42 @@ func GetTemplateData(fileName string) (TemplateData, error) { } func getCmdUse(method string, op *openapi3.Operation, spec *openapi3.T) string { - methodMap := map[string]string{ - "GET": "get", - "POST": "create", - "PUT": "replace", // TODO: confirm this - "DELETE": "delete", - "PATCH": "update", - } - - use := methodMap[method] - - var schema *openapi3.SchemaRef - for respType, respInfo := range op.Responses.Map() { - respCode, _ := strconv.Atoi(respType) - if respCode < 300 { - for _, s := range respInfo.Value.Content { - schemaName := strings.TrimPrefix(s.Schema.Ref, "#/components/schemas/") - schema = spec.Components.Schemas[schemaName] - } - } - } - - if schema == nil { - // probably won't need to keep this logging in but leaving it for debugging purposes - log.Printf("No response type defined for %s", op.OperationID) - } else { - for propName := range schema.Value.Properties { - if propName == "items" { - use = "list" - break - } - } - } - return use + return strcase.ToKebab(op.OperationID) + + // TODO: work with operation ID & response type to stripe out resource name and update post -> create, get -> list, etc. + //methodMap := map[string]string{ + // "GET": "get", + // "POST": "create", + // "PUT": "replace", // TODO: confirm this + // "DELETE": "delete", + // "PATCH": "update", + //} + // + //use := methodMap[method] + // + //var schema *openapi3.SchemaRef + //for respType, respInfo := range op.Responses.Map() { + // respCode, _ := strconv.Atoi(respType) + // if respCode < 300 { + // for _, s := range respInfo.Value.Content { + // schemaName := strings.TrimPrefix(s.Schema.Ref, "#/components/schemas/") + // schema = spec.Components.Schemas[schemaName] + // } + // } + //} + // + //if schema == nil { + // // probably won't need to keep this logging in but leaving it for debugging purposes + // log.Printf("No response type defined for %s", op.OperationID) + //} else { + // for propName := range schema.Value.Properties { + // if propName == "items" { + // use = "list" + // break + // } + // } + //} + //return use } func NewResourceCmd(parentCmd *cobra.Command, analyticsTracker analytics.Tracker, resourceName, shortDescription, longDescription string) *cobra.Command { @@ -200,25 +218,18 @@ func (op *OperationCmd) initFlags() error { } for _, p := range op.Params { - shorthand := fmt.Sprintf(p.Name[0:1]) // todo: how do we handle potential dupes - // TODO: consider handling these all as strings - switch p.Type { - case "string": - op.cmd.Flags().StringP(p.Name, shorthand, "", p.Description) - case "int": - op.cmd.Flags().IntP(p.Name, shorthand, 0, p.Description) - case "boolean": - op.cmd.Flags().BoolP(p.Name, shorthand, false, p.Description) - } + flagName := strcase.ToKebab(p.Name) + + op.cmd.Flags().String(flagName, "", p.Description) - if p.In == "path" { - err := op.cmd.MarkFlagRequired(p.Name) + if p.In == "path" || p.Required { + err := op.cmd.MarkFlagRequired(flagName) if err != nil { return err } } - err := viper.BindPFlag(p.Name, op.cmd.Flags().Lookup(p.Name)) + err := viper.BindPFlag(flagName, op.cmd.Flags().Lookup(flagName)) if err != nil { return err } diff --git a/go.mod b/go.mod index 72aabd28..8e71836f 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/charmbracelet/lipgloss v0.10.0 github.com/getkin/kin-openapi v0.124.0 github.com/google/uuid v1.6.0 + github.com/iancoleman/strcase v0.3.0 github.com/launchdarkly/api-client-go/v14 v14.0.0 github.com/mitchellh/go-homedir v1.1.0 github.com/muesli/reflow v0.3.0 diff --git a/go.sum b/go.sum index 58f11249..1b1ea4f7 100644 --- a/go.sum +++ b/go.sum @@ -140,6 +140,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= diff --git a/vendor/github.com/iancoleman/strcase/.travis.yml b/vendor/github.com/iancoleman/strcase/.travis.yml new file mode 100644 index 00000000..79ebc569 --- /dev/null +++ b/vendor/github.com/iancoleman/strcase/.travis.yml @@ -0,0 +1,10 @@ +sudo: false +language: go +go: + - 1.10.x + - 1.11.x + - 1.12.x + - 1.13.x + - 1.14.x + - 1.15.x + - master diff --git a/vendor/github.com/iancoleman/strcase/LICENSE b/vendor/github.com/iancoleman/strcase/LICENSE new file mode 100644 index 00000000..3e87ff70 --- /dev/null +++ b/vendor/github.com/iancoleman/strcase/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Ian Coleman +Copyright (c) 2018 Ma_124, + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, Subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or Substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/iancoleman/strcase/README.md b/vendor/github.com/iancoleman/strcase/README.md new file mode 100644 index 00000000..e72d9e97 --- /dev/null +++ b/vendor/github.com/iancoleman/strcase/README.md @@ -0,0 +1,59 @@ +# strcase +[![Godoc Reference](https://godoc.org/github.com/iancoleman/strcase?status.svg)](http://godoc.org/github.com/iancoleman/strcase) +[![Build Status](https://travis-ci.com/iancoleman/strcase.svg)](https://travis-ci.com/iancoleman/strcase) +[![Coverage](http://gocover.io/_badge/github.com/iancoleman/strcase?0)](http://gocover.io/github.com/iancoleman/strcase) +[![Go Report Card](https://goreportcard.com/badge/github.com/iancoleman/strcase)](https://goreportcard.com/report/github.com/iancoleman/strcase) + +strcase is a go package for converting string case to various cases (e.g. [snake case](https://en.wikipedia.org/wiki/Snake_case) or [camel case](https://en.wikipedia.org/wiki/CamelCase)) to see the full conversion table below. + +## Example + +```go +s := "AnyKind of_string" +``` + +| Function | Result | +|-------------------------------------------|----------------------| +| `ToSnake(s)` | `any_kind_of_string` | +| `ToSnakeWithIgnore(s, '.')` | `any_kind.of_string` | +| `ToScreamingSnake(s)` | `ANY_KIND_OF_STRING` | +| `ToKebab(s)` | `any-kind-of-string` | +| `ToScreamingKebab(s)` | `ANY-KIND-OF-STRING` | +| `ToDelimited(s, '.')` | `any.kind.of.string` | +| `ToScreamingDelimited(s, '.', '', true)` | `ANY.KIND.OF.STRING` | +| `ToScreamingDelimited(s, '.', ' ', true)` | `ANY.KIND OF.STRING` | +| `ToCamel(s)` | `AnyKindOfString` | +| `ToLowerCamel(s)` | `anyKindOfString` | + + +## Install + +```bash +go get -u github.com/iancoleman/strcase +``` + +## Custom Acronyms for ToCamel && ToLowerCamel + +Often times text can contain specific acronyms which you need to be handled a certain way. +Out of the box `strcase` treats the string "ID" as "Id" or "id" but there is no way to cater +for every case in the wild. + +To configure your custom acronym globally you can use the following before running any conversion + +```go +import ( + "github.com/iancoleman/strcase" +) + +func init() { + // results in "Api" using ToCamel("API") + // results in "api" using ToLowerCamel("API") + strcase.ConfigureAcronym("API", "api") + + // results in "PostgreSQL" using ToCamel("PostgreSQL") + // results in "postgreSQL" using ToLowerCamel("PostgreSQL") + strcase.ConfigureAcronym("PostgreSQL", "PostgreSQL") + +} + +``` diff --git a/vendor/github.com/iancoleman/strcase/acronyms.go b/vendor/github.com/iancoleman/strcase/acronyms.go new file mode 100644 index 00000000..7a2fb09e --- /dev/null +++ b/vendor/github.com/iancoleman/strcase/acronyms.go @@ -0,0 +1,13 @@ +package strcase + +import ( + "sync" +) + +var uppercaseAcronym = sync.Map{} + //"ID": "id", + +// ConfigureAcronym allows you to add additional words which will be considered acronyms +func ConfigureAcronym(key, val string) { + uppercaseAcronym.Store(key, val) +} diff --git a/vendor/github.com/iancoleman/strcase/camel.go b/vendor/github.com/iancoleman/strcase/camel.go new file mode 100644 index 00000000..6b886894 --- /dev/null +++ b/vendor/github.com/iancoleman/strcase/camel.go @@ -0,0 +1,87 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Ian Coleman + * Copyright (c) 2018 Ma_124, + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, Subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or Substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package strcase + +import ( + "strings" +) + +// Converts a string to CamelCase +func toCamelInitCase(s string, initCase bool) string { + s = strings.TrimSpace(s) + if s == "" { + return s + } + a, hasAcronym := uppercaseAcronym.Load(s) + if hasAcronym { + s = a.(string) + } + + n := strings.Builder{} + n.Grow(len(s)) + capNext := initCase + prevIsCap := false + for i, v := range []byte(s) { + vIsCap := v >= 'A' && v <= 'Z' + vIsLow := v >= 'a' && v <= 'z' + if capNext { + if vIsLow { + v += 'A' + v -= 'a' + } + } else if i == 0 { + if vIsCap { + v += 'a' + v -= 'A' + } + } else if prevIsCap && vIsCap && !hasAcronym { + v += 'a' + v -= 'A' + } + prevIsCap = vIsCap + + if vIsCap || vIsLow { + n.WriteByte(v) + capNext = false + } else if vIsNum := v >= '0' && v <= '9'; vIsNum { + n.WriteByte(v) + capNext = true + } else { + capNext = v == '_' || v == ' ' || v == '-' || v == '.' + } + } + return n.String() +} + +// ToCamel converts a string to CamelCase +func ToCamel(s string) string { + return toCamelInitCase(s, true) +} + +// ToLowerCamel converts a string to lowerCamelCase +func ToLowerCamel(s string) string { + return toCamelInitCase(s, false) +} diff --git a/vendor/github.com/iancoleman/strcase/doc.go b/vendor/github.com/iancoleman/strcase/doc.go new file mode 100644 index 00000000..5e1825b9 --- /dev/null +++ b/vendor/github.com/iancoleman/strcase/doc.go @@ -0,0 +1,12 @@ +// Package strcase converts strings to various cases. See the conversion table below: +// | Function | Result | +// |---------------------------------|--------------------| +// | ToSnake(s) | any_kind_of_string | +// | ToScreamingSnake(s) | ANY_KIND_OF_STRING | +// | ToKebab(s) | any-kind-of-string | +// | ToScreamingKebab(s) | ANY-KIND-OF-STRING | +// | ToDelimited(s, '.') | any.kind.of.string | +// | ToScreamingDelimited(s, '.') | ANY.KIND.OF.STRING | +// | ToCamel(s) | AnyKindOfString | +// | ToLowerCamel(s) | anyKindOfString | +package strcase diff --git a/vendor/github.com/iancoleman/strcase/snake.go b/vendor/github.com/iancoleman/strcase/snake.go new file mode 100644 index 00000000..df018bc7 --- /dev/null +++ b/vendor/github.com/iancoleman/strcase/snake.go @@ -0,0 +1,115 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Ian Coleman + * Copyright (c) 2018 Ma_124, + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, Subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or Substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package strcase + +import ( + "strings" +) + +// ToSnake converts a string to snake_case +func ToSnake(s string) string { + return ToDelimited(s, '_') +} + +func ToSnakeWithIgnore(s string, ignore string) string { + return ToScreamingDelimited(s, '_', ignore, false) +} + +// ToScreamingSnake converts a string to SCREAMING_SNAKE_CASE +func ToScreamingSnake(s string) string { + return ToScreamingDelimited(s, '_', "", true) +} + +// ToKebab converts a string to kebab-case +func ToKebab(s string) string { + return ToDelimited(s, '-') +} + +// ToScreamingKebab converts a string to SCREAMING-KEBAB-CASE +func ToScreamingKebab(s string) string { + return ToScreamingDelimited(s, '-', "", true) +} + +// ToDelimited converts a string to delimited.snake.case +// (in this case `delimiter = '.'`) +func ToDelimited(s string, delimiter uint8) string { + return ToScreamingDelimited(s, delimiter, "", false) +} + +// ToScreamingDelimited converts a string to SCREAMING.DELIMITED.SNAKE.CASE +// (in this case `delimiter = '.'; screaming = true`) +// or delimited.snake.case +// (in this case `delimiter = '.'; screaming = false`) +func ToScreamingDelimited(s string, delimiter uint8, ignore string, screaming bool) string { + s = strings.TrimSpace(s) + n := strings.Builder{} + n.Grow(len(s) + 2) // nominal 2 bytes of extra space for inserted delimiters + for i, v := range []byte(s) { + vIsCap := v >= 'A' && v <= 'Z' + vIsLow := v >= 'a' && v <= 'z' + if vIsLow && screaming { + v += 'A' + v -= 'a' + } else if vIsCap && !screaming { + v += 'a' + v -= 'A' + } + + // treat acronyms as words, eg for JSONData -> JSON is a whole word + if i+1 < len(s) { + next := s[i+1] + vIsNum := v >= '0' && v <= '9' + nextIsCap := next >= 'A' && next <= 'Z' + nextIsLow := next >= 'a' && next <= 'z' + nextIsNum := next >= '0' && next <= '9' + // add underscore if next letter case type is changed + if (vIsCap && (nextIsLow || nextIsNum)) || (vIsLow && (nextIsCap || nextIsNum)) || (vIsNum && (nextIsCap || nextIsLow)) { + prevIgnore := ignore != "" && i > 0 && strings.ContainsAny(string(s[i-1]), ignore) + if !prevIgnore { + if vIsCap && nextIsLow { + if prevIsCap := i > 0 && s[i-1] >= 'A' && s[i-1] <= 'Z'; prevIsCap { + n.WriteByte(delimiter) + } + } + n.WriteByte(v) + if vIsLow || vIsNum || nextIsNum { + n.WriteByte(delimiter) + } + continue + } + } + } + + if (v == ' ' || v == '_' || v == '-' || v == '.') && !strings.ContainsAny(string(v), ignore) { + // replace space/underscore/hyphen/dot with delimiter + n.WriteByte(delimiter) + } else { + n.WriteByte(v) + } + } + + return n.String() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index e81de125..ba1c104e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -108,6 +108,9 @@ github.com/hashicorp/hcl/hcl/token github.com/hashicorp/hcl/json/parser github.com/hashicorp/hcl/json/scanner github.com/hashicorp/hcl/json/token +# github.com/iancoleman/strcase v0.3.0 +## explicit; go 1.16 +github.com/iancoleman/strcase # github.com/inconshreveable/mousetrap v1.1.0 ## explicit; go 1.18 github.com/inconshreveable/mousetrap From 5956bc110a71e045f657bed0adcb4cf202b2cab8 Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Thu, 2 May 2024 16:52:04 -0700 Subject: [PATCH 2/5] add supports semantic patch to template and data --- cmd/resources/resource_cmds.go | 56 ++++++++++++++++++-------------- cmd/resources/resource_cmds.tmpl | 7 ++-- cmd/resources/resources.go | 20 ++++++++---- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/cmd/resources/resource_cmds.go b/cmd/resources/resource_cmds.go index b25b4146..547cc72c 100644 --- a/cmd/resources/resource_cmds.go +++ b/cmd/resources/resource_cmds.go @@ -32,9 +32,10 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti Type: "string", }, }, - HTTPMethod: "DELETE", - RequiresBody: false, - Path: "/api/v2/teams/{teamKey}", + HTTPMethod: "DELETE", + RequiresBody: false, + Path: "/api/v2/teams/{teamKey}", + SupportsSemanticPatch: false, }) NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ @@ -55,9 +56,10 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti Type: "string", }, }, - HTTPMethod: "GET", - RequiresBody: false, - Path: "/api/v2/teams/{teamKey}", + HTTPMethod: "GET", + RequiresBody: false, + Path: "/api/v2/teams/{teamKey}", + SupportsSemanticPatch: false, }) NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ @@ -84,9 +86,10 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti Type: "integer", }, }, - HTTPMethod: "GET", - RequiresBody: false, - Path: "/api/v2/teams/{teamKey}/maintainers", + HTTPMethod: "GET", + RequiresBody: false, + Path: "/api/v2/teams/{teamKey}/maintainers", + SupportsSemanticPatch: false, }) NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ @@ -113,9 +116,10 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti Type: "integer", }, }, - HTTPMethod: "GET", - RequiresBody: false, - Path: "/api/v2/teams/{teamKey}/roles", + HTTPMethod: "GET", + RequiresBody: false, + Path: "/api/v2/teams/{teamKey}/roles", + SupportsSemanticPatch: false, }) NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ @@ -148,9 +152,10 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti Type: "string", }, }, - HTTPMethod: "GET", - RequiresBody: false, - Path: "/api/v2/teams", + HTTPMethod: "GET", + RequiresBody: false, + Path: "/api/v2/teams", + SupportsSemanticPatch: false, }) NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ @@ -171,9 +176,10 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti Type: "string", }, }, - HTTPMethod: "PATCH", - RequiresBody: true, - Path: "/api/v2/teams/{teamKey}", + HTTPMethod: "PATCH", + RequiresBody: true, + Path: "/api/v2/teams/{teamKey}", + SupportsSemanticPatch: true, }) NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ @@ -188,9 +194,10 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti Type: "string", }, }, - HTTPMethod: "POST", - RequiresBody: true, - Path: "/api/v2/teams", + HTTPMethod: "POST", + RequiresBody: true, + Path: "/api/v2/teams", + SupportsSemanticPatch: false, }) NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ @@ -205,9 +212,10 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti Type: "string", }, }, - HTTPMethod: "POST", - RequiresBody: true, - Path: "/api/v2/teams/{teamKey}/members", + HTTPMethod: "POST", + RequiresBody: true, + Path: "/api/v2/teams/{teamKey}/members", + SupportsSemanticPatch: false, }) } diff --git a/cmd/resources/resource_cmds.tmpl b/cmd/resources/resource_cmds.tmpl index 763c29ad..6e16495b 100644 --- a/cmd/resources/resource_cmds.tmpl +++ b/cmd/resources/resource_cmds.tmpl @@ -33,9 +33,10 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti Type: "{{ $param.Type }}", }, {{ end }} }, - HTTPMethod: "{{ $opData.HTTPMethod }}", - RequiresBody: {{ $opData.RequiresBody }}, - Path: "{{ $opData.Path }}", + HTTPMethod: "{{ $opData.HTTPMethod }}", + RequiresBody: {{ $opData.RequiresBody }}, + Path: "{{ $opData.Path }}", + SupportsSemanticPatch: {{ $opData.SupportsSemanticPatch }}, }) {{ end }}{{ end }} diff --git a/cmd/resources/resources.go b/cmd/resources/resources.go index f044975b..3910bc40 100644 --- a/cmd/resources/resources.go +++ b/cmd/resources/resources.go @@ -99,14 +99,20 @@ func GetTemplateData(fileName string) (TemplateData, error) { use := getCmdUse(method, op, spec) + var supportsSemanticPatch bool + if strings.Contains(op.Description, "semantic patch") { + supportsSemanticPatch = true + } + operation := OperationData{ - Short: jsonString(op.Summary), - Long: jsonString(op.Description), - Use: use, - Params: make([]Param, 0), - HTTPMethod: method, - RequiresBody: method == "PUT" || method == "POST" || method == "PATCH", - Path: path, + Short: jsonString(op.Summary), + Long: jsonString(op.Description), + Use: use, + Params: make([]Param, 0), + HTTPMethod: method, + RequiresBody: method == "PUT" || method == "POST" || method == "PATCH", + Path: path, + SupportsSemanticPatch: supportsSemanticPatch, } for _, p := range op.Parameters { From 61e7522a520fb8a46c80368a59b04a30f649b5bd Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Thu, 2 May 2024 17:12:55 -0700 Subject: [PATCH 3/5] fix test --- cmd/resources/gen_resources_test.go | 4 +- .../test_data/expected_template_data.json | 166 +++++++++--------- 2 files changed, 87 insertions(+), 83 deletions(-) diff --git a/cmd/resources/gen_resources_test.go b/cmd/resources/gen_resources_test.go index 95d15541..f239115a 100644 --- a/cmd/resources/gen_resources_test.go +++ b/cmd/resources/gen_resources_test.go @@ -22,7 +22,5 @@ func TestGetTemplateData(t *testing.T) { err = json.Unmarshal(expectedFromFile, &expected) require.NoError(t, err) - t.Run("succeeds with single get resource", func(t *testing.T) { - assert.Equal(t, expected, actual) - }) + assert.Equal(t, expected, actual) } diff --git a/cmd/resources/test_data/expected_template_data.json b/cmd/resources/test_data/expected_template_data.json index 91973e93..e433dbd5 100644 --- a/cmd/resources/test_data/expected_template_data.json +++ b/cmd/resources/test_data/expected_template_data.json @@ -1,107 +1,113 @@ { - "Resources": { - "Teams": { - "Name": "Teams", - "Description": "A team is a group of members in your LaunchDarkly account.", - "Operations": { - "deleteTeam": { - "Short": "Delete team", - "Long": "Delete a team by key.", - "Use": "delete", - "Params": [ + "Resources":{ + "Teams":{ + "Name":"teams", + "DisplayName":"teams", + "Description":"\"A team is a group of members in your LaunchDarkly account.\"", + "Operations":{ + "deleteTeam":{ + "Short":"\"Delete team\"", + "Long":"\"Delete a team by key.\"", + "Use":"delete-team", + "Params":[ { - "Name": "teamKey", - "In": "path", - "Description": "The team key", - "Type": "string", - "Required": true + "Name":"team-key", + "In":"path", + "Description":"\"The team key\"", + "Type":"string", + "Required":true } ], - "HTTPMethod": "DELETE", - "RequiresBody": false, - "Path": "/api/v2/teams/{teamKey}" + "HTTPMethod":"DELETE", + "RequiresBody":false, + "Path":"/api/v2/teams/{teamKey}", + "SupportsSemanticPatch":false }, - "getTeam": { - "Short": "Get team", - "Long": "Get team", - "Use": "get", - "Params": [ + "getTeam":{ + "Short":"\"Get team\"", + "Long":"\"Get team\"", + "Use":"get-team", + "Params":[ { - "Name": "teamKey", - "In": "path", - "Description": "The team key.", - "Type": "string", - "Required": true + "Name":"team-key", + "In":"path", + "Description":"\"The team key.\"", + "Type":"string", + "Required":true }, { - "Name": "expand", - "In": "query", - "Description": "A comma-separated list of properties that can reveal additional information in the response.", - "Type": "string", - "Required": false + "Name":"expand", + "In":"query", + "Description":"\"A comma-separated list of properties that can reveal additional information in the response.\"", + "Type":"string", + "Required":false } ], - "HTTPMethod": "GET", - "RequiresBody": false, - "Path": "/api/v2/teams/{teamKey}" + "HTTPMethod":"GET", + "RequiresBody":false, + "Path":"/api/v2/teams/{teamKey}", + "SupportsSemanticPatch":false }, - "getTeams": { - "Short": "List teams", - "Long": "Return a list of teams.", - "Use": "list", - "Params": [ + "getTeams":{ + "Short":"\"List teams\"", + "Long":"\"Return a list of teams.\"", + "Use":"get-teams", + "Params":[ { - "Name": "limit", - "In": "query", - "Description": "The number of teams to return in the response. Defaults to 20.", - "Type": "integer", - "Required": false + "Name":"limit", + "In":"query", + "Description":"\"The number of teams to return in the response. Defaults to 20.\"", + "Type":"integer", + "Required":false } ], - "HTTPMethod": "GET", - "RequiresBody": false, - "Path": "/api/v2/teams" + "HTTPMethod":"GET", + "RequiresBody":false, + "Path":"/api/v2/teams", + "SupportsSemanticPatch":false }, - "patchTeam": { - "Short": "Update team", - "Long": "Perform a partial update to a team.", - "Use": "update", - "Params": [ + "patchTeam":{ + "Short":"\"Update team\"", + "Long":"\"Perform a partial update to a team.\"", + "Use":"patch-team", + "Params":[ { - "Name": "teamKey", - "In": "path", - "Description": "The team key", - "Type": "string", - "Required": true + "Name":"team-key", + "In":"path", + "Description":"\"The team key\"", + "Type":"string", + "Required":true }, { - "Name": "expand", - "In": "query", - "Description": "A comma-separated list of properties.", - "Type": "string", - "Required": false + "Name":"expand", + "In":"query", + "Description":"\"A comma-separated list of properties.\"", + "Type":"string", + "Required":false } ], - "HTTPMethod": "PATCH", - "RequiresBody": true, - "Path": "/api/v2/teams/{teamKey}" + "HTTPMethod":"PATCH", + "RequiresBody":true, + "Path":"/api/v2/teams/{teamKey}", + "SupportsSemanticPatch":false }, - "postTeam": { - "Short": "Create team", - "Long": "Create a team.", - "Use": "create", - "Params": [ + "postTeam":{ + "Short":"\"Create team\"", + "Long":"\"Create a team.\"", + "Use":"post-team", + "Params":[ { - "Name": "expand", - "In": "query", - "Description": "A comma-separated list of properties.", - "Type": "string", - "Required": false + "Name":"expand", + "In":"query", + "Description":"\"A comma-separated list of properties.\"", + "Type":"string", + "Required":false } ], - "HTTPMethod": "POST", - "RequiresBody": true, - "Path": "/api/v2/teams" + "HTTPMethod":"POST", + "RequiresBody":true, + "Path":"/api/v2/teams", + "SupportsSemanticPatch":false } } } From 5555c6e705093ad9c6462ddc91cc47c491cfac23 Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Thu, 2 May 2024 17:15:36 -0700 Subject: [PATCH 4/5] fix more tests --- cmd/resources/resource_cmds_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/resources/resource_cmds_test.go b/cmd/resources/resource_cmds_test.go index 464b567e..a2d9826a 100644 --- a/cmd/resources/resource_cmds_test.go +++ b/cmd/resources/resource_cmds_test.go @@ -14,7 +14,7 @@ func TestCreateTeam(t *testing.T) { t.Run("help shows postTeam description", func(t *testing.T) { args := []string{ "teams", - "create", + "post-team", // temporary command name "--help", } @@ -27,7 +27,7 @@ func TestCreateTeam(t *testing.T) { t.Skip("TODO: add back when mock client is added") args := []string{ "teams", - "create", + "post-team", // temporary command name "--access-token", "abcd1234", "--data", From 52f7538bc54c575ea764bd2798f0688993d16e47 Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Fri, 3 May 2024 09:44:04 -0700 Subject: [PATCH 5/5] update template --- cmd/resources/resource_cmds.go | 2 ++ cmd/resources/resource_cmds.tmpl | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/resources/resource_cmds.go b/cmd/resources/resource_cmds.go index 547cc72c..112ca4be 100644 --- a/cmd/resources/resource_cmds.go +++ b/cmd/resources/resource_cmds.go @@ -11,6 +11,7 @@ import ( func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyticsTracker analytics.Tracker) { // Resource commands + gen_TeamsResourceCmd := NewResourceCmd( rootCmd, analyticsTracker, @@ -20,6 +21,7 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti ) // Operation commands + NewOperationCmd(gen_TeamsResourceCmd, client, OperationData{ Short: "Delete team", Long: "Delete a team by key. To learn more, read [Deleting a team](https://docs.launchdarkly.com/home/teams/managing#deleting-a-team).", diff --git a/cmd/resources/resource_cmds.tmpl b/cmd/resources/resource_cmds.tmpl index 6e16495b..1f3b2de2 100644 --- a/cmd/resources/resource_cmds.tmpl +++ b/cmd/resources/resource_cmds.tmpl @@ -10,7 +10,8 @@ import ( ) func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyticsTracker analytics.Tracker) { - // Resource commands{{ range $resName, $resData := .Resources }} + // Resource commands + {{ range $resName, $resData := .Resources }} gen_{{ $resName }}ResourceCmd := NewResourceCmd( rootCmd, analyticsTracker, @@ -20,7 +21,8 @@ func AddAllResourceCmds(rootCmd *cobra.Command, client resources.Client, analyti ) {{ end }} - // Operation commands{{ range $resName, $resData := .Resources }}{{ range $opName, $opData := $resData.Operations }} + // Operation commands + {{ range $resName, $resData := .Resources }}{{ range $opName, $opData := $resData.Operations }} NewOperationCmd(gen_{{ $resName }}ResourceCmd, client, OperationData{ Short: {{ $opData.Short }}, Long: {{ $opData.Long }},