diff --git a/go.mod b/go.mod index 921caab..d59bd57 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22.3 require ( github.com/BurntSushi/toml v1.4.0 - github.com/aep-dev/aep-lib-go v0.0.0-20250226051706-6eab5b746243 + github.com/aep-dev/aep-lib-go v0.0.0-20250506053130-6fd1d4e67239 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 ) @@ -14,5 +14,6 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/text v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index df8d9b4..21f6d90 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,16 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/aep-dev/aep-lib-go v0.0.0-20250226051706-6eab5b746243 h1:R0zh8mlKsE04B5MWcvs22xaeGzQrXWtRxqHemCh4jBY= -github.com/aep-dev/aep-lib-go v0.0.0-20250226051706-6eab5b746243/go.mod h1:M+h1D6T2uIUPelmaEsJbjR6JhqKsTlPX3lxp25zQQsk= +github.com/aep-dev/aep-lib-go v0.0.0-20250506053130-6fd1d4e67239 h1:X9ylZ4HkzEpBny2vAE9zLDtTXpoe1BHeqhnIpsZZfaA= +github.com/aep-dev/aep-lib-go v0.0.0-20250506053130-6fd1d4e67239/go.mod h1:YWfg3gjAGpvwFJnEQl1JjBGd0evGBKobt5wU/QxV6pA= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= +github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -16,7 +20,11 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go index 456c536..c8ad423 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,7 +25,7 @@ type API struct { func ReadConfigFromFile(file string) (*Config, error) { // Check if file exists first if _, err := os.Stat(file); os.IsNotExist(err) { - slog.Warn("Config file does not exist, using default configuration", "file", file) + slog.Debug("Config file does not exist, using default configuration", "file", file) return &Config{APIs: make(map[string]API)}, nil } diff --git a/internal/service/resource_definition.go b/internal/service/resource_definition.go index d4c51c9..b8ff91d 100644 --- a/internal/service/resource_definition.go +++ b/internal/service/resource_definition.go @@ -19,8 +19,9 @@ func ExecuteResourceCommand(r *api.Resource, args []string) (*http.Request, stri var parents []*string i := 1 - for i < len(r.PatternElems)-1 { - p := r.PatternElems[i] + patternElems := r.PatternElems() + for i < len(patternElems)-1 { + p := patternElems[i] flagName := p[1 : len(p)-1] var flagValue string parents = append(parents, &flagValue) @@ -31,9 +32,9 @@ func ExecuteResourceCommand(r *api.Resource, args []string) (*http.Request, stri withPrefix := func(path string) string { pElems := []string{} - for i, p := range r.PatternElems { + for i, p := range patternElems { // last element, we assume this was handled by the caller. - if i == len(r.PatternElems)-1 { + if i == len(patternElems)-1 { continue } if i%2 == 0 { @@ -46,10 +47,10 @@ func ExecuteResourceCommand(r *api.Resource, args []string) (*http.Request, stri return fmt.Sprintf("%s%s", prefix, path) } - if r.CreateMethod != nil { + if r.Methods.Create != nil { use := "create [id]" args := cobra.ExactArgs(1) - if !r.CreateMethod.SupportsUserSettableCreate { + if !r.Methods.Create.SupportsUserSettableCreate { use = "create" args = cobra.ExactArgs(0) } @@ -60,7 +61,7 @@ func ExecuteResourceCommand(r *api.Resource, args []string) (*http.Request, stri Args: args, Run: func(cmd *cobra.Command, args []string) { p := withPrefix("") - if r.CreateMethod.SupportsUserSettableCreate { + if r.Methods.Create.SupportsUserSettableCreate { id := args[0] p = withPrefix(fmt.Sprintf("?id=%s", id)) } @@ -78,7 +79,7 @@ func ExecuteResourceCommand(r *api.Resource, args []string) (*http.Request, stri c.AddCommand(createCmd) } - if r.GetMethod != nil { + if r.Methods.Get != nil { getCmd := &cobra.Command{ Use: "get [id]", Short: fmt.Sprintf("Get a %v", strings.ToLower(r.Singular)), @@ -92,7 +93,7 @@ func ExecuteResourceCommand(r *api.Resource, args []string) (*http.Request, stri c.AddCommand(getCmd) } - if r.UpdateMethod != nil { + if r.Methods.Update != nil { updateArgs := map[string]interface{}{} updateCmd := &cobra.Command{ @@ -116,7 +117,7 @@ func ExecuteResourceCommand(r *api.Resource, args []string) (*http.Request, stri c.AddCommand(updateCmd) } - if r.DeleteMethod != nil { + if r.Methods.Delete != nil { deleteCmd := &cobra.Command{ Use: "delete [id]", @@ -131,7 +132,7 @@ func ExecuteResourceCommand(r *api.Resource, args []string) (*http.Request, stri c.AddCommand(deleteCmd) } - if r.ListMethod != nil { + if r.Methods.List != nil { listCmd := &cobra.Command{ Use: "list", diff --git a/internal/service/resource_definition_test.go b/internal/service/resource_definition_test.go index aa17400..b41ddd6 100644 --- a/internal/service/resource_definition_test.go +++ b/internal/service/resource_definition_test.go @@ -8,50 +8,91 @@ import ( "github.com/aep-dev/aep-lib-go/pkg/openapi" ) -var projectResource = api.Resource{ - Singular: "project", - Plural: "projects", - PatternElems: []string{"projects", "{project}"}, - Parents: []*api.Resource{}, - Schema: &openapi.Schema{ - Properties: map[string]openapi.Schema{ - "name": { - Type: "string", - }, - "description": { - Type: "string", +func getTestAPI() *api.API { + projectResource := api.Resource{ + Singular: "project", + Plural: "projects", + Parents: []string{}, + Schema: &openapi.Schema{ + Properties: map[string]openapi.Schema{ + "name": { + Type: "string", + }, + "description": { + Type: "string", + }, + "active": { + Type: "boolean", + }, + "tags": { + Type: "array", + Items: &openapi.Schema{ + Type: "string", + }, + }, + "metadata": { + Type: "object", + }, + "priority": { + Type: "integer", + }, }, - "active": { - Type: "boolean", + Required: []string{"name"}, + }, + Methods: api.Methods{ + Get: &api.GetMethod{}, + List: &api.ListMethod{}, + Create: &api.CreateMethod{ + SupportsUserSettableCreate: true, }, - "tags": { - Type: "array", - Items: &openapi.Schema{ - Type: "string", + Update: &api.UpdateMethod{}, + Delete: &api.DeleteMethod{}, + }, + } + + a := &api.API{ + Name: "test", + ServerURL: "https://api.example.com", + Resources: map[string]*api.Resource{ + "project": &projectResource, + "dataset": &api.Resource{ + Singular: "dataset", + Plural: "datasets", + Parents: []string{"project"}, + Schema: &openapi.Schema{}, + Methods: api.Methods{ + Get: &api.GetMethod{}, + List: &api.ListMethod{}, + Create: &api.CreateMethod{}, + Update: &api.UpdateMethod{}, + Delete: &api.DeleteMethod{}, }, }, - "metadata": { - Type: "object", + "user": &api.Resource{ + Singular: "user", + Plural: "users", + Parents: []string{}, + Schema: &openapi.Schema{}, }, - "priority": { - Type: "integer", + "comment": &api.Resource{ + Singular: "comment", + Plural: "comments", + Parents: []string{}, + Schema: &openapi.Schema{}, }, }, - Required: []string{"name"}, - }, - GetMethod: &api.GetMethod{}, - ListMethod: &api.ListMethod{}, - CreateMethod: &api.CreateMethod{ - SupportsUserSettableCreate: true, - }, - UpdateMethod: &api.UpdateMethod{}, - DeleteMethod: &api.DeleteMethod{}, + } + err := api.AddImplicitFieldsAndValidate(a) + if err != nil { + panic(err) + } + return a } func TestExecuteCommand(t *testing.T) { tests := []struct { name string - resource api.Resource + resource string args []string expectedQuery string expectedPath string @@ -61,7 +102,7 @@ func TestExecuteCommand(t *testing.T) { }{ { name: "simple resource no parents", - resource: projectResource, + resource: "project", args: []string{"list"}, expectedPath: "projects", expectedMethod: "GET", @@ -70,7 +111,7 @@ func TestExecuteCommand(t *testing.T) { }, { name: "create with tags", - resource: projectResource, + resource: "project", args: []string{"create", "myproject", "--name=test-project", "--tags=tag1,tag2,tag3"}, expectedPath: "projects", expectedMethod: "POST", @@ -80,7 +121,7 @@ func TestExecuteCommand(t *testing.T) { }, { name: "create with tags quoted", - resource: projectResource, + resource: "project", args: []string{"create", "myproject", "--name=test-project", "--tags=\"tag1,\",tag2,tag3"}, expectedPath: "projects", expectedMethod: "POST", @@ -89,19 +130,8 @@ func TestExecuteCommand(t *testing.T) { body: `{"name":"test-project","tags":["tag1,","tag2","tag3"]}`, }, { - name: "resource with parent", - resource: api.Resource{ - Singular: "dataset", - Plural: "datasets", - PatternElems: []string{"projects", "{project}", "datasets", "{dataset}"}, - Parents: []*api.Resource{&projectResource}, - Schema: &openapi.Schema{}, - GetMethod: &api.GetMethod{}, - ListMethod: &api.ListMethod{}, - CreateMethod: &api.CreateMethod{}, - UpdateMethod: &api.UpdateMethod{}, - DeleteMethod: &api.DeleteMethod{}, - }, + name: "resource with parent", + resource: "dataset", args: []string{"--project=foo", "get", "abc"}, expectedPath: "projects/foo/datasets/abc", expectedMethod: "GET", @@ -112,7 +142,8 @@ func TestExecuteCommand(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - req, _, err := ExecuteResourceCommand(&tt.resource, tt.args) + a := getTestAPI() + req, _, err := ExecuteResourceCommand(a.Resources[tt.resource], tt.args) if (err != nil) != tt.wantErr { t.Errorf("ExecuteCommand() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/internal/service/service.go b/internal/service/service.go index 5f56e73..7c27eeb 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -57,11 +57,21 @@ func (s *ServiceCommand) Execute(args []string) (string, error) { if err != nil { return "", fmt.Errorf("unable to execute request: %v", err) } - return strings.Join([]string{output, reqOutput}, "\n"), nil + outputs := []string{} + for _, o := range []string{output, reqOutput} { + if o != "" { + outputs = append(outputs, o) + } + } + return strings.Join(outputs, "\n"), nil } func (s *ServiceCommand) doRequest(r *http.Request) (string, error) { - r.Header.Set("Content-Type", "application/json") + contentType := "application/json" + if r.Method == http.MethodPatch { + contentType = "application/merge-patch+json" + } + r.Header.Set("Content-Type", contentType) for k, v := range s.Headers { r.Header.Set(k, v) } diff --git a/internal/service/service_test.go b/internal/service/service_test.go index 66afa4d..9b98d33 100644 --- a/internal/service/service_test.go +++ b/internal/service/service_test.go @@ -3,21 +3,11 @@ package service import ( "strings" "testing" - - "github.com/aep-dev/aep-lib-go/pkg/api" ) func TestService_ExecuteCommand_ListResources(t *testing.T) { // Test setup - svc := NewServiceCommand(&api.API{ - ServerURL: "http://test.com", - Resources: map[string]*api.Resource{ - "project": &projectResource, - "user": {}, - "post": {}, - "comment": {}, - }, - }, nil, false, false) + svc := NewServiceCommand(getTestAPI(), nil, false, false) tests := []struct { name string @@ -28,12 +18,12 @@ func TestService_ExecuteCommand_ListResources(t *testing.T) { { name: "no arguments", args: []string{}, - expected: "Available resources:\n - comment\n - post\n - project\n - user\n", + expected: "Available resources:\n - comment\n - dataset\n - project\n - user\n", }, { name: "help flag", args: []string{"--help"}, - expected: "Available resources:\n - comment\n - post\n - project\n - user\n", + expected: "Available resources:\n - comment\n - dataset\n - project\n - user\n", }, { name: "unknown resource",