diff --git a/internal/actions/completion.go b/internal/actions/completion.go index 00f029e..ce527c1 100644 --- a/internal/actions/completion.go +++ b/internal/actions/completion.go @@ -1,6 +1,7 @@ package actions import ( + "fmt" "slices" "strings" @@ -36,6 +37,38 @@ func completePayloadName(acts *Actions) completionFunc { } } +func completeHTTPRoute(acts *Actions) completionFunc { + return func( + cmd *cobra.Command, + args []string, + _ string, + ) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveError + } + + payload, err := cmd.Flags().GetString("payload") + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + routes, err := (*acts).HTTPRoutesList(cmd.Context(), HTTPRoutesListParams{ + PayloadName: payload, + }) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + completions := make([]string, len(routes)) + + for i, r := range routes { + completions[i] = fmt.Sprintf("%d\t%s %s -> %d", r.Index, r.Method, r.Path, r.Code) + } + + return completions, cobra.ShellCompDirectiveNoFileComp + } +} + func completeOne(list []string) completionFunc { return func( _ *cobra.Command, diff --git a/internal/actions/http_routes.go b/internal/actions/http_routes.go index 1b36446..21a43a6 100644 --- a/internal/actions/http_routes.go +++ b/internal/actions/http_routes.go @@ -20,6 +20,7 @@ import ( const ( HTTPRoutesCreateResultID = "http-routes/create" + HTTPRoutesUpdateResultID = "http-routes/update" HTTPRoutesDeleteResultID = "http-routes/delete" HTTPRoutesClearResultID = "http-routes/clear" HTTPRoutesListResultID = "http-routes/list" @@ -27,6 +28,7 @@ const ( type HTTPActions interface { HTTPRoutesCreate(context.Context, HTTPRoutesCreateParams) (*HTTPRoutesCreateResult, errors.Error) + HTTPRoutesUpdate(context.Context, HTTPRoutesUpdateParams) (*HTTPRoutesUpdateResult, errors.Error) HTTPRoutesDelete(context.Context, HTTPRoutesDeleteParams) (*HTTPRoutesDeleteResult, errors.Error) HTTPRoutesClear(context.Context, HTTPRoutesClearParams) (HTTPRoutesClearResult, errors.Error) HTTPRoutesList(context.Context, HTTPRoutesListParams) (HTTPRoutesListResult, errors.Error) @@ -145,6 +147,150 @@ func HTTPRoutesCreateCommand(acts *Actions, p *HTTPRoutesCreateParams, local boo } } +// +// Update +// + +type HTTPRoutesUpdateParams struct { + Payload string `err:"payload" path:"payload" json:"-"` + Index int64 `err:"index" path:"index" json:"-"` + Method *string `err:"method" json:"method,omitempty"` + Path *string `err:"path" json:"path,omitempty"` + Code *int `err:"code" json:"code,omitempty"` + Headers map[string][]string `err:"headers" json:"headers,omitempty"` + Body *string `err:"body" json:"body,omitempty"` + IsDynamic *bool `err:"isDynamic" json:"isDynamic,omitempty"` +} + +func (p HTTPRoutesUpdateParams) Validate() error { + methods := []string{models.HTTPMethodAny} + methods = append(methods, models.HTTPMethods...) + + return validation.ValidateStruct(&p, + validation.Field(&p.Payload, validation.Required), + validation.Field(&p.Method, + validation.When(p.Method != nil, valid.OneOf(methods, false))), + validation.Field(&p.Path, + validation.When(p.Path != nil, validation.Match(regexp.MustCompile("^/.*")).Error(`path must start with "/"`))), + ) +} + +type HTTPRoutesUpdateResult struct { + HTTPRoute +} + +func (r HTTPRoutesUpdateResult) ResultID() string { + return HTTPRoutesUpdateResultID +} + +func HTTPRoutesUpdateCommand(acts *Actions, p *HTTPRoutesUpdateParams, local bool) (*cobra.Command, PrepareCommandFunc) { + cmd := &cobra.Command{ + Use: "mod INDEX", + Short: "Update HTTP route", + Args: oneArg("INDEX"), + ValidArgsFunction: completeHTTPRoute(acts), + } + + var ( + headers []string + file bool + + method string + path string + code int + isDynamic bool + body string + ) + + methods := append([]string{models.HTTPMethodAny}, models.HTTPMethods...) + + cmd.Flags().StringVarP(&p.Payload, "payload", "p", "", "Payload name") + cmd.Flags().StringVarP(&method, "method", "m", "GET", + fmt.Sprintf("Request method (one of %s)", quoteAndJoin(methods))) + cmd.Flags().StringVarP(&path, "path", "P", "/", "Request path") + cmd.Flags().StringArrayVarP(&headers, "header", "H", []string{}, "Response header") + cmd.Flags().IntVarP(&code, "code", "c", 200, "Response status code") + cmd.Flags().BoolVarP(&isDynamic, "dynamic", "d", false, "Interpret body and headers as templates") + cmd.Flags().StringVarP(&body, "body", "b", "", "Response body") + + // Add file flag only for local client, i.e. terminal. + // Otherwise anyone will be able to read files from server using telegram client. + if local { + cmd.Flags().BoolVarP(&file, "file", "f", false, "Treat BODY as path to file") + } + + _ = cmd.RegisterFlagCompletionFunc("payload", completePayloadName(acts)) + _ = cmd.RegisterFlagCompletionFunc("method", completeOne(methods)) + + return cmd, func(cmd *cobra.Command, args []string) errors.Error { + // Index + i, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + return errors.Validationf("invalid integer value %q", args[0]) + } + p.Index = i + + // Method + if cmd.Flags().Changed("method") { + p.Method = &method + } + + // Path + if cmd.Flags().Changed("path") { + p.Path = &path + } + + // Headers + if cmd.Flags().Changed("header") { + hh := make(map[string][]string) + for _, header := range headers { + if !strings.Contains(header, ":") { + return errors.Validationf(`header %q must contain ":"`, header) + } + parts := strings.SplitN(header, ":", 2) + name, value := parts[0], strings.TrimLeft(parts[1], " ") + + if h, ok := hh[name]; ok { + h = append(h, value) + } else { + hh[name] = []string{value} + } + } + p.Headers = hh + } + + // Code + if cmd.Flags().Changed("code") { + p.Code = &code + } + + // IsDynamic + if cmd.Flags().Changed("dynamic") { + p.IsDynamic = &isDynamic + } + + // Body + if cmd.Flags().Changed("body") { + var bodyBytes []byte + + if file { + b, err := ioutil.ReadFile(body) + if err != nil { + return errors.Validationf("fail to read file %q", body) + } + bodyBytes = b + } else { + bodyBytes = []byte(body) + } + bodyBase64 := base64.StdEncoding.EncodeToString(bodyBytes) + + p.Body = &bodyBase64 + } + + return nil + } +} + // // Delete // diff --git a/internal/actions/mock/Actions.go b/internal/actions/mock/Actions.go index cbdcffe..c3b295b 100644 --- a/internal/actions/mock/Actions.go +++ b/internal/actions/mock/Actions.go @@ -337,6 +337,38 @@ func (_m *Actions) HTTPRoutesList(_a0 context.Context, _a1 actions.HTTPRoutesLis return r0, r1 } +// HTTPRoutesUpdate provides a mock function with given fields: _a0, _a1 +func (_m *Actions) HTTPRoutesUpdate(_a0 context.Context, _a1 actions.HTTPRoutesUpdateParams) (*actions.HTTPRoutesUpdateResult, errors.Error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for HTTPRoutesUpdate") + } + + var r0 *actions.HTTPRoutesUpdateResult + var r1 errors.Error + if rf, ok := ret.Get(0).(func(context.Context, actions.HTTPRoutesUpdateParams) (*actions.HTTPRoutesUpdateResult, errors.Error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, actions.HTTPRoutesUpdateParams) *actions.HTTPRoutesUpdateResult); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*actions.HTTPRoutesUpdateResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, actions.HTTPRoutesUpdateParams) errors.Error); ok { + r1 = rf(_a0, _a1) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(errors.Error) + } + } + + return r0, r1 +} + // PayloadsClear provides a mock function with given fields: _a0, _a1 func (_m *Actions) PayloadsClear(_a0 context.Context, _a1 actions.PayloadsClearParams) (actions.PayloadsClearResult, errors.Error) { ret := _m.Called(_a0, _a1) diff --git a/internal/actionsdb/http_routes.go b/internal/actionsdb/http_routes.go index 0cc3fdc..97b8271 100644 --- a/internal/actionsdb/http_routes.go +++ b/internal/actionsdb/http_routes.go @@ -68,6 +68,66 @@ func (act *dbactions) HTTPRoutesCreate(ctx context.Context, p actions.HTTPRoutes return &actions.HTTPRoutesCreateResult{HTTPRoute: HTTPRoute(*rec, payload.Subdomain)}, nil } +func (act *dbactions) HTTPRoutesUpdate(ctx context.Context, p actions.HTTPRoutesUpdateParams) (*actions.HTTPRoutesUpdateResult, errors.Error) { + u, err := GetUser(ctx) + if err != nil { + return nil, errors.Internal(err) + } + + if err := p.Validate(); err != nil { + return nil, errors.Validation(err) + } + + payload, err := act.db.PayloadsGetByUserAndName(u.ID, p.Payload) + if err == sql.ErrNoRows { + return nil, errors.NotFoundf("payload with name %q not found", p.Payload) + } + + rec, err := act.db.HTTPRoutesGetByPayloadIDAndIndex(payload.ID, p.Index) + if err == sql.ErrNoRows { + return nil, errors.NotFoundf("http route for payload %q with index %d not found", + p.Payload, p.Index) + } else if err != nil { + return nil, errors.Internal(err) + } + + if p.Method != nil { + rec.Method = *p.Method + } + + if p.Path != nil { + rec.Path = *p.Path + } + + if p.Code != nil { + rec.Code = *p.Code + } + + if p.Headers != nil { + rec.Headers = p.Headers + } + + if p.Body != nil { + body, err := base64.StdEncoding.DecodeString(*p.Body) + + if err != nil { + return nil, errors.Validationf("body: invalid base64 data") + } + + rec.Body = body + } + + if p.IsDynamic != nil { + rec.IsDynamic = *p.IsDynamic + } + + if err := act.db.HTTPRoutesUpdate(rec); err != nil { + return nil, errors.Internal(err) + } + + return &actions.HTTPRoutesUpdateResult{HTTPRoute: HTTPRoute(*rec, payload.Subdomain)}, nil +} + func (act *dbactions) HTTPRoutesDelete(ctx context.Context, p actions.HTTPRoutesDeleteParams) (*actions.HTTPRoutesDeleteResult, errors.Error) { u, err := GetUser(ctx) if err != nil { diff --git a/internal/actionsdb/http_routes_test.go b/internal/actionsdb/http_routes_test.go index d908025..1d8c7ea 100644 --- a/internal/actionsdb/http_routes_test.go +++ b/internal/actionsdb/http_routes_test.go @@ -502,3 +502,167 @@ func TestHTTPRoutesClear_Error(t *testing.T) { }) } } + +func ptr[T any](v T) *T { + return &v +} + +func TestHTTPRoutesUpdate_Success(t *testing.T) { + tests := []struct { + name string + p actions.HTTPRoutesUpdateParams + }{ + { + "GET", + actions.HTTPRoutesUpdateParams{ + Payload: "payload1", + Index: 1, + Method: ptr("GET"), + Path: ptr("/test"), + Code: ptr(200), + Headers: models.Headers{ + "Test": {"test"}, + }, + Body: ptr("test"), + IsDynamic: ptr(false), + }, + }, + { + "POST", + actions.HTTPRoutesUpdateParams{ + Payload: "payload1", + Index: 2, + Method: ptr("POST"), + Path: ptr("/test-2"), + Code: ptr(201), + Headers: models.Headers{ + "Test": {"test"}, + }, + Body: ptr("test"), + IsDynamic: ptr(true), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setup(t) + defer teardown(t) + + u, err := db.UsersGetByID(1) + require.NoError(t, err) + + ctx := actionsdb.SetUser(context.Background(), u) + + r, err := acts.HTTPRoutesUpdate(ctx, tt.p) + require.NoError(t, err) + require.NotNil(t, r) + + assert.Equal(t, *tt.p.Method, r.Method) + assert.Equal(t, *tt.p.Path, r.Path) + assert.Equal(t, *tt.p.Code, r.Code) + assert.Equal(t, tt.p.Headers, r.Headers) + assert.Equal(t, *tt.p.Body, r.Body) + assert.Equal(t, *tt.p.IsDynamic, r.IsDynamic) + }) + } +} + +func TestHTTPRoutesUpdate_Error(t *testing.T) { + + tests := []struct { + name string + userID int + p actions.HTTPRoutesUpdateParams + err errors.Error + }{ + { + "no user in ctx", + 0, + actions.HTTPRoutesUpdateParams{ + Payload: "payload1", + Index: 1, + Method: ptr("GET"), + Path: ptr("/test"), + Code: ptr(200), + Headers: models.Headers{ + "Test": {"test"}, + }, + Body: ptr("test"), + IsDynamic: ptr(false), + }, + &errors.InternalError{}, + }, + { + "not existing payload name", + 1, + actions.HTTPRoutesUpdateParams{ + Payload: "not_exists", + Index: 1, + Method: ptr("GET"), + Path: ptr("/test"), + Code: ptr(200), + Headers: models.Headers{ + "Test": {"test"}, + }, + Body: ptr("test"), + IsDynamic: ptr(false), + }, + &errors.NotFoundError{}, + }, + { + "not existing index", + 1, + actions.HTTPRoutesUpdateParams{ + Payload: "payload1", + Index: 1337, + Method: ptr("GET"), + Path: ptr("/test"), + Code: ptr(200), + Headers: models.Headers{ + "Test": {"test"}, + }, + Body: ptr("test"), + IsDynamic: ptr(false), + }, + &errors.NotFoundError{}, + }, + { + "invalid body", + 1, + actions.HTTPRoutesUpdateParams{ + Payload: "payload1", + Index: 1, + Method: ptr("GET"), + Path: ptr("/test"), + Code: ptr(200), + Headers: models.Headers{ + "Test": {"test"}, + }, + Body: ptr("xxxxxx"), + IsDynamic: ptr(false), + }, + &errors.ValidationError{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setup(t) + defer teardown(t) + + ctx := context.Background() + + if tt.userID != 0 { + u, err := db.UsersGetByID(1) + require.NoError(t, err) + + ctx = actionsdb.SetUser(context.Background(), u) + } + + _, err := acts.HTTPRoutesUpdate(ctx, tt.p) + assert.Error(t, err) + assert.IsType(t, tt.err, err) + }) + } +} diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 308e17e..4823943 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -93,6 +93,7 @@ func (c *Command) root(onResult func(actions.Result) error) *cobra.Command { }) http.AddCommand(c.withAuthCheck(c.HTTPRoutesCreate(onResult))) + http.AddCommand(c.withAuthCheck(c.HTTPRoutesUpdate(onResult))) http.AddCommand(c.withAuthCheck(c.HTTPRoutesDelete(onResult))) http.AddCommand(c.withAuthCheck(c.HTTPRoutesList(onResult))) http.AddCommand(c.withAuthCheck(c.HTTPRoutesClear(onResult))) diff --git a/internal/cmd/cmd_test.go b/internal/cmd/cmd_test.go index 0b53994..185695d 100644 --- a/internal/cmd/cmd_test.go +++ b/internal/cmd/cmd_test.go @@ -38,6 +38,10 @@ func prepare() (*cmd.Command, *actions_mock.Actions, *ResultMock) { return c, actions, res } +func ptr[T any](v T) *T { + return &v +} + func TestCmd(t *testing.T) { tests := []struct { cmdline string @@ -307,6 +311,26 @@ func TestCmd(t *testing.T) { &actions.HTTPRoutesCreateResult{}, }, + // Update + + { + "http -p payload mod 1 -m POST -P /test -c 201 -H 'Content-Type: application/json' -d -b test", + "HTTPRoutesUpdate", + actions.HTTPRoutesUpdateParams{ + Payload: "payload", + Index: 1, + Method: ptr("POST"), + Path: ptr("/test"), + Code: ptr(201), + Headers: map[string][]string{ + "Content-Type": {"application/json"}, + }, + Body: ptr("dGVzdA=="), + IsDynamic: ptr(true), + }, + &actions.HTTPRoutesUpdateResult{}, + }, + // List { diff --git a/internal/cmd/generated.go b/internal/cmd/generated.go index 37d0a9b..07baaea 100644 --- a/internal/cmd/generated.go +++ b/internal/cmd/generated.go @@ -276,6 +276,33 @@ func (c *Command) HTTPRoutesList(onResult func(actions.Result) error) *cobra.Com return cmd } +func (c *Command) HTTPRoutesUpdate(onResult func(actions.Result) error) *cobra.Command { + var params actions.HTTPRoutesUpdateParams + + cmd, prepareFunc := actions.HTTPRoutesUpdateCommand(&c.actions, ¶ms, c.options.allowFileAccess) + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + if prepareFunc != nil { + if err := prepareFunc(cmd, args); err != nil { + return err + } + } + + if err := params.Validate(); err != nil { + return err + } + + res, err := c.actions.HTTPRoutesUpdate(cmd.Context(), params) + if err != nil { + return err + } + + return onResult(res) + } + + return cmd +} + func (c *Command) PayloadsClear(onResult func(actions.Result) error) *cobra.Command { var params actions.PayloadsClearParams diff --git a/internal/cmd/messengers.go b/internal/cmd/messengers.go index 724b064..c24a4aa 100644 --- a/internal/cmd/messengers.go +++ b/internal/cmd/messengers.go @@ -46,5 +46,3 @@ Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} Use "{{if .HasParent}}{{.CommandPath | replace "sonar " "/"}} {{else}}/{{end}}[command] --help" for more information about a command.{{end}}` - - diff --git a/internal/codegen/main.go b/internal/codegen/main.go index 0561e37..a51f253 100644 --- a/internal/codegen/main.go +++ b/internal/codegen/main.go @@ -64,7 +64,7 @@ func main() { act.Resource = resourceName(strings.Replace(m.Name, "Create", "", 1)) act.Verb = "create" } else if strings.Contains(m.Name, "Update") { - act.HTTPMethod = "PUT" + act.HTTPMethod = "PATCH" act.Resource = resourceName(strings.Replace(m.Name, "Update", "", 1)) act.Verb = "update" } else if strings.Contains(m.Name, "Delete") { diff --git a/internal/modules/api/api.go b/internal/modules/api/api.go index 21df7b2..69741f7 100644 --- a/internal/modules/api/api.go +++ b/internal/modules/api/api.go @@ -56,7 +56,7 @@ func (api *API) Router() http.Handler { r.Delete("/", api.PayloadsClear) r.Route("/{name}", func(r chi.Router) { r.Delete("/", api.PayloadsDelete) - r.Put("/", api.PayloadsUpdate) + r.Patch("/", api.PayloadsUpdate) }) }) @@ -78,6 +78,7 @@ func (api *API) Router() http.Handler { r.Delete("/", api.HTTPRoutesClear) r.Route("/{index}", func(r chi.Router) { r.Delete("/", api.HTTPRoutesDelete) + r.Patch("/", api.HTTPRoutesUpdate) }) }) }) diff --git a/internal/modules/api/api_test.go b/internal/modules/api/api_test.go index e34c807..577d321 100644 --- a/internal/modules/api/api_test.go +++ b/internal/modules/api/api_test.go @@ -262,7 +262,7 @@ func TestAPI(t *testing.T) { // Update { - method: "PUT", + method: "PATCH", path: "/payloads/payload1", token: User1Token, json: `{"name":"test", "notifyProtocols": ["smtp"], "storeEvents": false}`, @@ -275,7 +275,7 @@ func TestAPI(t *testing.T) { status: 200, }, { - method: "PUT", + method: "PATCH", path: "/payloads/payload1", token: User1Token, json: `{"name":"test", "notifyProtocols": ["smtp"], "storeEvents": null}`, @@ -288,7 +288,7 @@ func TestAPI(t *testing.T) { status: 200, }, { - method: "PUT", + method: "PATCH", path: "/payloads/payload1", token: User1Token, json: `{"invalid": 1}`, @@ -300,7 +300,7 @@ func TestAPI(t *testing.T) { status: 400, }, { - method: "PUT", + method: "PATCH", path: "/payloads/payload1", token: User1Token, json: `{"name":"test", "notifyProtocols": ["invalid"]}`, @@ -312,7 +312,7 @@ func TestAPI(t *testing.T) { status: 400, }, { - method: "PUT", + method: "PATCH", path: "/payloads/invalid", token: User1Token, json: `{"name":"test", "notifyProtocols": ["smtp"]}`, @@ -702,6 +702,73 @@ func TestAPI(t *testing.T) { status: 409, }, + // Update + + { + method: "PATCH", + path: "/http-routes/payload1/1", + token: User1Token, + json: `{ + "method": "POST", + "path": "/test2", + "code": 301, + "headers": {"X":["x"]}, + "body": "MTIzNAo=", + "isDynamic": false + }`, + schema: actions.HTTPRoutesUpdateResult{}, + result: map[string]matcher{ + "$.method": equal("POST"), + "$.path": equal("/test2"), + "$.code": equal(301), + "$.headers": equal(map[string]interface{}{"X": []interface{}{"x"}}), + "$.body": equal("MTIzNAo="), + "$.isDynamic": equal(false), + }, + status: 200, + }, + { + method: "PATCH", + path: "/http-routes/payload1/1", + token: User1Token, + json: `{"invalid": 1}`, + schema: &errors.BadFormatError{}, + result: map[string]matcher{ + "$.message": contains("format"), + "$.details": contains("json"), + }, + status: 400, + }, + { + method: "PATCH", + path: "/http-routes/payload1/1", + token: User1Token, + json: `{"method": "X"}`, + schema: &errors.ValidationError{}, + result: map[string]matcher{ + "$.message": contains("validation"), + }, + status: 400, + }, + { + method: "PATCH", + path: "/http-routes/payload1/1337", + token: User1Token, + json: `{ + "method": "POST", + "path": "/test2", + "code": 301, + "headers": {"X":["x"]}, + "body": "MTIzNAo=", + "isDynamic": false + }`, + schema: &errors.ValidationError{}, + result: map[string]matcher{ + "$.message": contains("not found"), + }, + status: 404, + }, + // List { diff --git a/internal/modules/api/apiclient/client_test.go b/internal/modules/api/apiclient/client_test.go index 4d93d1b..a9655c4 100644 --- a/internal/modules/api/apiclient/client_test.go +++ b/internal/modules/api/apiclient/client_test.go @@ -460,6 +460,32 @@ func TestClient(t *testing.T) { nil, }, + // Update + + { + actions.HTTPRoutesUpdateParams{ + Payload: "payload1", + Index: 1, + Method: ptr("PUT"), + Path: ptr("/123"), + Code: ptr(302), + Headers: map[string][]string{ + "Location": {"http://example.com"}, + }, + IsDynamic: ptr(true), + Body: ptr("dGVzdA=="), + }, + map[string]matcher{ + "Method": equal("PUT"), + "Path": equal("/123"), + "Code": equal(302), + "Headers": equal(map[string][]string{"Location": {"http://example.com"}}), + "IsDynamic": equal(true), + "Body": equal("dGVzdA=="), + }, + nil, + }, + // List { @@ -555,6 +581,8 @@ func TestClient(t *testing.T) { res, err = uc.HTTPRoutesDelete(context.Background(), p) case actions.HTTPRoutesClearParams: res, err = uc.HTTPRoutesClear(context.Background(), p) + case actions.HTTPRoutesUpdateParams: + res, err = uc.HTTPRoutesUpdate(context.Background(), p) // Profile case nil: @@ -579,3 +607,7 @@ func TestClient(t *testing.T) { }) } } + +func ptr[T any](v T) *T { + return &v +} diff --git a/internal/modules/api/apiclient/generated.go b/internal/modules/api/apiclient/generated.go index d3be6db..8559d4e 100644 --- a/internal/modules/api/apiclient/generated.go +++ b/internal/modules/api/apiclient/generated.go @@ -180,6 +180,24 @@ func (c *Client) HTTPRoutesList(ctx context.Context, params actions.HTTPRoutesLi return res, nil } +func (c *Client) HTTPRoutesUpdate(ctx context.Context, params actions.HTTPRoutesUpdateParams) (*actions.HTTPRoutesUpdateResult, errors.Error) { + var res *actions.HTTPRoutesUpdateResult + + err := handle(c.client.R(). + SetBody(params). + SetPathParams(toPath(params)). + SetError(&APIError{}). + SetResult(&res). + SetContext(ctx). + Patch("/http-routes/{payload}/{index}")) + + if err != nil { + return nil, err + } + + return res, nil +} + func (c *Client) PayloadsClear(ctx context.Context, params actions.PayloadsClearParams) (actions.PayloadsClearResult, errors.Error) { var res actions.PayloadsClearResult @@ -257,7 +275,7 @@ func (c *Client) PayloadsUpdate(ctx context.Context, params actions.PayloadsUpda SetError(&APIError{}). SetResult(&res). SetContext(ctx). - Put("/payloads/{name}")) + Patch("/payloads/{name}")) if err != nil { return nil, err diff --git a/internal/modules/api/generated.go b/internal/modules/api/generated.go index d512775..fa53866 100644 --- a/internal/modules/api/generated.go +++ b/internal/modules/api/generated.go @@ -105,12 +105,12 @@ func (api *API) EventsList(w http.ResponseWriter, r *http.Request) { var params actions.EventsListParams - if err := fromQuery(r, ¶ms); err != nil { + if err := fromPath(r, ¶ms); err != nil { api.handleError(w, r, err) return } - if err := fromPath(r, ¶ms); err != nil { + if err := fromQuery(r, ¶ms); err != nil { api.handleError(w, r, err) return } @@ -201,6 +201,29 @@ func (api *API) HTTPRoutesList(w http.ResponseWriter, r *http.Request) { responseJSON(w, res, http.StatusOK) } +func (api *API) HTTPRoutesUpdate(w http.ResponseWriter, r *http.Request) { + + var params actions.HTTPRoutesUpdateParams + + if err := fromJSON(r, ¶ms); err != nil { + api.handleError(w, r, err) + return + } + + if err := fromPath(r, ¶ms); err != nil { + api.handleError(w, r, err) + return + } + + res, err := api.actions.HTTPRoutesUpdate(r.Context(), params) + if err != nil { + api.handleError(w, r, err) + return + } + + responseJSON(w, res, http.StatusOK) +} + func (api *API) PayloadsClear(w http.ResponseWriter, r *http.Request) { var params actions.PayloadsClearParams @@ -277,12 +300,12 @@ func (api *API) PayloadsUpdate(w http.ResponseWriter, r *http.Request) { var params actions.PayloadsUpdateParams - if err := fromPath(r, ¶ms); err != nil { + if err := fromJSON(r, ¶ms); err != nil { api.handleError(w, r, err) return } - if err := fromJSON(r, ¶ms); err != nil { + if err := fromPath(r, ¶ms); err != nil { api.handleError(w, r, err) return } diff --git a/internal/templates/content.go b/internal/templates/content.go index 224b6eb..d703e62 100644 --- a/internal/templates/content.go +++ b/internal/templates/content.go @@ -25,6 +25,7 @@ var templatesMap = map[string]string{ actions.HTTPRoutesListResultID: httpRoutesList, actions.HTTPRoutesCreateResultID: httpRoutesCreate, + actions.HTTPRoutesUpdateResultID: httpRoutesUpdate, actions.HTTPRoutesDeleteResultID: httpRoutesDelete, actions.HTTPRoutesClearResultID: httpRoutesClear, @@ -88,6 +89,7 @@ var httpRoutesList = fmt.Sprintf(` %s {{ else }}nothing found{{ end }}`, httpRoute) var httpRoutesCreate = httpRoute +var httpRoutesUpdate = httpRoute var httpRoutesDelete = "http route deleted" var httpRoutesClear = `{{ len . }} http routes deleted` diff --git a/internal/utils/valid/valid.go b/internal/utils/valid/valid.go index d67a05a..bd3a36b 100644 --- a/internal/utils/valid/valid.go +++ b/internal/utils/valid/valid.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "reflect" "regexp" "strconv" "strings" @@ -135,7 +136,15 @@ type OneOfRule struct { } func (r *OneOfRule) Validate(value interface{}) error { - val, _ := value.(string) + if value == nil { + return fmt.Errorf("invalid nil value") + } + + v := reflect.ValueOf(value) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + val, _ := v.Interface().(string) if !r.caseSensetive { val = strings.ToLower(val)