-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: generate teams operation data from openapi spec (#226)
* add temp openapi spec just for teams * vendor openapi loader, read from file * generate template data from openapi spec (teams only) * get schema from component name not tag * add tests * fix imports * fix tests, remove pointers
- Loading branch information
1 parent
0141d07
commit e96fb54
Showing
131 changed files
with
22,824 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package resources | ||
|
||
import ( | ||
"log" | ||
"os" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/getkin/kin-openapi/openapi3" | ||
) | ||
|
||
type TemplateData struct { | ||
Resources map[string]ResourceData | ||
} | ||
|
||
type ResourceData struct { | ||
Name string | ||
Description string | ||
Operations map[string]OperationData | ||
} | ||
|
||
type OperationData struct { | ||
Short string | ||
Long string | ||
Use string | ||
Params []Param | ||
HTTPMethod string | ||
RequiresBody bool | ||
Path string | ||
SupportsSemanticPatch bool | ||
} | ||
|
||
type Param struct { | ||
Name string | ||
In string | ||
Description string | ||
Type string | ||
Required bool | ||
} | ||
|
||
func GetTemplateData(fileName string) (TemplateData, error) { | ||
rawFile, err := os.ReadFile(fileName) | ||
if err != nil { | ||
return TemplateData{}, err | ||
} | ||
|
||
loader := openapi3.NewLoader() | ||
spec, err := loader.LoadFromData(rawFile) | ||
if err != nil { | ||
return TemplateData{}, err | ||
} | ||
|
||
resources := make(map[string]ResourceData) | ||
for _, r := range spec.Tags { | ||
resources[r.Name] = ResourceData{ | ||
Name: r.Name, | ||
Description: 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 | ||
resource, ok := resources[tag] | ||
if !ok { | ||
log.Printf("Matching resource not found for %s operation's tag: %s", op.OperationID, tag) | ||
continue | ||
} | ||
|
||
use := getCmdUse(method, op, spec) | ||
|
||
operation := OperationData{ | ||
Short: op.Summary, | ||
Long: op.Description, | ||
Use: use, | ||
Params: make([]Param, 0), | ||
HTTPMethod: method, | ||
RequiresBody: method == "PUT" || method == "POST" || method == "PATCH", | ||
Path: path, | ||
} | ||
|
||
for _, p := range op.Parameters { | ||
if p.Value != nil { | ||
// 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, | ||
In: p.Value.In, | ||
Description: p.Value.Description, | ||
Type: types[0], | ||
Required: p.Value.Required, | ||
} | ||
operation.Params = append(operation.Params, param) | ||
} | ||
} | ||
|
||
resource.Operations[op.OperationID] = operation | ||
} | ||
} | ||
|
||
return TemplateData{Resources: resources}, nil | ||
} | ||
|
||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package resources_test | ||
|
||
import ( | ||
"encoding/json" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"ldcli/cmd/resources" | ||
) | ||
|
||
func TestGetTemplateData(t *testing.T) { | ||
actual, err := resources.GetTemplateData("test_data/test-openapi.json") | ||
assert.NoError(t, err) | ||
|
||
expectedFromFile, err := os.ReadFile("test_data/expected_template_data.json") | ||
require.NoError(t, err) | ||
|
||
var expected resources.TemplateData | ||
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) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
{ | ||
"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": [ | ||
{ | ||
"Name": "teamKey", | ||
"In": "path", | ||
"Description": "The team key", | ||
"Type": "string", | ||
"Required": true | ||
} | ||
], | ||
"HTTPMethod": "DELETE", | ||
"RequiresBody": false, | ||
"Path": "/api/v2/teams/{teamKey}" | ||
}, | ||
"getTeam": { | ||
"Short": "Get team", | ||
"Long": "Get team", | ||
"Use": "get", | ||
"Params": [ | ||
{ | ||
"Name": "teamKey", | ||
"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 | ||
} | ||
], | ||
"HTTPMethod": "GET", | ||
"RequiresBody": false, | ||
"Path": "/api/v2/teams/{teamKey}" | ||
}, | ||
"getTeams": { | ||
"Short": "List teams", | ||
"Long": "Return a list of teams.", | ||
"Use": "list", | ||
"Params": [ | ||
{ | ||
"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" | ||
}, | ||
"patchTeam": { | ||
"Short": "Update team", | ||
"Long": "Perform a partial update to a team.", | ||
"Use": "update", | ||
"Params": [ | ||
{ | ||
"Name": "teamKey", | ||
"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 | ||
} | ||
], | ||
"HTTPMethod": "PATCH", | ||
"RequiresBody": true, | ||
"Path": "/api/v2/teams/{teamKey}" | ||
}, | ||
"postTeam": { | ||
"Short": "Create team", | ||
"Long": "Create a team.", | ||
"Use": "create", | ||
"Params": [ | ||
{ | ||
"Name": "expand", | ||
"In": "query", | ||
"Description": "A comma-separated list of properties.", | ||
"Type": "string", | ||
"Required": false | ||
} | ||
], | ||
"HTTPMethod": "POST", | ||
"RequiresBody": true, | ||
"Path": "/api/v2/teams" | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.