From 71579a85a6916981ee3ec6e6f517abcf7830d7a5 Mon Sep 17 00:00:00 2001 From: Ben Schumacher Date: Tue, 7 Oct 2025 12:19:21 +0200 Subject: [PATCH] [MM-64633] Rewrite Go client using Generics (#31805) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alejandro GarcĂ­a Montoro --- server/channels/api4/channel_category_test.go | 12 +- server/channels/api4/channel_test.go | 3 +- server/channels/api4/cloud_test.go | 4 +- server/channels/api4/content_flagging_test.go | 2 +- server/channels/api4/post.go | 8 +- server/channels/api4/user_test.go | 4 +- server/cmd/mmctl/client/client.go | 4 +- server/cmd/mmctl/commands/system.go | 4 +- server/cmd/mmctl/commands/system_test.go | 39 +- server/cmd/mmctl/mocks/client_mock.go | 8 +- server/public/model/client4.go | 4982 +++++------------ server/public/model/client4_test.go | 41 + 12 files changed, 1454 insertions(+), 3657 deletions(-) diff --git a/server/channels/api4/channel_category_test.go b/server/channels/api4/channel_category_test.go index 9d879016944..1b8731c6ea3 100644 --- a/server/channels/api4/channel_category_test.go +++ b/server/channels/api4/channel_category_test.go @@ -98,9 +98,9 @@ func TestCreateCategoryForTeamForUser(t *testing.T) { t.Run("should not crash with null input", func(t *testing.T) { require.NotPanics(t, func() { user, client := setupUserForSubtest(t, th) - payload := []byte(`null`) + payload := `null` route := fmt.Sprintf("/users/%s/teams/%s/channels/categories", user.Id, th.BasicTeam.Id) - r, err := client.DoAPIPostBytes(context.Background(), route, payload) + r, err := client.DoAPIPost(context.Background(), route, payload) require.Error(t, err) closeBody(r) }) @@ -486,9 +486,9 @@ func TestUpdateCategoryForTeamForUser(t *testing.T) { dmsCategory := categories.Categories[2] - payload := []byte(`null`) + payload := `null` route := fmt.Sprintf("/users/%s/teams/%s/channels/categories/%s", user.Id, th.BasicTeam.Id, dmsCategory.Id) - r, err := client.DoAPIPutBytes(context.Background(), route, payload) + r, err := client.DoAPIPut(context.Background(), route, payload) require.Error(t, err) closeBody(r) }) @@ -988,9 +988,9 @@ func TestUpdateCategoryOrderForTeamForUser(t *testing.T) { t.Run("should not crash with null input", func(t *testing.T) { require.NotPanics(t, func() { user, client := setupUserForSubtest(t, th) - payload := []byte(`null`) + payload := `null` route := fmt.Sprintf("/users/%s/teams/%s/channels/categories/order", user.Id, th.BasicTeam.Id) - r, err := client.DoAPIPutBytes(context.Background(), route, payload) + r, err := client.DoAPIPut(context.Background(), route, payload) require.Error(t, err) closeBody(r) }) diff --git a/server/channels/api4/channel_test.go b/server/channels/api4/channel_test.go index bc4ba88a627..0b77e6242d9 100644 --- a/server/channels/api4/channel_test.go +++ b/server/channels/api4/channel_test.go @@ -2026,8 +2026,7 @@ func TestGetChannelsForTeamForUser(t *testing.T) { } channels, resp, err = client.GetChannelsForTeamForUser(context.Background(), th.BasicTeam.Id, th.BasicUser.Id, false, resp.Etag) - // an error is expected as the server didn't return any data and the client still tries to unmarshal the response - require.Error(t, err) + require.NoError(t, err) CheckEtag(t, channels, resp) _, resp, err = client.GetChannelsForTeamForUser(context.Background(), th.BasicTeam.Id, "junk", false, "") diff --git a/server/channels/api4/cloud_test.go b/server/channels/api4/cloud_test.go index e3f9efd86d2..8da88246854 100644 --- a/server/channels/api4/cloud_test.go +++ b/server/channels/api4/cloud_test.go @@ -173,7 +173,7 @@ func TestValidateBusinessEmail(t *testing.T) { th.App.Srv().SetLicense(model.NewTestLicense("cloud")) - r, err := th.SystemAdminClient.DoAPIPostBytes(context.Background(), "/cloud/validate-business-email", nil) + r, err := th.SystemAdminClient.DoAPIPost(context.Background(), "/cloud/validate-business-email", "") require.Error(t, err) closeBody(r) require.Equal(t, http.StatusBadRequest, r.StatusCode, "Status Bad Request") @@ -282,7 +282,7 @@ func TestValidateWorkspaceBusinessEmail(t *testing.T) { }() th.App.Srv().Cloud = &cloud - r, err := th.SystemAdminClient.DoAPIPostBytes(context.Background(), "/cloud/validate-workspace-business-email", nil) + r, err := th.SystemAdminClient.DoAPIPost(context.Background(), "/cloud/validate-workspace-business-email", "") require.Error(t, err) closeBody(r) require.Equal(t, http.StatusBadRequest, r.StatusCode, "Status Bad Request") diff --git a/server/channels/api4/content_flagging_test.go b/server/channels/api4/content_flagging_test.go index 790ebd8650b..4a7a687413f 100644 --- a/server/channels/api4/content_flagging_test.go +++ b/server/channels/api4/content_flagging_test.go @@ -145,7 +145,7 @@ func TestGetPostPropertyValues(t *testing.T) { require.NoError(t, err) require.Equal(t, http.StatusOK, resp.StatusCode) require.NotNil(t, propertyValues) - require.Len(t, *propertyValues, 5) + require.Len(t, propertyValues, 5) }) } diff --git a/server/channels/api4/post.go b/server/channels/api4/post.go index cd914da734b..af61c6e3cef 100644 --- a/server/channels/api4/post.go +++ b/server/channels/api4/post.go @@ -199,10 +199,10 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) { return } } - skipFetchThreads := r.URL.Query().Get("skipFetchThreads") == "true" - collapsedThreads := r.URL.Query().Get("collapsedThreads") == "true" - collapsedThreadsExtended := r.URL.Query().Get("collapsedThreadsExtended") == "true" - includeDeleted := r.URL.Query().Get("include_deleted") == "true" + skipFetchThreads, _ := strconv.ParseBool(r.URL.Query().Get("skipFetchThreads")) + collapsedThreads, _ := strconv.ParseBool(r.URL.Query().Get("collapsedThreads")) + collapsedThreadsExtended, _ := strconv.ParseBool(r.URL.Query().Get("collapsedThreadsExtended")) + includeDeleted, _ := strconv.ParseBool(r.URL.Query().Get("include_deleted")) channelId := c.Params.ChannelId page := c.Params.Page perPage := c.Params.PerPage diff --git a/server/channels/api4/user_test.go b/server/channels/api4/user_test.go index f9a6e43548e..fbc732daf7e 100644 --- a/server/channels/api4/user_test.go +++ b/server/channels/api4/user_test.go @@ -552,7 +552,7 @@ func TestCreateUserWithToken(t *testing.T) { _, _, err := th.Client.CreateUserWithToken(context.Background(), &user, "") require.Error(t, err) - CheckErrorID(t, err, "api.user.create_user.missing_token.app_error") + assert.ErrorContains(t, err, "token ID is required") }) t.Run("TokenExpired", func(t *testing.T) { @@ -902,7 +902,7 @@ func TestCreateUserWithInviteId(t *testing.T) { _, _, err := th.Client.CreateUserWithInviteId(context.Background(), &user, "") require.Error(t, err) - CheckErrorID(t, err, "api.user.create_user.missing_invite_id.app_error") + assert.ErrorContains(t, err, "invite ID is required") }) t.Run("ExpiredInviteId", func(t *testing.T) { diff --git a/server/cmd/mmctl/client/client.go b/server/cmd/mmctl/client/client.go index 5fbc080b0a0..c2c6b0d0c59 100644 --- a/server/cmd/mmctl/client/client.go +++ b/server/cmd/mmctl/client/client.go @@ -123,8 +123,8 @@ type Client interface { MigrateAuthToLdap(ctx context.Context, fromAuthService string, matchField string, force bool) (*model.Response, error) MigrateAuthToSaml(ctx context.Context, fromAuthService string, usersMap map[string]string, auto bool) (*model.Response, error) GetPing(ctx context.Context) (string, *model.Response, error) - GetPingWithFullServerStatus(ctx context.Context) (map[string]string, *model.Response, error) - GetPingWithOptions(ctx context.Context, options model.SystemPingOptions) (map[string]string, *model.Response, error) + GetPingWithFullServerStatus(ctx context.Context) (map[string]any, *model.Response, error) + GetPingWithOptions(ctx context.Context, options model.SystemPingOptions) (map[string]any, *model.Response, error) CreateUpload(ctx context.Context, us *model.UploadSession) (*model.UploadSession, *model.Response, error) GetUpload(ctx context.Context, uploadID string) (*model.UploadSession, *model.Response, error) GetUploadsForUser(ctx context.Context, userID string) ([]*model.UploadSession, *model.Response, error) diff --git a/server/cmd/mmctl/commands/system.go b/server/cmd/mmctl/commands/system.go index 646acec5bea..bb8329fcf44 100644 --- a/server/cmd/mmctl/commands/system.go +++ b/server/cmd/mmctl/commands/system.go @@ -173,10 +173,10 @@ Filestore Status: {{.filestore_status}}`, status) if status["status"] != model.StatusOk { return fmt.Errorf("server status is unhealthy: %s", status["status"]) } - if dbStatus := status["database_status"]; dbStatus != "" && dbStatus != model.StatusOk { + if dbStatus, ok := status["database_status"]; ok && dbStatus != model.StatusOk { return fmt.Errorf("database status is unhealthy: %s", dbStatus) } - if filestoreStatus := status["filestore_status"]; filestoreStatus != "" && filestoreStatus != model.StatusOk { + if filestoreStatus, ok := status["filestore_status"]; ok && filestoreStatus != model.StatusOk { return fmt.Errorf("filestore status is unhealthy: %s", filestoreStatus) } diff --git a/server/cmd/mmctl/commands/system_test.go b/server/cmd/mmctl/commands/system_test.go index e6b9cacadae..f06b708b2ab 100644 --- a/server/cmd/mmctl/commands/system_test.go +++ b/server/cmd/mmctl/commands/system_test.go @@ -160,7 +160,7 @@ func (s *MmctlUnitTestSuite) TestServerVersionCmd() { s.Require().Nil(err) s.Require().Len(printer.GetErrorLines(), 0) s.Require().Len(printer.GetLines(), 1) - s.Require().Equal(printer.GetLines()[0], map[string]string{"version": expectedVersion}) + s.Require().Equal(map[string]string{"version": expectedVersion}, printer.GetLines()[0]) }) s.Run("Request to the server fails", func() { @@ -183,7 +183,7 @@ func (s *MmctlUnitTestSuite) TestServerStatusCmd() { s.Run("Print server status - all healthy", func() { printer.Clean() - expectedStatus := map[string]string{ + expectedStatus := map[string]any{ "status": model.StatusOk, "database_status": model.StatusOk, "filestore_status": model.StatusOk, @@ -207,7 +207,7 @@ func (s *MmctlUnitTestSuite) TestServerStatusCmd() { s.Run("Status fields missing - should succeed", func() { printer.Clean() - expectedStatus := map[string]string{"status": "OK"} + expectedStatus := map[string]any{"status": "OK"} s.client. EXPECT(). GetPingWithOptions(context.TODO(), model.SystemPingOptions{ @@ -242,12 +242,11 @@ func (s *MmctlUnitTestSuite) TestServerStatusCmd() { s.Require().Len(printer.GetLines(), 0) }) - s.Run("Empty string database status is ignored", func() { + s.Run("Missing database status is ignored", func() { printer.Clean() - emptyDbStatus := map[string]string{ + emptyDbStatus := map[string]any{ "status": model.StatusOk, - "database_status": "", "filestore_status": model.StatusOk, } s.client. @@ -265,10 +264,32 @@ func (s *MmctlUnitTestSuite) TestServerStatusCmd() { s.Require().Len(printer.GetLines(), 1) }) + s.Run("filestore database status is ignored", func() { + printer.Clean() + + emptyDbStatus := map[string]any{ + "status": model.StatusOk, + "database_status": model.StatusOk, + } + s.client. + EXPECT(). + GetPingWithOptions(context.TODO(), model.SystemPingOptions{ + FullStatus: true, + RESTSemantics: true, + }). + Return(emptyDbStatus, &model.Response{}, nil). + Times(1) + + err := systemStatusCmdF(s.client, &cobra.Command{}, []string{}) + s.Require().Nil(err) + s.Require().Len(printer.GetErrorLines(), 0) + s.Require().Len(printer.GetLines(), 1) + }) + s.Run("Unhealthy server status should return true", func() { printer.Clean() - unhealthyStatus := map[string]string{ + unhealthyStatus := map[string]any{ "status": model.StatusUnhealthy, "database_status": model.StatusOk, "filestore_status": model.StatusOk, @@ -293,7 +314,7 @@ func (s *MmctlUnitTestSuite) TestServerStatusCmd() { s.Run("Unhealthy database status should return true", func() { printer.Clean() - unhealthyStatus := map[string]string{ + unhealthyStatus := map[string]any{ "status": model.StatusOk, "database_status": model.StatusUnhealthy, "filestore_status": model.StatusOk, @@ -318,7 +339,7 @@ func (s *MmctlUnitTestSuite) TestServerStatusCmd() { s.Run("Unhealthy filestore status should return true", func() { printer.Clean() - unhealthyStatus := map[string]string{ + unhealthyStatus := map[string]any{ "status": model.StatusOk, "database_status": model.StatusOk, "filestore_status": model.StatusUnhealthy, diff --git a/server/cmd/mmctl/mocks/client_mock.go b/server/cmd/mmctl/mocks/client_mock.go index 0ce60d5daca..f6d534cb7bc 100644 --- a/server/cmd/mmctl/mocks/client_mock.go +++ b/server/cmd/mmctl/mocks/client_mock.go @@ -1149,10 +1149,10 @@ func (mr *MockClientMockRecorder) GetPing(arg0 interface{}) *gomock.Call { } // GetPingWithFullServerStatus mocks base method. -func (m *MockClient) GetPingWithFullServerStatus(arg0 context.Context) (map[string]string, *model.Response, error) { +func (m *MockClient) GetPingWithFullServerStatus(arg0 context.Context) (map[string]interface{}, *model.Response, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPingWithFullServerStatus", arg0) - ret0, _ := ret[0].(map[string]string) + ret0, _ := ret[0].(map[string]interface{}) ret1, _ := ret[1].(*model.Response) ret2, _ := ret[2].(error) return ret0, ret1, ret2 @@ -1165,10 +1165,10 @@ func (mr *MockClientMockRecorder) GetPingWithFullServerStatus(arg0 interface{}) } // GetPingWithOptions mocks base method. -func (m *MockClient) GetPingWithOptions(arg0 context.Context, arg1 model.SystemPingOptions) (map[string]string, *model.Response, error) { +func (m *MockClient) GetPingWithOptions(arg0 context.Context, arg1 model.SystemPingOptions) (map[string]interface{}, *model.Response, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPingWithOptions", arg0, arg1) - ret0, _ := ret[0].(map[string]string) + ret0, _ := ret[0].(map[string]interface{}) ret1, _ := ret[1].(*model.Response) ret2, _ := ret[2].(error) return ret0, ret1, ret2 diff --git a/server/public/model/client4.go b/server/public/model/client4.go index f4a70dbd865..3bfe3d441c9 100644 --- a/server/public/model/client4.go +++ b/server/public/model/client4.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "mime" @@ -105,15 +106,6 @@ func (c *Client4) boolString(value bool) string { return "false" } -func (c *Client4) ArrayFromJSON(data io.Reader) []string { - var objmap []string - json.NewDecoder(data).Decode(&objmap) - if objmap == nil { - return make([]string, 0) - } - return objmap -} - func closeBody(r *http.Response) { if r.Body != nil { _, _ = io.Copy(io.Discard, r.Body) @@ -153,6 +145,32 @@ func BuildResponse(r *http.Response) *Response { } } +// DecodeJSONFromResponse decodes JSON from an HTTP response and returns the result. +// Handles 304 Not Modified responses and calls [BuildResponse] automatically. +func DecodeJSONFromResponse[T any](r *http.Response) (T, *Response, error) { + var result T + if r.StatusCode == http.StatusNotModified { + return result, BuildResponse(r), nil + } + if err := json.NewDecoder(r.Body).Decode(&result); err != nil { + return result, BuildResponse(r), fmt.Errorf("failed to decode JSON response: %w", err) + } + return result, BuildResponse(r), nil +} + +// ReadBytesFromResponse reads all bytes from an HTTP response body and returns them. +// Handles 304 Not Modified responses and calls [BuildResponse] automatically. +func ReadBytesFromResponse(r *http.Response) ([]byte, *Response, error) { + if r.StatusCode == http.StatusNotModified { + return nil, BuildResponse(r), nil + } + data, err := io.ReadAll(r.Body) + if err != nil { + return nil, BuildResponse(r), err + } + return data, BuildResponse(r), nil +} + func (c *Client4) SetToken(token string) { c.AuthToken = token c.AuthType = HeaderBearer @@ -269,13 +287,8 @@ func (c *Client4) channelByNameRoute(channelName, teamId string) string { return fmt.Sprintf(c.teamRoute(teamId)+"/channels/name/%v", channelName) } -func (c *Client4) channelsForTeamForUserRoute(teamId, userId string, includeDeleted bool) string { - route := c.userRoute(userId) + c.teamRoute(teamId) + "/channels" - if includeDeleted { - query := fmt.Sprintf("?include_deleted=%v", includeDeleted) - return route + query - } - return route +func (c *Client4) channelsForTeamForUserRoute(teamId, userId string) string { + return c.userRoute(userId) + c.teamRoute(teamId) + "/channels" } func (c *Client4) channelByNameForTeamNameRoute(channelName, teamName string) string { @@ -610,6 +623,18 @@ func (c *Client4) customProfileAttributesRoute() string { return "/custom_profile_attributes" } +func (c *Client4) bookmarksRoute(channelId string) string { + return c.channelRoute(channelId) + "/bookmarks" +} + +func (c *Client4) bookmarkRoute(channelId, bookmarkId string) string { + return fmt.Sprintf(c.bookmarksRoute(channelId)+"/%v", bookmarkId) +} + +func (c *Client4) clientPerfMetricsRoute() string { + return "/client_perf" +} + func (c *Client4) userCustomProfileAttributesRoute(userID string) string { return fmt.Sprintf("%s/custom_profile_attributes", c.userRoute(userID)) } @@ -638,213 +663,87 @@ func (c *Client4) accessControlPolicyRoute(policyID string) string { return fmt.Sprintf(c.accessControlPoliciesRoute()+"/%v", url.PathEscape(policyID)) } -func (c *Client4) GetServerLimits(ctx context.Context) (*ServerLimits, *Response, error) { - r, err := c.DoAPIGet(ctx, c.limitsRoute()+"/server", "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var serverLimits ServerLimits - if r.StatusCode == http.StatusNotModified { - return &serverLimits, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&serverLimits); err != nil { - return nil, nil, NewAppError("GetServerLimits", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &serverLimits, BuildResponse(r), nil -} - -func (c *Client4) CreateScheduledPost(ctx context.Context, scheduledPost *ScheduledPost) (*ScheduledPost, *Response, error) { - buf, err := json.Marshal(scheduledPost) - if err != nil { - return nil, nil, NewAppError("CreateScheduledPost", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPost(ctx, c.postsRoute()+"/schedule", string(buf)) - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var createdScheduledPost ScheduledPost - if err := json.NewDecoder(r.Body).Decode(&createdScheduledPost); err != nil { - return nil, nil, NewAppError("CreateScheduledPost", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &createdScheduledPost, BuildResponse(r), nil -} - -func (c *Client4) GetUserScheduledPosts(ctx context.Context, teamId string, includeDirectChannels bool) (map[string][]*ScheduledPost, *Response, error) { - query := url.Values{} - query.Set("includeDirectChannels", fmt.Sprintf("%t", includeDirectChannels)) - - r, err := c.DoAPIGet(ctx, c.postsRoute()+"/scheduled/team/"+teamId+"?"+query.Encode(), "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var scheduledPostsByTeam map[string][]*ScheduledPost - if err := json.NewDecoder(r.Body).Decode(&scheduledPostsByTeam); err != nil { - return nil, nil, NewAppError("GetUserScheduledPosts", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return scheduledPostsByTeam, BuildResponse(r), nil -} - -func (c *Client4) UpdateScheduledPost(ctx context.Context, scheduledPost *ScheduledPost) (*ScheduledPost, *Response, error) { - buf, err := json.Marshal(scheduledPost) - if err != nil { - return nil, nil, NewAppError("UpdateScheduledPost", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPut(ctx, c.postsRoute()+"/schedule/"+scheduledPost.Id, string(buf)) - if err != nil { - return nil, BuildResponse(r), err - } - - defer closeBody(r) - var updatedScheduledPost ScheduledPost - if err := json.NewDecoder(r.Body).Decode(&updatedScheduledPost); err != nil { - return nil, nil, NewAppError("UpdateScheduledPost", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &updatedScheduledPost, BuildResponse(r), nil -} - -func (c *Client4) DeleteScheduledPost(ctx context.Context, scheduledPostId string) (*ScheduledPost, *Response, error) { - r, err := c.DoAPIDelete(ctx, c.postsRoute()+"/schedule/"+scheduledPostId) - if err != nil { - return nil, BuildResponse(r), err - } - - defer closeBody(r) - var deletedScheduledPost ScheduledPost - if err := json.NewDecoder(r.Body).Decode(&deletedScheduledPost); err != nil { - return nil, nil, NewAppError("DeleteScheduledPost", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &deletedScheduledPost, BuildResponse(r), nil -} - -func (c *Client4) GetFlaggingConfiguration(ctx context.Context) (*ContentFlaggingReportingConfig, *Response, error) { - r, err := c.DoAPIGet(ctx, c.contentFlaggingRoute()+"/flag/config", "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var config ContentFlaggingReportingConfig - if err := json.NewDecoder(r.Body).Decode(&config); err != nil { - return nil, nil, NewAppError("GetFlaggingConfiguration", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &config, BuildResponse(r), nil -} - -func (c *Client4) FlagPostForContentReview(ctx context.Context, postId string, flagRequest *FlagContentRequest) (*Response, error) { - buf, err := json.Marshal(flagRequest) - if err != nil { - return nil, NewAppError("FlagPostForContentReview", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPost(ctx, fmt.Sprintf("%s/post/%s/flag", c.contentFlaggingRoute(), postId), string(buf)) - if err != nil { - return BuildResponse(r), err - } - - defer closeBody(r) - return BuildResponse(r), nil -} - -func (c *Client4) GetTeamPostFlaggingFeatureStatus(ctx context.Context, teamId string) (map[string]bool, *Response, error) { - r, err := c.DoAPIGet(ctx, c.contentFlaggingRoute()+"/team/"+teamId+"/status", "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var status map[string]bool - if err := json.NewDecoder(r.Body).Decode(&status); err != nil { - return nil, nil, NewAppError("GetFlaggingConfiguration", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return status, BuildResponse(r), nil -} - -func (c *Client4) GetPostPropertyValues(ctx context.Context, postId string) (*[]PropertyValue, *Response, error) { - r, err := c.DoAPIGet(ctx, c.contentFlaggingRoute()+"/post/"+postId+"/field_values", "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var propertyValues []PropertyValue - if err := json.NewDecoder(r.Body).Decode(&propertyValues); err != nil { - return nil, nil, NewAppError("GetFlaggingConfiguration", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &propertyValues, BuildResponse(r), nil -} - -func (c *Client4) GetContentFlaggedPost(ctx context.Context, postId string) (*Post, *Response, error) { - r, err := c.DoAPIGet(ctx, c.contentFlaggingRoute()+"/post/"+postId, "") - if err != nil { - return nil, BuildResponse(r), err - } - defer closeBody(r) - var post Post - if err := json.NewDecoder(r.Body).Decode(&post); err != nil { - return nil, nil, NewAppError("GetContentFlaggedPost", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &post, BuildResponse(r), nil -} - -func (c *Client4) bookmarksRoute(channelId string) string { - return c.channelRoute(channelId) + "/bookmarks" -} - -func (c *Client4) bookmarkRoute(channelId, bookmarkId string) string { - return fmt.Sprintf(c.bookmarksRoute(channelId)+"/%v", bookmarkId) -} - -func (c *Client4) clientPerfMetricsRoute() string { - return "/client_perf" -} - +// Returns the HTTP response or any error that occurred during the request. func (c *Client4) DoAPIGet(ctx context.Context, url string, etag string) (*http.Response, error) { - return c.DoAPIRequest(ctx, http.MethodGet, c.APIURL+url, "", etag) + return c.doAPIRequest(ctx, http.MethodGet, c.APIURL+url, "", etag) } -func (c *Client4) DoAPIPost(ctx context.Context, url string, data string) (*http.Response, error) { - return c.DoAPIRequest(ctx, http.MethodPost, c.APIURL+url, data, "") +// DoAPIPost makes a POST request to the specified URL with optional string data. +// Returns the HTTP response or any error that occurred during the request. +func (c *Client4) DoAPIPost(ctx context.Context, url, data string) (*http.Response, error) { + return c.doAPIRequest(ctx, http.MethodPost, c.APIURL+url, data, "") } -func (c *Client4) DoAPIDeleteBytes(ctx context.Context, url string, data []byte) (*http.Response, error) { - return c.DoAPIRequestBytes(ctx, http.MethodDelete, c.APIURL+url, data, "") +// DoAPIPostJSON marshals the provided data to JSON and makes a POST request to the specified URL. +// Returns the HTTP response or any error that occurred during marshaling or request. +func (c *Client4) DoAPIPostJSON(ctx context.Context, url string, data any) (*http.Response, error) { + buf, err := json.Marshal(data) + if err != nil { + return nil, err + } + return c.doAPIRequestBytes(ctx, http.MethodPost, c.APIURL+url, buf, "") } -func (c *Client4) DoAPIPatchBytes(ctx context.Context, url string, data []byte) (*http.Response, error) { - return c.DoAPIRequestBytes(ctx, http.MethodPatch, c.APIURL+url, data, "") +// DoAPIPut makes a PUT request to the specified URL with optional string data. +// Returns the HTTP response or any error that occurred during the request. +func (c *Client4) DoAPIPut(ctx context.Context, url, data string) (*http.Response, error) { + return c.doAPIRequest(ctx, http.MethodPut, c.APIURL+url, data, "") } -func (c *Client4) DoAPIPostBytes(ctx context.Context, url string, data []byte) (*http.Response, error) { - return c.DoAPIRequestBytes(ctx, http.MethodPost, c.APIURL+url, data, "") +// DoAPIPutJSON marshals the provided data to JSON and makes a PUT request to the specified URL. +// Returns the HTTP response or any error that occurred during marshaling or request. +func (c *Client4) DoAPIPutJSON(ctx context.Context, url string, data any) (*http.Response, error) { + buf, err := json.Marshal(data) + if err != nil { + return nil, err + } + return c.doAPIRequestBytes(ctx, http.MethodPut, c.APIURL+url, buf, "") } -func (c *Client4) DoAPIPut(ctx context.Context, url string, data string) (*http.Response, error) { - return c.DoAPIRequest(ctx, http.MethodPut, c.APIURL+url, data, "") -} - -func (c *Client4) DoAPIPutBytes(ctx context.Context, url string, data []byte) (*http.Response, error) { - return c.DoAPIRequestBytes(ctx, http.MethodPut, c.APIURL+url, data, "") +// DoAPIPatchJSON marshals the provided data to JSON and makes a PATCH request to the specified URL. +// Returns the HTTP response or any error that occurred during marshaling or request. +func (c *Client4) DoAPIPatchJSON(ctx context.Context, url string, data any) (*http.Response, error) { + buf, err := json.Marshal(data) + if err != nil { + return nil, err + } + return c.doAPIRequestBytes(ctx, http.MethodPatch, c.APIURL+url, buf, "") } +// DoAPIDelete makes a DELETE request to the specified URL. +// Returns the HTTP response or any error that occurred during the request. func (c *Client4) DoAPIDelete(ctx context.Context, url string) (*http.Response, error) { - return c.DoAPIRequest(ctx, http.MethodDelete, c.APIURL+url, "", "") + return c.doAPIRequest(ctx, http.MethodDelete, c.APIURL+url, "", "") } -func (c *Client4) DoAPIRequest(ctx context.Context, method, url, data, etag string) (*http.Response, error) { - return c.DoAPIRequestReader(ctx, method, url, strings.NewReader(data), map[string]string{HeaderEtagClient: etag}) +// DoAPIDeleteJSON marshals the provided data to JSON and makes a DELETE request to the specified URL. +// Returns the HTTP response or any error that occurred during marshaling or request. +func (c *Client4) DoAPIDeleteJSON(ctx context.Context, url string, data any) (*http.Response, error) { + buf, err := json.Marshal(data) + if err != nil { + return nil, err + } + return c.doAPIRequestBytes(ctx, http.MethodDelete, c.APIURL+url, buf, "") } +// DoAPIRequestWithHeaders makes an HTTP request with the specified method, URL, and custom headers. +// Returns the HTTP response or any error that occurred during the request. func (c *Client4) DoAPIRequestWithHeaders(ctx context.Context, method, url, data string, headers map[string]string) (*http.Response, error) { - return c.DoAPIRequestReader(ctx, method, url, strings.NewReader(data), headers) + return c.doAPIRequestReader(ctx, method, url, "", strings.NewReader(data), headers) } -func (c *Client4) DoAPIRequestBytes(ctx context.Context, method, url string, data []byte, etag string) (*http.Response, error) { - return c.DoAPIRequestReader(ctx, method, url, bytes.NewReader(data), map[string]string{HeaderEtagClient: etag}) +func (c *Client4) doAPIRequest(ctx context.Context, method, url, data, etag string) (*http.Response, error) { + return c.doAPIRequestReader(ctx, method, url, "", strings.NewReader(data), map[string]string{HeaderEtagClient: etag}) } -func (c *Client4) DoAPIRequestReader(ctx context.Context, method, url string, data io.Reader, headers map[string]string) (*http.Response, error) { +func (c *Client4) doAPIRequestBytes(ctx context.Context, method, url string, data []byte, etag string) (*http.Response, error) { + return c.doAPIRequestReader(ctx, method, url, "", bytes.NewReader(data), map[string]string{HeaderEtagClient: etag}) +} + +// doAPIRequestReader makes an HTTP request using an io.Reader for the request body and custom headers. +// This is the most flexible DoAPI method, supporting streaming data and custom headers. +// Returns the HTTP response or any error that occurred during the request. +func (c *Client4) doAPIRequestReader(ctx context.Context, method, url, contentType string, data io.Reader, headers map[string]string) (*http.Response, error) { rq, err := http.NewRequestWithContext(ctx, method, url, data) if err != nil { return nil, err @@ -858,6 +757,10 @@ func (c *Client4) DoAPIRequestReader(ctx context.Context, method, url string, da rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken) } + if contentType != "" { + rq.Header.Set("Content-Type", contentType) + } + if len(c.HTTPHeader) > 0 { for k, v := range c.HTTPHeader { rq.Header.Set(k, v) @@ -882,90 +785,12 @@ func (c *Client4) DoAPIRequestReader(ctx context.Context, method, url string, da } func (c *Client4) DoUploadFile(ctx context.Context, url string, data []byte, contentType string) (*FileUploadResponse, *Response, error) { - return c.doUploadFile(ctx, url, bytes.NewReader(data), contentType, 0) -} - -func (c *Client4) doUploadFile(ctx context.Context, url string, body io.Reader, contentType string, contentLength int64) (*FileUploadResponse, *Response, error) { - rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+url, body) + r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.APIURL+url, contentType, bytes.NewReader(data), nil) if err != nil { - return nil, nil, err + return nil, BuildResponse(r), err } - if contentLength != 0 { - rq.ContentLength = contentLength - } - rq.Header.Set("Content-Type", contentType) - - if c.AuthToken != "" { - rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken) - } - - rp, err := c.HTTPClient.Do(rq) - if err != nil { - return nil, BuildResponse(rp), err - } - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body) - } - - var res FileUploadResponse - if err := json.NewDecoder(rp.Body).Decode(&res); err != nil { - return nil, nil, NewAppError("doUploadFile", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &res, BuildResponse(rp), nil -} - -func (c *Client4) DoEmojiUploadFile(ctx context.Context, url string, data []byte, contentType string) (*Emoji, *Response, error) { - rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+url, bytes.NewReader(data)) - if err != nil { - return nil, nil, err - } - rq.Header.Set("Content-Type", contentType) - - if c.AuthToken != "" { - rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken) - } - - rp, err := c.HTTPClient.Do(rq) - if err != nil { - return nil, BuildResponse(rp), err - } - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body) - } - - var e Emoji - if err := json.NewDecoder(rp.Body).Decode(&e); err != nil { - return nil, nil, NewAppError("DoEmojiUploadFile", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &e, BuildResponse(rp), nil -} - -func (c *Client4) DoUploadImportTeam(ctx context.Context, url string, data []byte, contentType string) (map[string]string, *Response, error) { - rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+url, bytes.NewReader(data)) - if err != nil { - return nil, nil, err - } - rq.Header.Set("Content-Type", contentType) - - if c.AuthToken != "" { - rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken) - } - - rp, err := c.HTTPClient.Do(rq) - if err != nil { - return nil, BuildResponse(rp), err - } - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body) - } - - return MapFromJSON(rp.Body), BuildResponse(rp), nil + defer closeBody(r) + return DecodeJSONFromResponse[*FileUploadResponse](r) } // Authentication Section @@ -1017,7 +842,7 @@ func (c *Client4) LoginWithMFA(ctx context.Context, loginId, password, mfaToken } func (c *Client4) login(ctx context.Context, m map[string]string) (*User, *Response, error) { - r, err := c.DoAPIPost(ctx, "/users/login", MapToJSON(m)) + r, err := c.DoAPIPostJSON(ctx, "/users/login", m) if err != nil { return nil, BuildResponse(r), err } @@ -1025,18 +850,14 @@ func (c *Client4) login(ctx context.Context, m map[string]string) (*User, *Respo c.AuthToken = r.Header.Get(HeaderToken) c.AuthType = HeaderBearer - var user User - if err := json.NewDecoder(r.Body).Decode(&user); err != nil { - return nil, nil, NewAppError("login", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &user, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } func (c *Client4) LoginWithDesktopToken(ctx context.Context, token, deviceId string) (*User, *Response, error) { m := make(map[string]string) m["token"] = token m["deviceId"] = deviceId - r, err := c.DoAPIPost(ctx, "/users/login/desktop_token", MapToJSON(m)) + r, err := c.DoAPIPostJSON(ctx, "/users/login/desktop_token", m) if err != nil { return nil, BuildResponse(r), err } @@ -1044,11 +865,7 @@ func (c *Client4) LoginWithDesktopToken(ctx context.Context, token, deviceId str c.AuthToken = r.Header.Get(HeaderToken) c.AuthType = HeaderBearer - var user User - if err := json.NewDecoder(r.Body).Decode(&user); err != nil { - return nil, nil, NewAppError("loginWithDesktopToken", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &user, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // Logout terminates the current user's session. @@ -1065,85 +882,60 @@ func (c *Client4) Logout(ctx context.Context) (*Response, error) { // SwitchAccountType changes a user's login type from one type to another. func (c *Client4) SwitchAccountType(ctx context.Context, switchRequest *SwitchRequest) (string, *Response, error) { - buf, err := json.Marshal(switchRequest) - if err != nil { - return "", BuildResponse(nil), NewAppError("SwitchAccountType", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.usersRoute()+"/login/switch", buf) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/login/switch", switchRequest) if err != nil { return "", BuildResponse(r), err } defer closeBody(r) - return MapFromJSON(r.Body)["follow_link"], BuildResponse(r), nil + result, resp, err := DecodeJSONFromResponse[map[string]string](r) + if err != nil { + return "", resp, err + } + return result["follow_link"], resp, nil } // User Section // CreateUser creates a user in the system based on the provided user struct. func (c *Client4) CreateUser(ctx context.Context, user *User) (*User, *Response, error) { - userJSON, err := json.Marshal(user) - if err != nil { - return nil, nil, NewAppError("CreateUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPost(ctx, c.usersRoute(), string(userJSON)) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute(), user) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var u User - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("CreateUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // CreateUserWithToken creates a user in the system based on the provided tokenId. func (c *Client4) CreateUserWithToken(ctx context.Context, user *User, tokenId string) (*User, *Response, error) { if tokenId == "" { - return nil, nil, NewAppError("MissingHashOrData", "api.user.create_user.missing_token.app_error", nil, "", http.StatusBadRequest) + return nil, nil, errors.New("token ID is required") } - query := "?t=" + tokenId - buf, err := json.Marshal(user) - if err != nil { - return nil, nil, NewAppError("CreateUserWithToken", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.usersRoute()+query, buf) + values := url.Values{} + values.Set("t", tokenId) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"?"+values.Encode(), user) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var u User - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("CreateUserWithToken", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // CreateUserWithInviteId creates a user in the system based on the provided invited id. func (c *Client4) CreateUserWithInviteId(ctx context.Context, user *User, inviteId string) (*User, *Response, error) { if inviteId == "" { - return nil, nil, NewAppError("MissingInviteId", "api.user.create_user.missing_invite_id.app_error", nil, "", http.StatusBadRequest) + return nil, nil, errors.New("invite ID is required") } - query := "?iid=" + url.QueryEscape(inviteId) - buf, err := json.Marshal(user) - if err != nil { - return nil, nil, NewAppError("CreateUserWithInviteId", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.usersRoute()+query, buf) + values := url.Values{} + values.Set("iid", inviteId) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"?"+values.Encode(), user) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var u User - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("CreateUserWithInviteId", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // GetMe returns the logged in user. @@ -1153,14 +945,7 @@ func (c *Client4) GetMe(ctx context.Context, etag string) (*User, *Response, err return nil, BuildResponse(r), err } defer closeBody(r) - var u User - if r.StatusCode == http.StatusNotModified { - return &u, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("GetMe", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // GetUser returns a user based on the provided user id string. @@ -1170,14 +955,7 @@ func (c *Client4) GetUser(ctx context.Context, userId, etag string) (*User, *Res return nil, BuildResponse(r), err } defer closeBody(r) - var u User - if r.StatusCode == http.StatusNotModified { - return &u, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("GetUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // GetUserByUsername returns a user based on the provided user name string. @@ -1187,14 +965,7 @@ func (c *Client4) GetUserByUsername(ctx context.Context, userName, etag string) return nil, BuildResponse(r), err } defer closeBody(r) - var u User - if r.StatusCode == http.StatusNotModified { - return &u, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("GetUserByUsername", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // GetUserByEmail returns a user based on the provided user email string. @@ -1204,68 +975,49 @@ func (c *Client4) GetUserByEmail(ctx context.Context, email, etag string) (*User return nil, BuildResponse(r), err } defer closeBody(r) - var u User - if r.StatusCode == http.StatusNotModified { - return &u, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("GetUserByEmail", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // AutocompleteUsersInTeam returns the users on a team based on search term. func (c *Client4) AutocompleteUsersInTeam(ctx context.Context, teamId string, username string, limit int, etag string) (*UserAutocomplete, *Response, error) { - query := fmt.Sprintf("?in_team=%v&name=%v&limit=%d", teamId, username, limit) - r, err := c.DoAPIGet(ctx, c.usersRoute()+"/autocomplete"+query, etag) + values := url.Values{} + values.Set("in_team", teamId) + values.Set("name", username) + values.Set("limit", strconv.Itoa(limit)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"/autocomplete?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var u UserAutocomplete - if r.StatusCode == http.StatusNotModified { - return &u, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("AutocompleteUsersInTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*UserAutocomplete](r) } // AutocompleteUsersInChannel returns the users in a channel based on search term. func (c *Client4) AutocompleteUsersInChannel(ctx context.Context, teamId string, channelId string, username string, limit int, etag string) (*UserAutocomplete, *Response, error) { - query := fmt.Sprintf("?in_team=%v&in_channel=%v&name=%v&limit=%d", teamId, channelId, username, limit) - r, err := c.DoAPIGet(ctx, c.usersRoute()+"/autocomplete"+query, etag) + values := url.Values{} + values.Set("in_team", teamId) + values.Set("in_channel", channelId) + values.Set("name", username) + values.Set("limit", strconv.Itoa(limit)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"/autocomplete?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var u UserAutocomplete - if r.StatusCode == http.StatusNotModified { - return &u, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("AutocompleteUsersInChannel", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*UserAutocomplete](r) } // AutocompleteUsers returns the users in the system based on search term. func (c *Client4) AutocompleteUsers(ctx context.Context, username string, limit int, etag string) (*UserAutocomplete, *Response, error) { - query := fmt.Sprintf("?name=%v&limit=%d", username, limit) - r, err := c.DoAPIGet(ctx, c.usersRoute()+"/autocomplete"+query, etag) + values := url.Values{} + values.Set("name", username) + values.Set("limit", strconv.Itoa(limit)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"/autocomplete?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var u UserAutocomplete - if r.StatusCode == http.StatusNotModified { - return &u, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("AutocompleteUsers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*UserAutocomplete](r) } // GetDefaultProfileImage gets the default user's profile image. Must be logged in. @@ -1275,13 +1027,7 @@ func (c *Client4) GetDefaultProfileImage(ctx context.Context, userId string) ([] return nil, BuildResponse(r), err } defer closeBody(r) - - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetDefaultProfileImage", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // GetProfileImage gets user's profile image. Must be logged in. @@ -1291,174 +1037,135 @@ func (c *Client4) GetProfileImage(ctx context.Context, userId, etag string) ([]b return nil, BuildResponse(r), err } defer closeBody(r) - - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetProfileImage", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // GetUsers returns a page of users on the system. Page counting starts at 0. func (c *Client4) GetUsers(ctx context.Context, page int, perPage int, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersWithCustomQueryParameters returns a page of users on the system. Page counting starts at 0. func (c *Client4) GetUsersWithCustomQueryParameters(ctx context.Context, page int, perPage int, queryParameters, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&%v", page, perPage, queryParameters) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode()+"&"+queryParameters, etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersWithCustomQueryParameters", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersInTeam returns a page of users on a team. Page counting starts at 0. func (c *Client4) GetUsersInTeam(ctx context.Context, teamId string, page int, perPage int, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?in_team=%v&page=%v&per_page=%v", teamId, page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("in_team", teamId) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersInTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetNewUsersInTeam returns a page of users on a team. Page counting starts at 0. func (c *Client4) GetNewUsersInTeam(ctx context.Context, teamId string, page int, perPage int, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?sort=create_at&in_team=%v&page=%v&per_page=%v", teamId, page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("sort", "create_at") + values.Set("in_team", teamId) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetNewUsersInTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetRecentlyActiveUsersInTeam returns a page of users on a team. Page counting starts at 0. func (c *Client4) GetRecentlyActiveUsersInTeam(ctx context.Context, teamId string, page int, perPage int, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?sort=last_activity_at&in_team=%v&page=%v&per_page=%v", teamId, page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("sort", "last_activity_at") + values.Set("in_team", teamId) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetRecentlyActiveUsersInTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetActiveUsersInTeam returns a page of users on a team. Page counting starts at 0. func (c *Client4) GetActiveUsersInTeam(ctx context.Context, teamId string, page int, perPage int, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?active=true&in_team=%v&page=%v&per_page=%v", teamId, page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("active", "true") + values.Set("in_team", teamId) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetActiveUsersInTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersNotInTeam returns a page of users who are not in a team. Page counting starts at 0. func (c *Client4) GetUsersNotInTeam(ctx context.Context, teamId string, page int, perPage int, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?not_in_team=%v&page=%v&per_page=%v", teamId, page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("not_in_team", teamId) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersNotInTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersInChannel returns a page of users in a channel. Page counting starts at 0. func (c *Client4) GetUsersInChannel(ctx context.Context, channelId string, page int, perPage int, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v", channelId, page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("in_channel", channelId) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersInChannel", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersInChannelByStatus returns a page of users in a channel. Page counting starts at 0. Sorted by Status func (c *Client4) GetUsersInChannelByStatus(ctx context.Context, channelId string, page int, perPage int, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?in_channel=%v&page=%v&per_page=%v&sort=status", channelId, page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("in_channel", channelId) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("sort", "status") + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersInChannelByStatus", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersNotInChannel returns a page of users not in a channel. Page counting starts at 0. @@ -1475,91 +1182,73 @@ func (c *Client4) GetUsersNotInChannel(ctx context.Context, teamId, channelId st // GetUsersNotInChannelWithOptionsStruct returns a page of users not in a channel using the options struct. func (c *Client4) GetUsersNotInChannelWithOptions(ctx context.Context, channelId string, options *GetUsersNotInChannelOptions) ([]*User, *Response, error) { - query := fmt.Sprintf("?in_team=%v¬_in_channel=%v&page=%v&per_page=%v", options.TeamID, channelId, options.Page, options.Limit) - if options.CursorID != "" { - query += fmt.Sprintf("&cursor_id=%v", options.CursorID) + values := url.Values{} + if options != nil { + values.Set("in_team", options.TeamID) + values.Set("not_in_channel", channelId) + values.Set("page", strconv.Itoa(options.Page)) + values.Set("per_page", strconv.Itoa(options.Limit)) + values.Set("cursor_id", options.CursorID) } - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, options.Etag) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), options.Etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersNotInChannelWithOptionsStruct", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersWithoutTeam returns a page of users on the system that aren't on any teams. Page counting starts at 0. func (c *Client4) GetUsersWithoutTeam(ctx context.Context, page int, perPage int, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?without_team=1&page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("without_team", "1") + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersWithoutTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersInGroup returns a page of users in a group. Page counting starts at 0. func (c *Client4) GetUsersInGroup(ctx context.Context, groupID string, page int, perPage int, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?in_group=%v&page=%v&per_page=%v", groupID, page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("in_group", groupID) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersInGroup", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersInGroup returns a page of users in a group. Page counting starts at 0. func (c *Client4) GetUsersInGroupByDisplayName(ctx context.Context, groupID string, page int, perPage int, etag string) ([]*User, *Response, error) { - query := fmt.Sprintf("?sort=display_name&in_group=%v&page=%v&per_page=%v", groupID, page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, etag) + values := url.Values{} + values.Set("sort", "display_name") + values.Set("in_group", groupID) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersInGroupByDisplayName", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersByIds returns a list of users based on the provided user ids. func (c *Client4) GetUsersByIds(ctx context.Context, userIds []string) ([]*User, *Response, error) { - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/ids", ArrayToJSON(userIds)) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/ids", userIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersByIds", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersByIds returns a list of users based on the provided user ids. @@ -1574,117 +1263,73 @@ func (c *Client4) GetUsersByIdsWithOptions(ctx context.Context, userIds []string url += "?" + v.Encode() } - r, err := c.DoAPIPost(ctx, url, ArrayToJSON(userIds)) + r, err := c.DoAPIPostJSON(ctx, url, userIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var list []*User - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersByIdsWithOptions", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersByUsernames returns a list of users based on the provided usernames. func (c *Client4) GetUsersByUsernames(ctx context.Context, usernames []string) ([]*User, *Response, error) { - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/usernames", ArrayToJSON(usernames)) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/usernames", usernames) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersByUsernames", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // GetUsersByGroupChannelIds returns a map with channel ids as keys // and a list of users as values based on the provided user ids. func (c *Client4) GetUsersByGroupChannelIds(ctx context.Context, groupChannelIds []string) (map[string][]*User, *Response, error) { - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/group_channels", ArrayToJSON(groupChannelIds)) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/group_channels", groupChannelIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - usersByChannelId := map[string][]*User{} - json.NewDecoder(r.Body).Decode(&usersByChannelId) - return usersByChannelId, BuildResponse(r), nil + return DecodeJSONFromResponse[map[string][]*User](r) } // SearchUsers returns a list of users based on some search criteria. func (c *Client4) SearchUsers(ctx context.Context, search *UserSearch) ([]*User, *Response, error) { - buf, err := json.Marshal(search) - if err != nil { - return nil, nil, NewAppError("SearchUsers", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.usersRoute()+"/search", buf) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/search", search) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("SearchUsers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } // UpdateUser updates a user in the system based on the provided user struct. func (c *Client4) UpdateUser(ctx context.Context, user *User) (*User, *Response, error) { - buf, err := json.Marshal(user) - if err != nil { - return nil, nil, NewAppError("UpdateUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.userRoute(user.Id), buf) + r, err := c.DoAPIPutJSON(ctx, c.userRoute(user.Id), user) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var u User - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("UpdateUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // PatchUser partially updates a user in the system. Any missing fields are not updated. func (c *Client4) PatchUser(ctx context.Context, userId string, patch *UserPatch) (*User, *Response, error) { - buf, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.userRoute(userId)+"/patch", buf) + r, err := c.DoAPIPutJSON(ctx, c.userRoute(userId)+"/patch", patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var u User - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("PatchUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // UpdateUserAuth updates a user AuthData (uthData, authService and password) in the system. func (c *Client4) UpdateUserAuth(ctx context.Context, userId string, userAuth *UserAuth) (*UserAuth, *Response, error) { - buf, err := json.Marshal(userAuth) - if err != nil { - return nil, nil, NewAppError("UpdateUserAuth", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.userRoute(userId)+"/auth", buf) + r, err := c.DoAPIPutJSON(ctx, c.userRoute(userId)+"/auth", userAuth) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var ua UserAuth - if err := json.NewDecoder(r.Body).Decode(&ua); err != nil { - return nil, nil, NewAppError("UpdateUserAuth", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &ua, BuildResponse(r), nil + return DecodeJSONFromResponse[*UserAuth](r) } // UpdateUserMfa activates multi-factor authentication for a user if activate @@ -1695,7 +1340,7 @@ func (c *Client4) UpdateUserMfa(ctx context.Context, userId, code string, activa requestBody["activate"] = activate requestBody["code"] = code - r, err := c.DoAPIPut(ctx, c.userRoute(userId)+"/mfa", StringInterfaceToJSON(requestBody)) + r, err := c.DoAPIPutJSON(ctx, c.userRoute(userId)+"/mfa", requestBody) if err != nil { return BuildResponse(r), err } @@ -1711,17 +1356,13 @@ func (c *Client4) GenerateMfaSecret(ctx context.Context, userId string) (*MfaSec return nil, BuildResponse(r), err } defer closeBody(r) - var secret MfaSecret - if err := json.NewDecoder(r.Body).Decode(&secret); err != nil { - return nil, nil, NewAppError("GenerateMfaSecret", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &secret, BuildResponse(r), nil + return DecodeJSONFromResponse[*MfaSecret](r) } // UpdateUserPassword updates a user's password. Must be logged in as the user or be a system administrator. func (c *Client4) UpdateUserPassword(ctx context.Context, userId, currentPassword, newPassword string) (*Response, error) { requestBody := map[string]string{"current_password": currentPassword, "new_password": newPassword} - r, err := c.DoAPIPut(ctx, c.userRoute(userId)+"/password", MapToJSON(requestBody)) + r, err := c.DoAPIPutJSON(ctx, c.userRoute(userId)+"/password", requestBody) if err != nil { return BuildResponse(r), err } @@ -1732,7 +1373,7 @@ func (c *Client4) UpdateUserPassword(ctx context.Context, userId, currentPasswor // UpdateUserHashedPassword updates a user's password with an already-hashed password. Must be a system administrator. func (c *Client4) UpdateUserHashedPassword(ctx context.Context, userId, newHashedPassword string) (*Response, error) { requestBody := map[string]string{"already_hashed": "true", "new_password": newHashedPassword} - r, err := c.DoAPIPut(ctx, c.userRoute(userId)+"/password", MapToJSON(requestBody)) + r, err := c.DoAPIPutJSON(ctx, c.userRoute(userId)+"/password", requestBody) if err != nil { return BuildResponse(r), err } @@ -1763,7 +1404,7 @@ func (c *Client4) DemoteUserToGuest(ctx context.Context, guestId string) (*Respo // UpdateUserRoles updates a user's roles in the system. A user can have "system_user" and "system_admin" roles. func (c *Client4) UpdateUserRoles(ctx context.Context, userId, roles string) (*Response, error) { requestBody := map[string]string{"roles": roles} - r, err := c.DoAPIPut(ctx, c.userRoute(userId)+"/roles", MapToJSON(requestBody)) + r, err := c.DoAPIPutJSON(ctx, c.userRoute(userId)+"/roles", requestBody) if err != nil { return BuildResponse(r), err } @@ -1775,7 +1416,7 @@ func (c *Client4) UpdateUserRoles(ctx context.Context, userId, roles string) (*R func (c *Client4) UpdateUserActive(ctx context.Context, userId string, active bool) (*Response, error) { requestBody := make(map[string]any) requestBody["active"] = active - r, err := c.DoAPIPut(ctx, c.userRoute(userId)+"/active", StringInterfaceToJSON(requestBody)) + r, err := c.DoAPIPutJSON(ctx, c.userRoute(userId)+"/active", requestBody) if err != nil { return BuildResponse(r), err } @@ -1821,12 +1462,7 @@ func (c *Client4) ConvertUserToBot(ctx context.Context, userId string) (*Bot, *R return nil, BuildResponse(r), err } defer closeBody(r) - var bot *Bot - err = json.NewDecoder(r.Body).Decode(&bot) - if err != nil { - return nil, BuildResponse(r), NewAppError("ConvertUserToBot", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return bot, BuildResponse(r), nil + return DecodeJSONFromResponse[*Bot](r) } // ConvertBotToUser converts a bot user to a user. @@ -1835,20 +1471,12 @@ func (c *Client4) ConvertBotToUser(ctx context.Context, userId string, userPatch if setSystemAdmin { query = "?set_system_admin=true" } - buf, err := json.Marshal(userPatch) - if err != nil { - return nil, nil, NewAppError("ConvertBotToUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.botRoute(userId)+"/convert_to_user"+query, buf) + r, err := c.DoAPIPostJSON(ctx, c.botRoute(userId)+"/convert_to_user"+query, userPatch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var u User - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("ConvertBotToUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // PermanentDeleteAll permanently deletes all users in the system. This is a local only endpoint @@ -1865,7 +1493,7 @@ func (c *Client4) PermanentDeleteAllUsers(ctx context.Context) (*Response, error // provided email. func (c *Client4) SendPasswordResetEmail(ctx context.Context, email string) (*Response, error) { requestBody := map[string]string{"email": email} - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/password/reset/send", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/password/reset/send", requestBody) if err != nil { return BuildResponse(r), err } @@ -1876,7 +1504,7 @@ func (c *Client4) SendPasswordResetEmail(ctx context.Context, email string) (*Re // ResetPassword uses a recovery code to update reset a user's password. func (c *Client4) ResetPassword(ctx context.Context, token, newPassword string) (*Response, error) { requestBody := map[string]string{"token": token, "new_password": newPassword} - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/password/reset", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/password/reset", requestBody) if err != nil { return BuildResponse(r), err } @@ -1891,17 +1519,13 @@ func (c *Client4) GetSessions(ctx context.Context, userId, etag string) ([]*Sess return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Session - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetSessions", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Session](r) } // RevokeSession revokes a user session based on the provided user id and session id strings. func (c *Client4) RevokeSession(ctx context.Context, userId, sessionId string) (*Response, error) { requestBody := map[string]string{"session_id": sessionId} - r, err := c.DoAPIPost(ctx, c.userRoute(userId)+"/sessions/revoke", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.userRoute(userId)+"/sessions/revoke", requestBody) if err != nil { return BuildResponse(r), err } @@ -1931,7 +1555,7 @@ func (c *Client4) RevokeSessionsFromAllUsers(ctx context.Context) (*Response, er // AttachDeviceProps attaches a mobile device ID to the current session and other props. func (c *Client4) AttachDeviceProps(ctx context.Context, newProps map[string]string) (*Response, error) { - r, err := c.DoAPIPut(ctx, c.usersRoute()+"/sessions/device", MapToJSON(newProps)) + r, err := c.DoAPIPutJSON(ctx, c.usersRoute()+"/sessions/device", newProps) if err != nil { return BuildResponse(r), err } @@ -1944,50 +1568,36 @@ func (c *Client4) AttachDeviceProps(ctx context.Context, newProps map[string]str // An optional team ID can be set to exclude that team from the results. // An optional boolean can be set to include collapsed thread unreads. Must be authenticated. func (c *Client4) GetTeamsUnreadForUser(ctx context.Context, userId, teamIdToExclude string, includeCollapsedThreads bool) ([]*TeamUnread, *Response, error) { - query := url.Values{} - + values := url.Values{} if teamIdToExclude != "" { - query.Set("exclude_team", teamIdToExclude) + values.Set("exclude_team", teamIdToExclude) } - - if includeCollapsedThreads { - query.Set("include_collapsed_threads", "true") - } - - r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/teams/unread?"+query.Encode(), "") + values.Set("include_collapsed_threads", c.boolString(includeCollapsedThreads)) + r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/teams/unread?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var list []*TeamUnread - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetTeamsUnreadForUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*TeamUnread](r) } // GetUserAudits returns a list of audit based on the provided user id string. func (c *Client4) GetUserAudits(ctx context.Context, userId string, page int, perPage int, etag string) (Audits, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/audits"+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/audits?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var audits Audits - err = json.NewDecoder(r.Body).Decode(&audits) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetUserAudits", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return audits, BuildResponse(r), nil + return DecodeJSONFromResponse[Audits](r) } // VerifyUserEmail will verify a user's email using the supplied token. func (c *Client4) VerifyUserEmail(ctx context.Context, token string) (*Response, error) { requestBody := map[string]string{"token": token} - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/email/verify", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/email/verify", requestBody) if err != nil { return BuildResponse(r), err } @@ -2002,11 +1612,7 @@ func (c *Client4) VerifyUserEmailWithoutToken(ctx context.Context, userId string return nil, BuildResponse(r), err } defer closeBody(r) - var u User - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("VerifyUserEmailWithoutToken", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*User](r) } // SendVerificationEmail will send an email to the user with the provided email address, if @@ -2014,7 +1620,7 @@ func (c *Client4) VerifyUserEmailWithoutToken(ctx context.Context, userId string // email address. func (c *Client4) SendVerificationEmail(ctx context.Context, email string) (*Response, error) { requestBody := map[string]string{"email": email} - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/email/verify/send", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/email/verify/send", requestBody) if err != nil { return BuildResponse(r), err } @@ -2038,38 +1644,24 @@ func (c *Client4) SetProfileImage(ctx context.Context, userId string, data []byt part, err := writer.CreateFormFile("image", "profile.png") if err != nil { - return nil, NewAppError("SetProfileImage", "model.client.set_profile_user.no_file.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to create form file: %w", err) } if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return nil, NewAppError("SetProfileImage", "model.client.set_profile_user.no_file.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to copy data to form file: %w", err) } if err = writer.Close(); err != nil { - return nil, NewAppError("SetProfileImage", "model.client.set_profile_user.writer.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to close multipart writer: %w", err) } - rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+c.userRoute(userId)+"/image", bytes.NewReader(body.Bytes())) + r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.APIURL+c.userRoute(userId)+"/image", writer.FormDataContentType(), body, nil) if err != nil { - return nil, err + return BuildResponse(r), err } - rq.Header.Set("Content-Type", writer.FormDataContentType()) + defer closeBody(r) - if c.AuthToken != "" { - rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken) - } - - rp, err := c.HTTPClient.Do(rq) - if err != nil { - return BuildResponse(rp), err - } - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return BuildResponse(rp), AppErrorFromJSON(rp.Body) - } - - return BuildResponse(rp), nil + return BuildResponse(r), nil } // CreateUserAccessToken will generate a user access token that can be used in place @@ -2078,33 +1670,27 @@ func (c *Client4) SetProfileImage(ctx context.Context, userId string, data []byt // permission. A non-blank description is required. func (c *Client4) CreateUserAccessToken(ctx context.Context, userId, description string) (*UserAccessToken, *Response, error) { requestBody := map[string]string{"description": description} - r, err := c.DoAPIPost(ctx, c.userRoute(userId)+"/tokens", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.userRoute(userId)+"/tokens", requestBody) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var uat UserAccessToken - if err := json.NewDecoder(r.Body).Decode(&uat); err != nil { - return nil, nil, NewAppError("CreateUserAccessToken", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &uat, BuildResponse(r), nil + return DecodeJSONFromResponse[*UserAccessToken](r) } // GetUserAccessTokens will get a page of access tokens' id, description, is_active // and the user_id in the system. The actual token will not be returned. Must have // the 'manage_system' permission. func (c *Client4) GetUserAccessTokens(ctx context.Context, page int, perPage int) ([]*UserAccessToken, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.userAccessTokensRoute()+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.userAccessTokensRoute()+"?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*UserAccessToken - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUserAccessTokens", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*UserAccessToken](r) } // GetUserAccessToken will get a user access tokens' id, description, is_active @@ -2117,11 +1703,7 @@ func (c *Client4) GetUserAccessToken(ctx context.Context, tokenId string) (*User return nil, BuildResponse(r), err } defer closeBody(r) - var uat UserAccessToken - if err := json.NewDecoder(r.Body).Decode(&uat); err != nil { - return nil, nil, NewAppError("GetUserAccessToken", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &uat, BuildResponse(r), nil + return DecodeJSONFromResponse[*UserAccessToken](r) } // GetUserAccessTokensForUser will get a paged list of user access tokens showing id, @@ -2129,17 +1711,15 @@ func (c *Client4) GetUserAccessToken(ctx context.Context, tokenId string) (*User // the 'read_user_access_token' permission and if getting for another user, must have the // 'edit_other_users' permission. func (c *Client4) GetUserAccessTokensForUser(ctx context.Context, userId string, page, perPage int) ([]*UserAccessToken, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/tokens"+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/tokens?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*UserAccessToken - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUserAccessTokensForUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*UserAccessToken](r) } // RevokeUserAccessToken will revoke a user access token by id. Must have the @@ -2147,7 +1727,7 @@ func (c *Client4) GetUserAccessTokensForUser(ctx context.Context, userId string, // 'edit_other_users' permission. func (c *Client4) RevokeUserAccessToken(ctx context.Context, tokenId string) (*Response, error) { requestBody := map[string]string{"token_id": tokenId} - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/tokens/revoke", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/tokens/revoke", requestBody) if err != nil { return BuildResponse(r), err } @@ -2157,20 +1737,12 @@ func (c *Client4) RevokeUserAccessToken(ctx context.Context, tokenId string) (*R // SearchUserAccessTokens returns user access tokens matching the provided search term. func (c *Client4) SearchUserAccessTokens(ctx context.Context, search *UserAccessTokenSearch) ([]*UserAccessToken, *Response, error) { - buf, err := json.Marshal(search) - if err != nil { - return nil, nil, NewAppError("SearchUserAccessTokens", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.usersRoute()+"/tokens/search", buf) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/tokens/search", search) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*UserAccessToken - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("SearchUserAccessTokens", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*UserAccessToken](r) } // DisableUserAccessToken will disable a user access token by id. Must have the @@ -2178,7 +1750,7 @@ func (c *Client4) SearchUserAccessTokens(ctx context.Context, search *UserAccess // 'edit_other_users' permission. func (c *Client4) DisableUserAccessToken(ctx context.Context, tokenId string) (*Response, error) { requestBody := map[string]string{"token_id": tokenId} - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/tokens/disable", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/tokens/disable", requestBody) if err != nil { return BuildResponse(r), err } @@ -2191,7 +1763,7 @@ func (c *Client4) DisableUserAccessToken(ctx context.Context, tokenId string) (* // 'edit_other_users' permission. func (c *Client4) EnableUserAccessToken(ctx context.Context, tokenId string) (*Response, error) { requestBody := map[string]string{"token_id": tokenId} - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/tokens/enable", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/tokens/enable", requestBody) if err != nil { return BuildResponse(r), err } @@ -2243,55 +1815,29 @@ func (c *Client4) GetUsersForReporting(ctx context.Context, options *UserReportO return nil, BuildResponse(r), err } defer closeBody(r) - var list []*UserReport - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersForReporting", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*UserReport](r) } // Bots section // CreateBot creates a bot in the system based on the provided bot struct. func (c *Client4) CreateBot(ctx context.Context, bot *Bot) (*Bot, *Response, error) { - buf, err := json.Marshal(bot) - if err != nil { - return nil, nil, NewAppError("CreateBot", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.botsRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.botsRoute(), bot) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var resp *Bot - err = json.NewDecoder(r.Body).Decode(&resp) - if err != nil { - return nil, BuildResponse(r), NewAppError("CreateBot", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return resp, BuildResponse(r), nil + return DecodeJSONFromResponse[*Bot](r) } // PatchBot partially updates a bot. Any missing fields are not updated. func (c *Client4) PatchBot(ctx context.Context, userId string, patch *BotPatch) (*Bot, *Response, error) { - buf, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchBot", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.botRoute(userId), buf) + r, err := c.DoAPIPutJSON(ctx, c.botRoute(userId), patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var bot *Bot - err = json.NewDecoder(r.Body).Decode(&bot) - if err != nil { - return nil, BuildResponse(r), NewAppError("PatchBot", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return bot, BuildResponse(r), nil + return DecodeJSONFromResponse[*Bot](r) } // GetBot fetches the given, undeleted bot. @@ -2301,14 +1847,7 @@ func (c *Client4) GetBot(ctx context.Context, userId string, etag string) (*Bot, return nil, BuildResponse(r), err } defer closeBody(r) - - var bot *Bot - err = json.NewDecoder(r.Body).Decode(&bot) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetBot", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return bot, BuildResponse(r), nil + return DecodeJSONFromResponse[*Bot](r) } // GetBotIncludeDeleted fetches the given bot, even if it is deleted. @@ -2318,136 +1857,90 @@ func (c *Client4) GetBotIncludeDeleted(ctx context.Context, userId string, etag return nil, BuildResponse(r), err } defer closeBody(r) - - var bot *Bot - err = json.NewDecoder(r.Body).Decode(&bot) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetBotIncludeDeleted", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return bot, BuildResponse(r), nil + return DecodeJSONFromResponse[*Bot](r) } // GetBots fetches the given page of bots, excluding deleted. func (c *Client4) GetBots(ctx context.Context, page, perPage int, etag string) ([]*Bot, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.botsRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.botsRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var bots BotList - err = json.NewDecoder(r.Body).Decode(&bots) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetBots", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return bots, BuildResponse(r), nil + return DecodeJSONFromResponse[BotList](r) } // GetBotsIncludeDeleted fetches the given page of bots, including deleted. func (c *Client4) GetBotsIncludeDeleted(ctx context.Context, page, perPage int, etag string) ([]*Bot, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&include_deleted="+c.boolString(true), page, perPage) - r, err := c.DoAPIGet(ctx, c.botsRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("include_deleted", c.boolString(true)) + r, err := c.DoAPIGet(ctx, c.botsRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var bots BotList - err = json.NewDecoder(r.Body).Decode(&bots) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetBotsIncludeDeleted", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return bots, BuildResponse(r), nil + return DecodeJSONFromResponse[BotList](r) } // GetBotsOrphaned fetches the given page of bots, only including orphaned bots. func (c *Client4) GetBotsOrphaned(ctx context.Context, page, perPage int, etag string) ([]*Bot, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&only_orphaned="+c.boolString(true), page, perPage) - r, err := c.DoAPIGet(ctx, c.botsRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("only_orphaned", c.boolString(true)) + r, err := c.DoAPIGet(ctx, c.botsRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var bots BotList - err = json.NewDecoder(r.Body).Decode(&bots) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetBotsOrphaned", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return bots, BuildResponse(r), nil + return DecodeJSONFromResponse[BotList](r) } // DisableBot disables the given bot in the system. func (c *Client4) DisableBot(ctx context.Context, botUserId string) (*Bot, *Response, error) { - r, err := c.DoAPIPostBytes(ctx, c.botRoute(botUserId)+"/disable", nil) + r, err := c.DoAPIPost(ctx, c.botRoute(botUserId)+"/disable", "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var bot *Bot - err = json.NewDecoder(r.Body).Decode(&bot) - if err != nil { - return nil, BuildResponse(r), NewAppError("DisableBot", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return bot, BuildResponse(r), nil + return DecodeJSONFromResponse[*Bot](r) } // EnableBot disables the given bot in the system. func (c *Client4) EnableBot(ctx context.Context, botUserId string) (*Bot, *Response, error) { - r, err := c.DoAPIPostBytes(ctx, c.botRoute(botUserId)+"/enable", nil) + r, err := c.DoAPIPost(ctx, c.botRoute(botUserId)+"/enable", "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var bot *Bot - err = json.NewDecoder(r.Body).Decode(&bot) - if err != nil { - return nil, BuildResponse(r), NewAppError("EnableBot", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return bot, BuildResponse(r), nil + return DecodeJSONFromResponse[*Bot](r) } // AssignBot assigns the given bot to the given user func (c *Client4) AssignBot(ctx context.Context, botUserId, newOwnerId string) (*Bot, *Response, error) { - r, err := c.DoAPIPostBytes(ctx, c.botRoute(botUserId)+"/assign/"+newOwnerId, nil) + r, err := c.DoAPIPost(ctx, c.botRoute(botUserId)+"/assign/"+newOwnerId, "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var bot *Bot - err = json.NewDecoder(r.Body).Decode(&bot) - if err != nil { - return nil, BuildResponse(r), NewAppError("AssignBot", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return bot, BuildResponse(r), nil + return DecodeJSONFromResponse[*Bot](r) } // Team Section // CreateTeam creates a team in the system based on the provided team struct. func (c *Client4) CreateTeam(ctx context.Context, team *Team) (*Team, *Response, error) { - buf, err := json.Marshal(team) - if err != nil { - return nil, nil, NewAppError("CreateTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.teamsRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.teamsRoute(), team) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var t Team - if err := json.NewDecoder(r.Body).Decode(&t); err != nil { - return nil, nil, NewAppError("CreateTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &t, BuildResponse(r), nil + return DecodeJSONFromResponse[*Team](r) } // GetTeam returns a team based on the provided team id string. @@ -2457,57 +1950,53 @@ func (c *Client4) GetTeam(ctx context.Context, teamId, etag string) (*Team, *Res return nil, BuildResponse(r), err } defer closeBody(r) - var t Team - if err := json.NewDecoder(r.Body).Decode(&t); err != nil { - return nil, nil, NewAppError("GetTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &t, BuildResponse(r), nil + return DecodeJSONFromResponse[*Team](r) } // GetAllTeams returns all teams based on permissions. func (c *Client4) GetAllTeams(ctx context.Context, etag string, page int, perPage int) ([]*Team, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.teamsRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.teamsRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Team - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetAllTeams", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Team](r) } // GetAllTeamsWithTotalCount returns all teams based on permissions. func (c *Client4) GetAllTeamsWithTotalCount(ctx context.Context, etag string, page int, perPage int) ([]*Team, int64, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&include_total_count="+c.boolString(true), page, perPage) - r, err := c.DoAPIGet(ctx, c.teamsRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("include_total_count", c.boolString(true)) + r, err := c.DoAPIGet(ctx, c.teamsRoute()+"?"+values.Encode(), etag) if err != nil { return nil, 0, BuildResponse(r), err } defer closeBody(r) - var listWithCount TeamsWithCount - if err := json.NewDecoder(r.Body).Decode(&listWithCount); err != nil { - return nil, 0, nil, NewAppError("GetAllTeamsWithTotalCount", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + listWithCount, resp, err := DecodeJSONFromResponse[TeamsWithCount](r) + if err != nil { + return nil, 0, resp, err } - return listWithCount.Teams, listWithCount.TotalCount, BuildResponse(r), nil + return listWithCount.Teams, listWithCount.TotalCount, resp, nil } // GetAllTeamsExcludePolicyConstrained returns all teams which are not part of a data retention policy. // Must be a system administrator. func (c *Client4) GetAllTeamsExcludePolicyConstrained(ctx context.Context, etag string, page int, perPage int) ([]*Team, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&exclude_policy_constrained=%v", page, perPage, true) - r, err := c.DoAPIGet(ctx, c.teamsRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("exclude_policy_constrained", c.boolString(true)) + r, err := c.DoAPIGet(ctx, c.teamsRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Team - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetAllTeamsExcludePolicyConstrained", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Team](r) } // GetTeamByName returns a team based on the provided team name string. @@ -2517,29 +2006,17 @@ func (c *Client4) GetTeamByName(ctx context.Context, name, etag string) (*Team, return nil, BuildResponse(r), err } defer closeBody(r) - var t Team - if err := json.NewDecoder(r.Body).Decode(&t); err != nil { - return nil, nil, NewAppError("GetTeamByName", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &t, BuildResponse(r), nil + return DecodeJSONFromResponse[*Team](r) } // SearchTeams returns teams matching the provided search term. func (c *Client4) SearchTeams(ctx context.Context, search *TeamSearch) ([]*Team, *Response, error) { - buf, err := json.Marshal(search) - if err != nil { - return nil, nil, NewAppError("SearchTeams", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.teamsRoute()+"/search", buf) + r, err := c.DoAPIPostJSON(ctx, c.teamsRoute()+"/search", search) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Team - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("SearchTeams", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Team](r) } // SearchTeamsPaged returns a page of teams and the total count matching the provided search term. @@ -2550,20 +2027,16 @@ func (c *Client4) SearchTeamsPaged(ctx context.Context, search *TeamSearch) ([]* if search.PerPage == nil { search.PerPage = NewPointer(100) } - buf, err := json.Marshal(search) - if err != nil { - return nil, 0, BuildResponse(nil), NewAppError("SearchTeamsPaged", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.teamsRoute()+"/search", buf) + r, err := c.DoAPIPostJSON(ctx, c.teamsRoute()+"/search", search) if err != nil { return nil, 0, BuildResponse(r), err } defer closeBody(r) - var listWithCount TeamsWithCount - if err := json.NewDecoder(r.Body).Decode(&listWithCount); err != nil { - return nil, 0, nil, NewAppError("GetAllTeamsWithTotalCount", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + listWithCount, resp, err := DecodeJSONFromResponse[TeamsWithCount](r) + if err != nil { + return nil, 0, resp, err } - return listWithCount.Teams, listWithCount.TotalCount, BuildResponse(r), nil + return listWithCount.Teams, listWithCount.TotalCount, resp, nil } // TeamExists returns true or false if the team exist or not. @@ -2584,11 +2057,7 @@ func (c *Client4) GetTeamsForUser(ctx context.Context, userId, etag string) ([]* return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Team - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetTeamsForUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Team](r) } // GetTeamMember returns a team member based on the provided team and user id strings. @@ -2598,20 +2067,13 @@ func (c *Client4) GetTeamMember(ctx context.Context, teamId, userId, etag string return nil, BuildResponse(r), err } defer closeBody(r) - var tm TeamMember - if r.StatusCode == http.StatusNotModified { - return &tm, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&tm); err != nil { - return nil, nil, NewAppError("GetTeamMember", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &tm, BuildResponse(r), nil + return DecodeJSONFromResponse[*TeamMember](r) } // UpdateTeamMemberRoles will update the roles on a team for a user. func (c *Client4) UpdateTeamMemberRoles(ctx context.Context, teamId, userId, newRoles string) (*Response, error) { requestBody := map[string]string{"roles": newRoles} - r, err := c.DoAPIPut(ctx, c.teamMemberRoute(teamId, userId)+"/roles", MapToJSON(requestBody)) + r, err := c.DoAPIPutJSON(ctx, c.teamMemberRoute(teamId, userId)+"/roles", requestBody) if err != nil { return BuildResponse(r), err } @@ -2621,11 +2083,7 @@ func (c *Client4) UpdateTeamMemberRoles(ctx context.Context, teamId, userId, new // UpdateTeamMemberSchemeRoles will update the scheme-derived roles on a team for a user. func (c *Client4) UpdateTeamMemberSchemeRoles(ctx context.Context, teamId string, userId string, schemeRoles *SchemeRoles) (*Response, error) { - buf, err := json.Marshal(schemeRoles) - if err != nil { - return nil, NewAppError("UpdateTeamMemberSchemeRoles", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.teamMemberRoute(teamId, userId)+"/schemeRoles", buf) + r, err := c.DoAPIPutJSON(ctx, c.teamMemberRoute(teamId, userId)+"/schemeRoles", schemeRoles) if err != nil { return BuildResponse(r), err } @@ -2635,38 +2093,22 @@ func (c *Client4) UpdateTeamMemberSchemeRoles(ctx context.Context, teamId string // UpdateTeam will update a team. func (c *Client4) UpdateTeam(ctx context.Context, team *Team) (*Team, *Response, error) { - buf, err := json.Marshal(team) - if err != nil { - return nil, nil, NewAppError("UpdateTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.teamRoute(team.Id), buf) + r, err := c.DoAPIPutJSON(ctx, c.teamRoute(team.Id), team) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var t Team - if err := json.NewDecoder(r.Body).Decode(&t); err != nil { - return nil, nil, NewAppError("UpdateTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &t, BuildResponse(r), nil + return DecodeJSONFromResponse[*Team](r) } // PatchTeam partially updates a team. Any missing fields are not updated. func (c *Client4) PatchTeam(ctx context.Context, teamId string, patch *TeamPatch) (*Team, *Response, error) { - buf, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.teamRoute(teamId)+"/patch", buf) + r, err := c.DoAPIPutJSON(ctx, c.teamRoute(teamId)+"/patch", patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var t Team - if err := json.NewDecoder(r.Body).Decode(&t); err != nil { - return nil, nil, NewAppError("PatchTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &t, BuildResponse(r), nil + return DecodeJSONFromResponse[*Team](r) } // RestoreTeam restores a previously deleted team. @@ -2676,11 +2118,7 @@ func (c *Client4) RestoreTeam(ctx context.Context, teamId string) (*Team, *Respo return nil, BuildResponse(r), err } defer closeBody(r) - var t Team - if err := json.NewDecoder(r.Body).Decode(&t); err != nil { - return nil, nil, NewAppError("RestoreTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &t, BuildResponse(r), nil + return DecodeJSONFromResponse[*Team](r) } // RegenerateTeamInviteId requests a new invite ID to be generated. @@ -2690,11 +2128,7 @@ func (c *Client4) RegenerateTeamInviteId(ctx context.Context, teamId string) (*T return nil, BuildResponse(r), err } defer closeBody(r) - var t Team - if err := json.NewDecoder(r.Body).Decode(&t); err != nil { - return nil, nil, NewAppError("RegenerateTeamInviteId", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &t, BuildResponse(r), nil + return DecodeJSONFromResponse[*Team](r) } // SoftDeleteTeam deletes the team softly (archive only, not permanent delete). @@ -2722,53 +2156,41 @@ func (c *Client4) PermanentDeleteTeam(ctx context.Context, teamId string) (*Resp // the corresponding AllowOpenInvite appropriately. func (c *Client4) UpdateTeamPrivacy(ctx context.Context, teamId string, privacy string) (*Team, *Response, error) { requestBody := map[string]string{"privacy": privacy} - r, err := c.DoAPIPut(ctx, c.teamRoute(teamId)+"/privacy", MapToJSON(requestBody)) + r, err := c.DoAPIPutJSON(ctx, c.teamRoute(teamId)+"/privacy", requestBody) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var t Team - if err := json.NewDecoder(r.Body).Decode(&t); err != nil { - return nil, nil, NewAppError("UpdateTeamPrivacy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &t, BuildResponse(r), nil + return DecodeJSONFromResponse[*Team](r) } // GetTeamMembers returns team members based on the provided team id string. func (c *Client4) GetTeamMembers(ctx context.Context, teamId string, page int, perPage int, etag string) ([]*TeamMember, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.teamMembersRoute(teamId)+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.teamMembersRoute(teamId)+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var tms []*TeamMember - if r.StatusCode == http.StatusNotModified { - return tms, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&tms); err != nil { - return nil, nil, NewAppError("GetTeamMembers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return tms, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*TeamMember](r) } // GetTeamMembersWithoutDeletedUsers returns team members based on the provided team id string. Additional parameters of sort and exclude_deleted_users accepted as well // Could not add it to above function due to it be a breaking change. func (c *Client4) GetTeamMembersSortAndWithoutDeletedUsers(ctx context.Context, teamId string, page int, perPage int, sort string, excludeDeletedUsers bool, etag string) ([]*TeamMember, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&sort=%v&exclude_deleted_users=%v", page, perPage, sort, excludeDeletedUsers) - r, err := c.DoAPIGet(ctx, c.teamMembersRoute(teamId)+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("sort", sort) + values.Set("exclude_deleted_users", c.boolString(excludeDeletedUsers)) + r, err := c.DoAPIGet(ctx, c.teamMembersRoute(teamId)+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var tms []*TeamMember - if r.StatusCode == http.StatusNotModified { - return tms, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&tms); err != nil { - return nil, nil, NewAppError("GetTeamMembersSortAndWithoutDeletedUsers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return tms, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*TeamMember](r) } // GetTeamMembersForUser returns the team members for a user. @@ -2778,73 +2200,43 @@ func (c *Client4) GetTeamMembersForUser(ctx context.Context, userId string, etag return nil, BuildResponse(r), err } defer closeBody(r) - var tms []*TeamMember - if r.StatusCode == http.StatusNotModified { - return tms, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&tms); err != nil { - return nil, nil, NewAppError("GetTeamMembersForUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return tms, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*TeamMember](r) } // GetTeamMembersByIds will return an array of team members based on the // team id and a list of user ids provided. Must be authenticated. func (c *Client4) GetTeamMembersByIds(ctx context.Context, teamId string, userIds []string) ([]*TeamMember, *Response, error) { - r, err := c.DoAPIPost(ctx, fmt.Sprintf("/teams/%v/members/ids", teamId), ArrayToJSON(userIds)) + r, err := c.DoAPIPostJSON(ctx, fmt.Sprintf("/teams/%v/members/ids", teamId), userIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var tms []*TeamMember - if err := json.NewDecoder(r.Body).Decode(&tms); err != nil { - return nil, nil, NewAppError("GetTeamMembersByIds", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return tms, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*TeamMember](r) } // AddTeamMember adds user to a team and return a team member. func (c *Client4) AddTeamMember(ctx context.Context, teamId, userId string) (*TeamMember, *Response, error) { member := &TeamMember{TeamId: teamId, UserId: userId} - buf, err := json.Marshal(member) - if err != nil { - return nil, nil, NewAppError("AddTeamMember", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.teamMembersRoute(teamId), buf) + r, err := c.DoAPIPostJSON(ctx, c.teamMembersRoute(teamId), member) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var tm TeamMember - if err := json.NewDecoder(r.Body).Decode(&tm); err != nil { - return nil, nil, NewAppError("AddTeamMember", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &tm, BuildResponse(r), nil + return DecodeJSONFromResponse[*TeamMember](r) } // AddTeamMemberFromInvite adds a user to a team and return a team member using an invite id // or an invite token/data pair. func (c *Client4) AddTeamMemberFromInvite(ctx context.Context, token, inviteId string) (*TeamMember, *Response, error) { - var query string - - if inviteId != "" { - query += fmt.Sprintf("?invite_id=%v", inviteId) - } - - if token != "" { - query += fmt.Sprintf("?token=%v", token) - } - - r, err := c.DoAPIPost(ctx, c.teamsRoute()+"/members/invite"+query, "") + values := url.Values{} + values.Set("invite_id", inviteId) + values.Set("token", token) + r, err := c.DoAPIPost(ctx, c.teamsRoute()+"/members/invite"+"?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var tm TeamMember - if err := json.NewDecoder(r.Body).Decode(&tm); err != nil { - return nil, nil, NewAppError("AddTeamMemberFromInvite", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &tm, BuildResponse(r), nil + return DecodeJSONFromResponse[*TeamMember](r) } // AddTeamMembers adds a number of users to a team and returns the team members. @@ -2854,20 +2246,12 @@ func (c *Client4) AddTeamMembers(ctx context.Context, teamId string, userIds []s member := &TeamMember{TeamId: teamId, UserId: userId} members = append(members, member) } - js, err := json.Marshal(members) - if err != nil { - return nil, nil, NewAppError("AddTeamMembers", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.teamMembersRoute(teamId)+"/batch", string(js)) + r, err := c.DoAPIPostJSON(ctx, c.teamMembersRoute(teamId)+"/batch", members) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var tms []*TeamMember - if err := json.NewDecoder(r.Body).Decode(&tms); err != nil { - return nil, nil, NewAppError("AddTeamMembers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return tms, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*TeamMember](r) } // AddTeamMembers adds a number of users to a team and returns the team members. @@ -2877,21 +2261,12 @@ func (c *Client4) AddTeamMembersGracefully(ctx context.Context, teamId string, u member := &TeamMember{TeamId: teamId, UserId: userId} members = append(members, member) } - js, err := json.Marshal(members) - if err != nil { - return nil, nil, NewAppError("AddTeamMembersGracefully", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPost(ctx, c.teamMembersRoute(teamId)+"/batch?graceful="+c.boolString(true), string(js)) + r, err := c.DoAPIPostJSON(ctx, c.teamMembersRoute(teamId)+"/batch?graceful="+c.boolString(true), members) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var tms []*TeamMemberWithError - if err := json.NewDecoder(r.Body).Decode(&tms); err != nil { - return nil, nil, NewAppError("AddTeamMembersGracefully", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return tms, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*TeamMemberWithError](r) } // RemoveTeamMember will remove a user from a team. @@ -2912,11 +2287,7 @@ func (c *Client4) GetTeamStats(ctx context.Context, teamId, etag string) (*TeamS return nil, BuildResponse(r), err } defer closeBody(r) - var ts TeamStats - if err := json.NewDecoder(r.Body).Decode(&ts); err != nil { - return nil, nil, NewAppError("GetTeamStats", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &ts, BuildResponse(r), nil + return DecodeJSONFromResponse[*TeamStats](r) } // GetTotalUsersStats returns a total system user stats. @@ -2927,11 +2298,7 @@ func (c *Client4) GetTotalUsersStats(ctx context.Context, etag string) (*UsersSt return nil, BuildResponse(r), err } defer closeBody(r) - var stats UsersStats - if err := json.NewDecoder(r.Body).Decode(&stats); err != nil { - return nil, nil, NewAppError("GetTotalUsersStats", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &stats, BuildResponse(r), nil + return DecodeJSONFromResponse[*UsersStats](r) } // GetTeamUnread will return a TeamUnread object that contains the amount of @@ -2943,11 +2310,7 @@ func (c *Client4) GetTeamUnread(ctx context.Context, teamId, userId string) (*Te return nil, BuildResponse(r), err } defer closeBody(r) - var tu TeamUnread - if err := json.NewDecoder(r.Body).Decode(&tu); err != nil { - return nil, nil, NewAppError("GetTeamUnread", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &tu, BuildResponse(r), nil + return DecodeJSONFromResponse[*TeamUnread](r) } // ImportTeam will import an exported team from other app into a existing team. @@ -2978,20 +2341,25 @@ func (c *Client4) ImportTeam(ctx context.Context, data []byte, filesize int, imp return nil, nil, err } - if _, err := io.Copy(part, strings.NewReader(importFrom)); err != nil { + if _, err = io.Copy(part, strings.NewReader(importFrom)); err != nil { return nil, nil, err } - if err := writer.Close(); err != nil { + if err = writer.Close(); err != nil { return nil, nil, err } - return c.DoUploadImportTeam(ctx, c.teamImportRoute(teamId), body.Bytes(), writer.FormDataContentType()) + r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.APIURL+c.teamImportRoute(teamId), writer.FormDataContentType(), body, nil) + if err != nil { + return nil, BuildResponse(r), err + } + defer closeBody(r) + return DecodeJSONFromResponse[map[string]string](r) } // InviteUsersToTeam invite users by email to the team. func (c *Client4) InviteUsersToTeam(ctx context.Context, teamId string, userEmails []string) (*Response, error) { - r, err := c.DoAPIPost(ctx, c.teamRoute(teamId)+"/invite/email", ArrayToJSON(userEmails)) + r, err := c.DoAPIPostJSON(ctx, c.teamRoute(teamId)+"/invite/email", userEmails) if err != nil { return BuildResponse(r), err } @@ -3006,11 +2374,7 @@ func (c *Client4) InviteGuestsToTeam(ctx context.Context, teamId string, userEma Channels: channels, Message: message, } - buf, err := json.Marshal(guestsInvite) - if err != nil { - return nil, NewAppError("InviteGuestsToTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.teamRoute(teamId)+"/invite-guests/email", buf) + r, err := c.DoAPIPostJSON(ctx, c.teamRoute(teamId)+"/invite-guests/email", guestsInvite) if err != nil { return BuildResponse(r), err } @@ -3020,16 +2384,12 @@ func (c *Client4) InviteGuestsToTeam(ctx context.Context, teamId string, userEma // InviteUsersToTeam invite users by email to the team. func (c *Client4) InviteUsersToTeamGracefully(ctx context.Context, teamId string, userEmails []string) ([]*EmailInviteWithError, *Response, error) { - r, err := c.DoAPIPost(ctx, c.teamRoute(teamId)+"/invite/email?graceful="+c.boolString(true), ArrayToJSON(userEmails)) + r, err := c.DoAPIPostJSON(ctx, c.teamRoute(teamId)+"/invite/email?graceful="+c.boolString(true), userEmails) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*EmailInviteWithError - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("InviteUsersToTeamGracefully", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*EmailInviteWithError](r) } // InviteUsersToTeam invite users by email to the team. @@ -3039,20 +2399,12 @@ func (c *Client4) InviteUsersToTeamAndChannelsGracefully(ctx context.Context, te ChannelIds: channelIds, Message: message, } - buf, err := json.Marshal(memberInvite) - if err != nil { - return nil, nil, NewAppError("InviteMembersToTeamAndChannels", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.teamRoute(teamId)+"/invite/email?graceful="+c.boolString(true), buf) + r, err := c.DoAPIPostJSON(ctx, c.teamRoute(teamId)+"/invite/email?graceful="+c.boolString(true), memberInvite) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*EmailInviteWithError - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("InviteUsersToTeamGracefully", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*EmailInviteWithError](r) } // InviteGuestsToTeam invite guest by email to some channels in a team. @@ -3062,20 +2414,12 @@ func (c *Client4) InviteGuestsToTeamGracefully(ctx context.Context, teamId strin Channels: channels, Message: message, } - buf, err := json.Marshal(guestsInvite) - if err != nil { - return nil, nil, NewAppError("InviteGuestsToTeamGracefully", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.teamRoute(teamId)+"/invite-guests/email?graceful="+c.boolString(true), buf) + r, err := c.DoAPIPostJSON(ctx, c.teamRoute(teamId)+"/invite-guests/email?graceful="+c.boolString(true), guestsInvite) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*EmailInviteWithError - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("InviteGuestsToTeamGracefully", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*EmailInviteWithError](r) } // InvalidateEmailInvites will invalidate active email invitations that have not been accepted by the user. @@ -3095,11 +2439,7 @@ func (c *Client4) GetTeamInviteInfo(ctx context.Context, inviteId string) (*Team return nil, BuildResponse(r), err } defer closeBody(r) - var t Team - if err := json.NewDecoder(r.Body).Decode(&t); err != nil { - return nil, nil, NewAppError("GetTeamInviteInfo", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &t, BuildResponse(r), nil + return DecodeJSONFromResponse[*Team](r) } // SetTeamIcon sets team icon of the team. @@ -3109,38 +2449,23 @@ func (c *Client4) SetTeamIcon(ctx context.Context, teamId string, data []byte) ( part, err := writer.CreateFormFile("image", "teamIcon.png") if err != nil { - return nil, NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to create form file for team icon: %w", err) } if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return nil, NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to copy data to team icon form file: %w", err) } if err = writer.Close(); err != nil { - return nil, NewAppError("SetTeamIcon", "model.client.set_team_icon.writer.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to close multipart writer for team icon: %w", err) } - rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+c.teamRoute(teamId)+"/image", bytes.NewReader(body.Bytes())) + r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.APIURL+c.teamRoute(teamId)+"/image", writer.FormDataContentType(), body, nil) if err != nil { - return nil, err + return BuildResponse(r), err } - rq.Header.Set("Content-Type", writer.FormDataContentType()) - - if c.AuthToken != "" { - rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken) - } - - rp, err := c.HTTPClient.Do(rq) - if err != nil { - return BuildResponse(rp), err - } - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return BuildResponse(rp), AppErrorFromJSON(rp.Body) - } - - return BuildResponse(rp), nil + defer closeBody(r) + return BuildResponse(r), nil } // GetTeamIcon gets the team icon of the team. @@ -3150,12 +2475,7 @@ func (c *Client4) GetTeamIcon(ctx context.Context, teamId, etag string) ([]byte, return nil, BuildResponse(r), err } defer closeBody(r) - - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetTeamIcon", "model.client.get_team_icon.app_error", nil, "", r.StatusCode).Wrap(err) - } - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // RemoveTeamIcon updates LastTeamIconUpdate to 0 which indicates team icon is removed. @@ -3187,114 +2507,77 @@ func (c *Client4) GetAllChannelsExcludePolicyConstrained(ctx context.Context, pa } func (c *Client4) getAllChannels(ctx context.Context, page int, perPage int, etag string, opts ChannelSearchOpts) (ChannelListWithTeamData, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&include_deleted=%v&exclude_policy_constrained=%v", - page, perPage, opts.IncludeDeleted, opts.ExcludePolicyConstrained) - r, err := c.DoAPIGet(ctx, c.channelsRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("include_deleted", c.boolString(opts.IncludeDeleted)) + values.Set("exclude_policy_constrained", c.boolString(opts.ExcludePolicyConstrained)) + r, err := c.DoAPIGet(ctx, c.channelsRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch ChannelListWithTeamData - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("getAllChannels", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[ChannelListWithTeamData](r) } // GetAllChannelsWithCount get all the channels including the total count. Must be a system administrator. func (c *Client4) GetAllChannelsWithCount(ctx context.Context, page int, perPage int, etag string) (ChannelListWithTeamData, int64, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&include_total_count="+c.boolString(true), page, perPage) - r, err := c.DoAPIGet(ctx, c.channelsRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("include_total_count", c.boolString(true)) + r, err := c.DoAPIGet(ctx, c.channelsRoute()+"?"+values.Encode(), etag) if err != nil { return nil, 0, BuildResponse(r), err } defer closeBody(r) - var cwc *ChannelsWithCount - err = json.NewDecoder(r.Body).Decode(&cwc) + cwc, resp, err := DecodeJSONFromResponse[*ChannelsWithCount](r) if err != nil { - return nil, 0, BuildResponse(r), NewAppError("GetAllChannelsWithCount", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + return nil, 0, resp, err } - return cwc.Channels, cwc.TotalCount, BuildResponse(r), nil + return cwc.Channels, cwc.TotalCount, resp, nil } // CreateChannel creates a channel based on the provided channel struct. func (c *Client4) CreateChannel(ctx context.Context, channel *Channel) (*Channel, *Response, error) { - channelJSON, err := json.Marshal(channel) - if err != nil { - return nil, nil, NewAppError("CreateChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.channelsRoute(), string(channelJSON)) + r, err := c.DoAPIPostJSON(ctx, c.channelsRoute(), channel) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("CreateChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // UpdateChannel updates a channel based on the provided channel struct. func (c *Client4) UpdateChannel(ctx context.Context, channel *Channel) (*Channel, *Response, error) { - channelJSON, err := json.Marshal(channel) - if err != nil { - return nil, nil, NewAppError("UpdateChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPut(ctx, c.channelRoute(channel.Id), string(channelJSON)) + r, err := c.DoAPIPutJSON(ctx, c.channelRoute(channel.Id), channel) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("UpdateChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // PatchChannel partially updates a channel. Any missing fields are not updated. func (c *Client4) PatchChannel(ctx context.Context, channelId string, patch *ChannelPatch) (*Channel, *Response, error) { - buf, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.channelRoute(channelId)+"/patch", buf) + r, err := c.DoAPIPutJSON(ctx, c.channelRoute(channelId)+"/patch", patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("PatchChannel", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // UpdateChannelPrivacy updates channel privacy func (c *Client4) UpdateChannelPrivacy(ctx context.Context, channelId string, privacy ChannelType) (*Channel, *Response, error) { requestBody := map[string]string{"privacy": string(privacy)} - r, err := c.DoAPIPut(ctx, c.channelRoute(channelId)+"/privacy", MapToJSON(requestBody)) + r, err := c.DoAPIPutJSON(ctx, c.channelRoute(channelId)+"/privacy", requestBody) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("UpdateChannelPrivacy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // RestoreChannel restores a previously deleted channel. Any missing fields are not updated. @@ -3304,47 +2587,29 @@ func (c *Client4) RestoreChannel(ctx context.Context, channelId string) (*Channe return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("RestoreChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // CreateDirectChannel creates a direct message channel based on the two user // ids provided. func (c *Client4) CreateDirectChannel(ctx context.Context, userId1, userId2 string) (*Channel, *Response, error) { requestBody := []string{userId1, userId2} - r, err := c.DoAPIPost(ctx, c.channelsRoute()+"/direct", ArrayToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.channelsRoute()+"/direct", requestBody) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("CreateDirectChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // CreateGroupChannel creates a group message channel based on userIds provided. func (c *Client4) CreateGroupChannel(ctx context.Context, userIds []string) (*Channel, *Response, error) { - r, err := c.DoAPIPost(ctx, c.channelsRoute()+"/group", ArrayToJSON(userIds)) + r, err := c.DoAPIPostJSON(ctx, c.channelsRoute()+"/group", userIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("CreateGroupChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // GetChannel returns a channel based on the provided channel id string. @@ -3354,43 +2619,31 @@ func (c *Client4) GetChannel(ctx context.Context, channelId, etag string) (*Chan return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // GetChannelStats returns statistics for a channel. func (c *Client4) GetChannelStats(ctx context.Context, channelId string, etag string, excludeFilesCount bool) (*ChannelStats, *Response, error) { - route := c.channelRoute(channelId) + fmt.Sprintf("/stats?exclude_files_count=%v", excludeFilesCount) + values := url.Values{} + values.Set("exclude_files_count", c.boolString(excludeFilesCount)) + route := c.channelRoute(channelId) + "/stats?" + values.Encode() r, err := c.DoAPIGet(ctx, route, etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var stats ChannelStats - if err := json.NewDecoder(r.Body).Decode(&stats); err != nil { - return nil, nil, NewAppError("GetChannelStats", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &stats, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelStats](r) } // GetChannelsMemberCount get channel member count for a given array of channel ids func (c *Client4) GetChannelsMemberCount(ctx context.Context, channelIDs []string) (map[string]int64, *Response, error) { route := c.channelsRoute() + "/stats/member_count" - r, err := c.DoAPIPost(ctx, route, ArrayToJSON(channelIDs)) + r, err := c.DoAPIPostJSON(ctx, route, channelIDs) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var counts map[string]int64 - if err := json.NewDecoder(r.Body).Decode(&counts); err != nil { - return nil, nil, NewAppError("GetChannelsMemberCount", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return counts, BuildResponse(r), nil + return DecodeJSONFromResponse[map[string]int64](r) } // GetChannelMembersTimezones gets a list of timezones for a channel. @@ -3400,7 +2653,7 @@ func (c *Client4) GetChannelMembersTimezones(ctx context.Context, channelId stri return nil, BuildResponse(r), err } defer closeBody(r) - return c.ArrayFromJSON(r.Body), BuildResponse(r), nil + return DecodeJSONFromResponse[[]string](r) } // GetPinnedPosts gets a list of pinned posts. @@ -3410,175 +2663,115 @@ func (c *Client4) GetPinnedPosts(ctx context.Context, channelId string, etag str return nil, BuildResponse(r), err } defer closeBody(r) - - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil - } - - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetPinnedPosts", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) } // GetPrivateChannelsForTeam returns a list of private channels based on the provided team id string. func (c *Client4) GetPrivateChannelsForTeam(ctx context.Context, teamId string, page int, perPage int, etag string) ([]*Channel, *Response, error) { - query := fmt.Sprintf("/private?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.channelsForTeamRoute(teamId)+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.channelsForTeamRoute(teamId)+"/private?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetPrivateChannelsForTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Channel](r) } // GetPublicChannelsForTeam returns a list of public channels based on the provided team id string. func (c *Client4) GetPublicChannelsForTeam(ctx context.Context, teamId string, page int, perPage int, etag string) ([]*Channel, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.channelsForTeamRoute(teamId)+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.channelsForTeamRoute(teamId)+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetPublicChannelsForTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Channel](r) } // GetDeletedChannelsForTeam returns a list of public channels based on the provided team id string. func (c *Client4) GetDeletedChannelsForTeam(ctx context.Context, teamId string, page int, perPage int, etag string) ([]*Channel, *Response, error) { - query := fmt.Sprintf("/deleted?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.channelsForTeamRoute(teamId)+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.channelsForTeamRoute(teamId)+"/deleted?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetDeletedChannelsForTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Channel](r) } // GetPublicChannelsByIdsForTeam returns a list of public channels based on provided team id string. func (c *Client4) GetPublicChannelsByIdsForTeam(ctx context.Context, teamId string, channelIds []string) ([]*Channel, *Response, error) { - r, err := c.DoAPIPost(ctx, c.channelsForTeamRoute(teamId)+"/ids", ArrayToJSON(channelIds)) + r, err := c.DoAPIPostJSON(ctx, c.channelsForTeamRoute(teamId)+"/ids", channelIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetPublicChannelsByIdsForTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Channel](r) } // GetChannelsForTeamForUser returns a list channels of on a team for a user. func (c *Client4) GetChannelsForTeamForUser(ctx context.Context, teamId, userId string, includeDeleted bool, etag string) ([]*Channel, *Response, error) { - r, err := c.DoAPIGet(ctx, c.channelsForTeamForUserRoute(teamId, userId, includeDeleted), etag) + values := url.Values{} + values.Set("include_deleted", c.boolString(includeDeleted)) + r, err := c.DoAPIGet(ctx, c.channelsForTeamForUserRoute(teamId, userId)+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelsForTeamForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Channel](r) } // GetChannelsForTeamAndUserWithLastDeleteAt returns a list channels of a team for a user, additionally filtered with lastDeleteAt. This does not have any effect if includeDeleted is set to false. func (c *Client4) GetChannelsForTeamAndUserWithLastDeleteAt(ctx context.Context, teamId, userId string, includeDeleted bool, lastDeleteAt int, etag string) ([]*Channel, *Response, error) { - route := c.userRoute(userId) + c.teamRoute(teamId) + "/channels" - route += fmt.Sprintf("?include_deleted=%v&last_delete_at=%d", includeDeleted, lastDeleteAt) + values := url.Values{} + values.Set("include_deleted", c.boolString(includeDeleted)) + values.Set("last_delete_at", strconv.Itoa(lastDeleteAt)) + route := c.userRoute(userId) + c.teamRoute(teamId) + "/channels?" + values.Encode() r, err := c.DoAPIGet(ctx, route, etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelsForTeamAndUserWithLastDeleteAt", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Channel](r) } // GetChannelsForUserWithLastDeleteAt returns a list channels for a user, additionally filtered with lastDeleteAt. func (c *Client4) GetChannelsForUserWithLastDeleteAt(ctx context.Context, userID string, lastDeleteAt int) ([]*Channel, *Response, error) { - route := c.userRoute(userID) + "/channels" - route += fmt.Sprintf("?last_delete_at=%d", lastDeleteAt) + values := url.Values{} + values.Set("last_delete_at", strconv.Itoa(lastDeleteAt)) + route := c.userRoute(userID) + "/channels?" + values.Encode() r, err := c.DoAPIGet(ctx, route, "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelsForUserWithLastDeleteAt", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Channel](r) } // SearchChannels returns the channels on a team matching the provided search term. func (c *Client4) SearchChannels(ctx context.Context, teamId string, search *ChannelSearch) ([]*Channel, *Response, error) { - searchJSON, err := json.Marshal(search) - if err != nil { - return nil, nil, NewAppError("SearchChannels", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.channelsForTeamRoute(teamId)+"/search", string(searchJSON)) + r, err := c.DoAPIPostJSON(ctx, c.channelsForTeamRoute(teamId)+"/search", search) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("SearchChannels", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Channel](r) } // SearchAllChannels search in all the channels. Must be a system administrator. func (c *Client4) SearchAllChannels(ctx context.Context, search *ChannelSearch) (ChannelListWithTeamData, *Response, error) { - searchJSON, err := json.Marshal(search) - if err != nil { - return nil, nil, NewAppError("SearchAllChannels", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.channelsRoute()+"/search", string(searchJSON)) + r, err := c.DoAPIPostJSON(ctx, c.channelsRoute()+"/search", search) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch ChannelListWithTeamData - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("SearchAllChannels", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[ChannelListWithTeamData](r) } // SearchAllChannelsForUser search in all the channels for a regular user. @@ -3586,62 +2779,32 @@ func (c *Client4) SearchAllChannelsForUser(ctx context.Context, term string) (Ch search := &ChannelSearch{ Term: term, } - searchJSON, err := json.Marshal(search) - if err != nil { - return nil, nil, NewAppError("SearchAllChannelsForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.channelsRoute()+"/search?system_console=false", string(searchJSON)) + r, err := c.DoAPIPostJSON(ctx, c.channelsRoute()+"/search?system_console=false", search) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch ChannelListWithTeamData - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("SearchAllChannelsForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[ChannelListWithTeamData](r) } // SearchAllChannelsPaged searches all the channels and returns the results paged with the total count. func (c *Client4) SearchAllChannelsPaged(ctx context.Context, search *ChannelSearch) (*ChannelsWithCount, *Response, error) { - searchJSON, err := json.Marshal(search) - if err != nil { - return nil, nil, NewAppError("SearchAllChannelsPaged", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.channelsRoute()+"/search", string(searchJSON)) + r, err := c.DoAPIPostJSON(ctx, c.channelsRoute()+"/search", search) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var cwc *ChannelsWithCount - err = json.NewDecoder(r.Body).Decode(&cwc) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetAllChannelsWithCount", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return cwc, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelsWithCount](r) } // SearchGroupChannels returns the group channels of the user whose members' usernames match the search term. func (c *Client4) SearchGroupChannels(ctx context.Context, search *ChannelSearch) ([]*Channel, *Response, error) { - searchJSON, err := json.Marshal(search) - if err != nil { - return nil, nil, NewAppError("SearchGroupChannels", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.channelsRoute()+"/group/search", string(searchJSON)) + r, err := c.DoAPIPostJSON(ctx, c.channelsRoute()+"/group/search", search) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("SearchGroupChannels", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Channel](r) } // DeleteChannel deletes channel based on the provided channel id string. @@ -3670,18 +2833,12 @@ func (c *Client4) MoveChannel(ctx context.Context, channelId, teamId string, for "team_id": teamId, "force": force, } - r, err := c.DoAPIPost(ctx, c.channelRoute(channelId)+"/move", StringInterfaceToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.channelRoute(channelId)+"/move", requestBody) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("MoveChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // GetChannelByName returns a channel based on the provided channel name and team id strings. @@ -3691,13 +2848,7 @@ func (c *Client4) GetChannelByName(ctx context.Context, channelName, teamId stri return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelByName", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // GetChannelByNameIncludeDeleted returns a channel based on the provided channel name and team id strings. Other then GetChannelByName it will also return deleted channels. @@ -3707,13 +2858,7 @@ func (c *Client4) GetChannelByNameIncludeDeleted(ctx context.Context, channelNam return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelByNameIncludeDeleted", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // GetChannelByNameForTeamName returns a channel based on the provided channel name and team name strings. @@ -3723,13 +2868,7 @@ func (c *Client4) GetChannelByNameForTeamName(ctx context.Context, channelName, return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelByNameForTeamName", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // GetChannelByNameForTeamNameIncludeDeleted returns a channel based on the provided channel name and team name strings. Other then GetChannelByNameForTeamName it will also return deleted channels. @@ -3739,36 +2878,28 @@ func (c *Client4) GetChannelByNameForTeamNameIncludeDeleted(ctx context.Context, return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *Channel - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelByNameForTeamNameIncludeDeleted", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*Channel](r) } // GetChannelMembers gets a page of channel members specific to a channel. func (c *Client4) GetChannelMembers(ctx context.Context, channelId string, page, perPage int, etag string) (ChannelMembers, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.channelMembersRoute(channelId)+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.channelMembersRoute(channelId)+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch ChannelMembers - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelMembers", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[ChannelMembers](r) } // GetChannelMembersWithTeamData gets a page of all channel members for a user. func (c *Client4) GetChannelMembersWithTeamData(ctx context.Context, userID string, page, perPage int) (ChannelMembersWithTeamData, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.userRoute(userID)+"/channel_members"+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.userRoute(userID)+"/channel_members?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } @@ -3791,14 +2922,14 @@ func (c *Client4) GetChannelMembersWithTeamData(ctx context.Context, userID stri } var member ChannelMemberWithTeamData - if err2 := json.Unmarshal([]byte(line), &member); err2 != nil { - return nil, BuildResponse(r), NewAppError("GetChannelMembersWithTeamData", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err2) + if err = json.Unmarshal([]byte(line), &member); err != nil { + return nil, BuildResponse(r), fmt.Errorf("failed to unmarshal channel member data: %w", err) } ch = append(ch, member) } - if err2 := scanner.Err(); err2 != nil { - return nil, BuildResponse(r), NewAppError("GetChannelMembersWithTeamData", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err2) + if err = scanner.Err(); err != nil { + return nil, BuildResponse(r), fmt.Errorf("scanner error while reading channel members: %w", err) } return ch, BuildResponse(r), nil @@ -3806,27 +2937,17 @@ func (c *Client4) GetChannelMembersWithTeamData(ctx context.Context, userID stri } // Standard JSON format - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelMembersWithTeamData", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[ChannelMembersWithTeamData](r) } // GetChannelMembersByIds gets the channel members in a channel for a list of user ids. func (c *Client4) GetChannelMembersByIds(ctx context.Context, channelId string, userIds []string) (ChannelMembers, *Response, error) { - r, err := c.DoAPIPost(ctx, c.channelMembersRoute(channelId)+"/ids", ArrayToJSON(userIds)) + r, err := c.DoAPIPostJSON(ctx, c.channelMembersRoute(channelId)+"/ids", userIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch ChannelMembers - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelMembersByIds", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[ChannelMembers](r) } // GetChannelMember gets a channel member. @@ -3836,13 +2957,7 @@ func (c *Client4) GetChannelMember(ctx context.Context, channelId, userId, etag return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *ChannelMember - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelMember", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelMember](r) } // GetChannelMembersForUser gets all the channel members for a user on a team. @@ -3852,55 +2967,29 @@ func (c *Client4) GetChannelMembersForUser(ctx context.Context, userId, teamId, return nil, BuildResponse(r), err } defer closeBody(r) - - var ch ChannelMembers - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelMembersForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[ChannelMembers](r) } // ViewChannel performs a view action for a user. Synonymous with switching channels or marking channels as read by a user. func (c *Client4) ViewChannel(ctx context.Context, userId string, view *ChannelView) (*ChannelViewResponse, *Response, error) { url := fmt.Sprintf(c.channelsRoute()+"/members/%v/view", userId) - buf, err := json.Marshal(view) - if err != nil { - return nil, nil, NewAppError("ViewChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, url, buf) + r, err := c.DoAPIPostJSON(ctx, url, view) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *ChannelViewResponse - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("ViewChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelViewResponse](r) } // ReadMultipleChannels performs a view action on several channels at the same time for a user. func (c *Client4) ReadMultipleChannels(ctx context.Context, userId string, channelIds []string) (*ChannelViewResponse, *Response, error) { url := fmt.Sprintf(c.channelsRoute()+"/members/%v/mark_read", userId) - buf, err := json.Marshal(channelIds) - if err != nil { - return nil, nil, NewAppError("ReadMultipleChannels", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, url, buf) + r, err := c.DoAPIPostJSON(ctx, url, channelIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *ChannelViewResponse - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("ReadMultipleChannels", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelViewResponse](r) } // GetChannelUnread will return a ChannelUnread object that contains the number of @@ -3911,19 +3000,13 @@ func (c *Client4) GetChannelUnread(ctx context.Context, channelId, userId string return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *ChannelUnread - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelUnread", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelUnread](r) } // UpdateChannelRoles will update the roles on a channel for a user. func (c *Client4) UpdateChannelRoles(ctx context.Context, channelId, userId, roles string) (*Response, error) { requestBody := map[string]string{"roles": roles} - r, err := c.DoAPIPut(ctx, c.channelMemberRoute(channelId, userId)+"/roles", MapToJSON(requestBody)) + r, err := c.DoAPIPutJSON(ctx, c.channelMemberRoute(channelId, userId)+"/roles", requestBody) if err != nil { return BuildResponse(r), err } @@ -3933,11 +3016,7 @@ func (c *Client4) UpdateChannelRoles(ctx context.Context, channelId, userId, rol // UpdateChannelMemberSchemeRoles will update the scheme-derived roles on a channel for a user. func (c *Client4) UpdateChannelMemberSchemeRoles(ctx context.Context, channelId string, userId string, schemeRoles *SchemeRoles) (*Response, error) { - buf, err := json.Marshal(schemeRoles) - if err != nil { - return nil, NewAppError("UpdateChannelMemberSchemeRoles", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.channelMemberRoute(channelId, userId)+"/schemeRoles", buf) + r, err := c.DoAPIPutJSON(ctx, c.channelMemberRoute(channelId, userId)+"/schemeRoles", schemeRoles) if err != nil { return BuildResponse(r), err } @@ -3947,7 +3026,7 @@ func (c *Client4) UpdateChannelMemberSchemeRoles(ctx context.Context, channelId // UpdateChannelNotifyProps will update the notification properties on a channel for a user. func (c *Client4) UpdateChannelNotifyProps(ctx context.Context, channelId, userId string, props map[string]string) (*Response, error) { - r, err := c.DoAPIPut(ctx, c.channelMemberRoute(channelId, userId)+"/notify_props", MapToJSON(props)) + r, err := c.DoAPIPutJSON(ctx, c.channelMemberRoute(channelId, userId)+"/notify_props", props) if err != nil { return BuildResponse(r), err } @@ -3958,52 +3037,34 @@ func (c *Client4) UpdateChannelNotifyProps(ctx context.Context, channelId, userI // AddChannelMember adds user to channel and return a channel member. func (c *Client4) AddChannelMember(ctx context.Context, channelId, userId string) (*ChannelMember, *Response, error) { requestBody := map[string]string{"user_id": userId} - r, err := c.DoAPIPost(ctx, c.channelMembersRoute(channelId)+"", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.channelMembersRoute(channelId)+"", requestBody) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *ChannelMember - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("AddChannelMember", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelMember](r) } // AddChannelMembers adds users to a channel and return an array of channel members. func (c *Client4) AddChannelMembers(ctx context.Context, channelId, postRootId string, userIds []string) ([]*ChannelMember, *Response, error) { requestBody := map[string]any{"user_ids": userIds, "post_root_id": postRootId} - r, err := c.DoAPIPost(ctx, c.channelMembersRoute(channelId)+"", StringInterfaceToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.channelMembersRoute(channelId)+"", requestBody) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*ChannelMember - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("AddChannelMembers", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*ChannelMember](r) } // AddChannelMemberWithRootId adds user to channel and return a channel member. Post add to channel message has the postRootId. func (c *Client4) AddChannelMemberWithRootId(ctx context.Context, channelId, userId, postRootId string) (*ChannelMember, *Response, error) { requestBody := map[string]string{"user_id": userId, "post_root_id": postRootId} - r, err := c.DoAPIPost(ctx, c.channelMembersRoute(channelId)+"", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.channelMembersRoute(channelId)+"", requestBody) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch *ChannelMember - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("AddChannelMemberWithRootId", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelMember](r) } // RemoveUserFromChannel will delete the channel member object for a user, effectively removing the user from a channel. @@ -4018,131 +3079,74 @@ func (c *Client4) RemoveUserFromChannel(ctx context.Context, channelId, userId s // AutocompleteChannelsForTeam will return an ordered list of channels autocomplete suggestions. func (c *Client4) AutocompleteChannelsForTeam(ctx context.Context, teamId, name string) (ChannelList, *Response, error) { - query := fmt.Sprintf("?name=%v", name) - r, err := c.DoAPIGet(ctx, c.channelsForTeamRoute(teamId)+"/autocomplete"+query, "") + values := url.Values{} + values.Set("name", name) + r, err := c.DoAPIGet(ctx, c.channelsForTeamRoute(teamId)+"/autocomplete?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch ChannelList - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("AutocompleteChannelsForTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[ChannelList](r) } // AutocompleteChannelsForTeamForSearch will return an ordered list of your channels autocomplete suggestions. func (c *Client4) AutocompleteChannelsForTeamForSearch(ctx context.Context, teamId, name string) (ChannelList, *Response, error) { - query := fmt.Sprintf("?name=%v", name) - r, err := c.DoAPIGet(ctx, c.channelsForTeamRoute(teamId)+"/search_autocomplete"+query, "") + values := url.Values{} + values.Set("name", name) + r, err := c.DoAPIGet(ctx, c.channelsForTeamRoute(teamId)+"/search_autocomplete?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch ChannelList - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("AutocompleteChannelsForTeamForSearch", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[ChannelList](r) } // Post Section // CreatePost creates a post based on the provided post struct. func (c *Client4) CreatePost(ctx context.Context, post *Post) (*Post, *Response, error) { - postJSON, err := json.Marshal(post) - if err != nil { - return nil, nil, NewAppError("CreatePost", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.postsRoute(), string(postJSON)) + r, err := c.DoAPIPostJSON(ctx, c.postsRoute(), post) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var p Post - if r.StatusCode == http.StatusNotModified { - return &p, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("CreatePost", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*Post](r) } // CreatePostEphemeral creates a ephemeral post based on the provided post struct which is send to the given user id. func (c *Client4) CreatePostEphemeral(ctx context.Context, post *PostEphemeral) (*Post, *Response, error) { - postJSON, err := json.Marshal(post) - if err != nil { - return nil, nil, NewAppError("CreatePostEphemeral", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.postsEphemeralRoute(), string(postJSON)) + r, err := c.DoAPIPostJSON(ctx, c.postsEphemeralRoute(), post) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var p Post - if r.StatusCode == http.StatusNotModified { - return &p, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("CreatePostEphemeral", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*Post](r) } // UpdatePost updates a post based on the provided post struct. func (c *Client4) UpdatePost(ctx context.Context, postId string, post *Post) (*Post, *Response, error) { - postJSON, err := json.Marshal(post) - if err != nil { - return nil, nil, NewAppError("UpdatePost", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPut(ctx, c.postRoute(postId), string(postJSON)) + r, err := c.DoAPIPutJSON(ctx, c.postRoute(postId), post) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var p Post - if r.StatusCode == http.StatusNotModified { - return &p, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("UpdatePost", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*Post](r) } // PatchPost partially updates a post. Any missing fields are not updated. func (c *Client4) PatchPost(ctx context.Context, postId string, patch *PostPatch) (*Post, *Response, error) { - buf, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchPost", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.postRoute(postId)+"/patch", buf) + r, err := c.DoAPIPutJSON(ctx, c.postRoute(postId)+"/patch", patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var p Post - if r.StatusCode == http.StatusNotModified { - return &p, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("PatchPost", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*Post](r) } // SetPostUnread marks channel where post belongs as unread on the time of the provided post. func (c *Client4) SetPostUnread(ctx context.Context, userId string, postId string, collapsedThreadsSupported bool) (*Response, error) { - b, err := json.Marshal(map[string]bool{"collapsed_threads_supported": collapsedThreadsSupported}) - if err != nil { - return nil, NewAppError("SetPostUnread", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.userRoute(userId)+c.postRoute(postId)+"/set_unread", b) + reqData := map[string]bool{"collapsed_threads_supported": collapsedThreadsSupported} + r, err := c.DoAPIPostJSON(ctx, c.userRoute(userId)+c.postRoute(postId)+"/set_unread", reqData) if err != nil { return BuildResponse(r), err } @@ -4154,12 +3158,7 @@ func (c *Client4) SetPostUnread(ctx context.Context, userId string, postId strin // The time needs to be in UTC epoch in seconds. It is always truncated to a // 5 minute resolution minimum. func (c *Client4) SetPostReminder(ctx context.Context, reminder *PostReminder) (*Response, error) { - b, err := json.Marshal(reminder) - if err != nil { - return nil, NewAppError("SetPostReminder", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPostBytes(ctx, c.userRoute(reminder.UserId)+c.postRoute(reminder.PostId)+"/reminder", b) + r, err := c.DoAPIPostJSON(ctx, c.userRoute(reminder.UserId)+c.postRoute(reminder.PostId)+"/reminder", reminder) if err != nil { return BuildResponse(r), err } @@ -4194,15 +3193,7 @@ func (c *Client4) GetPost(ctx context.Context, postId string, etag string) (*Pos return nil, BuildResponse(r), err } defer closeBody(r) - - var post Post - if r.StatusCode == http.StatusNotModified { - return &post, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&post); err != nil { - return nil, nil, NewAppError("GetPost", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &post, BuildResponse(r), nil + return DecodeJSONFromResponse[*Post](r) } // GetPostIncludeDeleted gets a single post, including deleted. @@ -4212,15 +3203,7 @@ func (c *Client4) GetPostIncludeDeleted(ctx context.Context, postId string, etag return nil, BuildResponse(r), err } defer closeBody(r) - - var post Post - if r.StatusCode == http.StatusNotModified { - return &post, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&post); err != nil { - return nil, nil, NewAppError("GetPostIncludeDeleted", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &post, BuildResponse(r), nil + return DecodeJSONFromResponse[*Post](r) } // DeletePost deletes a post from the provided post id string. @@ -4245,23 +3228,14 @@ func (c *Client4) PermanentDeletePost(ctx context.Context, postId string) (*Resp // GetPostThread gets a post with all the other posts in the same thread. func (c *Client4) GetPostThread(ctx context.Context, postId string, etag string, collapsedThreads bool) (*PostList, *Response, error) { - url := c.postRoute(postId) + "/thread" - if collapsedThreads { - url += "?collapsedThreads=true" - } - r, err := c.DoAPIGet(ctx, url, etag) + values := url.Values{} + values.Set("collapsedThreads", c.boolString(collapsedThreads)) + r, err := c.DoAPIGet(ctx, c.postRoute(postId)+"/thread?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetPostThread", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) } // GetPostThreadWithOpts gets a post with all the other posts in the same thread. @@ -4303,222 +3277,145 @@ func (c *Client4) GetPostThreadWithOpts(ctx context.Context, postID string, etag return nil, BuildResponse(r), err } defer closeBody(r) - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetPostThread", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) } // GetPostsForChannel gets a page of posts with an array for ordering for a channel. func (c *Client4) GetPostsForChannel(ctx context.Context, channelId string, page, perPage int, etag string, collapsedThreads bool, includeDeleted bool) (*PostList, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - if collapsedThreads { - query += "&collapsedThreads=true" - } - - if includeDeleted { - query += "&include_deleted=true" - } - r, err := c.DoAPIGet(ctx, c.channelRoute(channelId)+"/posts"+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("collapsedThreads", c.boolString(collapsedThreads)) + values.Set("include_deleted", c.boolString(includeDeleted)) + r, err := c.DoAPIGet(ctx, c.channelRoute(channelId)+"/posts?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetPostsForChannel", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) } // GetPostsByIds gets a list of posts by taking an array of post ids func (c *Client4) GetPostsByIds(ctx context.Context, postIds []string) ([]*Post, *Response, error) { - js, err := json.Marshal(postIds) - if err != nil { - return nil, nil, NewAppError("SearchFilesWithParams", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.postsRoute()+"/ids", string(js)) + r, err := c.DoAPIPostJSON(ctx, c.postsRoute()+"/ids", postIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Post - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetPostsByIds", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Post](r) } // GetEditHistoryForPost gets a list of posts by taking a post ids func (c *Client4) GetEditHistoryForPost(ctx context.Context, postId string) ([]*Post, *Response, error) { js, err := json.Marshal(postId) if err != nil { - return nil, nil, NewAppError("GetEditHistoryForPost", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + return nil, nil, fmt.Errorf("failed to marshal edit history request: %w", err) } r, err := c.DoAPIGet(ctx, c.postRoute(postId)+"/edit_history", string(js)) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Post - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetEditHistoryForPost", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Post](r) } // GetFlaggedPostsForUser returns flagged posts of a user based on user id string. func (c *Client4) GetFlaggedPostsForUser(ctx context.Context, userId string, page int, perPage int) (*PostList, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/posts/flagged"+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/posts/flagged?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetFlaggedPostsForUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) } // GetFlaggedPostsForUserInTeam returns flagged posts in team of a user based on user id string. func (c *Client4) GetFlaggedPostsForUserInTeam(ctx context.Context, userId string, teamId string, page int, perPage int) (*PostList, *Response, error) { if !IsValidId(teamId) { - return nil, nil, NewAppError("GetFlaggedPostsForUserInTeam", "model.client.get_flagged_posts_in_team.missing_parameter.app_error", nil, "", http.StatusBadRequest) + return nil, nil, errors.New("teamId is invalid") } - query := fmt.Sprintf("?team_id=%v&page=%v&per_page=%v", teamId, page, perPage) - r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/posts/flagged"+query, "") + values := url.Values{} + values.Set("team_id", teamId) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/posts/flagged?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetFlaggedPostsForUserInTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) } // GetFlaggedPostsForUserInChannel returns flagged posts in channel of a user based on user id string. func (c *Client4) GetFlaggedPostsForUserInChannel(ctx context.Context, userId string, channelId string, page int, perPage int) (*PostList, *Response, error) { if !IsValidId(channelId) { - return nil, nil, NewAppError("GetFlaggedPostsForUserInChannel", "model.client.get_flagged_posts_in_channel.missing_parameter.app_error", nil, "", http.StatusBadRequest) + return nil, nil, errors.New("channelId is invalid") } - query := fmt.Sprintf("?channel_id=%v&page=%v&per_page=%v", channelId, page, perPage) - r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/posts/flagged"+query, "") + values := url.Values{} + values.Set("channel_id", channelId) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/posts/flagged?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetFlaggedPostsForUserInChannel", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) } // GetPostsSince gets posts created after a specified time as Unix time in milliseconds. func (c *Client4) GetPostsSince(ctx context.Context, channelId string, time int64, collapsedThreads bool) (*PostList, *Response, error) { - query := fmt.Sprintf("?since=%v", time) - if collapsedThreads { - query += "&collapsedThreads=true" - } - r, err := c.DoAPIGet(ctx, c.channelRoute(channelId)+"/posts"+query, "") + values := url.Values{} + values.Set("since", strconv.FormatInt(time, 10)) + values.Set("collapsedThreads", c.boolString(collapsedThreads)) + r, err := c.DoAPIGet(ctx, c.channelRoute(channelId)+"/posts?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetPostsSince", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) } // GetPostsAfter gets a page of posts that were posted after the post provided. func (c *Client4) GetPostsAfter(ctx context.Context, channelId, postId string, page, perPage int, etag string, collapsedThreads bool, includeDeleted bool) (*PostList, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&after=%v", page, perPage, postId) - if collapsedThreads { - query += "&collapsedThreads=true" - } - if includeDeleted { - query += "&include_deleted=true" - } - r, err := c.DoAPIGet(ctx, c.channelRoute(channelId)+"/posts"+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("after", postId) + values.Set("collapsedThreads", c.boolString(collapsedThreads)) + values.Set("include_deleted", c.boolString(includeDeleted)) + r, err := c.DoAPIGet(ctx, c.channelRoute(channelId)+"/posts?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetPostsAfter", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) } // GetPostsBefore gets a page of posts that were posted before the post provided. func (c *Client4) GetPostsBefore(ctx context.Context, channelId, postId string, page, perPage int, etag string, collapsedThreads bool, includeDeleted bool) (*PostList, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&before=%v", page, perPage, postId) - if collapsedThreads { - query += "&collapsedThreads=true" - } - if includeDeleted { - query += "&include_deleted=true" - } - r, err := c.DoAPIGet(ctx, c.channelRoute(channelId)+"/posts"+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("before", postId) + values.Set("collapsedThreads", c.boolString(collapsedThreads)) + values.Set("include_deleted", c.boolString(includeDeleted)) + r, err := c.DoAPIGet(ctx, c.channelRoute(channelId)+"/posts?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetPostsBefore", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) } // MoveThread moves a thread based on provided post id, and channel id string. func (c *Client4) MoveThread(ctx context.Context, postId string, params *MoveThreadParams) (*Response, error) { - js, err := json.Marshal(params) - if err != nil { - return nil, NewAppError("MoveThread", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPost(ctx, c.postRoute(postId)+"/move", string(js)) + r, err := c.DoAPIPostJSON(ctx, c.postRoute(postId)+"/move", params) if err != nil { return BuildResponse(r), err } @@ -4528,23 +3425,93 @@ func (c *Client4) MoveThread(ctx context.Context, postId string, params *MoveThr // GetPostsAroundLastUnread gets a list of posts around last unread post by a user in a channel. func (c *Client4) GetPostsAroundLastUnread(ctx context.Context, userId, channelId string, limitBefore, limitAfter int, collapsedThreads bool) (*PostList, *Response, error) { - query := fmt.Sprintf("?limit_before=%v&limit_after=%v", limitBefore, limitAfter) - if collapsedThreads { - query += "&collapsedThreads=true" - } - r, err := c.DoAPIGet(ctx, c.userRoute(userId)+c.channelRoute(channelId)+"/posts/unread"+query, "") + values := url.Values{} + values.Set("limit_before", strconv.Itoa(limitBefore)) + values.Set("limit_after", strconv.Itoa(limitAfter)) + values.Set("collapsedThreads", c.boolString(collapsedThreads)) + r, err := c.DoAPIGet(ctx, c.userRoute(userId)+c.channelRoute(channelId)+"/posts/unread?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) +} + +func (c *Client4) CreateScheduledPost(ctx context.Context, scheduledPost *ScheduledPost) (*ScheduledPost, *Response, error) { + r, err := c.DoAPIPostJSON(ctx, c.postsRoute()+"/schedule", scheduledPost) + if err != nil { + return nil, BuildResponse(r), err } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetPostsAroundLastUnread", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + defer closeBody(r) + return DecodeJSONFromResponse[*ScheduledPost](r) +} + +func (c *Client4) GetUserScheduledPosts(ctx context.Context, teamId string, includeDirectChannels bool) (map[string][]*ScheduledPost, *Response, error) { + values := url.Values{} + values.Set("includeDirectChannels", fmt.Sprintf("%t", includeDirectChannels)) + r, err := c.DoAPIGet(ctx, c.postsRoute()+"/scheduled/team/"+teamId+"?"+values.Encode(), "") + if err != nil { + return nil, BuildResponse(r), err } - return &list, BuildResponse(r), nil + defer closeBody(r) + return DecodeJSONFromResponse[map[string][]*ScheduledPost](r) +} + +func (c *Client4) UpdateScheduledPost(ctx context.Context, scheduledPost *ScheduledPost) (*ScheduledPost, *Response, error) { + r, err := c.DoAPIPutJSON(ctx, c.postsRoute()+"/schedule/"+scheduledPost.Id, scheduledPost) + if err != nil { + return nil, BuildResponse(r), err + } + defer closeBody(r) + return DecodeJSONFromResponse[*ScheduledPost](r) +} + +func (c *Client4) DeleteScheduledPost(ctx context.Context, scheduledPostId string) (*ScheduledPost, *Response, error) { + r, err := c.DoAPIDelete(ctx, c.postsRoute()+"/schedule/"+scheduledPostId) + if err != nil { + return nil, BuildResponse(r), err + } + + defer closeBody(r) + return DecodeJSONFromResponse[*ScheduledPost](r) +} + +func (c *Client4) FlagPostForContentReview(ctx context.Context, postId string, flagRequest *FlagContentRequest) (*Response, error) { + r, err := c.DoAPIPostJSON(ctx, fmt.Sprintf("%s/post/%s/flag", c.contentFlaggingRoute(), postId), flagRequest) + if err != nil { + return BuildResponse(r), err + } + + defer closeBody(r) + return BuildResponse(r), nil +} + +func (c *Client4) GetContentFlaggedPost(ctx context.Context, postId string) (*Post, *Response, error) { + r, err := c.DoAPIGet(ctx, c.contentFlaggingRoute()+"/post/"+postId, "") + if err != nil { + return nil, BuildResponse(r), err + } + defer closeBody(r) + + return DecodeJSONFromResponse[*Post](r) +} + +func (c *Client4) GetFlaggingConfiguration(ctx context.Context) (*ContentFlaggingReportingConfig, *Response, error) { + r, err := c.DoAPIGet(ctx, c.contentFlaggingRoute()+"/flag/config", "") + if err != nil { + return nil, BuildResponse(r), err + } + defer closeBody(r) + return DecodeJSONFromResponse[*ContentFlaggingReportingConfig](r) +} + +func (c *Client4) GetTeamPostFlaggingFeatureStatus(ctx context.Context, teamId string) (map[string]bool, *Response, error) { + r, err := c.DoAPIGet(ctx, c.contentFlaggingRoute()+"/team/"+teamId+"/status", "") + if err != nil { + return nil, BuildResponse(r), err + } + defer closeBody(r) + return DecodeJSONFromResponse[map[string]bool](r) } // SearchFiles returns any posts with matching terms string. @@ -4558,26 +3525,16 @@ func (c *Client4) SearchFiles(ctx context.Context, teamId string, terms string, // SearchFilesWithParams returns any posts with matching terms string. func (c *Client4) SearchFilesWithParams(ctx context.Context, teamId string, params *SearchParameter) (*FileInfoList, *Response, error) { - js, err := json.Marshal(params) - if err != nil { - return nil, nil, NewAppError("SearchFilesWithParams", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - route := c.teamRoute(teamId) + "/files/search" if teamId == "" { route = c.filesRoute() + "/search" } - r, err := c.DoAPIPost(ctx, route, string(js)) + r, err := c.DoAPIPostJSON(ctx, route, params) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var list FileInfoList - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("SearchFilesWithParams", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*FileInfoList](r) } // SearchFilesAcrossTeams returns any posts with matching terms string. @@ -4600,29 +3557,18 @@ func (c *Client4) SearchPosts(ctx context.Context, teamId string, terms string, // SearchPostsWithParams returns any posts with matching terms string. func (c *Client4) SearchPostsWithParams(ctx context.Context, teamId string, params *SearchParameter) (*PostList, *Response, error) { - js, err := json.Marshal(params) - if err != nil { - return nil, nil, NewAppError("SearchFilesWithParams", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } var route string if teamId == "" { route = c.postsRoute() + "/search" } else { route = c.teamRoute(teamId) + "/posts/search" } - r, err := c.DoAPIPost(ctx, route, string(js)) + r, err := c.DoAPIPostJSON(ctx, route, params) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list PostList - if r.StatusCode == http.StatusNotModified { - return &list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("SearchFilesWithParams", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &list, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostList](r) } // SearchPostsWithMatches returns any posts with matching terms string, including. @@ -4634,16 +3580,12 @@ func (c *Client4) SearchPostsWithMatches(ctx context.Context, teamId string, ter } else { route = c.teamRoute(teamId) + "/posts/search" } - r, err := c.DoAPIPost(ctx, route, StringInterfaceToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, route, requestBody) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var psr PostSearchResults - if err := json.NewDecoder(r.Body).Decode(&psr); err != nil { - return nil, nil, NewAppError("SearchPostsWithMatches", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &psr, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostSearchResults](r) } // DoPostAction performs a post action. @@ -4658,18 +3600,20 @@ func (c *Client4) DoPostAction(ctx context.Context, postId, actionId string) (*R // DoPostActionWithCookie performs a post action with extra arguments func (c *Client4) DoPostActionWithCookie(ctx context.Context, postId, actionId, selected, cookieStr string) (*Response, error) { - var body []byte - if selected != "" || cookieStr != "" { - var err error - body, err = json.Marshal(DoPostActionRequest{ - SelectedOption: selected, - Cookie: cookieStr, - }) + if selected == "" && cookieStr == "" { + r, err := c.DoAPIPost(ctx, c.postRoute(postId)+"/actions/"+actionId, "") if err != nil { - return nil, NewAppError("DoPostActionWithCookie", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + return BuildResponse(r), err } + defer closeBody(r) + return BuildResponse(r), nil } - r, err := c.DoAPIPost(ctx, c.postRoute(postId)+"/actions/"+actionId, string(body)) + + req := DoPostActionRequest{ + SelectedOption: selected, + Cookie: cookieStr, + } + r, err := c.DoAPIPostJSON(ctx, c.postRoute(postId)+"/actions/"+actionId, req) if err != nil { return BuildResponse(r), err } @@ -4682,11 +3626,7 @@ func (c *Client4) DoPostActionWithCookie(ctx context.Context, postId, actionId, // provided data. Used with interactive message buttons, menus and // slash commands. func (c *Client4) OpenInteractiveDialog(ctx context.Context, request OpenDialogRequest) (*Response, error) { - b, err := json.Marshal(request) - if err != nil { - return nil, NewAppError("OpenInteractiveDialog", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, "/actions/dialogs/open", string(b)) + r, err := c.DoAPIPostJSON(ctx, "/actions/dialogs/open", request) if err != nil { return BuildResponse(r), err } @@ -4697,40 +3637,24 @@ func (c *Client4) OpenInteractiveDialog(ctx context.Context, request OpenDialogR // SubmitInteractiveDialog will submit the provided dialog data to the integration // configured by the URL. Used with the interactive dialogs integration feature. func (c *Client4) SubmitInteractiveDialog(ctx context.Context, request SubmitDialogRequest) (*SubmitDialogResponse, *Response, error) { - b, err := json.Marshal(request) - if err != nil { - return nil, nil, NewAppError("SubmitInteractiveDialog", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, "/actions/dialogs/submit", string(b)) + r, err := c.DoAPIPostJSON(ctx, "/actions/dialogs/submit", request) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var resp SubmitDialogResponse - json.NewDecoder(r.Body).Decode(&resp) - return &resp, BuildResponse(r), nil + return DecodeJSONFromResponse[*SubmitDialogResponse](r) } // LookupInteractiveDialog will perform a lookup request for dynamic select elements // in interactive dialogs. Used to fetch options for dynamic select fields. func (c *Client4) LookupInteractiveDialog(ctx context.Context, request SubmitDialogRequest) (*LookupDialogResponse, *Response, error) { - b, err := json.Marshal(request) - if err != nil { - return nil, nil, NewAppError("LookupInteractiveDialog", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, "/actions/dialogs/lookup", string(b)) + r, err := c.DoAPIPostJSON(ctx, "/actions/dialogs/lookup", request) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var resp LookupDialogResponse - err = json.NewDecoder(r.Body).Decode(&resp) - if err != nil { - return nil, BuildResponse(r), NewAppError("LookupInteractiveDialog", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &resp, BuildResponse(r), nil + return DecodeJSONFromResponse[*LookupDialogResponse](r) } // UploadFile will upload a file to a channel using a multipart request, to be later attached to a post. @@ -4769,7 +3693,10 @@ func (c *Client4) UploadFile(ctx context.Context, data []byte, channelId string, // UploadFileAsRequestBody will upload a file to a channel as the body of a request, to be later attached // to a post. This method is functionally equivalent to Client4.UploadFile. func (c *Client4) UploadFileAsRequestBody(ctx context.Context, data []byte, channelId string, filename string) (*FileUploadResponse, *Response, error) { - return c.DoUploadFile(ctx, c.filesRoute()+fmt.Sprintf("?channel_id=%v&filename=%v", url.QueryEscape(channelId), url.QueryEscape(filename)), data, http.DetectContentType(data)) + values := url.Values{} + values.Set("channel_id", channelId) + values.Set("filename", filename) + return c.DoUploadFile(ctx, c.filesRoute()+"?"+values.Encode(), data, http.DetectContentType(data)) } // GetFile gets the bytes for a file by id. @@ -4779,27 +3706,19 @@ func (c *Client4) GetFile(ctx context.Context, fileId string) ([]byte, *Response return nil, BuildResponse(r), err } defer closeBody(r) - - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetFile", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // DownloadFile gets the bytes for a file by id, optionally adding headers to force the browser to download it. func (c *Client4) DownloadFile(ctx context.Context, fileId string, download bool) ([]byte, *Response, error) { - r, err := c.DoAPIGet(ctx, c.fileRoute(fileId)+fmt.Sprintf("?download=%v", download), "") + values := url.Values{} + values.Set("download", c.boolString(download)) + r, err := c.DoAPIGet(ctx, c.fileRoute(fileId)+"?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("DownloadFile", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // GetFileThumbnail gets the bytes for a file by id. @@ -4809,27 +3728,19 @@ func (c *Client4) GetFileThumbnail(ctx context.Context, fileId string) ([]byte, return nil, BuildResponse(r), err } defer closeBody(r) - - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetFileThumbnail", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // DownloadFileThumbnail gets the bytes for a file by id, optionally adding headers to force the browser to download it. func (c *Client4) DownloadFileThumbnail(ctx context.Context, fileId string, download bool) ([]byte, *Response, error) { - r, err := c.DoAPIGet(ctx, c.fileRoute(fileId)+fmt.Sprintf("/thumbnail?download=%v", download), "") + values := url.Values{} + values.Set("download", c.boolString(download)) + r, err := c.DoAPIGet(ctx, c.fileRoute(fileId)+"/thumbnail?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("DownloadFileThumbnail", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // GetFileLink gets the public link of a file by id. @@ -4839,7 +3750,11 @@ func (c *Client4) GetFileLink(ctx context.Context, fileId string) (string, *Resp return "", BuildResponse(r), err } defer closeBody(r) - return MapFromJSON(r.Body)["link"], BuildResponse(r), nil + result, resp, err := DecodeJSONFromResponse[map[string]string](r) + if err != nil { + return "", resp, err + } + return result["link"], resp, nil } // GetFilePreview gets the bytes for a file by id. @@ -4849,27 +3764,19 @@ func (c *Client4) GetFilePreview(ctx context.Context, fileId string) ([]byte, *R return nil, BuildResponse(r), err } defer closeBody(r) - - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetFilePreview", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // DownloadFilePreview gets the bytes for a file by id. func (c *Client4) DownloadFilePreview(ctx context.Context, fileId string, download bool) ([]byte, *Response, error) { - r, err := c.DoAPIGet(ctx, c.fileRoute(fileId)+fmt.Sprintf("/preview?download=%v", download), "") + values := url.Values{} + values.Set("download", c.boolString(download)) + r, err := c.DoAPIGet(ctx, c.fileRoute(fileId)+"/preview?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("DownloadFilePreview", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // GetFileInfo gets all the file info objects. @@ -4879,12 +3786,7 @@ func (c *Client4) GetFileInfo(ctx context.Context, fileId string) (*FileInfo, *R return nil, BuildResponse(r), err } defer closeBody(r) - - var fi FileInfo - if err := json.NewDecoder(r.Body).Decode(&fi); err != nil { - return nil, nil, NewAppError("GetFileInfo", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &fi, BuildResponse(r), nil + return DecodeJSONFromResponse[*FileInfo](r) } // GetFileInfosForPost gets all the file info objects attached to a post. @@ -4894,15 +3796,7 @@ func (c *Client4) GetFileInfosForPost(ctx context.Context, postId string, etag s return nil, BuildResponse(r), err } defer closeBody(r) - - var list []*FileInfo - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetFileInfosForPost", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*FileInfo](r) } // GetFileInfosForPost gets all the file info objects attached to a post, including deleted @@ -4912,15 +3806,7 @@ func (c *Client4) GetFileInfosForPostIncludeDeleted(ctx context.Context, postId return nil, BuildResponse(r), err } defer closeBody(r) - - var list []*FileInfo - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetFileInfosForPostIncludeDeleted", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*FileInfo](r) } // General/System Section @@ -4947,7 +3833,7 @@ func (c *Client4) GetPing(ctx context.Context) (string, *Response, error) { ping, resp, err := c.GetPingWithOptions(ctx, SystemPingOptions{}) status := "" if ping != nil { - status = ping["status"] + status = ping["status"].(string) } return status, resp, err } @@ -4959,7 +3845,7 @@ func (c *Client4) GetPingWithServerStatus(ctx context.Context) (string, *Respons ping, resp, err := c.GetPingWithOptions(ctx, SystemPingOptions{FullStatus: true}) status := "" if ping != nil { - status = ping["status"] + status = ping["status"].(string) } return status, resp, err } @@ -4967,12 +3853,12 @@ func (c *Client4) GetPingWithServerStatus(ctx context.Context) (string, *Respons // GetPingWithFullServerStatus will return the full status if several basic server // health checks all pass successfully. // DEPRECATED: Use GetPingWithOptions method instead. -func (c *Client4) GetPingWithFullServerStatus(ctx context.Context) (map[string]string, *Response, error) { +func (c *Client4) GetPingWithFullServerStatus(ctx context.Context) (map[string]any, *Response, error) { return c.GetPingWithOptions(ctx, SystemPingOptions{FullStatus: true}) } // GetPingWithOptions will return the status according to the options -func (c *Client4) GetPingWithOptions(ctx context.Context, options SystemPingOptions) (map[string]string, *Response, error) { +func (c *Client4) GetPingWithOptions(ctx context.Context, options SystemPingOptions) (map[string]any, *Response, error) { pingURL, err := url.Parse(c.systemRoute() + "/ping") if err != nil { return nil, nil, fmt.Errorf("could not parse query: %w", err) @@ -4984,22 +3870,27 @@ func (c *Client4) GetPingWithOptions(ctx context.Context, options SystemPingOpti r, err := c.DoAPIGet(ctx, pingURL.String(), "") if r != nil && r.StatusCode == 500 { defer r.Body.Close() - return map[string]string{"status": StatusUnhealthy}, BuildResponse(r), err + return map[string]any{"status": StatusUnhealthy}, BuildResponse(r), err } if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - return MapFromJSON(r.Body), BuildResponse(r), nil + return DecodeJSONFromResponse[map[string]any](r) +} + +func (c *Client4) GetServerLimits(ctx context.Context) (*ServerLimits, *Response, error) { + r, err := c.DoAPIGet(ctx, c.limitsRoute()+"/server", "") + if err != nil { + return nil, BuildResponse(r), err + } + defer closeBody(r) + return DecodeJSONFromResponse[*ServerLimits](r) } // TestEmail will attempt to connect to the configured SMTP server. func (c *Client4) TestEmail(ctx context.Context, config *Config) (*Response, error) { - buf, err := json.Marshal(config) - if err != nil { - return nil, NewAppError("TestEmail", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.testEmailRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.testEmailRoute(), config) if err != nil { return BuildResponse(r), err } @@ -5020,7 +3911,7 @@ func (c *Client4) TestNotifications(ctx context.Context) (*Response, error) { func (c *Client4) TestSiteURL(ctx context.Context, siteURL string) (*Response, error) { requestBody := make(map[string]string) requestBody["site_url"] = siteURL - r, err := c.DoAPIPost(ctx, c.testSiteURLRoute(), MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.testSiteURLRoute(), requestBody) if err != nil { return BuildResponse(r), err } @@ -5030,11 +3921,7 @@ func (c *Client4) TestSiteURL(ctx context.Context, siteURL string) (*Response, e // TestS3Connection will attempt to connect to the AWS S3. func (c *Client4) TestS3Connection(ctx context.Context, config *Config) (*Response, error) { - buf, err := json.Marshal(config) - if err != nil { - return nil, NewAppError("TestS3Connection", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.testS3Route(), buf) + r, err := c.DoAPIPostJSON(ctx, c.testS3Route(), config) if err != nil { return BuildResponse(r), err } @@ -5049,10 +3936,7 @@ func (c *Client4) GetConfig(ctx context.Context) (*Config, *Response, error) { return nil, BuildResponse(r), err } defer closeBody(r) - - var cfg *Config - d := json.NewDecoder(r.Body) - return cfg, BuildResponse(r), d.Decode(&cfg) + return DecodeJSONFromResponse[*Config](r) } // GetConfig will retrieve the server config with some sanitized items. @@ -5074,9 +3958,7 @@ func (c *Client4) GetConfigWithOptions(ctx context.Context, options GetConfigOpt return nil, BuildResponse(r), err } defer closeBody(r) - - var cfg map[string]any - return cfg, BuildResponse(r), json.NewDecoder(r.Body).Decode(&cfg) + return DecodeJSONFromResponse[map[string]any](r) } // ReloadConfig will reload the server configuration. @@ -5096,7 +3978,7 @@ func (c *Client4) GetClientConfig(ctx context.Context, etag string) (map[string] return nil, BuildResponse(r), err } defer closeBody(r) - return MapFromJSON(r.Body), BuildResponse(r), nil + return DecodeJSONFromResponse[map[string]string](r) } // GetEnvironmentConfig will retrieve a map mirroring the server configuration where fields @@ -5119,7 +4001,7 @@ func (c *Client4) GetOldClientLicense(ctx context.Context, etag string) (map[str return nil, BuildResponse(r), err } defer closeBody(r) - return MapFromJSON(r.Body), BuildResponse(r), nil + return DecodeJSONFromResponse[map[string]string](r) } // DatabaseRecycle will recycle the connections. Discard current connection and get new one. @@ -5144,19 +4026,12 @@ func (c *Client4) InvalidateCaches(ctx context.Context) (*Response, error) { // UpdateConfig will update the server configuration. func (c *Client4) UpdateConfig(ctx context.Context, config *Config) (*Config, *Response, error) { - buf, err := json.Marshal(config) - if err != nil { - return nil, nil, NewAppError("UpdateConfig", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.configRoute(), buf) + r, err := c.DoAPIPutJSON(ctx, c.configRoute(), config) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var cfg *Config - d := json.NewDecoder(r.Body) - return cfg, BuildResponse(r), d.Decode(&cfg) + return DecodeJSONFromResponse[*Config](r) } // MigrateConfig will migrate existing config to the new one. @@ -5167,7 +4042,7 @@ func (c *Client4) MigrateConfig(ctx context.Context, from, to string) (*Response m := make(map[string]string, 2) m["from"] = from m["to"] = to - r, err := c.DoAPIPost(ctx, c.configRoute()+"/migrate", MapToJSON(m)) + r, err := c.DoAPIPostJSON(ctx, c.configRoute()+"/migrate", m) if err != nil { return BuildResponse(r), err } @@ -5182,38 +4057,23 @@ func (c *Client4) UploadLicenseFile(ctx context.Context, data []byte) (*Response part, err := writer.CreateFormFile("license", "test-license.mattermost-license") if err != nil { - return nil, NewAppError("UploadLicenseFile", "model.client.set_profile_user.no_file.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to create form file for license upload: %w", err) } if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return nil, NewAppError("UploadLicenseFile", "model.client.set_profile_user.no_file.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to copy license data to form file: %w", err) } if err = writer.Close(); err != nil { - return nil, NewAppError("UploadLicenseFile", "model.client.set_profile_user.writer.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to close multipart writer for license upload: %w", err) } - rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+c.licenseRoute(), bytes.NewReader(body.Bytes())) + r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.APIURL+c.licenseRoute(), writer.FormDataContentType(), body, nil) if err != nil { - return nil, err + return BuildResponse(r), err } - rq.Header.Set("Content-Type", writer.FormDataContentType()) - - if c.AuthToken != "" { - rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken) - } - - rp, err := c.HTTPClient.Do(rq) - if err != nil { - return BuildResponse(rp), err - } - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return BuildResponse(rp), AppErrorFromJSON(rp.Body) - } - - return BuildResponse(rp), nil + defer closeBody(r) + return BuildResponse(r), nil } // RemoveLicenseFile will remove the server license it exists. Note that this will @@ -5235,13 +4095,7 @@ func (c *Client4) GetLicenseLoadMetric(ctx context.Context) (map[string]int, *Re return nil, BuildResponse(r), err } defer closeBody(r) - - var loadData map[string]int - if err := json.NewDecoder(r.Body).Decode(&loadData); err != nil { - return nil, BuildResponse(r), NewAppError("GetLicenseLoadMetric", "api.unmarshal_error", nil, "", r.StatusCode).Wrap(err) - } - - return loadData, BuildResponse(r), nil + return DecodeJSONFromResponse[map[string]int](r) } // GetAnalyticsOld will retrieve analytics using the old format. New format is not @@ -5249,113 +4103,78 @@ func (c *Client4) GetLicenseLoadMetric(ctx context.Context) (map[string]int, *Re // and defaults to "standard". The "teamId" argument is optional and will limit results // to a specific team. func (c *Client4) GetAnalyticsOld(ctx context.Context, name, teamId string) (AnalyticsRows, *Response, error) { - query := fmt.Sprintf("?name=%v&team_id=%v", name, teamId) - r, err := c.DoAPIGet(ctx, c.analyticsRoute()+"/old"+query, "") + values := url.Values{} + values.Set("name", name) + values.Set("team_id", teamId) + r, err := c.DoAPIGet(ctx, c.analyticsRoute()+"/old?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var rows AnalyticsRows - err = json.NewDecoder(r.Body).Decode(&rows) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetAnalyticsOld", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return rows, BuildResponse(r), nil + return DecodeJSONFromResponse[AnalyticsRows](r) } // Webhooks Section // CreateIncomingWebhook creates an incoming webhook for a channel. func (c *Client4) CreateIncomingWebhook(ctx context.Context, hook *IncomingWebhook) (*IncomingWebhook, *Response, error) { - buf, err := json.Marshal(hook) - if err != nil { - return nil, nil, NewAppError("CreateIncomingWebhook", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.incomingWebhooksRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.incomingWebhooksRoute(), hook) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var iw IncomingWebhook - if err := json.NewDecoder(r.Body).Decode(&iw); err != nil { - return nil, nil, NewAppError("CreateIncomingWebhook", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &iw, BuildResponse(r), nil + return DecodeJSONFromResponse[*IncomingWebhook](r) } // UpdateIncomingWebhook updates an incoming webhook for a channel. func (c *Client4) UpdateIncomingWebhook(ctx context.Context, hook *IncomingWebhook) (*IncomingWebhook, *Response, error) { - buf, err := json.Marshal(hook) - if err != nil { - return nil, nil, NewAppError("UpdateIncomingWebhook", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.incomingWebhookRoute(hook.Id), buf) + r, err := c.DoAPIPutJSON(ctx, c.incomingWebhookRoute(hook.Id), hook) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var iw IncomingWebhook - if err := json.NewDecoder(r.Body).Decode(&iw); err != nil { - return nil, nil, NewAppError("UpdateIncomingWebhook", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &iw, BuildResponse(r), nil + return DecodeJSONFromResponse[*IncomingWebhook](r) } // GetIncomingWebhooks returns a page of incoming webhooks on the system. Page counting starts at 0. func (c *Client4) GetIncomingWebhooks(ctx context.Context, page int, perPage int, etag string) ([]*IncomingWebhook, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.incomingWebhooksRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.incomingWebhooksRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var iwl []*IncomingWebhook - if r.StatusCode == http.StatusNotModified { - return iwl, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&iwl); err != nil { - return nil, nil, NewAppError("GetIncomingWebhooks", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return iwl, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*IncomingWebhook](r) } // GetIncomingWebhooksWithCount returns a page of incoming webhooks on the system including the total count. Page counting starts at 0. func (c *Client4) GetIncomingWebhooksWithCount(ctx context.Context, page int, perPage int, etag string) (*IncomingWebhooksWithCount, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&include_total_count="+c.boolString(true), page, perPage) - r, err := c.DoAPIGet(ctx, c.incomingWebhooksRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("include_total_count", c.boolString(true)) + r, err := c.DoAPIGet(ctx, c.incomingWebhooksRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var iwl *IncomingWebhooksWithCount - if r.StatusCode == http.StatusNotModified { - return iwl, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&iwl); err != nil { - return nil, nil, NewAppError("GetIncomingWebhooksWithCount", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return iwl, BuildResponse(r), nil + return DecodeJSONFromResponse[*IncomingWebhooksWithCount](r) } // GetIncomingWebhooksForTeam returns a page of incoming webhooks for a team. Page counting starts at 0. func (c *Client4) GetIncomingWebhooksForTeam(ctx context.Context, teamId string, page int, perPage int, etag string) ([]*IncomingWebhook, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&team_id=%v", page, perPage, teamId) - r, err := c.DoAPIGet(ctx, c.incomingWebhooksRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("team_id", teamId) + r, err := c.DoAPIGet(ctx, c.incomingWebhooksRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var iwl []*IncomingWebhook - if r.StatusCode == http.StatusNotModified { - return iwl, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&iwl); err != nil { - return nil, nil, NewAppError("GetIncomingWebhooksForTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return iwl, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*IncomingWebhook](r) } // GetIncomingWebhook returns an Incoming webhook given the hook ID. @@ -5365,14 +4184,7 @@ func (c *Client4) GetIncomingWebhook(ctx context.Context, hookID string, etag st return nil, BuildResponse(r), err } defer closeBody(r) - var iw IncomingWebhook - if r.StatusCode == http.StatusNotModified { - return &iw, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&iw); err != nil { - return nil, nil, NewAppError("GetIncomingWebhook", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &iw, BuildResponse(r), nil + return DecodeJSONFromResponse[*IncomingWebhook](r) } // DeleteIncomingWebhook deletes and Incoming Webhook given the hook ID. @@ -5387,56 +4199,35 @@ func (c *Client4) DeleteIncomingWebhook(ctx context.Context, hookID string) (*Re // CreateOutgoingWebhook creates an outgoing webhook for a team or channel. func (c *Client4) CreateOutgoingWebhook(ctx context.Context, hook *OutgoingWebhook) (*OutgoingWebhook, *Response, error) { - buf, err := json.Marshal(hook) - if err != nil { - return nil, nil, NewAppError("CreateOutgoingWebhook", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.outgoingWebhooksRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.outgoingWebhooksRoute(), hook) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var ow OutgoingWebhook - if err := json.NewDecoder(r.Body).Decode(&ow); err != nil { - return nil, nil, NewAppError("CreateOutgoingWebhook", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &ow, BuildResponse(r), nil + return DecodeJSONFromResponse[*OutgoingWebhook](r) } // UpdateOutgoingWebhook creates an outgoing webhook for a team or channel. func (c *Client4) UpdateOutgoingWebhook(ctx context.Context, hook *OutgoingWebhook) (*OutgoingWebhook, *Response, error) { - buf, err := json.Marshal(hook) - if err != nil { - return nil, nil, NewAppError("UpdateOutgoingWebhook", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.outgoingWebhookRoute(hook.Id), buf) + r, err := c.DoAPIPutJSON(ctx, c.outgoingWebhookRoute(hook.Id), hook) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var ow OutgoingWebhook - if err := json.NewDecoder(r.Body).Decode(&ow); err != nil { - return nil, nil, NewAppError("UpdateOutgoingWebhook", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &ow, BuildResponse(r), nil + return DecodeJSONFromResponse[*OutgoingWebhook](r) } // GetOutgoingWebhooks returns a page of outgoing webhooks on the system. Page counting starts at 0. func (c *Client4) GetOutgoingWebhooks(ctx context.Context, page int, perPage int, etag string) ([]*OutgoingWebhook, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.outgoingWebhooksRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.outgoingWebhooksRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var owl []*OutgoingWebhook - if r.StatusCode == http.StatusNotModified { - return owl, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&owl); err != nil { - return nil, nil, NewAppError("GetOutgoingWebhooks", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return owl, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*OutgoingWebhook](r) } // GetOutgoingWebhook outgoing webhooks on the system requested by Hook Id. @@ -5446,47 +4237,35 @@ func (c *Client4) GetOutgoingWebhook(ctx context.Context, hookId string) (*Outgo return nil, BuildResponse(r), err } defer closeBody(r) - var ow OutgoingWebhook - if err := json.NewDecoder(r.Body).Decode(&ow); err != nil { - return nil, nil, NewAppError("GetOutgoingWebhook", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &ow, BuildResponse(r), nil + return DecodeJSONFromResponse[*OutgoingWebhook](r) } // GetOutgoingWebhooksForChannel returns a page of outgoing webhooks for a channel. Page counting starts at 0. func (c *Client4) GetOutgoingWebhooksForChannel(ctx context.Context, channelId string, page int, perPage int, etag string) ([]*OutgoingWebhook, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&channel_id=%v", page, perPage, channelId) - r, err := c.DoAPIGet(ctx, c.outgoingWebhooksRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("channel_id", channelId) + r, err := c.DoAPIGet(ctx, c.outgoingWebhooksRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var owl []*OutgoingWebhook - if r.StatusCode == http.StatusNotModified { - return owl, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&owl); err != nil { - return nil, nil, NewAppError("GetOutgoingWebhooksForChannel", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return owl, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*OutgoingWebhook](r) } // GetOutgoingWebhooksForTeam returns a page of outgoing webhooks for a team. Page counting starts at 0. func (c *Client4) GetOutgoingWebhooksForTeam(ctx context.Context, teamId string, page int, perPage int, etag string) ([]*OutgoingWebhook, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&team_id=%v", page, perPage, teamId) - r, err := c.DoAPIGet(ctx, c.outgoingWebhooksRoute()+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("team_id", teamId) + r, err := c.DoAPIGet(ctx, c.outgoingWebhooksRoute()+"?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var owl []*OutgoingWebhook - if r.StatusCode == http.StatusNotModified { - return owl, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&owl); err != nil { - return nil, nil, NewAppError("GetOutgoingWebhooksForTeam", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return owl, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*OutgoingWebhook](r) } // RegenOutgoingHookToken regenerate the outgoing webhook token. @@ -5496,11 +4275,7 @@ func (c *Client4) RegenOutgoingHookToken(ctx context.Context, hookId string) (*O return nil, BuildResponse(r), err } defer closeBody(r) - var ow OutgoingWebhook - if err := json.NewDecoder(r.Body).Decode(&ow); err != nil { - return nil, nil, NewAppError("RegenOutgoingHookToken", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &ow, BuildResponse(r), nil + return DecodeJSONFromResponse[*OutgoingWebhook](r) } // DeleteOutgoingWebhook delete the outgoing webhook on the system requested by Hook Id. @@ -5522,21 +4297,12 @@ func (c *Client4) GetPreferences(ctx context.Context, userId string) (Preference return nil, BuildResponse(r), err } defer closeBody(r) - - var prefs Preferences - if err := json.NewDecoder(r.Body).Decode(&prefs); err != nil { - return nil, nil, NewAppError("GetPreferences", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return prefs, BuildResponse(r), nil + return DecodeJSONFromResponse[Preferences](r) } // UpdatePreferences saves the user's preferences. func (c *Client4) UpdatePreferences(ctx context.Context, userId string, preferences Preferences) (*Response, error) { - buf, err := json.Marshal(preferences) - if err != nil { - return nil, NewAppError("UpdatePreferences", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.preferencesRoute(userId), buf) + r, err := c.DoAPIPutJSON(ctx, c.preferencesRoute(userId), preferences) if err != nil { return BuildResponse(r), err } @@ -5546,11 +4312,7 @@ func (c *Client4) UpdatePreferences(ctx context.Context, userId string, preferen // DeletePreferences deletes the user's preferences. func (c *Client4) DeletePreferences(ctx context.Context, userId string, preferences Preferences) (*Response, error) { - buf, err := json.Marshal(preferences) - if err != nil { - return nil, NewAppError("DeletePreferences", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.preferencesRoute(userId)+"/delete", buf) + r, err := c.DoAPIPostJSON(ctx, c.preferencesRoute(userId)+"/delete", preferences) if err != nil { return BuildResponse(r), err } @@ -5566,11 +4328,7 @@ func (c *Client4) GetPreferencesByCategory(ctx context.Context, userId string, c return nil, BuildResponse(r), err } defer closeBody(r) - var prefs Preferences - if err := json.NewDecoder(r.Body).Decode(&prefs); err != nil { - return nil, nil, NewAppError("GetPreferencesByCategory", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return prefs, BuildResponse(r), nil + return DecodeJSONFromResponse[Preferences](r) } // GetPreferenceByCategoryAndName returns the user's preferences from the provided category and preference name string. @@ -5581,12 +4339,7 @@ func (c *Client4) GetPreferenceByCategoryAndName(ctx context.Context, userId str return nil, BuildResponse(r), err } defer closeBody(r) - - var pref Preference - if err := json.NewDecoder(r.Body).Decode(&pref); err != nil { - return nil, nil, NewAppError("GetPreferenceByCategoryAndName", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &pref, BuildResponse(r), nil + return DecodeJSONFromResponse[*Preference](r) } // SAML Section @@ -5621,7 +4374,7 @@ func fileToMultipart(data []byte, filename string) ([]byte, *multipart.Writer, e return nil, nil, err } - if err := writer.Close(); err != nil { + if err = writer.Close(); err != nil { return nil, nil, err } @@ -5633,7 +4386,7 @@ func fileToMultipart(data []byte, filename string) ([]byte, *multipart.Writer, e func (c *Client4) UploadSamlIdpCertificate(ctx context.Context, data []byte, filename string) (*Response, error) { body, writer, err := fileToMultipart(data, filename) if err != nil { - return nil, NewAppError("UploadSamlIdpCertificate", "model.client.upload_saml_cert.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to prepare SAML IDP certificate for upload: %w", err) } _, resp, err := c.DoUploadFile(ctx, c.samlRoute()+"/certificate/idp", body, writer.FormDataContentType()) @@ -5645,7 +4398,7 @@ func (c *Client4) UploadSamlIdpCertificate(ctx context.Context, data []byte, fil func (c *Client4) UploadSamlPublicCertificate(ctx context.Context, data []byte, filename string) (*Response, error) { body, writer, err := fileToMultipart(data, filename) if err != nil { - return nil, NewAppError("UploadSamlPublicCertificate", "model.client.upload_saml_cert.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to prepare SAML public certificate for upload: %w", err) } _, resp, err := c.DoUploadFile(ctx, c.samlRoute()+"/certificate/public", body, writer.FormDataContentType()) @@ -5657,7 +4410,7 @@ func (c *Client4) UploadSamlPublicCertificate(ctx context.Context, data []byte, func (c *Client4) UploadSamlPrivateCertificate(ctx context.Context, data []byte, filename string) (*Response, error) { body, writer, err := fileToMultipart(data, filename) if err != nil { - return nil, NewAppError("UploadSamlPrivateCertificate", "model.client.upload_saml_cert.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to prepare SAML private certificate for upload: %w", err) } _, resp, err := c.DoUploadFile(ctx, c.samlRoute()+"/certificate/private", body, writer.FormDataContentType()) @@ -5701,28 +4454,19 @@ func (c *Client4) GetSamlCertificateStatus(ctx context.Context) (*SamlCertificat return nil, BuildResponse(r), err } defer closeBody(r) - - var status SamlCertificateStatus - if err := json.NewDecoder(r.Body).Decode(&status); err != nil { - return nil, nil, NewAppError("GetSamlCertificateStatus", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &status, BuildResponse(r), nil + return DecodeJSONFromResponse[*SamlCertificateStatus](r) } func (c *Client4) GetSamlMetadataFromIdp(ctx context.Context, samlMetadataURL string) (*SamlMetadataResponse, *Response, error) { requestBody := make(map[string]string) requestBody["saml_metadata_url"] = samlMetadataURL - r, err := c.DoAPIPost(ctx, c.samlRoute()+"/metadatafromidp", MapToJSON(requestBody)) + r, err := c.DoAPIPostJSON(ctx, c.samlRoute()+"/metadatafromidp", requestBody) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var resp SamlMetadataResponse - if err := json.NewDecoder(r.Body).Decode(&resp); err != nil { - return nil, nil, NewAppError("GetSamlMetadataFromIdp", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &resp, BuildResponse(r), nil + return DecodeJSONFromResponse[*SamlMetadataResponse](r) } // ResetSamlAuthDataToEmail resets the AuthData field of SAML users to their Email. @@ -5732,56 +4476,41 @@ func (c *Client4) ResetSamlAuthDataToEmail(ctx context.Context, includeDeleted b "dry_run": dryRun, "user_ids": userIDs, } - b, err := json.Marshal(params) - if err != nil { - return 0, nil, NewAppError("ResetSamlAuthDataToEmail", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.samlRoute()+"/reset_auth_data", b) + r, err := c.DoAPIPostJSON(ctx, c.samlRoute()+"/reset_auth_data", params) if err != nil { return 0, BuildResponse(r), err } defer closeBody(r) - respBody := map[string]int64{} - err = json.NewDecoder(r.Body).Decode(&respBody) + respBody, resp, err := DecodeJSONFromResponse[map[string]int64](r) if err != nil { - return 0, BuildResponse(r), NewAppError("Api4.ResetSamlAuthDataToEmail", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + return 0, resp, err } - return respBody["num_affected"], BuildResponse(r), nil + return respBody["num_affected"], resp, nil } // Compliance Section // CreateComplianceReport creates an incoming webhook for a channel. func (c *Client4) CreateComplianceReport(ctx context.Context, report *Compliance) (*Compliance, *Response, error) { - buf, err := json.Marshal(report) - if err != nil { - return nil, nil, NewAppError("CreateComplianceReport", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.complianceReportsRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.complianceReportsRoute(), report) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var comp Compliance - if err := json.NewDecoder(r.Body).Decode(&comp); err != nil { - return nil, nil, NewAppError("CreateComplianceReport", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &comp, BuildResponse(r), nil + return DecodeJSONFromResponse[*Compliance](r) } // GetComplianceReports returns list of compliance reports. func (c *Client4) GetComplianceReports(ctx context.Context, page, perPage int) (Compliances, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.complianceReportsRoute()+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.complianceReportsRoute()+"?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var comp Compliances - if err := json.NewDecoder(r.Body).Decode(&comp); err != nil { - return nil, nil, NewAppError("GetComplianceReports", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return comp, BuildResponse(r), nil + return DecodeJSONFromResponse[Compliances](r) } // GetComplianceReport returns a compliance report. @@ -5791,40 +4520,17 @@ func (c *Client4) GetComplianceReport(ctx context.Context, reportId string) (*Co return nil, BuildResponse(r), err } defer closeBody(r) - var comp Compliance - if err := json.NewDecoder(r.Body).Decode(&comp); err != nil { - return nil, nil, NewAppError("GetComplianceReport", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &comp, BuildResponse(r), nil + return DecodeJSONFromResponse[*Compliance](r) } // DownloadComplianceReport returns a full compliance report as a file. func (c *Client4) DownloadComplianceReport(ctx context.Context, reportId string) ([]byte, *Response, error) { - rq, err := http.NewRequestWithContext(ctx, "GET", c.APIURL+c.complianceReportDownloadRoute(reportId), nil) + r, err := c.DoAPIGet(ctx, c.complianceReportDownloadRoute(reportId), "") if err != nil { - return nil, nil, err + return nil, BuildResponse(r), err } - - if c.AuthToken != "" { - rq.Header.Set(HeaderAuth, "BEARER "+c.AuthToken) - } - - rp, err := c.HTTPClient.Do(rq) - if err != nil { - return nil, BuildResponse(rp), err - } - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body) - } - - data, err := io.ReadAll(rp.Body) - if err != nil { - return nil, BuildResponse(rp), NewAppError("DownloadComplianceReport", "model.client.read_file.app_error", nil, "", rp.StatusCode).Wrap(err) - } - - return data, BuildResponse(rp), nil + defer closeBody(r) + return ReadBytesFromResponse(r) } // Cluster Section @@ -5836,18 +4542,14 @@ func (c *Client4) GetClusterStatus(ctx context.Context) ([]*ClusterInfo, *Respon return nil, BuildResponse(r), err } defer closeBody(r) - var list []*ClusterInfo - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetClusterStatus", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*ClusterInfo](r) } // LDAP Section // SyncLdap starts a run of the LDAP sync job. func (c *Client4) SyncLdap(ctx context.Context) (*Response, error) { - r, err := c.DoAPIPostBytes(ctx, c.ldapRoute()+"/sync", nil) + r, err := c.DoAPIPost(ctx, c.ldapRoute()+"/sync", "") if err != nil { return BuildResponse(r), err } @@ -5876,18 +4578,18 @@ func (c *Client4) GetLdapGroups(ctx context.Context) ([]*Group, *Response, error } defer closeBody(r) - responseData := struct { + responseData, resp, err := DecodeJSONFromResponse[struct { Count int `json:"count"` Groups []*Group `json:"groups"` - }{} - if err := json.NewDecoder(r.Body).Decode(&responseData); err != nil { - return nil, BuildResponse(r), NewAppError("Api4.GetLdapGroups", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + }](r) + if err != nil { + return nil, BuildResponse(r), fmt.Errorf("failed to decode LDAP groups response: %w", err) } for i := range responseData.Groups { responseData.Groups[i].DisplayName = *responseData.Groups[i].Name } - return responseData.Groups, BuildResponse(r), nil + return responseData.Groups, resp, nil } // LinkLdapGroup creates or undeletes a Mattermost group and associates it to the given LDAP group DN. @@ -5899,12 +4601,7 @@ func (c *Client4) LinkLdapGroup(ctx context.Context, dn string) (*Group, *Respon return nil, BuildResponse(r), err } defer closeBody(r) - - var g Group - if err := json.NewDecoder(r.Body).Decode(&g); err != nil { - return nil, nil, NewAppError("LinkLdapGroup", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &g, BuildResponse(r), nil + return DecodeJSONFromResponse[*Group](r) } // UnlinkLdapGroup deletes the Mattermost group associated with the given LDAP group DN. @@ -5916,19 +4613,14 @@ func (c *Client4) UnlinkLdapGroup(ctx context.Context, dn string) (*Group, *Resp return nil, BuildResponse(r), err } defer closeBody(r) - - var g Group - if err := json.NewDecoder(r.Body).Decode(&g); err != nil { - return nil, nil, NewAppError("UnlinkLdapGroup", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &g, BuildResponse(r), nil + return DecodeJSONFromResponse[*Group](r) } // MigrateIdLdap migrates the LDAP enabled users to given attribute func (c *Client4) MigrateIdLdap(ctx context.Context, toAttribute string) (*Response, error) { - r, err := c.DoAPIPost(ctx, c.ldapRoute()+"/migrateid", MapToJSON(map[string]string{ + r, err := c.DoAPIPostJSON(ctx, c.ldapRoute()+"/migrateid", map[string]string{ "toAttribute": toAttribute, - })) + }) if err != nil { return BuildResponse(r), err } @@ -5939,84 +4631,95 @@ func (c *Client4) MigrateIdLdap(ctx context.Context, toAttribute string) (*Respo func (c *Client4) GetGroupsByNames(ctx context.Context, names []string) ([]*Group, *Response, error) { path := fmt.Sprintf("%s/names", c.groupsRoute()) - r, err := c.DoAPIPost(ctx, path, ArrayToJSON(names)) + r, err := c.DoAPIPostJSON(ctx, path, names) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Group - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetGroupsByNames", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Group](r) } // GetGroupsByChannel retrieves the Mattermost Groups associated with a given channel func (c *Client4) GetGroupsByChannel(ctx context.Context, channelId string, opts GroupSearchOpts) ([]*GroupWithSchemeAdmin, int, *Response, error) { - path := fmt.Sprintf("%s/groups?q=%v&include_member_count=%v&filter_allow_reference=%v", c.channelRoute(channelId), opts.Q, opts.IncludeMemberCount, opts.FilterAllowReference) + values := url.Values{} + values.Set("q", opts.Q) + values.Set("include_member_count", c.boolString(opts.IncludeMemberCount)) + values.Set("filter_allow_reference", c.boolString(opts.FilterAllowReference)) if opts.PageOpts != nil { - path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage) + values.Set("page", strconv.Itoa(opts.PageOpts.Page)) + values.Set("per_page", strconv.Itoa(opts.PageOpts.PerPage)) } + path := c.channelRoute(channelId) + "/groups?" + values.Encode() r, err := c.DoAPIGet(ctx, path, "") if err != nil { return nil, 0, BuildResponse(r), err } defer closeBody(r) - responseData := struct { + responseData, resp, err := DecodeJSONFromResponse[struct { Groups []*GroupWithSchemeAdmin `json:"groups"` Count int `json:"total_group_count"` - }{} - if err := json.NewDecoder(r.Body).Decode(&responseData); err != nil { - return nil, 0, BuildResponse(r), NewAppError("Api4.GetGroupsByChannel", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + }](r) + if err != nil { + return nil, 0, BuildResponse(r), fmt.Errorf("failed to decode groups by channel response: %w", err) } - return responseData.Groups, responseData.Count, BuildResponse(r), nil + return responseData.Groups, responseData.Count, resp, nil } // GetGroupsByTeam retrieves the Mattermost Groups associated with a given team func (c *Client4) GetGroupsByTeam(ctx context.Context, teamId string, opts GroupSearchOpts) ([]*GroupWithSchemeAdmin, int, *Response, error) { - path := fmt.Sprintf("%s/groups?q=%v&include_member_count=%v&filter_allow_reference=%v", c.teamRoute(teamId), opts.Q, opts.IncludeMemberCount, opts.FilterAllowReference) + values := url.Values{} + values.Set("q", opts.Q) + values.Set("include_member_count", c.boolString(opts.IncludeMemberCount)) + values.Set("filter_allow_reference", c.boolString(opts.FilterAllowReference)) if opts.PageOpts != nil { - path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage) + values.Set("page", strconv.Itoa(opts.PageOpts.Page)) + values.Set("per_page", strconv.Itoa(opts.PageOpts.PerPage)) } + path := c.teamRoute(teamId) + "/groups?" + values.Encode() + r, err := c.DoAPIGet(ctx, path, "") if err != nil { return nil, 0, BuildResponse(r), err } defer closeBody(r) - responseData := struct { + responseData, resp, err := DecodeJSONFromResponse[struct { Groups []*GroupWithSchemeAdmin `json:"groups"` Count int `json:"total_group_count"` - }{} - if err := json.NewDecoder(r.Body).Decode(&responseData); err != nil { - return nil, 0, BuildResponse(r), NewAppError("Api4.GetGroupsByTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + }](r) + if err != nil { + return nil, 0, BuildResponse(r), fmt.Errorf("failed to decode groups by team response: %w", err) } - return responseData.Groups, responseData.Count, BuildResponse(r), nil + return responseData.Groups, responseData.Count, resp, nil } // GetGroupsAssociatedToChannelsByTeam retrieves the Mattermost Groups associated with channels in a given team func (c *Client4) GetGroupsAssociatedToChannelsByTeam(ctx context.Context, teamId string, opts GroupSearchOpts) (map[string][]*GroupWithSchemeAdmin, *Response, error) { - path := fmt.Sprintf("%s/groups_by_channels?q=%v&filter_allow_reference=%v", c.teamRoute(teamId), opts.Q, opts.FilterAllowReference) + values := url.Values{} + values.Set("q", opts.Q) + values.Set("filter_allow_reference", c.boolString(opts.FilterAllowReference)) if opts.PageOpts != nil { - path = fmt.Sprintf("%s&page=%v&per_page=%v", path, opts.PageOpts.Page, opts.PageOpts.PerPage) + values.Set("page", strconv.Itoa(opts.PageOpts.Page)) + values.Set("per_page", strconv.Itoa(opts.PageOpts.PerPage)) } + path := c.teamRoute(teamId) + "/groups_by_channels?" + values.Encode() r, err := c.DoAPIGet(ctx, path, "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - responseData := struct { + responseData, resp, err := DecodeJSONFromResponse[struct { GroupsAssociatedToChannels map[string][]*GroupWithSchemeAdmin `json:"groups"` - }{} - if err := json.NewDecoder(r.Body).Decode(&responseData); err != nil { - return nil, BuildResponse(r), NewAppError("Api4.GetGroupsAssociatedToChannelsByTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + }](r) + if err != nil { + return nil, BuildResponse(r), fmt.Errorf("failed to decode groups associated to channels by team response: %w", err) } - return responseData.GroupsAssociatedToChannels, BuildResponse(r), nil + return responseData.GroupsAssociatedToChannels, resp, nil } // GetGroups retrieves Mattermost Groups @@ -6048,12 +4751,7 @@ func (c *Client4) GetGroups(ctx context.Context, opts GroupSearchOpts) ([]*Group return nil, BuildResponse(r), err } defer closeBody(r) - - var list []*Group - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetGroups", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Group](r) } // GetGroupsByUserId retrieves Mattermost Groups for a user @@ -6069,19 +4767,15 @@ func (c *Client4) GetGroupsByUserId(ctx context.Context, userId string) ([]*Grou return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Group - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetGroupsByUserId", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Group](r) } func (c *Client4) MigrateAuthToLdap(ctx context.Context, fromAuthService string, matchField string, force bool) (*Response, error) { - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/migrate_auth/ldap", StringInterfaceToJSON(map[string]any{ + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/migrate_auth/ldap", map[string]any{ "from": fromAuthService, "force": force, "match_field": matchField, - })) + }) if err != nil { return BuildResponse(r), err } @@ -6090,11 +4784,11 @@ func (c *Client4) MigrateAuthToLdap(ctx context.Context, fromAuthService string, } func (c *Client4) MigrateAuthToSaml(ctx context.Context, fromAuthService string, usersMap map[string]string, auto bool) (*Response, error) { - r, err := c.DoAPIPost(ctx, c.usersRoute()+"/migrate_auth/saml", StringInterfaceToJSON(map[string]any{ + r, err := c.DoAPIPostJSON(ctx, c.usersRoute()+"/migrate_auth/saml", map[string]any{ "from": fromAuthService, "auto": auto, "matches": usersMap, - })) + }) if err != nil { return BuildResponse(r), err } @@ -6106,7 +4800,7 @@ func (c *Client4) MigrateAuthToSaml(ctx context.Context, fromAuthService string, func (c *Client4) UploadLdapPublicCertificate(ctx context.Context, data []byte) (*Response, error) { body, writer, err := fileToMultipart(data, LdapPublicCertificateName) if err != nil { - return nil, NewAppError("UploadLdapPublicCertificate", "model.client.upload_ldap_cert.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to prepare LDAP public certificate for upload: %w", err) } _, resp, err := c.DoUploadFile(ctx, c.ldapRoute()+"/certificate/public", body, writer.FormDataContentType()) @@ -6117,7 +4811,7 @@ func (c *Client4) UploadLdapPublicCertificate(ctx context.Context, data []byte) func (c *Client4) UploadLdapPrivateCertificate(ctx context.Context, data []byte) (*Response, error) { body, writer, err := fileToMultipart(data, LdapPrivateKeyName) if err != nil { - return nil, NewAppError("UploadLdapPrivateCertificate", "model.client.upload_Ldap_cert.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to prepare LDAP private certificate for upload: %w", err) } _, resp, err := c.DoUploadFile(ctx, c.ldapRoute()+"/certificate/private", body, writer.FormDataContentType()) @@ -6148,19 +4842,15 @@ func (c *Client4) DeleteLdapPrivateCertificate(ctx context.Context) (*Response, // GetAudits returns a list of audits for the whole system. func (c *Client4) GetAudits(ctx context.Context, page int, perPage int, etag string) (Audits, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, "/audits"+query, etag) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, "/audits?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var audits Audits - err = json.NewDecoder(r.Body).Decode(&audits) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetAudits", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return audits, BuildResponse(r), nil + return DecodeJSONFromResponse[Audits](r) } // Brand Section @@ -6177,12 +4867,7 @@ func (c *Client4) GetBrandImage(ctx context.Context) ([]byte, *Response, error) return nil, BuildResponse(r), AppErrorFromJSON(r.Body) } - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetBrandImage", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // DeleteBrandImage deletes the brand image for the system. @@ -6201,51 +4886,38 @@ func (c *Client4) UploadBrandImage(ctx context.Context, data []byte) (*Response, part, err := writer.CreateFormFile("image", "brand.png") if err != nil { - return nil, NewAppError("UploadBrandImage", "model.client.set_profile_user.no_file.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to create form file for brand image upload: %w", err) } if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { - return nil, NewAppError("UploadBrandImage", "model.client.set_profile_user.no_file.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to copy brand image data to form file: %w", err) } if err = writer.Close(); err != nil { - return nil, NewAppError("UploadBrandImage", "model.client.set_profile_user.writer.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, fmt.Errorf("failed to close multipart writer for brand image upload: %w", err) } - rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+c.brandRoute()+"/image", bytes.NewReader(body.Bytes())) + r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.APIURL+c.brandRoute()+"/image", writer.FormDataContentType(), body, nil) if err != nil { - return nil, err + return BuildResponse(r), err } - rq.Header.Set("Content-Type", writer.FormDataContentType()) - - if c.AuthToken != "" { - rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken) - } - - rp, err := c.HTTPClient.Do(rq) - if err != nil { - return BuildResponse(rp), err - } - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return BuildResponse(rp), AppErrorFromJSON(rp.Body) - } - - return BuildResponse(rp), nil + defer closeBody(r) + return BuildResponse(r), nil } // Logs Section // GetLogs page of logs as a string array. func (c *Client4) GetLogs(ctx context.Context, page, perPage int) ([]string, *Response, error) { - query := fmt.Sprintf("?page=%v&logs_per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, "/logs"+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("logs_per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, "/logs?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - return c.ArrayFromJSON(r.Body), BuildResponse(r), nil + return DecodeJSONFromResponse[[]string](r) } // Download logs as mattermost.log file @@ -6254,78 +4926,54 @@ func (c *Client4) DownloadLogs(ctx context.Context) ([]byte, *Response, error) { if err != nil { return nil, BuildResponse(r), err } - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("DownloadLogs", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // PostLog is a convenience Web Service call so clients can log messages into // the server-side logs. For example we typically log javascript error messages // into the server-side. It returns the log message if the logging was successful. func (c *Client4) PostLog(ctx context.Context, message map[string]string) (map[string]string, *Response, error) { - r, err := c.DoAPIPost(ctx, "/logs", MapToJSON(message)) + r, err := c.DoAPIPostJSON(ctx, "/logs", message) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - return MapFromJSON(r.Body), BuildResponse(r), nil + return DecodeJSONFromResponse[map[string]string](r) } // OAuth Section // CreateOAuthApp will register a new OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider. func (c *Client4) CreateOAuthApp(ctx context.Context, app *OAuthApp) (*OAuthApp, *Response, error) { - buf, err := json.Marshal(app) - if err != nil { - return nil, nil, NewAppError("CreateOAuthApp", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.oAuthAppsRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.oAuthAppsRoute(), app) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var oapp OAuthApp - if err := json.NewDecoder(r.Body).Decode(&oapp); err != nil { - return nil, nil, NewAppError("CreateOAuthApp", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &oapp, BuildResponse(r), nil + return DecodeJSONFromResponse[*OAuthApp](r) } // UpdateOAuthApp updates a page of registered OAuth 2.0 client applications with Mattermost acting as an OAuth 2.0 service provider. func (c *Client4) UpdateOAuthApp(ctx context.Context, app *OAuthApp) (*OAuthApp, *Response, error) { - buf, err := json.Marshal(app) - if err != nil { - return nil, nil, NewAppError("UpdateOAuthApp", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.oAuthAppRoute(app.Id), buf) + r, err := c.DoAPIPutJSON(ctx, c.oAuthAppRoute(app.Id), app) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var oapp OAuthApp - if err := json.NewDecoder(r.Body).Decode(&oapp); err != nil { - return nil, nil, NewAppError("UpdateOAuthApp", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &oapp, BuildResponse(r), nil + return DecodeJSONFromResponse[*OAuthApp](r) } // GetOAuthApps gets a page of registered OAuth 2.0 client applications with Mattermost acting as an OAuth 2.0 service provider. func (c *Client4) GetOAuthApps(ctx context.Context, page, perPage int) ([]*OAuthApp, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.oAuthAppsRoute()+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.oAuthAppsRoute()+"?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*OAuthApp - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetOAuthApps", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*OAuthApp](r) } // GetOAuthApp gets a registered OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider. @@ -6335,11 +4983,7 @@ func (c *Client4) GetOAuthApp(ctx context.Context, appId string) (*OAuthApp, *Re return nil, BuildResponse(r), err } defer closeBody(r) - var oapp OAuthApp - if err := json.NewDecoder(r.Body).Decode(&oapp); err != nil { - return nil, nil, NewAppError("GetOAuthApp", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &oapp, BuildResponse(r), nil + return DecodeJSONFromResponse[*OAuthApp](r) } // GetOAuthAppInfo gets a sanitized version of a registered OAuth 2.0 client application with Mattermost acting as an OAuth 2.0 service provider. @@ -6349,11 +4993,7 @@ func (c *Client4) GetOAuthAppInfo(ctx context.Context, appId string) (*OAuthApp, return nil, BuildResponse(r), err } defer closeBody(r) - var oapp OAuthApp - if err := json.NewDecoder(r.Body).Decode(&oapp); err != nil { - return nil, nil, NewAppError("GetOAuthAppInfo", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &oapp, BuildResponse(r), nil + return DecodeJSONFromResponse[*OAuthApp](r) } // DeleteOAuthApp deletes a registered OAuth 2.0 client application. @@ -6373,46 +5013,51 @@ func (c *Client4) RegenerateOAuthAppSecret(ctx context.Context, appId string) (* return nil, BuildResponse(r), err } defer closeBody(r) - var oapp OAuthApp - if err := json.NewDecoder(r.Body).Decode(&oapp); err != nil { - return nil, nil, NewAppError("RegenerateOAuthAppSecret", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &oapp, BuildResponse(r), nil + return DecodeJSONFromResponse[*OAuthApp](r) } // GetAuthorizedOAuthAppsForUser gets a page of OAuth 2.0 client applications the user has authorized to use access their account. func (c *Client4) GetAuthorizedOAuthAppsForUser(ctx context.Context, userId string, page, perPage int) ([]*OAuthApp, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/oauth/apps/authorized"+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.userRoute(userId)+"/oauth/apps/authorized?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*OAuthApp - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetAuthorizedOAuthAppsForUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*OAuthApp](r) } // AuthorizeOAuthApp will authorize an OAuth 2.0 client application to access a user's account and provide a redirect link to follow. func (c *Client4) AuthorizeOAuthApp(ctx context.Context, authRequest *AuthorizeRequest) (string, *Response, error) { buf, err := json.Marshal(authRequest) if err != nil { - return "", BuildResponse(nil), NewAppError("AuthorizeOAuthApp", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + return "", nil, err } - r, err := c.DoAPIRequestBytes(ctx, http.MethodPost, c.URL+"/oauth/authorize", buf, "") + // The request doesn't go to the /api/v4 subpath, so we can't use the usual helper methods + r, err := c.doAPIRequestBytes(ctx, http.MethodPost, c.URL+"/oauth/authorize", buf, "") if err != nil { return "", BuildResponse(r), err } defer closeBody(r) - return MapFromJSON(r.Body)["redirect"], BuildResponse(r), nil + + result, resp, err := DecodeJSONFromResponse[map[string]string](r) + if err != nil { + return "", resp, err + } + return result["redirect"], resp, nil } // DeauthorizeOAuthApp will deauthorize an OAuth 2.0 client application from accessing a user's account. func (c *Client4) DeauthorizeOAuthApp(ctx context.Context, appId string) (*Response, error) { requestData := map[string]string{"client_id": appId} - r, err := c.DoAPIRequest(ctx, http.MethodPost, c.URL+"/oauth/deauthorize", MapToJSON(requestData), "") + buf, err := json.Marshal(requestData) + if err != nil { + return nil, err + } + // The request doesn't go to the /api/v4 subpath, so we can't use the usual helper methods + r, err := c.doAPIRequestBytes(ctx, http.MethodPost, c.URL+"/oauth/deauthorize", buf, "") if err != nil { return BuildResponse(r), err } @@ -6422,34 +5067,13 @@ func (c *Client4) DeauthorizeOAuthApp(ctx context.Context, appId string) (*Respo // GetOAuthAccessToken is a test helper function for the OAuth access token endpoint. func (c *Client4) GetOAuthAccessToken(ctx context.Context, data url.Values) (*AccessResponse, *Response, error) { - url := c.URL + "/oauth/access_token" - rq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(data.Encode())) + r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.URL+"/oauth/access_token", "application/x-www-form-urlencoded", strings.NewReader(data.Encode()), nil) if err != nil { - return nil, nil, err + return nil, BuildResponse(r), err } - rq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + defer closeBody(r) - if c.AuthToken != "" { - rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken) - } - - rp, err := c.HTTPClient.Do(rq) - if err != nil { - return nil, BuildResponse(rp), err - } - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body) - } - - var ar *AccessResponse - err = json.NewDecoder(rp.Body).Decode(&ar) - if err != nil { - return nil, BuildResponse(rp), NewAppError(url, "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return ar, BuildResponse(rp), nil + return DecodeJSONFromResponse[*AccessResponse](r) } // OutgoingOAuthConnection section @@ -6461,11 +5085,7 @@ func (c *Client4) GetOutgoingOAuthConnections(ctx context.Context, filters Outgo return nil, BuildResponse(r), err } defer closeBody(r) - var connections []*OutgoingOAuthConnection - if err := json.NewDecoder(r.Body).Decode(&connections); err != nil { - return nil, nil, NewAppError("GetOutgoingOAuthConnections", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return connections, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*OutgoingOAuthConnection](r) } // GetOutgoingOAuthConnection retrieves the outgoing OAuth connection with the given ID. @@ -6475,11 +5095,7 @@ func (c *Client4) GetOutgoingOAuthConnection(ctx context.Context, id string) (*O return nil, BuildResponse(r), err } defer closeBody(r) - var connection *OutgoingOAuthConnection - if err := json.NewDecoder(r.Body).Decode(&connection); err != nil { - return nil, nil, NewAppError("GetOutgoingOAuthConnection", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return connection, BuildResponse(r), nil + return DecodeJSONFromResponse[*OutgoingOAuthConnection](r) } // DeleteOutgoingOAuthConnection deletes the outgoing OAuth connection with the given ID. @@ -6494,39 +5110,22 @@ func (c *Client4) DeleteOutgoingOAuthConnection(ctx context.Context, id string) // UpdateOutgoingOAuthConnection updates the outgoing OAuth connection with the given ID. func (c *Client4) UpdateOutgoingOAuthConnection(ctx context.Context, connection *OutgoingOAuthConnection) (*OutgoingOAuthConnection, *Response, error) { - buf, err := json.Marshal(connection) - if err != nil { - return nil, nil, NewAppError("UpdateOutgoingOAuthConnection", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.outgoingOAuthConnectionRoute(connection.Id), buf) + r, err := c.DoAPIPutJSON(ctx, c.outgoingOAuthConnectionRoute(connection.Id), connection) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var resultConnection OutgoingOAuthConnection - if err := json.NewDecoder(r.Body).Decode(&resultConnection); err != nil { - return nil, nil, NewAppError("UpdateOutgoingOAuthConnection", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &resultConnection, BuildResponse(r), nil + return DecodeJSONFromResponse[*OutgoingOAuthConnection](r) } // CreateOutgoingOAuthConnection creates a new outgoing OAuth connection. func (c *Client4) CreateOutgoingOAuthConnection(ctx context.Context, connection *OutgoingOAuthConnection) (*OutgoingOAuthConnection, *Response, error) { - buf, err := json.Marshal(connection) - if err != nil { - return nil, nil, NewAppError("CreateOutgoingOAuthConnection", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.outgoingOAuthConnectionsRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.outgoingOAuthConnectionsRoute(), connection) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var resultConnection OutgoingOAuthConnection - if err := json.NewDecoder(r.Body).Decode(&resultConnection); err != nil { - return nil, nil, NewAppError("CreateOutgoingOAuthConnection", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &resultConnection, BuildResponse(r), nil + return DecodeJSONFromResponse[*OutgoingOAuthConnection](r) } // Elasticsearch Section @@ -6561,11 +5160,7 @@ func (c *Client4) GetDataRetentionPolicy(ctx context.Context) (*GlobalRetentionP return nil, BuildResponse(r), err } defer closeBody(r) - var p GlobalRetentionPolicy - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("GetDataRetentionPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*GlobalRetentionPolicy](r) } // GetDataRetentionPolicyByID will get the details for the granular data retention policy with the specified ID. @@ -6575,12 +5170,7 @@ func (c *Client4) GetDataRetentionPolicyByID(ctx context.Context, policyID strin return nil, BuildResponse(r), err } defer closeBody(r) - - var p RetentionPolicyWithTeamAndChannelCounts - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("GetDataRetentionPolicyByID", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*RetentionPolicyWithTeamAndChannelCounts](r) } // GetDataRetentionPoliciesCount will get the total number of granular data retention policies. @@ -6592,47 +5182,35 @@ func (c *Client4) GetDataRetentionPoliciesCount(ctx context.Context) (int64, *Re if err != nil { return 0, BuildResponse(r), err } - var countObj CountBody - err = json.NewDecoder(r.Body).Decode(&countObj) + countObj, resp, err := DecodeJSONFromResponse[CountBody](r) if err != nil { - return 0, nil, NewAppError("Client4.GetDataRetentionPoliciesCount", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) + return 0, resp, err } - return countObj.TotalCount, BuildResponse(r), nil + return countObj.TotalCount, resp, nil } // GetDataRetentionPolicies will get the current granular data retention policies' details. func (c *Client4) GetDataRetentionPolicies(ctx context.Context, page, perPage int) (*RetentionPolicyWithTeamAndChannelCountsList, *Response, error) { - query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage) - r, err := c.DoAPIGet(ctx, c.dataRetentionRoute()+"/policies"+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.dataRetentionRoute()+"/policies?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var p RetentionPolicyWithTeamAndChannelCountsList - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("GetDataRetentionPolicies", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*RetentionPolicyWithTeamAndChannelCountsList](r) } // CreateDataRetentionPolicy will create a new granular data retention policy which will be applied to // the specified teams and channels. The Id field of `policy` must be empty. func (c *Client4) CreateDataRetentionPolicy(ctx context.Context, policy *RetentionPolicyWithTeamAndChannelIDs) (*RetentionPolicyWithTeamAndChannelCounts, *Response, error) { - policyJSON, err := json.Marshal(policy) - if err != nil { - return nil, nil, NewAppError("CreateDataRetentionPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.dataRetentionRoute()+"/policies", policyJSON) + r, err := c.DoAPIPostJSON(ctx, c.dataRetentionRoute()+"/policies", policy) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var p RetentionPolicyWithTeamAndChannelCounts - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("CreateDataRetentionPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*RetentionPolicyWithTeamAndChannelCounts](r) } // DeleteDataRetentionPolicy will delete the granular data retention policy with the specified ID. @@ -6648,63 +5226,39 @@ func (c *Client4) DeleteDataRetentionPolicy(ctx context.Context, policyID string // PatchDataRetentionPolicy will patch the granular data retention policy with the specified ID. // The Id field of `patch` must be non-empty. func (c *Client4) PatchDataRetentionPolicy(ctx context.Context, patch *RetentionPolicyWithTeamAndChannelIDs) (*RetentionPolicyWithTeamAndChannelCounts, *Response, error) { - patchJSON, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchDataRetentionPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPatchBytes(ctx, c.dataRetentionPolicyRoute(patch.ID), patchJSON) + r, err := c.DoAPIPatchJSON(ctx, c.dataRetentionPolicyRoute(patch.ID), patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var p RetentionPolicyWithTeamAndChannelCounts - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("PatchDataRetentionPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*RetentionPolicyWithTeamAndChannelCounts](r) } // GetTeamsForRetentionPolicy will get the teams to which the specified policy is currently applied. func (c *Client4) GetTeamsForRetentionPolicy(ctx context.Context, policyID string, page, perPage int) (*TeamsWithCount, *Response, error) { - query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage) - r, err := c.DoAPIGet(ctx, c.dataRetentionPolicyRoute(policyID)+"/teams"+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.dataRetentionPolicyRoute(policyID)+"/teams?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } - var teams *TeamsWithCount - err = json.NewDecoder(r.Body).Decode(&teams) - if err != nil { - return nil, BuildResponse(r), NewAppError("Client4.GetTeamsForRetentionPolicy", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) - } - return teams, BuildResponse(r), nil + return DecodeJSONFromResponse[*TeamsWithCount](r) } // SearchTeamsForRetentionPolicy will search the teams to which the specified policy is currently applied. func (c *Client4) SearchTeamsForRetentionPolicy(ctx context.Context, policyID string, term string) ([]*Team, *Response, error) { - body, err := json.Marshal(map[string]any{"term": term}) - if err != nil { - return nil, nil, NewAppError("SearchTeamsForRetentionPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.dataRetentionPolicyRoute(policyID)+"/teams/search", body) + r, err := c.DoAPIPostJSON(ctx, c.dataRetentionPolicyRoute(policyID)+"/teams/search", map[string]any{"term": term}) if err != nil { return nil, BuildResponse(r), err } - var teams []*Team - err = json.NewDecoder(r.Body).Decode(&teams) - if err != nil { - return nil, BuildResponse(r), NewAppError("Client4.SearchTeamsForRetentionPolicy", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) - } - return teams, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Team](r) } // AddTeamsToRetentionPolicy will add the specified teams to the granular data retention policy // with the specified ID. func (c *Client4) AddTeamsToRetentionPolicy(ctx context.Context, policyID string, teamIDs []string) (*Response, error) { - body, err := json.Marshal(teamIDs) - if err != nil { - return nil, NewAppError("AddTeamsToRetentionPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.dataRetentionPolicyRoute(policyID)+"/teams", body) + r, err := c.DoAPIPostJSON(ctx, c.dataRetentionPolicyRoute(policyID)+"/teams", teamIDs) if err != nil { return BuildResponse(r), err } @@ -6715,11 +5269,7 @@ func (c *Client4) AddTeamsToRetentionPolicy(ctx context.Context, policyID string // RemoveTeamsFromRetentionPolicy will remove the specified teams from the granular data retention policy // with the specified ID. func (c *Client4) RemoveTeamsFromRetentionPolicy(ctx context.Context, policyID string, teamIDs []string) (*Response, error) { - body, err := json.Marshal(teamIDs) - if err != nil { - return nil, NewAppError("RemoveTeamsFromRetentionPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIDeleteBytes(ctx, c.dataRetentionPolicyRoute(policyID)+"/teams", body) + r, err := c.DoAPIDeleteJSON(ctx, c.dataRetentionPolicyRoute(policyID)+"/teams", teamIDs) if err != nil { return BuildResponse(r), err } @@ -6729,45 +5279,29 @@ func (c *Client4) RemoveTeamsFromRetentionPolicy(ctx context.Context, policyID s // GetChannelsForRetentionPolicy will get the channels to which the specified policy is currently applied. func (c *Client4) GetChannelsForRetentionPolicy(ctx context.Context, policyID string, page, perPage int) (*ChannelsWithCount, *Response, error) { - query := fmt.Sprintf("?page=%d&per_page=%d", page, perPage) - r, err := c.DoAPIGet(ctx, c.dataRetentionPolicyRoute(policyID)+"/channels"+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.dataRetentionPolicyRoute(policyID)+"/channels?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } - var channels *ChannelsWithCount - err = json.NewDecoder(r.Body).Decode(&channels) - if err != nil { - return nil, BuildResponse(r), NewAppError("Client4.GetChannelsForRetentionPolicy", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) - } - return channels, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelsWithCount](r) } // SearchChannelsForRetentionPolicy will search the channels to which the specified policy is currently applied. func (c *Client4) SearchChannelsForRetentionPolicy(ctx context.Context, policyID string, term string) (ChannelListWithTeamData, *Response, error) { - body, err := json.Marshal(map[string]any{"term": term}) - if err != nil { - return nil, nil, NewAppError("SearchChannelsForRetentionPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.dataRetentionPolicyRoute(policyID)+"/channels/search", body) + r, err := c.DoAPIPostJSON(ctx, c.dataRetentionPolicyRoute(policyID)+"/channels/search", map[string]any{"term": term}) if err != nil { return nil, BuildResponse(r), err } - var channels ChannelListWithTeamData - err = json.NewDecoder(r.Body).Decode(&channels) - if err != nil { - return nil, BuildResponse(r), NewAppError("Client4.SearchChannelsForRetentionPolicy", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) - } - return channels, BuildResponse(r), nil + return DecodeJSONFromResponse[ChannelListWithTeamData](r) } // AddChannelsToRetentionPolicy will add the specified channels to the granular data retention policy // with the specified ID. func (c *Client4) AddChannelsToRetentionPolicy(ctx context.Context, policyID string, channelIDs []string) (*Response, error) { - body, err := json.Marshal(channelIDs) - if err != nil { - return nil, NewAppError("AddChannelsToRetentionPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.dataRetentionPolicyRoute(policyID)+"/channels", body) + r, err := c.DoAPIPostJSON(ctx, c.dataRetentionPolicyRoute(policyID)+"/channels", channelIDs) if err != nil { return BuildResponse(r), err } @@ -6778,11 +5312,7 @@ func (c *Client4) AddChannelsToRetentionPolicy(ctx context.Context, policyID str // RemoveChannelsFromRetentionPolicy will remove the specified channels from the granular data retention policy // with the specified ID. func (c *Client4) RemoveChannelsFromRetentionPolicy(ctx context.Context, policyID string, channelIDs []string) (*Response, error) { - body, err := json.Marshal(channelIDs) - if err != nil { - return nil, NewAppError("RemoveChannelsFromRetentionPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIDeleteBytes(ctx, c.dataRetentionPolicyRoute(policyID)+"/channels", body) + r, err := c.DoAPIDeleteJSON(ctx, c.dataRetentionPolicyRoute(policyID)+"/channels", channelIDs) if err != nil { return BuildResponse(r), err } @@ -6796,12 +5326,7 @@ func (c *Client4) GetTeamPoliciesForUser(ctx context.Context, userID string, off if err != nil { return nil, BuildResponse(r), err } - var teams RetentionPolicyForTeamList - err = json.NewDecoder(r.Body).Decode(&teams) - if err != nil { - return nil, BuildResponse(r), NewAppError("Client4.GetTeamPoliciesForUser", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) - } - return &teams, BuildResponse(r), nil + return DecodeJSONFromResponse[*RetentionPolicyForTeamList](r) } // GetChannelPoliciesForUser will get the data retention policies for the channels to which a user belongs. @@ -6810,35 +5335,19 @@ func (c *Client4) GetChannelPoliciesForUser(ctx context.Context, userID string, if err != nil { return nil, BuildResponse(r), err } - var channels RetentionPolicyForChannelList - err = json.NewDecoder(r.Body).Decode(&channels) - if err != nil { - return nil, BuildResponse(r), NewAppError("Client4.GetChannelPoliciesForUser", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) - } - return &channels, BuildResponse(r), nil + return DecodeJSONFromResponse[*RetentionPolicyForChannelList](r) } // Drafts Sections // UpsertDraft will create a new draft or update a draft if it already exists func (c *Client4) UpsertDraft(ctx context.Context, draft *Draft) (*Draft, *Response, error) { - buf, err := json.Marshal(draft) - if err != nil { - return nil, nil, NewAppError("UpsertDraft", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPostBytes(ctx, c.draftsRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.draftsRoute(), draft) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var df Draft - err = json.NewDecoder(r.Body).Decode(&df) - if err != nil { - return nil, nil, NewAppError("UpsertDraft", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &df, BuildResponse(r), err + return DecodeJSONFromResponse[*Draft](r) } // GetDrafts will get all drafts for a user @@ -6848,12 +5357,7 @@ func (c *Client4) GetDrafts(ctx context.Context, userId, teamId string) ([]*Draf return nil, BuildResponse(r), err } defer closeBody(r) - var drafts []*Draft - err = json.NewDecoder(r.Body).Decode(&drafts) - if err != nil { - return nil, nil, NewAppError("GetDrafts", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return drafts, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Draft](r) } func (c *Client4) DeleteDraft(ctx context.Context, userId, channelId, rootId string) (*Draft, *Response, error) { @@ -6862,62 +5366,35 @@ func (c *Client4) DeleteDraft(ctx context.Context, userId, channelId, rootId str return nil, BuildResponse(r), err } defer closeBody(r) - - var df *Draft - err = json.NewDecoder(r.Body).Decode(&df) - if err != nil { - return nil, BuildResponse(r), NewAppError("DeleteDraft", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return df, BuildResponse(r), nil + return DecodeJSONFromResponse[*Draft](r) } // Commands Section // CreateCommand will create a new command if the user have the right permissions. func (c *Client4) CreateCommand(ctx context.Context, cmd *Command) (*Command, *Response, error) { - buf, err := json.Marshal(cmd) - if err != nil { - return nil, nil, NewAppError("CreateCommand", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.commandsRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.commandsRoute(), cmd) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var command Command - if err := json.NewDecoder(r.Body).Decode(&command); err != nil { - return nil, nil, NewAppError("CreateCommand", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &command, BuildResponse(r), nil + return DecodeJSONFromResponse[*Command](r) } // UpdateCommand updates a command based on the provided Command struct. func (c *Client4) UpdateCommand(ctx context.Context, cmd *Command) (*Command, *Response, error) { - buf, err := json.Marshal(cmd) - if err != nil { - return nil, nil, NewAppError("UpdateCommand", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.commandRoute(cmd.Id), buf) + r, err := c.DoAPIPutJSON(ctx, c.commandRoute(cmd.Id), cmd) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var command Command - if err := json.NewDecoder(r.Body).Decode(&command); err != nil { - return nil, nil, NewAppError("UpdateCommand", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &command, BuildResponse(r), nil + return DecodeJSONFromResponse[*Command](r) } // MoveCommand moves a command to a different team. func (c *Client4) MoveCommand(ctx context.Context, teamId string, commandId string) (*Response, error) { cmr := CommandMoveRequest{TeamId: teamId} - buf, err := json.Marshal(cmr) - if err != nil { - return nil, NewAppError("MoveCommand", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.commandMoveRoute(commandId), buf) + r, err := c.DoAPIPutJSON(ctx, c.commandMoveRoute(commandId), cmr) if err != nil { return BuildResponse(r), err } @@ -6937,33 +5414,27 @@ func (c *Client4) DeleteCommand(ctx context.Context, commandId string) (*Respons // ListCommands will retrieve a list of commands available in the team. func (c *Client4) ListCommands(ctx context.Context, teamId string, customOnly bool) ([]*Command, *Response, error) { - query := fmt.Sprintf("?team_id=%v&custom_only=%v", teamId, customOnly) - r, err := c.DoAPIGet(ctx, c.commandsRoute()+query, "") + values := url.Values{} + values.Set("team_id", teamId) + values.Set("custom_only", c.boolString(customOnly)) + r, err := c.DoAPIGet(ctx, c.commandsRoute()+"?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var list []*Command - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("ListCommands", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Command](r) } // ListCommandAutocompleteSuggestions will retrieve a list of suggestions for a userInput. func (c *Client4) ListCommandAutocompleteSuggestions(ctx context.Context, userInput, teamId string) ([]AutocompleteSuggestion, *Response, error) { - query := fmt.Sprintf("/commands/autocomplete_suggestions?user_input=%v", userInput) - r, err := c.DoAPIGet(ctx, c.teamRoute(teamId)+query, "") + values := url.Values{} + values.Set("user_input", userInput) + r, err := c.DoAPIGet(ctx, c.teamRoute(teamId)+"/commands/autocomplete_suggestions?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []AutocompleteSuggestion - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("ListCommandAutocompleteSuggestions", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]AutocompleteSuggestion](r) } // GetCommandById will retrieve a command by id. @@ -6974,11 +5445,7 @@ func (c *Client4) GetCommandById(ctx context.Context, cmdId string) (*Command, * return nil, BuildResponse(r), err } defer closeBody(r) - var command Command - if err := json.NewDecoder(r.Body).Decode(&command); err != nil { - return nil, nil, NewAppError("GetCommandById", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &command, BuildResponse(r), nil + return DecodeJSONFromResponse[*Command](r) } // ExecuteCommand executes a given slash command. @@ -6987,11 +5454,7 @@ func (c *Client4) ExecuteCommand(ctx context.Context, channelId, command string) ChannelId: channelId, Command: command, } - buf, err := json.Marshal(commandArgs) - if err != nil { - return nil, nil, NewAppError("ExecuteCommand", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.commandsRoute()+"/execute", buf) + r, err := c.DoAPIPostJSON(ctx, c.commandsRoute()+"/execute", commandArgs) if err != nil { return nil, BuildResponse(r), err } @@ -6999,7 +5462,7 @@ func (c *Client4) ExecuteCommand(ctx context.Context, channelId, command string) response, err := CommandResponseFromJSON(r.Body) if err != nil { - return nil, BuildResponse(r), NewAppError("ExecuteCommand", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + return nil, BuildResponse(r), fmt.Errorf("failed to decode command response: %w", err) } return response, BuildResponse(r), nil } @@ -7012,11 +5475,7 @@ func (c *Client4) ExecuteCommandWithTeam(ctx context.Context, channelId, teamId, TeamId: teamId, Command: command, } - buf, err := json.Marshal(commandArgs) - if err != nil { - return nil, nil, NewAppError("ExecuteCommandWithTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.commandsRoute()+"/execute", buf) + r, err := c.DoAPIPostJSON(ctx, c.commandsRoute()+"/execute", commandArgs) if err != nil { return nil, BuildResponse(r), err } @@ -7024,7 +5483,7 @@ func (c *Client4) ExecuteCommandWithTeam(ctx context.Context, channelId, teamId, response, err := CommandResponseFromJSON(r.Body) if err != nil { - return nil, BuildResponse(r), NewAppError("ExecuteCommandWithTeam", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + return nil, BuildResponse(r), fmt.Errorf("failed to decode command response: %w", err) } return response, BuildResponse(r), nil } @@ -7036,11 +5495,7 @@ func (c *Client4) ListAutocompleteCommands(ctx context.Context, teamId string) ( return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Command - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("ListAutocompleteCommands", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Command](r) } // RegenCommandToken will create a new token if the user have the right permissions. @@ -7050,7 +5505,11 @@ func (c *Client4) RegenCommandToken(ctx context.Context, commandId string) (stri return "", BuildResponse(r), err } defer closeBody(r) - return MapFromJSON(r.Body)["token"], BuildResponse(r), nil + result, resp, err := DecodeJSONFromResponse[map[string]string](r) + if err != nil { + return "", resp, err + } + return result["token"], resp, nil } // Status Section @@ -7062,57 +5521,34 @@ func (c *Client4) GetUserStatus(ctx context.Context, userId, etag string) (*Stat return nil, BuildResponse(r), err } defer closeBody(r) - var s Status - if r.StatusCode == http.StatusNotModified { - return &s, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&s); err != nil { - return nil, nil, NewAppError("GetUserStatus", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &s, BuildResponse(r), nil + return DecodeJSONFromResponse[*Status](r) } // GetUsersStatusesByIds returns a list of users status based on the provided user ids. func (c *Client4) GetUsersStatusesByIds(ctx context.Context, userIds []string) ([]*Status, *Response, error) { - r, err := c.DoAPIPost(ctx, c.userStatusesRoute()+"/ids", ArrayToJSON(userIds)) + r, err := c.DoAPIPostJSON(ctx, c.userStatusesRoute()+"/ids", userIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Status - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsersStatusesByIds", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Status](r) } // UpdateUserStatus sets a user's status based on the provided user id string. func (c *Client4) UpdateUserStatus(ctx context.Context, userId string, userStatus *Status) (*Status, *Response, error) { - buf, err := json.Marshal(userStatus) - if err != nil { - return nil, nil, NewAppError("UpdateUserStatus", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.userStatusRoute(userId), buf) + r, err := c.DoAPIPutJSON(ctx, c.userStatusRoute(userId), userStatus) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var s Status - if err := json.NewDecoder(r.Body).Decode(&s); err != nil { - return nil, nil, NewAppError("UpdateUserStatus", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &s, BuildResponse(r), nil + return DecodeJSONFromResponse[*Status](r) } // UpdateUserCustomStatus sets a user's custom status based on the provided user id string. // The returned CustomStatus object is the same as the one passed, and it should be just // ignored. It's only kept to maintain compatibility. func (c *Client4) UpdateUserCustomStatus(ctx context.Context, userId string, userCustomStatus *CustomStatus) (*CustomStatus, *Response, error) { - buf, err := json.Marshal(userCustomStatus) - if err != nil { - return nil, nil, NewAppError("UpdateUserCustomStatus", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.userStatusRoute(userId)+"/custom", buf) + r, err := c.DoAPIPutJSON(ctx, c.userStatusRoute(userId)+"/custom", userCustomStatus) if err != nil { return nil, BuildResponse(r), err } @@ -7164,70 +5600,61 @@ func (c *Client4) CreateEmoji(ctx context.Context, emoji *Emoji, image []byte, f emojiJSON, err := json.Marshal(emoji) if err != nil { - return nil, nil, NewAppError("CreateEmoji", "api.marshal_error", nil, "", 0).Wrap(err) + return nil, nil, fmt.Errorf("failed to marshal emoji data: %w", err) } - if err := writer.WriteField("emoji", string(emojiJSON)); err != nil { + if err = writer.WriteField("emoji", string(emojiJSON)); err != nil { return nil, nil, err } - if err := writer.Close(); err != nil { + if err = writer.Close(); err != nil { return nil, nil, err } - return c.DoEmojiUploadFile(ctx, c.emojisRoute(), body.Bytes(), writer.FormDataContentType()) -} - -// GetEmojiList returns a page of custom emoji on the system. -func (c *Client4) GetEmojiList(ctx context.Context, page, perPage int) ([]*Emoji, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.emojisRoute()+query, "") + r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.APIURL+c.emojisRoute(), writer.FormDataContentType(), body, nil) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) + return DecodeJSONFromResponse[*Emoji](r) +} - var list []*Emoji - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetEmojiList", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) +// GetEmojiList returns a page of custom emoji on the system. +func (c *Client4) GetEmojiList(ctx context.Context, page, perPage int) ([]*Emoji, *Response, error) { + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.emojisRoute()+"?"+values.Encode(), "") + if err != nil { + return nil, BuildResponse(r), err } - return list, BuildResponse(r), nil + defer closeBody(r) + return DecodeJSONFromResponse[[]*Emoji](r) } // GetSortedEmojiList returns a page of custom emoji on the system sorted based on the sort // parameter, blank for no sorting and "name" to sort by emoji names. func (c *Client4) GetSortedEmojiList(ctx context.Context, page, perPage int, sort string) ([]*Emoji, *Response, error) { - query := fmt.Sprintf("?page=%v&per_page=%v&sort=%v", page, perPage, sort) - r, err := c.DoAPIGet(ctx, c.emojisRoute()+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("sort", sort) + r, err := c.DoAPIGet(ctx, c.emojisRoute()+"?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Emoji - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetSortedEmojiList", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Emoji](r) } // GetEmojisByNames takes an array of custom emoji names and returns an array of those emojis. func (c *Client4) GetEmojisByNames(ctx context.Context, names []string) ([]*Emoji, *Response, error) { - buf, err := json.Marshal(names) - if err != nil { - return nil, nil, NewAppError("GetEmojisByNames", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPostBytes(ctx, c.emojisRoute()+"/names", buf) + r, err := c.DoAPIPostJSON(ctx, c.emojisRoute()+"/names", names) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var list []*Emoji - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetEmojisByNames", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Emoji](r) } // DeleteEmoji delete an custom emoji on the provided emoji id string. @@ -7247,11 +5674,7 @@ func (c *Client4) GetEmoji(ctx context.Context, emojiId string) (*Emoji, *Respon return nil, BuildResponse(r), err } defer closeBody(r) - var e Emoji - if err := json.NewDecoder(r.Body).Decode(&e); err != nil { - return nil, nil, NewAppError("GetEmoji", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &e, BuildResponse(r), nil + return DecodeJSONFromResponse[*Emoji](r) } // GetEmojiByName returns a custom emoji based on the name string. @@ -7261,11 +5684,7 @@ func (c *Client4) GetEmojiByName(ctx context.Context, name string) (*Emoji, *Res return nil, BuildResponse(r), err } defer closeBody(r) - var e Emoji - if err := json.NewDecoder(r.Body).Decode(&e); err != nil { - return nil, nil, NewAppError("GetEmojiByName", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &e, BuildResponse(r), nil + return DecodeJSONFromResponse[*Emoji](r) } // GetEmojiImage returns the emoji image. @@ -7275,66 +5694,41 @@ func (c *Client4) GetEmojiImage(ctx context.Context, emojiId string) ([]byte, *R return nil, BuildResponse(r), err } defer closeBody(r) - - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetEmojiImage", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // SearchEmoji returns a list of emoji matching some search criteria. func (c *Client4) SearchEmoji(ctx context.Context, search *EmojiSearch) ([]*Emoji, *Response, error) { - buf, err := json.Marshal(search) - if err != nil { - return nil, nil, NewAppError("SearchEmoji", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.emojisRoute()+"/search", buf) + r, err := c.DoAPIPostJSON(ctx, c.emojisRoute()+"/search", search) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Emoji - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("SearchEmoji", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Emoji](r) } // AutocompleteEmoji returns a list of emoji starting with or matching name. func (c *Client4) AutocompleteEmoji(ctx context.Context, name string, etag string) ([]*Emoji, *Response, error) { - query := fmt.Sprintf("?name=%v", name) - r, err := c.DoAPIGet(ctx, c.emojisRoute()+"/autocomplete"+query, "") + values := url.Values{} + values.Set("name", name) + r, err := c.DoAPIGet(ctx, c.emojisRoute()+"/autocomplete?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Emoji - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("AutocompleteEmoji", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Emoji](r) } // Reaction Section // SaveReaction saves an emoji reaction for a post. Returns the saved reaction if successful, otherwise an error will be returned. func (c *Client4) SaveReaction(ctx context.Context, reaction *Reaction) (*Reaction, *Response, error) { - buf, err := json.Marshal(reaction) - if err != nil { - return nil, nil, NewAppError("SaveReaction", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.reactionsRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.reactionsRoute(), reaction) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var re Reaction - if err := json.NewDecoder(r.Body).Decode(&re); err != nil { - return nil, nil, NewAppError("SaveReaction", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &re, BuildResponse(r), nil + return DecodeJSONFromResponse[*Reaction](r) } // GetReactions returns a list of reactions to a post. @@ -7344,11 +5738,7 @@ func (c *Client4) GetReactions(ctx context.Context, postId string) ([]*Reaction, return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Reaction - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetReactions", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Reaction](r) } // DeleteReaction deletes reaction of a user in a post. @@ -7363,16 +5753,12 @@ func (c *Client4) DeleteReaction(ctx context.Context, reaction *Reaction) (*Resp // FetchBulkReactions returns a map of postIds and corresponding reactions func (c *Client4) GetBulkReactions(ctx context.Context, postIds []string) (map[string][]*Reaction, *Response, error) { - r, err := c.DoAPIPost(ctx, c.postsRoute()+"/ids/reactions", ArrayToJSON(postIds)) + r, err := c.DoAPIPostJSON(ctx, c.postsRoute()+"/ids/reactions", postIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - reactions := map[string][]*Reaction{} - if err := json.NewDecoder(r.Body).Decode(&reactions); err != nil { - return nil, nil, NewAppError("GetBulkReactions", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return reactions, BuildResponse(r), nil + return DecodeJSONFromResponse[map[string][]*Reaction](r) } // Timezone Section @@ -7384,9 +5770,7 @@ func (c *Client4) GetSupportedTimezone(ctx context.Context) ([]string, *Response return nil, BuildResponse(r), err } defer closeBody(r) - var timezones []string - json.NewDecoder(r.Body).Decode(&timezones) - return timezones, BuildResponse(r), nil + return DecodeJSONFromResponse[[]string](r) } // Jobs Section @@ -7398,57 +5782,45 @@ func (c *Client4) GetJob(ctx context.Context, id string) (*Job, *Response, error return nil, BuildResponse(r), err } defer closeBody(r) - var j Job - if err := json.NewDecoder(r.Body).Decode(&j); err != nil { - return nil, nil, NewAppError("GetJob", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &j, BuildResponse(r), nil + return DecodeJSONFromResponse[*Job](r) } // GetJobs gets all jobs, sorted with the job that was created most recently first. func (c *Client4) GetJobs(ctx context.Context, jobType string, status string, page int, perPage int) ([]*Job, *Response, error) { - r, err := c.DoAPIGet(ctx, c.jobsRoute()+fmt.Sprintf("?page=%v&per_page=%v&job_type=%v&status=%v", page, perPage, jobType, status), "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + values.Set("job_type", jobType) + values.Set("status", status) + r, err := c.DoAPIGet(ctx, c.jobsRoute()+"?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Job - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetJobs", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Job](r) } // GetJobsByType gets all jobs of a given type, sorted with the job that was created most recently first. func (c *Client4) GetJobsByType(ctx context.Context, jobType string, page int, perPage int) ([]*Job, *Response, error) { - r, err := c.DoAPIGet(ctx, c.jobsRoute()+fmt.Sprintf("/type/%v?page=%v&per_page=%v", jobType, page, perPage), "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.jobsRoute()+"/type/"+jobType+"?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Job - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetJobsByType", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Job](r) } // CreateJob creates a job based on the provided job struct. func (c *Client4) CreateJob(ctx context.Context, job *Job) (*Job, *Response, error) { - buf, err := json.Marshal(job) - if err != nil { - return nil, nil, NewAppError("CreateJob", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.jobsRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.jobsRoute(), job) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var j Job - if err := json.NewDecoder(r.Body).Decode(&j); err != nil { - return nil, nil, NewAppError("CreateJob", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &j, BuildResponse(r), nil + return DecodeJSONFromResponse[*Job](r) } // CancelJob requests the cancellation of the job with the provided Id. @@ -7468,24 +5840,16 @@ func (c *Client4) DownloadJob(ctx context.Context, jobId string) ([]byte, *Respo return nil, BuildResponse(r), err } defer closeBody(r) - - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetFile", "model.client.read_job_result_file.app_error", nil, "", r.StatusCode).Wrap(err) - } - return data, BuildResponse(r), nil + return ReadBytesFromResponse(r) } // UpdateJobStatus updates the status of a job func (c *Client4) UpdateJobStatus(ctx context.Context, jobId string, status string, force bool) (*Response, error) { - buf, err := json.Marshal(map[string]any{ + data := map[string]any{ "status": status, "force": force, - }) - if err != nil { - return nil, NewAppError("UpdateJobStatus", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) } - r, err := c.DoAPIPatchBytes(ctx, c.jobsRoute()+fmt.Sprintf("/%v/status", jobId), buf) + r, err := c.DoAPIPatchJSON(ctx, c.jobsRoute()+fmt.Sprintf("/%v/status", jobId), data) if err != nil { return BuildResponse(r), err } @@ -7502,11 +5866,7 @@ func (c *Client4) GetAllRoles(ctx context.Context) ([]*Role, *Response, error) { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Role - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetAllRoles", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Role](r) } // GetRole gets a single role by ID. @@ -7516,11 +5876,7 @@ func (c *Client4) GetRole(ctx context.Context, id string) (*Role, *Response, err return nil, BuildResponse(r), err } defer closeBody(r) - var role Role - if err := json.NewDecoder(r.Body).Decode(&role); err != nil { - return nil, nil, NewAppError("GetRole", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &role, BuildResponse(r), nil + return DecodeJSONFromResponse[*Role](r) } // GetRoleByName gets a single role by Name. @@ -7530,63 +5886,39 @@ func (c *Client4) GetRoleByName(ctx context.Context, name string) (*Role, *Respo return nil, BuildResponse(r), err } defer closeBody(r) - var role Role - if err := json.NewDecoder(r.Body).Decode(&role); err != nil { - return nil, nil, NewAppError("GetRoleByName", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &role, BuildResponse(r), nil + return DecodeJSONFromResponse[*Role](r) } // GetRolesByNames returns a list of roles based on the provided role names. func (c *Client4) GetRolesByNames(ctx context.Context, roleNames []string) ([]*Role, *Response, error) { - r, err := c.DoAPIPost(ctx, c.rolesRoute()+"/names", ArrayToJSON(roleNames)) + r, err := c.DoAPIPostJSON(ctx, c.rolesRoute()+"/names", roleNames) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Role - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetRolesByNames", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Role](r) } // PatchRole partially updates a role in the system. Any missing fields are not updated. func (c *Client4) PatchRole(ctx context.Context, roleId string, patch *RolePatch) (*Role, *Response, error) { - buf, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchRole", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.rolesRoute()+fmt.Sprintf("/%v/patch", roleId), buf) + r, err := c.DoAPIPutJSON(ctx, c.rolesRoute()+fmt.Sprintf("/%v/patch", roleId), patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var role Role - if err := json.NewDecoder(r.Body).Decode(&role); err != nil { - return nil, nil, NewAppError("PatchRole", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &role, BuildResponse(r), nil + return DecodeJSONFromResponse[*Role](r) } // Schemes Section // CreateScheme creates a new Scheme. func (c *Client4) CreateScheme(ctx context.Context, scheme *Scheme) (*Scheme, *Response, error) { - buf, err := json.Marshal(scheme) - if err != nil { - return nil, nil, NewAppError("CreateScheme", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.schemesRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.schemesRoute(), scheme) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var s Scheme - if err := json.NewDecoder(r.Body).Decode(&s); err != nil { - return nil, nil, NewAppError("CreateScheme", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &s, BuildResponse(r), nil + return DecodeJSONFromResponse[*Scheme](r) } // GetScheme gets a single scheme by ID. @@ -7596,25 +5928,21 @@ func (c *Client4) GetScheme(ctx context.Context, id string) (*Scheme, *Response, return nil, BuildResponse(r), err } defer closeBody(r) - var s Scheme - if err := json.NewDecoder(r.Body).Decode(&s); err != nil { - return nil, nil, NewAppError("GetScheme", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &s, BuildResponse(r), nil + return DecodeJSONFromResponse[*Scheme](r) } // GetSchemes ets all schemes, sorted with the most recently created first, optionally filtered by scope. func (c *Client4) GetSchemes(ctx context.Context, scope string, page int, perPage int) ([]*Scheme, *Response, error) { - r, err := c.DoAPIGet(ctx, c.schemesRoute()+fmt.Sprintf("?scope=%v&page=%v&per_page=%v", scope, page, perPage), "") + values := url.Values{} + values.Set("scope", scope) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.schemesRoute()+"?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Scheme - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetSchemes", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Scheme](r) } // DeleteScheme deletes a single scheme by ID. @@ -7629,50 +5957,38 @@ func (c *Client4) DeleteScheme(ctx context.Context, id string) (*Response, error // PatchScheme partially updates a scheme in the system. Any missing fields are not updated. func (c *Client4) PatchScheme(ctx context.Context, id string, patch *SchemePatch) (*Scheme, *Response, error) { - buf, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchScheme", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.schemeRoute(id)+"/patch", buf) + r, err := c.DoAPIPutJSON(ctx, c.schemeRoute(id)+"/patch", patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var s Scheme - if err := json.NewDecoder(r.Body).Decode(&s); err != nil { - return nil, nil, NewAppError("PatchScheme", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &s, BuildResponse(r), nil + return DecodeJSONFromResponse[*Scheme](r) } // GetTeamsForScheme gets the teams using this scheme, sorted alphabetically by display name. func (c *Client4) GetTeamsForScheme(ctx context.Context, schemeId string, page int, perPage int) ([]*Team, *Response, error) { - r, err := c.DoAPIGet(ctx, c.schemeRoute(schemeId)+fmt.Sprintf("/teams?page=%v&per_page=%v", page, perPage), "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.schemeRoute(schemeId)+"/teams?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*Team - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetTeamsForScheme", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Team](r) } // GetChannelsForScheme gets the channels using this scheme, sorted alphabetically by display name. func (c *Client4) GetChannelsForScheme(ctx context.Context, schemeId string, page int, perPage int) (ChannelList, *Response, error) { - r, err := c.DoAPIGet(ctx, c.schemeRoute(schemeId)+fmt.Sprintf("/channels?page=%v&per_page=%v", page, perPage), "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.schemeRoute(schemeId)+"/channels?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch ChannelList - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelsForScheme", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[ChannelList](r) } // Plugin Section @@ -7710,78 +6026,42 @@ func (c *Client4) uploadPlugin(ctx context.Context, file io.Reader, force bool) return nil, nil, err } - rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+c.pluginsRoute(), body) + r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.APIURL+c.pluginsRoute(), writer.FormDataContentType(), body, nil) if err != nil { - return nil, nil, err - } - rq.Header.Set("Content-Type", writer.FormDataContentType()) - - if c.AuthToken != "" { - rq.Header.Set(HeaderAuth, c.AuthType+" "+c.AuthToken) + return nil, BuildResponse(r), err } - rp, err := c.HTTPClient.Do(rq) - if err != nil { - return nil, BuildResponse(rp), err - } - defer closeBody(rp) - - if rp.StatusCode >= 300 { - return nil, BuildResponse(rp), AppErrorFromJSON(rp.Body) - } - - var m Manifest - if err := json.NewDecoder(rp.Body).Decode(&m); err != nil { - return nil, nil, NewAppError("uploadPlugin", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &m, BuildResponse(rp), nil + return DecodeJSONFromResponse[*Manifest](r) } func (c *Client4) InstallPluginFromURL(ctx context.Context, downloadURL string, force bool) (*Manifest, *Response, error) { - forceStr := c.boolString(force) - - url := fmt.Sprintf("%s?plugin_download_url=%s&force=%s", c.pluginsRoute()+"/install_from_url", url.QueryEscape(downloadURL), forceStr) + values := url.Values{} + values.Set("plugin_download_url", downloadURL) + values.Set("force", c.boolString(force)) + url := c.pluginsRoute() + "/install_from_url?" + values.Encode() r, err := c.DoAPIPost(ctx, url, "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var m Manifest - if err := json.NewDecoder(r.Body).Decode(&m); err != nil { - return nil, nil, NewAppError("InstallPluginFromUrl", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &m, BuildResponse(r), nil + return DecodeJSONFromResponse[*Manifest](r) } // InstallMarketplacePlugin will install marketplace plugin. func (c *Client4) InstallMarketplacePlugin(ctx context.Context, request *InstallMarketplacePluginRequest) (*Manifest, *Response, error) { - buf, err := json.Marshal(request) - if err != nil { - return nil, nil, NewAppError("InstallMarketplacePlugin", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.pluginsRoute()+"/marketplace", string(buf)) + r, err := c.DoAPIPostJSON(ctx, c.pluginsRoute()+"/marketplace", request) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var m Manifest - if err := json.NewDecoder(r.Body).Decode(&m); err != nil { - return nil, nil, NewAppError("InstallMarketplacePlugin", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &m, BuildResponse(r), nil + return DecodeJSONFromResponse[*Manifest](r) } // ReattachPlugin asks the server to reattach to a plugin launched by another process. // // Only available in local mode, and currently only used for testing. func (c *Client4) ReattachPlugin(ctx context.Context, request *PluginReattachRequest) (*Response, error) { - buf, err := json.Marshal(request) - if err != nil { - return nil, NewAppError("ReattachPlugin", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.pluginsRoute()+"/reattach", string(buf)) + r, err := c.DoAPIPostJSON(ctx, c.pluginsRoute()+"/reattach", request) if err != nil { return BuildResponse(r), err } @@ -7810,12 +6090,7 @@ func (c *Client4) GetPlugins(ctx context.Context) (*PluginsResponse, *Response, return nil, BuildResponse(r), err } defer closeBody(r) - - var resp PluginsResponse - if err := json.NewDecoder(r.Body).Decode(&resp); err != nil { - return nil, nil, NewAppError("GetPlugins", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &resp, BuildResponse(r), nil + return DecodeJSONFromResponse[*PluginsResponse](r) } // GetPluginStatuses will return the plugins installed on any server in the cluster, for reporting @@ -7826,11 +6101,7 @@ func (c *Client4) GetPluginStatuses(ctx context.Context) (PluginStatuses, *Respo return nil, BuildResponse(r), err } defer closeBody(r) - var list PluginStatuses - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetPluginStatuses", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[PluginStatuses](r) } // RemovePlugin will disable and delete a plugin. @@ -7850,12 +6121,7 @@ func (c *Client4) GetWebappPlugins(ctx context.Context) ([]*Manifest, *Response, return nil, BuildResponse(r), err } defer closeBody(r) - - var list []*Manifest - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetWebappPlugins", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Manifest](r) } // EnablePlugin will enable an plugin installed. @@ -7896,7 +6162,7 @@ func (c *Client4) GetMarketplacePlugins(ctx context.Context, filter *Marketplace plugins, err := MarketplacePluginsFromReader(r.Body) if err != nil { - return nil, BuildResponse(r), NewAppError(route, "model.client.parse_plugins.app_error", nil, "", http.StatusBadRequest).Wrap(err) + return nil, BuildResponse(r), fmt.Errorf("failed to parse marketplace plugins response: %w", err) } return plugins, BuildResponse(r), nil @@ -7905,11 +6171,7 @@ func (c *Client4) GetMarketplacePlugins(ctx context.Context, filter *Marketplace // UpdateChannelScheme will update a channel's scheme. func (c *Client4) UpdateChannelScheme(ctx context.Context, channelId, schemeId string) (*Response, error) { sip := &SchemeIDPatch{SchemeID: &schemeId} - buf, err := json.Marshal(sip) - if err != nil { - return nil, NewAppError("UpdateChannelScheme", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.channelSchemeRoute(channelId), buf) + r, err := c.DoAPIPutJSON(ctx, c.channelSchemeRoute(channelId), sip) if err != nil { return BuildResponse(r), err } @@ -7920,11 +6182,7 @@ func (c *Client4) UpdateChannelScheme(ctx context.Context, channelId, schemeId s // UpdateTeamScheme will update a team's scheme. func (c *Client4) UpdateTeamScheme(ctx context.Context, teamId, schemeId string) (*Response, error) { sip := &SchemeIDPatch{SchemeID: &schemeId} - buf, err := json.Marshal(sip) - if err != nil { - return nil, NewAppError("UpdateTeamScheme", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.teamSchemeRoute(teamId), buf) + r, err := c.DoAPIPutJSON(ctx, c.teamSchemeRoute(teamId), sip) if err != nil { return BuildResponse(r), err } @@ -7934,18 +6192,26 @@ func (c *Client4) UpdateTeamScheme(ctx context.Context, teamId, schemeId string) // GetRedirectLocation retrieves the value of the 'Location' header of an HTTP response for a given URL. func (c *Client4) GetRedirectLocation(ctx context.Context, urlParam, etag string) (string, *Response, error) { - url := fmt.Sprintf("%s?url=%s", c.redirectLocationRoute(), url.QueryEscape(urlParam)) + values := url.Values{} + values.Set("url", urlParam) + url := c.redirectLocationRoute() + "?" + values.Encode() r, err := c.DoAPIGet(ctx, url, etag) if err != nil { return "", BuildResponse(r), err } defer closeBody(r) - return MapFromJSON(r.Body)["location"], BuildResponse(r), nil + result, resp, err := DecodeJSONFromResponse[map[string]string](r) + if err != nil { + return "", resp, err + } + return result["location"], resp, nil } // SetServerBusy will mark the server as busy, which disables non-critical services for `secs` seconds. func (c *Client4) SetServerBusy(ctx context.Context, secs int) (*Response, error) { - url := fmt.Sprintf("%s?seconds=%d", c.serverBusyRoute(), secs) + values := url.Values{} + values.Set("seconds", strconv.Itoa(secs)) + url := c.serverBusyRoute() + "?" + values.Encode() r, err := c.DoAPIPost(ctx, url, "") if err != nil { return BuildResponse(r), err @@ -7972,19 +6238,14 @@ func (c *Client4) GetServerBusy(ctx context.Context) (*ServerBusyState, *Respons return nil, BuildResponse(r), err } defer closeBody(r) - - var sbs ServerBusyState - if err := json.NewDecoder(r.Body).Decode(&sbs); err != nil { - return nil, nil, NewAppError("GetServerBusy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &sbs, BuildResponse(r), nil + return DecodeJSONFromResponse[*ServerBusyState](r) } // RegisterTermsOfServiceAction saves action performed by a user against a specific terms of service. func (c *Client4) RegisterTermsOfServiceAction(ctx context.Context, userId, termsOfServiceId string, accepted bool) (*Response, error) { url := c.userTermsOfServiceRoute(userId) data := map[string]any{"termsOfServiceId": termsOfServiceId, "accepted": accepted} - r, err := c.DoAPIPost(ctx, url, StringInterfaceToJSON(data)) + r, err := c.DoAPIPostJSON(ctx, url, data) if err != nil { return BuildResponse(r), err } @@ -8000,11 +6261,7 @@ func (c *Client4) GetTermsOfService(ctx context.Context, etag string) (*TermsOfS return nil, BuildResponse(r), err } defer closeBody(r) - var tos TermsOfService - if err := json.NewDecoder(r.Body).Decode(&tos); err != nil { - return nil, nil, NewAppError("GetTermsOfService", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &tos, BuildResponse(r), nil + return DecodeJSONFromResponse[*TermsOfService](r) } // GetUserTermsOfService fetches user's latest terms of service action if the latest action was for acceptance. @@ -8015,27 +6272,19 @@ func (c *Client4) GetUserTermsOfService(ctx context.Context, userId, etag string return nil, BuildResponse(r), err } defer closeBody(r) - var u UserTermsOfService - if err := json.NewDecoder(r.Body).Decode(&u); err != nil { - return nil, nil, NewAppError("GetUserTermsOfService", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &u, BuildResponse(r), nil + return DecodeJSONFromResponse[*UserTermsOfService](r) } // CreateTermsOfService creates new terms of service. func (c *Client4) CreateTermsOfService(ctx context.Context, text, userId string) (*TermsOfService, *Response, error) { url := c.termsOfServiceRoute() data := map[string]any{"text": text} - r, err := c.DoAPIPost(ctx, url, StringInterfaceToJSON(data)) + r, err := c.DoAPIPostJSON(ctx, url, data) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var tos TermsOfService - if err := json.NewDecoder(r.Body).Decode(&tos); err != nil { - return nil, nil, NewAppError("CreateTermsOfService", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &tos, BuildResponse(r), nil + return DecodeJSONFromResponse[*TermsOfService](r) } func (c *Client4) GetGroup(ctx context.Context, groupID, etag string) (*Group, *Response, error) { @@ -8044,28 +6293,16 @@ func (c *Client4) GetGroup(ctx context.Context, groupID, etag string) (*Group, * return nil, BuildResponse(r), err } defer closeBody(r) - var g Group - if err := json.NewDecoder(r.Body).Decode(&g); err != nil { - return nil, nil, NewAppError("GetGroup", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &g, BuildResponse(r), nil + return DecodeJSONFromResponse[*Group](r) } func (c *Client4) CreateGroup(ctx context.Context, group *Group) (*Group, *Response, error) { - groupJSON, err := json.Marshal(group) - if err != nil { - return nil, nil, NewAppError("CreateGroup", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, "/groups", groupJSON) + r, err := c.DoAPIPostJSON(ctx, "/groups", group) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var p Group - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("CreateGroup", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*Group](r) } func (c *Client4) DeleteGroup(ctx context.Context, groupID string) (*Group, *Response, error) { @@ -8074,11 +6311,7 @@ func (c *Client4) DeleteGroup(ctx context.Context, groupID string) (*Group, *Res return nil, BuildResponse(r), err } defer closeBody(r) - var p Group - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("DeleteGroup", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*Group](r) } func (c *Client4) RestoreGroup(ctx context.Context, groupID string, etag string) (*Group, *Response, error) { @@ -8087,28 +6320,16 @@ func (c *Client4) RestoreGroup(ctx context.Context, groupID string, etag string) return nil, BuildResponse(r), err } defer closeBody(r) - var p Group - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("DeleteGroup", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*Group](r) } func (c *Client4) PatchGroup(ctx context.Context, groupID string, patch *GroupPatch) (*Group, *Response, error) { - payload, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchGroup", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPut(ctx, c.groupRoute(groupID)+"/patch", string(payload)) + r, err := c.DoAPIPutJSON(ctx, c.groupRoute(groupID)+"/patch", patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var g Group - if err := json.NewDecoder(r.Body).Decode(&g); err != nil { - return nil, nil, NewAppError("PatchGroup", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &g, BuildResponse(r), nil + return DecodeJSONFromResponse[*Group](r) } func (c *Client4) GetGroupMembers(ctx context.Context, groupID string) (*GroupMemberList, *Response, error) { @@ -8117,63 +6338,35 @@ func (c *Client4) GetGroupMembers(ctx context.Context, groupID string) (*GroupMe return nil, BuildResponse(r), err } defer closeBody(r) - var ml GroupMemberList - if err := json.NewDecoder(r.Body).Decode(&ml); err != nil { - return nil, nil, NewAppError("UpsertGroupMembers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &ml, BuildResponse(r), nil + return DecodeJSONFromResponse[*GroupMemberList](r) } func (c *Client4) UpsertGroupMembers(ctx context.Context, groupID string, userIds *GroupModifyMembers) ([]*GroupMember, *Response, error) { - payload, err := json.Marshal(userIds) - if err != nil { - return nil, nil, NewAppError("UpsertGroupMembers", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.groupRoute(groupID)+"/members", payload) + r, err := c.DoAPIPostJSON(ctx, c.groupRoute(groupID)+"/members", userIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var g []*GroupMember - if err := json.NewDecoder(r.Body).Decode(&g); err != nil { - return nil, nil, NewAppError("UpsertGroupMembers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return g, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*GroupMember](r) } func (c *Client4) DeleteGroupMembers(ctx context.Context, groupID string, userIds *GroupModifyMembers) ([]*GroupMember, *Response, error) { - payload, err := json.Marshal(userIds) - if err != nil { - return nil, nil, NewAppError("DeleteGroupMembers", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIDeleteBytes(ctx, c.groupRoute(groupID)+"/members", payload) + r, err := c.DoAPIDeleteJSON(ctx, c.groupRoute(groupID)+"/members", userIds) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var g []*GroupMember - if err := json.NewDecoder(r.Body).Decode(&g); err != nil { - return nil, nil, NewAppError("DeleteGroupMembers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return g, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*GroupMember](r) } func (c *Client4) LinkGroupSyncable(ctx context.Context, groupID, syncableID string, syncableType GroupSyncableType, patch *GroupSyncablePatch) (*GroupSyncable, *Response, error) { - payload, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("LinkGroupSyncable", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } url := fmt.Sprintf("%s/link", c.groupSyncableRoute(groupID, syncableID, syncableType)) - r, err := c.DoAPIPost(ctx, url, string(payload)) + r, err := c.DoAPIPostJSON(ctx, url, patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var gs GroupSyncable - if err := json.NewDecoder(r.Body).Decode(&gs); err != nil { - return nil, nil, NewAppError("LinkGroupSyncable", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &gs, BuildResponse(r), nil + return DecodeJSONFromResponse[*GroupSyncable](r) } func (c *Client4) UnlinkGroupSyncable(ctx context.Context, groupID, syncableID string, syncableType GroupSyncableType) (*Response, error) { @@ -8192,11 +6385,7 @@ func (c *Client4) GetGroupSyncable(ctx context.Context, groupID, syncableID stri return nil, BuildResponse(r), err } defer closeBody(r) - var gs GroupSyncable - if err := json.NewDecoder(r.Body).Decode(&gs); err != nil { - return nil, nil, NewAppError("GetGroupSyncable", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &gs, BuildResponse(r), nil + return DecodeJSONFromResponse[*GroupSyncable](r) } func (c *Client4) GetGroupSyncables(ctx context.Context, groupID string, syncableType GroupSyncableType, etag string) ([]*GroupSyncable, *Response, error) { @@ -8205,75 +6394,62 @@ func (c *Client4) GetGroupSyncables(ctx context.Context, groupID string, syncabl return nil, BuildResponse(r), err } defer closeBody(r) - var list []*GroupSyncable - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetGroupSyncables", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*GroupSyncable](r) } func (c *Client4) PatchGroupSyncable(ctx context.Context, groupID, syncableID string, syncableType GroupSyncableType, patch *GroupSyncablePatch) (*GroupSyncable, *Response, error) { - payload, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchGroupSyncable", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPut(ctx, c.groupSyncableRoute(groupID, syncableID, syncableType)+"/patch", string(payload)) + r, err := c.DoAPIPutJSON(ctx, c.groupSyncableRoute(groupID, syncableID, syncableType)+"/patch", patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var gs GroupSyncable - if err := json.NewDecoder(r.Body).Decode(&gs); err != nil { - return nil, nil, NewAppError("PatchGroupSyncable", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &gs, BuildResponse(r), nil + return DecodeJSONFromResponse[*GroupSyncable](r) } func (c *Client4) TeamMembersMinusGroupMembers(ctx context.Context, teamID string, groupIDs []string, page, perPage int, etag string) ([]*UserWithGroups, int64, *Response, error) { groupIDStr := strings.Join(groupIDs, ",") - query := fmt.Sprintf("?group_ids=%s&page=%d&per_page=%d", groupIDStr, page, perPage) - r, err := c.DoAPIGet(ctx, c.teamRoute(teamID)+"/members_minus_group_members"+query, etag) + values := url.Values{} + values.Set("group_ids", groupIDStr) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.teamRoute(teamID)+"/members_minus_group_members?"+values.Encode(), etag) if err != nil { return nil, 0, BuildResponse(r), err } defer closeBody(r) - var ugc UsersWithGroupsAndCount - if err := json.NewDecoder(r.Body).Decode(&ugc); err != nil { - return nil, 0, nil, NewAppError("TeamMembersMinusGroupMembers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + ugc, resp, err := DecodeJSONFromResponse[UsersWithGroupsAndCount](r) + if err != nil { + return nil, 0, nil, err } - return ugc.Users, ugc.Count, BuildResponse(r), nil + return ugc.Users, ugc.Count, resp, nil } func (c *Client4) ChannelMembersMinusGroupMembers(ctx context.Context, channelID string, groupIDs []string, page, perPage int, etag string) ([]*UserWithGroups, int64, *Response, error) { groupIDStr := strings.Join(groupIDs, ",") - query := fmt.Sprintf("?group_ids=%s&page=%d&per_page=%d", groupIDStr, page, perPage) - r, err := c.DoAPIGet(ctx, c.channelRoute(channelID)+"/members_minus_group_members"+query, etag) + values := url.Values{} + values.Set("group_ids", groupIDStr) + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.channelRoute(channelID)+"/members_minus_group_members?"+values.Encode(), etag) if err != nil { return nil, 0, BuildResponse(r), err } defer closeBody(r) - var ugc UsersWithGroupsAndCount - if err := json.NewDecoder(r.Body).Decode(&ugc); err != nil { - return nil, 0, nil, NewAppError("ChannelMembersMinusGroupMembers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) + ugc, resp, err := DecodeJSONFromResponse[UsersWithGroupsAndCount](r) + if err != nil { + return nil, 0, nil, err } - return ugc.Users, ugc.Count, BuildResponse(r), nil + return ugc.Users, ugc.Count, resp, nil } func (c *Client4) PatchConfig(ctx context.Context, config *Config) (*Config, *Response, error) { - buf, err := json.Marshal(config) - if err != nil { - return nil, nil, NewAppError("PatchConfig", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.configRoute()+"/patch", buf) + r, err := c.DoAPIPutJSON(ctx, c.configRoute()+"/patch", config) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var cfg *Config - d := json.NewDecoder(r.Body) - return cfg, BuildResponse(r), d.Decode(&cfg) + return DecodeJSONFromResponse[*Config](r) } func (c *Client4) GetChannelModerations(ctx context.Context, channelID string, etag string) ([]*ChannelModeration, *Response, error) { @@ -8282,33 +6458,16 @@ func (c *Client4) GetChannelModerations(ctx context.Context, channelID string, e return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*ChannelModeration - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelModerations", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*ChannelModeration](r) } func (c *Client4) PatchChannelModerations(ctx context.Context, channelID string, patch []*ChannelModerationPatch) ([]*ChannelModeration, *Response, error) { - payload, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchChannelModerations", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPut(ctx, c.channelRoute(channelID)+"/moderations/patch", string(payload)) + r, err := c.DoAPIPutJSON(ctx, c.channelRoute(channelID)+"/moderations/patch", patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*ChannelModeration - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("PatchChannelModerations", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*ChannelModeration](r) } func (c *Client4) GetKnownUsers(ctx context.Context) ([]string, *Response, error) { @@ -8317,18 +6476,12 @@ func (c *Client4) GetKnownUsers(ctx context.Context) ([]string, *Response, error return nil, BuildResponse(r), err } defer closeBody(r) - var userIds []string - json.NewDecoder(r.Body).Decode(&userIds) - return userIds, BuildResponse(r), nil + return DecodeJSONFromResponse[[]string](r) } // PublishUserTyping publishes a user is typing websocket event based on the provided TypingRequest. func (c *Client4) PublishUserTyping(ctx context.Context, userID string, typingRequest TypingRequest) (*Response, error) { - buf, err := json.Marshal(typingRequest) - if err != nil { - return nil, NewAppError("PublishUserTyping", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.publishUserTypingRoute(userID), buf) + r, err := c.DoAPIPostJSON(ctx, c.publishUserTypingRoute(userID), typingRequest) if err != nil { return BuildResponse(r), err } @@ -8337,27 +6490,18 @@ func (c *Client4) PublishUserTyping(ctx context.Context, userID string, typingRe } func (c *Client4) GetChannelMemberCountsByGroup(ctx context.Context, channelID string, includeTimezones bool, etag string) ([]*ChannelMemberCountByGroup, *Response, error) { - r, err := c.DoAPIGet(ctx, c.channelRoute(channelID)+"/member_counts_by_group?include_timezones="+strconv.FormatBool(includeTimezones), etag) + values := url.Values{} + values.Set("include_timezones", c.boolString(includeTimezones)) + r, err := c.DoAPIGet(ctx, c.channelRoute(channelID)+"/member_counts_by_group?"+values.Encode(), etag) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var ch []*ChannelMemberCountByGroup - err = json.NewDecoder(r.Body).Decode(&ch) - if err != nil { - return nil, BuildResponse(r), NewAppError("GetChannelMemberCountsByGroup", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ch, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*ChannelMemberCountByGroup](r) } func (c *Client4) RequestTrialLicenseWithExtraFields(ctx context.Context, trialRequest *TrialLicenseRequest) (*Response, error) { - b, err := json.Marshal(trialRequest) - if err != nil { - return nil, NewAppError("RequestTrialLicense", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPost(ctx, "/trial-license", string(b)) + r, err := c.DoAPIPostJSON(ctx, "/trial-license", trialRequest) if err != nil { return BuildResponse(r), err } @@ -8369,11 +6513,8 @@ func (c *Client4) RequestTrialLicenseWithExtraFields(ctx context.Context, trialR // RequestTrialLicense will request a trial license and install it in the server // DEPRECATED - USE RequestTrialLicenseWithExtraFields (this method remains for backwards compatibility) func (c *Client4) RequestTrialLicense(ctx context.Context, users int) (*Response, error) { - b, err := json.Marshal(map[string]any{"users": users, "terms_accepted": true}) - if err != nil { - return nil, NewAppError("RequestTrialLicense", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, "/trial-license", string(b)) + reqData := map[string]any{"users": users, "terms_accepted": true} + r, err := c.DoAPIPostJSON(ctx, "/trial-license", reqData) if err != nil { return BuildResponse(r), err } @@ -8388,11 +6529,7 @@ func (c *Client4) GetGroupStats(ctx context.Context, groupID string) (*GroupStat return nil, BuildResponse(r), err } defer closeBody(r) - var gs GroupStats - if err := json.NewDecoder(r.Body).Decode(&gs); err != nil { - return nil, nil, NewAppError("GetGroupStats", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &gs, BuildResponse(r), nil + return DecodeJSONFromResponse[*GroupStats](r) } func (c *Client4) GetSidebarCategoriesForTeamForUser(ctx context.Context, userID, teamID, etag string) (*OrderedSidebarCategories, *Response, error) { @@ -8402,53 +6539,28 @@ func (c *Client4) GetSidebarCategoriesForTeamForUser(ctx context.Context, userID return nil, BuildResponse(r), err } - var cat *OrderedSidebarCategories - err = json.NewDecoder(r.Body).Decode(&cat) - if err != nil { - return nil, BuildResponse(r), NewAppError("Client4.GetSidebarCategoriesForTeamForUser", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) - } - return cat, BuildResponse(r), nil + return DecodeJSONFromResponse[*OrderedSidebarCategories](r) } func (c *Client4) CreateSidebarCategoryForTeamForUser(ctx context.Context, userID, teamID string, category *SidebarCategoryWithChannels) (*SidebarCategoryWithChannels, *Response, error) { - payload, err := json.Marshal(category) - if err != nil { - return nil, nil, NewAppError("CreateSidebarCategoryForTeamForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } route := c.userCategoryRoute(userID, teamID) - r, err := c.DoAPIPostBytes(ctx, route, payload) + r, err := c.DoAPIPostJSON(ctx, route, category) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var cat *SidebarCategoryWithChannels - err = json.NewDecoder(r.Body).Decode(&cat) - if err != nil { - return nil, BuildResponse(r), NewAppError("Client4.CreateSidebarCategoryForTeamForUser", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) - } - return cat, BuildResponse(r), nil + return DecodeJSONFromResponse[*SidebarCategoryWithChannels](r) } func (c *Client4) UpdateSidebarCategoriesForTeamForUser(ctx context.Context, userID, teamID string, categories []*SidebarCategoryWithChannels) ([]*SidebarCategoryWithChannels, *Response, error) { - payload, err := json.Marshal(categories) - if err != nil { - return nil, nil, NewAppError("UpdateSidebarCategoriesForTeamForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } route := c.userCategoryRoute(userID, teamID) - r, err := c.DoAPIPutBytes(ctx, route, payload) + r, err := c.DoAPIPutJSON(ctx, route, categories) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var cat []*SidebarCategoryWithChannels - err = json.NewDecoder(r.Body).Decode(&cat) - if err != nil { - return nil, BuildResponse(r), NewAppError("Client4.UpdateSidebarCategoriesForTeamForUser", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) - } - - return cat, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*SidebarCategoryWithChannels](r) } func (c *Client4) GetSidebarCategoryOrderForTeamForUser(ctx context.Context, userID, teamID, etag string) ([]string, *Response, error) { @@ -8458,21 +6570,17 @@ func (c *Client4) GetSidebarCategoryOrderForTeamForUser(ctx context.Context, use return nil, BuildResponse(r), err } defer closeBody(r) - return c.ArrayFromJSON(r.Body), BuildResponse(r), nil + return DecodeJSONFromResponse[[]string](r) } func (c *Client4) UpdateSidebarCategoryOrderForTeamForUser(ctx context.Context, userID, teamID string, order []string) ([]string, *Response, error) { - payload, err := json.Marshal(order) - if err != nil { - return nil, nil, NewAppError("UpdateSidebarCategoryOrderForTeamForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } route := c.userCategoryRoute(userID, teamID) + "/order" - r, err := c.DoAPIPutBytes(ctx, route, payload) + r, err := c.DoAPIPutJSON(ctx, route, order) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - return c.ArrayFromJSON(r.Body), BuildResponse(r), nil + return DecodeJSONFromResponse[[]string](r) } func (c *Client4) GetSidebarCategoryForTeamForUser(ctx context.Context, userID, teamID, categoryID, etag string) (*SidebarCategoryWithChannels, *Response, error) { @@ -8482,33 +6590,17 @@ func (c *Client4) GetSidebarCategoryForTeamForUser(ctx context.Context, userID, return nil, BuildResponse(r), err } defer closeBody(r) - var cat *SidebarCategoryWithChannels - err = json.NewDecoder(r.Body).Decode(&cat) - if err != nil { - return nil, BuildResponse(r), NewAppError("Client4.UpdateSidebarCategoriesForTeamForUser", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) - } - - return cat, BuildResponse(r), nil + return DecodeJSONFromResponse[*SidebarCategoryWithChannels](r) } func (c *Client4) UpdateSidebarCategoryForTeamForUser(ctx context.Context, userID, teamID, categoryID string, category *SidebarCategoryWithChannels) (*SidebarCategoryWithChannels, *Response, error) { - payload, err := json.Marshal(category) - if err != nil { - return nil, nil, NewAppError("UpdateSidebarCategoryForTeamForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } route := c.userCategoryRoute(userID, teamID) + "/" + categoryID - r, err := c.DoAPIPutBytes(ctx, route, payload) + r, err := c.DoAPIPutJSON(ctx, route, category) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var cat *SidebarCategoryWithChannels - err = json.NewDecoder(r.Body).Decode(&cat) - if err != nil { - return nil, BuildResponse(r), NewAppError("Client4.UpdateSidebarCategoriesForTeamForUser", "model.utils.decode_json.app_error", nil, "", r.StatusCode).Wrap(err) - } - - return cat, BuildResponse(r), nil + return DecodeJSONFromResponse[*SidebarCategoryWithChannels](r) } // DeleteSidebarCategoryForTeamForUser deletes a sidebar category for a user in a team. @@ -8529,15 +6621,16 @@ func (c *Client4) CheckIntegrity(ctx context.Context) ([]IntegrityCheckResult, * return nil, BuildResponse(r), err } defer closeBody(r) - var results []IntegrityCheckResult - if err := json.NewDecoder(r.Body).Decode(&results); err != nil { - return nil, BuildResponse(r), NewAppError("Api4.CheckIntegrity", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return results, BuildResponse(r), nil + return DecodeJSONFromResponse[[]IntegrityCheckResult](r) } func (c *Client4) GetNotices(ctx context.Context, lastViewed int64, teamId string, client NoticeClientType, clientVersion, locale, etag string) (NoticeMessages, *Response, error) { - url := fmt.Sprintf("/system/notices/%s?lastViewed=%d&client=%s&clientVersion=%s&locale=%s", teamId, lastViewed, client, clientVersion, locale) + values := url.Values{} + values.Set("lastViewed", strconv.FormatInt(lastViewed, 10)) + values.Set("client", string(client)) + values.Set("clientVersion", clientVersion) + values.Set("locale", locale) + url := "/system/notices/" + teamId + "?" + values.Encode() r, err := c.DoAPIGet(ctx, url, etag) if err != nil { return nil, BuildResponse(r), err @@ -8551,7 +6644,7 @@ func (c *Client4) GetNotices(ctx context.Context, lastViewed int64, teamId strin } func (c *Client4) MarkNoticesViewed(ctx context.Context, ids []string) (*Response, error) { - r, err := c.DoAPIPut(ctx, "/system/notices/view", ArrayToJSON(ids)) + r, err := c.DoAPIPutJSON(ctx, "/system/notices/view", ids) if err != nil { return BuildResponse(r), err } @@ -8560,11 +6653,7 @@ func (c *Client4) MarkNoticesViewed(ctx context.Context, ids []string) (*Respons } func (c *Client4) CompleteOnboarding(ctx context.Context, request *CompleteOnboardingRequest) (*Response, error) { - buf, err := json.Marshal(request) - if err != nil { - return nil, NewAppError("CompleteOnboarding", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPost(ctx, c.systemRoute()+"/onboarding/complete", string(buf)) + r, err := c.DoAPIPostJSON(ctx, c.systemRoute()+"/onboarding/complete", request) if err != nil { return BuildResponse(r), err } @@ -8575,21 +6664,12 @@ func (c *Client4) CompleteOnboarding(ctx context.Context, request *CompleteOnboa // CreateUpload creates a new upload session. func (c *Client4) CreateUpload(ctx context.Context, us *UploadSession) (*UploadSession, *Response, error) { - buf, err := json.Marshal(us) - if err != nil { - return nil, nil, NewAppError("CreateUpload", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.uploadsRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.uploadsRoute(), us) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var s UploadSession - if err := json.NewDecoder(r.Body).Decode(&s); err != nil { - return nil, nil, NewAppError("CreateUpload", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &s, BuildResponse(r), nil + return DecodeJSONFromResponse[*UploadSession](r) } // GetUpload returns the upload session for the specified uploadId. @@ -8599,11 +6679,7 @@ func (c *Client4) GetUpload(ctx context.Context, uploadId string) (*UploadSessio return nil, BuildResponse(r), err } defer closeBody(r) - var s UploadSession - if err := json.NewDecoder(r.Body).Decode(&s); err != nil { - return nil, nil, NewAppError("GetUpload", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &s, BuildResponse(r), nil + return DecodeJSONFromResponse[*UploadSession](r) } // GetUploadsForUser returns the upload sessions created by the specified @@ -8614,35 +6690,27 @@ func (c *Client4) GetUploadsForUser(ctx context.Context, userId string) ([]*Uplo return nil, BuildResponse(r), err } defer closeBody(r) - var list []*UploadSession - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUploadsForUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*UploadSession](r) } // UploadData performs an upload. On success it returns // a FileInfo object. func (c *Client4) UploadData(ctx context.Context, uploadId string, data io.Reader) (*FileInfo, *Response, error) { url := c.uploadRoute(uploadId) - r, err := c.DoAPIRequestReader(ctx, "POST", c.APIURL+url, data, nil) + r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.APIURL+url, "", data, nil) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var fi FileInfo if r.StatusCode == http.StatusNoContent { return nil, BuildResponse(r), nil } - if err := json.NewDecoder(r.Body).Decode(&fi); err != nil { - return nil, nil, NewAppError("UploadData", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &fi, BuildResponse(r), nil + return DecodeJSONFromResponse[*FileInfo](r) } func (c *Client4) UpdatePassword(ctx context.Context, userId, currentPassword, newPassword string) (*Response, error) { requestBody := map[string]string{"current_password": currentPassword, "new_password": newPassword} - r, err := c.DoAPIPut(ctx, c.userRoute(userId)+"/password", MapToJSON(requestBody)) + r, err := c.DoAPIPutJSON(ctx, c.userRoute(userId)+"/password", requestBody) if err != nil { return BuildResponse(r), err } @@ -8658,11 +6726,7 @@ func (c *Client4) GetCloudProducts(ctx context.Context) ([]*Product, *Response, return nil, BuildResponse(r), err } defer closeBody(r) - - var cloudProducts []*Product - json.NewDecoder(r.Body).Decode(&cloudProducts) - - return cloudProducts, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Product](r) } func (c *Client4) GetSelfHostedProducts(ctx context.Context) ([]*Product, *Response, error) { @@ -8671,11 +6735,7 @@ func (c *Client4) GetSelfHostedProducts(ctx context.Context) ([]*Product, *Respo return nil, BuildResponse(r), err } defer closeBody(r) - - var products []*Product - json.NewDecoder(r.Body).Decode(&products) - - return products, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Product](r) } func (c *Client4) GetProductLimits(ctx context.Context) (*ProductLimits, *Response, error) { @@ -8684,11 +6744,7 @@ func (c *Client4) GetProductLimits(ctx context.Context) (*ProductLimits, *Respon return nil, BuildResponse(r), err } defer closeBody(r) - - var productLimits *ProductLimits - json.NewDecoder(r.Body).Decode(&productLimits) - - return productLimits, BuildResponse(r), nil + return DecodeJSONFromResponse[*ProductLimits](r) } func (c *Client4) GetIPFilters(ctx context.Context) (*AllowedIPRanges, *Response, error) { @@ -8698,29 +6754,17 @@ func (c *Client4) GetIPFilters(ctx context.Context) (*AllowedIPRanges, *Response } defer closeBody(r) - - var allowedIPRanges *AllowedIPRanges - json.NewDecoder(r.Body).Decode(&allowedIPRanges) - return allowedIPRanges, BuildResponse(r), nil + return DecodeJSONFromResponse[*AllowedIPRanges](r) } func (c *Client4) ApplyIPFilters(ctx context.Context, allowedRanges *AllowedIPRanges) (*AllowedIPRanges, *Response, error) { - payload, err := json.Marshal(allowedRanges) - if err != nil { - return nil, nil, NewAppError("ApplyIPFilters", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPostBytes(ctx, c.ipFiltersRoute(), payload) + r, err := c.DoAPIPostJSON(ctx, c.ipFiltersRoute(), allowedRanges) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var allowedIPRanges *AllowedIPRanges - json.NewDecoder(r.Body).Decode(&allowedIPRanges) - - return allowedIPRanges, BuildResponse(r), nil + return DecodeJSONFromResponse[*AllowedIPRanges](r) } func (c *Client4) GetMyIP(ctx context.Context) (*GetIPAddressResponse, *Response, error) { @@ -8730,11 +6774,7 @@ func (c *Client4) GetMyIP(ctx context.Context) (*GetIPAddressResponse, *Response } defer closeBody(r) - - var response *GetIPAddressResponse - json.NewDecoder(r.Body).Decode(&response) - - return response, BuildResponse(r), nil + return DecodeJSONFromResponse[*GetIPAddressResponse](r) } func (c *Client4) ValidateWorkspaceBusinessEmail(ctx context.Context) (*Response, error) { @@ -8748,12 +6788,7 @@ func (c *Client4) ValidateWorkspaceBusinessEmail(ctx context.Context) (*Response } func (c *Client4) NotifyAdmin(ctx context.Context, nr *NotifyAdminToUpgradeRequest) (int, error) { - nrJSON, err := json.Marshal(nr) - if err != nil { - return 0, err - } - - r, err := c.DoAPIPost(ctx, "/users/notify-admin", string(nrJSON)) + r, err := c.DoAPIPostJSON(ctx, "/users/notify-admin", nr) if err != nil { return r.StatusCode, err } @@ -8764,12 +6799,7 @@ func (c *Client4) NotifyAdmin(ctx context.Context, nr *NotifyAdminToUpgradeReque } func (c *Client4) TriggerNotifyAdmin(ctx context.Context, nr *NotifyAdminToUpgradeRequest) (int, error) { - nrJSON, err := json.Marshal(nr) - if err != nil { - return 0, err - } - - r, err := c.DoAPIPost(ctx, "/users/trigger-notify-admin-posts", string(nrJSON)) + r, err := c.DoAPIPostJSON(ctx, "/users/trigger-notify-admin-posts", nr) if err != nil { return r.StatusCode, err } @@ -8780,8 +6810,7 @@ func (c *Client4) TriggerNotifyAdmin(ctx context.Context, nr *NotifyAdminToUpgra } func (c *Client4) ValidateBusinessEmail(ctx context.Context, email *ValidateBusinessEmailRequest) (*Response, error) { - payload, _ := json.Marshal(email) - r, err := c.DoAPIPostBytes(ctx, c.cloudRoute()+"/validate-business-email", payload) + r, err := c.DoAPIPostJSON(ctx, c.cloudRoute()+"/validate-business-email", email) if err != nil { return BuildResponse(r), err } @@ -8796,11 +6825,7 @@ func (c *Client4) GetCloudCustomer(ctx context.Context) (*CloudCustomer, *Respon return nil, BuildResponse(r), err } defer closeBody(r) - - var cloudCustomer *CloudCustomer - json.NewDecoder(r.Body).Decode(&cloudCustomer) - - return cloudCustomer, BuildResponse(r), nil + return DecodeJSONFromResponse[*CloudCustomer](r) } func (c *Client4) GetSubscription(ctx context.Context) (*Subscription, *Response, error) { @@ -8809,11 +6834,7 @@ func (c *Client4) GetSubscription(ctx context.Context) (*Subscription, *Response return nil, BuildResponse(r), err } defer closeBody(r) - - var subscription *Subscription - json.NewDecoder(r.Body).Decode(&subscription) - - return subscription, BuildResponse(r), nil + return DecodeJSONFromResponse[*Subscription](r) } func (c *Client4) GetInvoicesForSubscription(ctx context.Context) ([]*Invoice, *Response, error) { @@ -8822,45 +6843,25 @@ func (c *Client4) GetInvoicesForSubscription(ctx context.Context) ([]*Invoice, * return nil, BuildResponse(r), err } defer closeBody(r) - - var invoices []*Invoice - json.NewDecoder(r.Body).Decode(&invoices) - - return invoices, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*Invoice](r) } func (c *Client4) UpdateCloudCustomer(ctx context.Context, customerInfo *CloudCustomerInfo) (*CloudCustomer, *Response, error) { - customerBytes, err := json.Marshal(customerInfo) - if err != nil { - return nil, nil, NewAppError("UpdateCloudCustomer", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.cloudRoute()+"/customer", customerBytes) + r, err := c.DoAPIPutJSON(ctx, c.cloudRoute()+"/customer", customerInfo) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var customer *CloudCustomer - json.NewDecoder(r.Body).Decode(&customer) - - return customer, BuildResponse(r), nil + return DecodeJSONFromResponse[*CloudCustomer](r) } func (c *Client4) UpdateCloudCustomerAddress(ctx context.Context, address *Address) (*CloudCustomer, *Response, error) { - addressBytes, err := json.Marshal(address) - if err != nil { - return nil, nil, NewAppError("UpdateCloudCustomerAddress", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPutBytes(ctx, c.cloudRoute()+"/customer/address", addressBytes) + r, err := c.DoAPIPutJSON(ctx, c.cloudRoute()+"/customer/address", address) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var customer *CloudCustomer - json.NewDecoder(r.Body).Decode(&customer) - - return customer, BuildResponse(r), nil + return DecodeJSONFromResponse[*CloudCustomer](r) } func (c *Client4) ListImports(ctx context.Context) ([]string, *Response, error) { @@ -8869,7 +6870,7 @@ func (c *Client4) ListImports(ctx context.Context) ([]string, *Response, error) return nil, BuildResponse(r), err } defer closeBody(r) - return c.ArrayFromJSON(r.Body), BuildResponse(r), nil + return DecodeJSONFromResponse[[]string](r) } func (c *Client4) DeleteImport(ctx context.Context, name string) (*Response, error) { @@ -8887,7 +6888,7 @@ func (c *Client4) ListExports(ctx context.Context) ([]string, *Response, error) return nil, BuildResponse(r), err } defer closeBody(r) - return c.ArrayFromJSON(r.Body), BuildResponse(r), nil + return DecodeJSONFromResponse[[]string](r) } func (c *Client4) DeleteExport(ctx context.Context, name string) (*Response, error) { @@ -8913,25 +6914,18 @@ func (c *Client4) DownloadExport(ctx context.Context, name string, wr io.Writer, defer closeBody(r) n, err := io.Copy(wr, r.Body) if err != nil { - return n, BuildResponse(r), NewAppError("DownloadExport", "model.client.copy.app_error", nil, "", r.StatusCode).Wrap(err) + return n, BuildResponse(r), fmt.Errorf("failed to copy export data to writer: %w", err) } return n, BuildResponse(r), nil } func (c *Client4) GeneratePresignedURL(ctx context.Context, name string) (*PresignURLResponse, *Response, error) { - r, err := c.DoAPIRequest(ctx, http.MethodPost, c.APIURL+c.exportRoute(name)+"/presign-url", "", "") + r, err := c.doAPIRequest(ctx, http.MethodPost, c.APIURL+c.exportRoute(name)+"/presign-url", "", "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - res := &PresignURLResponse{} - err = json.NewDecoder(r.Body).Decode(res) - if err != nil { - return nil, BuildResponse(r), NewAppError("GeneratePresignedURL", "model.client.json_decode.app_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return res, BuildResponse(r), nil + return DecodeJSONFromResponse[*PresignURLResponse](r) } func (c *Client4) GetUserThreads(ctx context.Context, userId, teamId string, options GetUserThreadsOpts) (*Threads, *Response, error) { @@ -8976,11 +6970,7 @@ func (c *Client4) GetUserThreads(ctx context.Context, userId, teamId string, opt return nil, BuildResponse(r), err } defer closeBody(r) - - var threads Threads - json.NewDecoder(r.Body).Decode(&threads) - - return &threads, BuildResponse(r), nil + return DecodeJSONFromResponse[*Threads](r) } func (c *Client4) DownloadComplianceExport(ctx context.Context, jobId string, wr io.Writer) (string, error) { @@ -9003,7 +6993,7 @@ func (c *Client4) DownloadComplianceExport(ctx context.Context, jobId string, wr _, err = io.Copy(wr, r.Body) if err != nil { - return filename, NewAppError("DownloadComplianceExport", "model.client.copy.app_error", nil, "", r.StatusCode).Wrap(err) + return filename, fmt.Errorf("failed to copy compliance export data to writer: %w", err) } return filename, nil } @@ -9018,11 +7008,7 @@ func (c *Client4) GetUserThread(ctx context.Context, userId, teamId, threadId st return nil, BuildResponse(r), err } defer closeBody(r) - - var thread ThreadResponse - json.NewDecoder(r.Body).Decode(&thread) - - return &thread, BuildResponse(r), nil + return DecodeJSONFromResponse[*ThreadResponse](r) } func (c *Client4) UpdateThreadsReadForUser(ctx context.Context, userId, teamId string) (*Response, error) { @@ -9041,10 +7027,7 @@ func (c *Client4) SetThreadUnreadByPostId(ctx context.Context, userId, teamId, t return nil, BuildResponse(r), err } defer closeBody(r) - var thread ThreadResponse - json.NewDecoder(r.Body).Decode(&thread) - - return &thread, BuildResponse(r), nil + return DecodeJSONFromResponse[*ThreadResponse](r) } func (c *Client4) UpdateThreadReadForUser(ctx context.Context, userId, teamId, threadId string, timestamp int64) (*ThreadResponse, *Response, error) { @@ -9053,10 +7036,7 @@ func (c *Client4) UpdateThreadReadForUser(ctx context.Context, userId, teamId, t return nil, BuildResponse(r), err } defer closeBody(r) - var thread ThreadResponse - json.NewDecoder(r.Body).Decode(&thread) - - return &thread, BuildResponse(r), nil + return DecodeJSONFromResponse[*ThreadResponse](r) } func (c *Client4) UpdateThreadFollowForUser(ctx context.Context, userId, teamId, threadId string, state bool) (*Response, error) { @@ -9076,17 +7056,16 @@ func (c *Client4) UpdateThreadFollowForUser(ctx context.Context, userId, teamId, } func (c *Client4) GetAllSharedChannels(ctx context.Context, teamID string, page, perPage int) ([]*SharedChannel, *Response, error) { - url := fmt.Sprintf("%s/%s?page=%d&per_page=%d", c.sharedChannelsRoute(), teamID, page, perPage) + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + url := c.sharedChannelsRoute() + "/" + teamID + "?" + values.Encode() r, err := c.DoAPIGet(ctx, url, "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var channels []*SharedChannel - json.NewDecoder(r.Body).Decode(&channels) - - return channels, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*SharedChannel](r) } func (c *Client4) GetRemoteClusterInfo(ctx context.Context, remoteID string) (RemoteClusterInfo, *Response, error) { @@ -9096,11 +7075,7 @@ func (c *Client4) GetRemoteClusterInfo(ctx context.Context, remoteID string) (Re return RemoteClusterInfo{}, BuildResponse(r), err } defer closeBody(r) - - var rci RemoteClusterInfo - json.NewDecoder(r.Body).Decode(&rci) - - return rci, BuildResponse(r), nil + return DecodeJSONFromResponse[RemoteClusterInfo](r) } func (c *Client4) GetRemoteClusters(ctx context.Context, page, perPage int, filter RemoteClusterQueryFilter) ([]*RemoteCluster, *Response, error) { @@ -9151,66 +7126,36 @@ func (c *Client4) GetRemoteClusters(ctx context.Context, page, perPage int, filt return nil, BuildResponse(r), err } defer closeBody(r) - - var rcs []*RemoteCluster - json.NewDecoder(r.Body).Decode(&rcs) - - return rcs, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*RemoteCluster](r) } func (c *Client4) CreateRemoteCluster(ctx context.Context, rcWithPassword *RemoteClusterWithPassword) (*RemoteClusterWithInvite, *Response, error) { - rcJSON, err := json.Marshal(rcWithPassword) - if err != nil { - return nil, nil, NewAppError("CreateRemoteCluster", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPost(ctx, c.remoteClusterRoute(), string(rcJSON)) + r, err := c.DoAPIPostJSON(ctx, c.remoteClusterRoute(), rcWithPassword) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var rcWithInvite RemoteClusterWithInvite - if err := json.NewDecoder(r.Body).Decode(&rcWithInvite); err != nil { - return nil, nil, NewAppError("CreateRemoteCluster", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &rcWithInvite, BuildResponse(r), nil + return DecodeJSONFromResponse[*RemoteClusterWithInvite](r) } func (c *Client4) RemoteClusterAcceptInvite(ctx context.Context, rcAcceptInvite *RemoteClusterAcceptInvite) (*RemoteCluster, *Response, error) { - rcAcceptInviteJSON, err := json.Marshal(rcAcceptInvite) - if err != nil { - return nil, nil, NewAppError("RemoteClusterAcceptInvite", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - url := fmt.Sprintf("%s/accept_invite", c.remoteClusterRoute()) - r, err := c.DoAPIPost(ctx, url, string(rcAcceptInviteJSON)) + r, err := c.DoAPIPostJSON(ctx, url, rcAcceptInvite) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var rc RemoteCluster - if err := json.NewDecoder(r.Body).Decode(&rc); err != nil { - return nil, nil, NewAppError("RemoteClusterAcceptInvite", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &rc, BuildResponse(r), nil + return DecodeJSONFromResponse[*RemoteCluster](r) } func (c *Client4) GenerateRemoteClusterInvite(ctx context.Context, remoteClusterId, password string) (string, *Response, error) { url := fmt.Sprintf("%s/%s/generate_invite", c.remoteClusterRoute(), remoteClusterId) - r, err := c.DoAPIPost(ctx, url, MapToJSON(map[string]string{"password": password})) + r, err := c.DoAPIPostJSON(ctx, url, map[string]string{"password": password}) if err != nil { return "", BuildResponse(r), err } defer closeBody(r) - - var inviteCode string - if err := json.NewDecoder(r.Body).Decode(&inviteCode); err != nil { - return "", nil, NewAppError("GenerateRemoteClusterInvite", "api.unmarshall_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return inviteCode, BuildResponse(r), nil + return DecodeJSONFromResponse[string](r) } func (c *Client4) GetRemoteCluster(ctx context.Context, remoteClusterId string) (*RemoteCluster, *Response, error) { @@ -9219,33 +7164,17 @@ func (c *Client4) GetRemoteCluster(ctx context.Context, remoteClusterId string) return nil, BuildResponse(r), err } defer closeBody(r) - - var rc *RemoteCluster - if err := json.NewDecoder(r.Body).Decode(&rc); err != nil { - return nil, nil, NewAppError("GetRemoteCluster", "api.unmarshall_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return rc, BuildResponse(r), nil + return DecodeJSONFromResponse[*RemoteCluster](r) } func (c *Client4) PatchRemoteCluster(ctx context.Context, remoteClusterId string, patch *RemoteClusterPatch) (*RemoteCluster, *Response, error) { - patchJSON, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchRemoteCluster", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - url := fmt.Sprintf("%s/%s", c.remoteClusterRoute(), remoteClusterId) - r, err := c.DoAPIPatchBytes(ctx, url, patchJSON) + r, err := c.DoAPIPatchJSON(ctx, url, patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var rc RemoteCluster - if err := json.NewDecoder(r.Body).Decode(&rc); err != nil { - return nil, nil, NewAppError("PatchRemoteCluster", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &rc, BuildResponse(r), nil + return DecodeJSONFromResponse[*RemoteCluster](r) } func (c *Client4) DeleteRemoteCluster(ctx context.Context, remoteClusterId string) (*Response, error) { @@ -9290,11 +7219,7 @@ func (c *Client4) GetSharedChannelRemotesByRemoteCluster(ctx context.Context, re return nil, BuildResponse(r), err } defer closeBody(r) - - var scs []*SharedChannelRemote - json.NewDecoder(r.Body).Decode(&scs) - - return scs, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*SharedChannelRemote](r) } func (c *Client4) InviteRemoteClusterToChannel(ctx context.Context, remoteId, channelId string) (*Response, error) { @@ -9320,31 +7245,24 @@ func (c *Client4) UninviteRemoteClusterToChannel(ctx context.Context, remoteId, func (c *Client4) GetAncillaryPermissions(ctx context.Context, subsectionPermissions []string) ([]string, *Response, error) { var returnedPermissions []string url := fmt.Sprintf("%s/ancillary", c.permissionsRoute()) - r, err := c.DoAPIPost(ctx, url, ArrayToJSON(subsectionPermissions)) + r, err := c.DoAPIPostJSON(ctx, url, subsectionPermissions) if err != nil { return returnedPermissions, BuildResponse(r), err } defer closeBody(r) - - json.NewDecoder(r.Body).Decode(&returnedPermissions) - return returnedPermissions, BuildResponse(r), nil + return DecodeJSONFromResponse[[]string](r) } func (c *Client4) GetUsersWithInvalidEmails(ctx context.Context, page, perPage int) ([]*User, *Response, error) { - query := fmt.Sprintf("/invalid_emails?page=%v&per_page=%v", page, perPage) - r, err := c.DoAPIGet(ctx, c.usersRoute()+query, "") + values := url.Values{} + values.Set("page", strconv.Itoa(page)) + values.Set("per_page", strconv.Itoa(perPage)) + r, err := c.DoAPIGet(ctx, c.usersRoute()+"/invalid_emails?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var list []*User - if r.StatusCode == http.StatusNotModified { - return list, BuildResponse(r), nil - } - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*User](r) } func (c *Client4) GetAppliedSchemaMigrations(ctx context.Context) ([]AppliedMigration, *Response, error) { @@ -9353,11 +7271,7 @@ func (c *Client4) GetAppliedSchemaMigrations(ctx context.Context) ([]AppliedMigr return nil, BuildResponse(r), err } defer closeBody(r) - var list []AppliedMigration - if err := json.NewDecoder(r.Body).Decode(&list); err != nil { - return nil, nil, NewAppError("GetUsers", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return list, BuildResponse(r), nil + return DecodeJSONFromResponse[[]AppliedMigration](r) } // Usage Section @@ -9369,10 +7283,7 @@ func (c *Client4) GetPostsUsage(ctx context.Context) (*PostsUsage, *Response, er return nil, BuildResponse(r), err } defer closeBody(r) - - var usage *PostsUsage - err = json.NewDecoder(r.Body).Decode(&usage) - return usage, BuildResponse(r), err + return DecodeJSONFromResponse[*PostsUsage](r) } // GetStorageUsage returns the file storage usage for the instance, @@ -9383,10 +7294,7 @@ func (c *Client4) GetStorageUsage(ctx context.Context) (*StorageUsage, *Response return nil, BuildResponse(r), err } defer closeBody(r) - - var usage *StorageUsage - err = json.NewDecoder(r.Body).Decode(&usage) - return usage, BuildResponse(r), err + return DecodeJSONFromResponse[*StorageUsage](r) } // GetTeamsUsage returns total usage of teams for the instance @@ -9396,10 +7304,7 @@ func (c *Client4) GetTeamsUsage(ctx context.Context) (*TeamsUsage, *Response, er return nil, BuildResponse(r), err } defer closeBody(r) - - var usage *TeamsUsage - err = json.NewDecoder(r.Body).Decode(&usage) - return usage, BuildResponse(r), err + return DecodeJSONFromResponse[*TeamsUsage](r) } func (c *Client4) GetPostInfo(ctx context.Context, postId string) (*PostInfo, *Response, error) { @@ -9408,12 +7313,7 @@ func (c *Client4) GetPostInfo(ctx context.Context, postId string) (*PostInfo, *R return nil, BuildResponse(r), err } defer closeBody(r) - - var info *PostInfo - if err = json.NewDecoder(r.Body).Decode(&info); err != nil { - return nil, nil, NewAppError("GetPostInfo", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return info, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostInfo](r) } func (c *Client4) AcknowledgePost(ctx context.Context, postId, userId string) (*PostAcknowledgement, *Response, error) { @@ -9422,11 +7322,7 @@ func (c *Client4) AcknowledgePost(ctx context.Context, postId, userId string) (* return nil, BuildResponse(r), err } defer closeBody(r) - var ack *PostAcknowledgement - if err := json.NewDecoder(r.Body).Decode(&ack); err != nil { - return nil, nil, NewAppError("AcknowledgePost", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return ack, BuildResponse(r), nil + return DecodeJSONFromResponse[*PostAcknowledgement](r) } func (c *Client4) UnacknowledgePost(ctx context.Context, postId, userId string) (*Response, error) { @@ -9459,56 +7355,32 @@ func (c *Client4) CheckCWSConnection(ctx context.Context, userId string) (*Respo // CreateChannelBookmark creates a channel bookmark based on the provided struct. func (c *Client4) CreateChannelBookmark(ctx context.Context, channelBookmark *ChannelBookmark) (*ChannelBookmarkWithFileInfo, *Response, error) { - channelBookmarkJSON, err := json.Marshal(channelBookmark) - if err != nil { - return nil, nil, NewAppError("CreateChannelBookmark", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.bookmarksRoute(channelBookmark.ChannelId), channelBookmarkJSON) + r, err := c.DoAPIPostJSON(ctx, c.bookmarksRoute(channelBookmark.ChannelId), channelBookmark) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var cb ChannelBookmarkWithFileInfo - if err := json.NewDecoder(r.Body).Decode(&cb); err != nil { - return nil, nil, NewAppError("CreateChannelBookmark", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &cb, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelBookmarkWithFileInfo](r) } // UpdateChannelBookmark updates a channel bookmark based on the provided struct. func (c *Client4) UpdateChannelBookmark(ctx context.Context, channelId, bookmarkId string, patch *ChannelBookmarkPatch) (*UpdateChannelBookmarkResponse, *Response, error) { - buf, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("UpdateChannelBookmark", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPatchBytes(ctx, c.bookmarkRoute(channelId, bookmarkId), buf) + r, err := c.DoAPIPatchJSON(ctx, c.bookmarkRoute(channelId, bookmarkId), patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var ucb UpdateChannelBookmarkResponse - if err := json.NewDecoder(r.Body).Decode(&ucb); err != nil { - return nil, nil, NewAppError("UpdateChannelBookmark", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &ucb, BuildResponse(r), nil + return DecodeJSONFromResponse[*UpdateChannelBookmarkResponse](r) } // UpdateChannelBookmarkSortOrder updates a channel bookmark's sort order based on the provided new index. func (c *Client4) UpdateChannelBookmarkSortOrder(ctx context.Context, channelId, bookmarkId string, sortOrder int64) ([]*ChannelBookmarkWithFileInfo, *Response, error) { - buf, err := json.Marshal(sortOrder) - if err != nil { - return nil, nil, NewAppError("UpdateChannelBookmarkSortOrder", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.bookmarkRoute(channelId, bookmarkId)+"/sort_order", buf) + r, err := c.DoAPIPostJSON(ctx, c.bookmarkRoute(channelId, bookmarkId)+"/sort_order", sortOrder) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var b []*ChannelBookmarkWithFileInfo - if err := json.NewDecoder(r.Body).Decode(&b); err != nil { - return nil, nil, NewAppError("UpdateChannelBookmarkSortOrder", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return b, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*ChannelBookmarkWithFileInfo](r) } // DeleteChannelBookmark deletes a channel bookmark. @@ -9518,33 +7390,22 @@ func (c *Client4) DeleteChannelBookmark(ctx context.Context, channelId, bookmark return nil, BuildResponse(r), err } defer closeBody(r) - var b *ChannelBookmarkWithFileInfo - if err := json.NewDecoder(r.Body).Decode(&b); err != nil { - return nil, nil, NewAppError("DeleteChannelBookmark", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return b, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelBookmarkWithFileInfo](r) } func (c *Client4) ListChannelBookmarksForChannel(ctx context.Context, channelId string, since int64) ([]*ChannelBookmarkWithFileInfo, *Response, error) { - query := fmt.Sprintf("?bookmarks_since=%v", since) - r, err := c.DoAPIGet(ctx, c.bookmarksRoute(channelId)+query, "") + values := url.Values{} + values.Set("bookmarks_since", strconv.FormatInt(since, 10)) + r, err := c.DoAPIGet(ctx, c.bookmarksRoute(channelId)+"?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - var b []*ChannelBookmarkWithFileInfo - if err := json.NewDecoder(r.Body).Decode(&b); err != nil { - return nil, nil, NewAppError("ListChannelBookmarksForChannel", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return b, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*ChannelBookmarkWithFileInfo](r) } func (c *Client4) SubmitClientMetrics(ctx context.Context, report *PerformanceReport) (*Response, error) { - buf, err := json.Marshal(report) - if err != nil { - return nil, NewAppError("SubmitClientMetrics", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - res, err := c.DoAPIPostBytes(ctx, c.clientPerfMetricsRoute(), buf) + res, err := c.DoAPIPostJSON(ctx, c.clientPerfMetricsRoute(), report) if err != nil { return BuildResponse(res), err } @@ -9570,18 +7431,12 @@ func (c *Client4) GetFilteredUsersStats(ctx context.Context, options *UserCountO v.Set("team_roles", strings.Join(options.TeamRoles, ",")) } - query := v.Encode() - r, err := c.DoAPIGet(ctx, c.usersRoute()+"/stats/filtered?"+query, "") + r, err := c.DoAPIGet(ctx, c.usersRoute()+"/stats/filtered?"+v.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var stats UsersStats - if err := json.NewDecoder(r.Body).Decode(&stats); err != nil { - return nil, nil, NewAppError("GetFilteredUsersStats", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &stats, BuildResponse(r), nil + return DecodeJSONFromResponse[*UsersStats](r) } func (c *Client4) RestorePostVersion(ctx context.Context, postId, versionId string) (*Post, *Response, error) { @@ -9591,29 +7446,16 @@ func (c *Client4) RestorePostVersion(ctx context.Context, postId, versionId stri } defer closeBody(r) - var restoredPost *Post - if err := json.NewDecoder(r.Body).Decode(&restoredPost); err != nil { - return nil, nil, NewAppError("RestorePostVersion", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return restoredPost, BuildResponse(r), nil + return DecodeJSONFromResponse[*Post](r) } func (c *Client4) CreateCPAField(ctx context.Context, field *PropertyField) (*PropertyField, *Response, error) { - buf, err := json.Marshal(field) - if err != nil { - return nil, nil, NewAppError("CreateCPAField", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPostBytes(ctx, c.customProfileAttributeFieldsRoute(), buf) + r, err := c.DoAPIPostJSON(ctx, c.customProfileAttributeFieldsRoute(), field) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var pf PropertyField - if err := json.NewDecoder(r.Body).Decode(&pf); err != nil { - return nil, nil, NewAppError("CreateCPAField", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &pf, BuildResponse(r), nil + return DecodeJSONFromResponse[*PropertyField](r) } func (c *Client4) ListCPAFields(ctx context.Context) ([]*PropertyField, *Response, error) { @@ -9622,30 +7464,16 @@ func (c *Client4) ListCPAFields(ctx context.Context) ([]*PropertyField, *Respons return nil, BuildResponse(r), err } defer closeBody(r) - - var fields []*PropertyField - if err := json.NewDecoder(r.Body).Decode(&fields); err != nil { - return nil, nil, NewAppError("ListCPAFields", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return fields, BuildResponse(r), nil + return DecodeJSONFromResponse[[]*PropertyField](r) } func (c *Client4) PatchCPAField(ctx context.Context, fieldID string, patch *PropertyFieldPatch) (*PropertyField, *Response, error) { - buf, err := json.Marshal(patch) - if err != nil { - return nil, nil, NewAppError("PatchCPAField", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - r, err := c.DoAPIPatchBytes(ctx, c.customProfileAttributeFieldRoute(fieldID), buf) + r, err := c.DoAPIPatchJSON(ctx, c.customProfileAttributeFieldRoute(fieldID), patch) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var pf PropertyField - if err := json.NewDecoder(r.Body).Decode(&pf); err != nil { - return nil, nil, NewAppError("PatchCPAField", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return &pf, BuildResponse(r), nil + return DecodeJSONFromResponse[*PropertyField](r) } func (c *Client4) DeleteCPAField(ctx context.Context, fieldID string) (*Response, error) { @@ -9663,75 +7491,46 @@ func (c *Client4) ListCPAValues(ctx context.Context, userID string) (map[string] return nil, BuildResponse(r), err } defer closeBody(r) - - fields := make(map[string]json.RawMessage) - if err := json.NewDecoder(r.Body).Decode(&fields); err != nil { - return nil, nil, NewAppError("ListCPAValues", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - return fields, BuildResponse(r), nil + return DecodeJSONFromResponse[map[string]json.RawMessage](r) } func (c *Client4) PatchCPAValues(ctx context.Context, values map[string]json.RawMessage) (map[string]json.RawMessage, *Response, error) { - buf, err := json.Marshal(values) - if err != nil { - return nil, nil, NewAppError("PatchCPAValues", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPatchBytes(ctx, c.customProfileAttributeValuesRoute(), buf) + r, err := c.DoAPIPatchJSON(ctx, c.customProfileAttributeValuesRoute(), values) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var patchedValues map[string]json.RawMessage - if err := json.NewDecoder(r.Body).Decode(&patchedValues); err != nil { - return nil, nil, NewAppError("PatchCPAValues", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return patchedValues, BuildResponse(r), nil + return DecodeJSONFromResponse[map[string]json.RawMessage](r) } func (c *Client4) PatchCPAValuesForUser(ctx context.Context, userID string, values map[string]json.RawMessage) (map[string]json.RawMessage, *Response, error) { - buf, err := json.Marshal(values) - if err != nil { - return nil, nil, NewAppError("PatchCPAValuesForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPatchBytes(ctx, c.userCustomProfileAttributesRoute(userID), buf) + r, err := c.DoAPIPatchJSON(ctx, c.userCustomProfileAttributesRoute(userID), values) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) + return DecodeJSONFromResponse[map[string]json.RawMessage](r) +} - var patchedValues map[string]json.RawMessage - if err := json.NewDecoder(r.Body).Decode(&patchedValues); err != nil { - return nil, nil, NewAppError("PatchCPAValuesForUser", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) +func (c *Client4) GetPostPropertyValues(ctx context.Context, postId string) ([]PropertyValue, *Response, error) { + r, err := c.DoAPIGet(ctx, c.contentFlaggingRoute()+"/post/"+postId+"/field_values", "") + if err != nil { + return nil, BuildResponse(r), err } - - return patchedValues, BuildResponse(r), nil + defer closeBody(r) + return DecodeJSONFromResponse[[]PropertyValue](r) // TODO: Fix! } // Access Control Policies Section // CreateAccessControlPolicy creates a new access control policy. func (c *Client4) CreateAccessControlPolicy(ctx context.Context, policy *AccessControlPolicy) (*AccessControlPolicy, *Response, error) { - b, err := json.Marshal(policy) - if err != nil { - return nil, nil, NewAppError("CreateAccessControlPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPutBytes(ctx, c.accessControlPoliciesRoute(), b) + r, err := c.DoAPIPutJSON(ctx, c.accessControlPoliciesRoute(), policy) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var p AccessControlPolicy - if err := json.NewDecoder(r.Body).Decode(&p); err != nil { - return nil, nil, NewAppError("CreateAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return &p, BuildResponse(r), nil + return DecodeJSONFromResponse[*AccessControlPolicy](r) } func (c *Client4) GetAccessControlPolicy(ctx context.Context, id string) (*AccessControlPolicy, *Response, error) { @@ -9740,13 +7539,7 @@ func (c *Client4) GetAccessControlPolicy(ctx context.Context, id string) (*Acces return nil, BuildResponse(r), err } defer closeBody(r) - - var policy AccessControlPolicy - if err := json.NewDecoder(r.Body).Decode(&policy); err != nil { - return nil, nil, NewAppError("GetAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return &policy, BuildResponse(r), nil + return DecodeJSONFromResponse[*AccessControlPolicy](r) } func (c *Client4) DeleteAccessControlPolicy(ctx context.Context, id string) (*Response, error) { @@ -9769,63 +7562,30 @@ func (c *Client4) CheckExpression(ctx context.Context, expression string, channe if len(channelId) > 0 && channelId[0] != "" { checkExpressionRequest.ChannelId = channelId[0] } - b, err := json.Marshal(checkExpressionRequest) - if err != nil { - return nil, nil, NewAppError("CheckExpression", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPostBytes(ctx, c.celRoute()+"/check", b) + r, err := c.DoAPIPostJSON(ctx, c.celRoute()+"/check", checkExpressionRequest) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var errors []CELExpressionError - if err := json.NewDecoder(r.Body).Decode(&errors); err != nil { - return nil, nil, NewAppError("CheckExpression", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return errors, BuildResponse(r), nil + return DecodeJSONFromResponse[[]CELExpressionError](r) } func (c *Client4) TestExpression(ctx context.Context, params QueryExpressionParams) (*AccessControlPolicyTestResponse, *Response, error) { - b, err := json.Marshal(params) - if err != nil { - return nil, nil, NewAppError("TestExpression", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPostBytes(ctx, c.celRoute()+"/test", b) + r, err := c.DoAPIPostJSON(ctx, c.celRoute()+"/test", params) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var testResponse AccessControlPolicyTestResponse - if err := json.NewDecoder(r.Body).Decode(&testResponse); err != nil { - return nil, nil, NewAppError("TestExpression", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return &testResponse, BuildResponse(r), nil + return DecodeJSONFromResponse[*AccessControlPolicyTestResponse](r) } func (c *Client4) SearchAccessControlPolicies(ctx context.Context, options AccessControlPolicySearch) (*AccessControlPoliciesWithCount, *Response, error) { - b, err := json.Marshal(options) - if err != nil { - return nil, nil, NewAppError("SearchAccessControlPolicies", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPostBytes(ctx, c.accessControlPoliciesRoute()+"/search", b) + r, err := c.DoAPIPostJSON(ctx, c.accessControlPoliciesRoute()+"/search", options) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var policies AccessControlPoliciesWithCount - if err := json.NewDecoder(r.Body).Decode(&policies); err != nil { - return nil, nil, NewAppError("SearchAccessControlPolicies", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return &policies, BuildResponse(r), nil + return DecodeJSONFromResponse[*AccessControlPoliciesWithCount](r) } func (c *Client4) AssignAccessControlPolicies(ctx context.Context, policyID string, resourceIDs []string) (*Response, error) { @@ -9834,12 +7594,7 @@ func (c *Client4) AssignAccessControlPolicies(ctx context.Context, policyID stri } assignments.ChannelIds = resourceIDs - b, err := json.Marshal(assignments) - if err != nil { - return nil, NewAppError("AssignAccessControlPolicies", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPostBytes(ctx, c.accessControlPolicyRoute(policyID)+"/assign", b) + r, err := c.DoAPIPostJSON(ctx, c.accessControlPolicyRoute(policyID)+"/assign", assignments) if err != nil { return BuildResponse(r), err } @@ -9854,12 +7609,7 @@ func (c *Client4) UnassignAccessControlPolicies(ctx context.Context, policyID st } unassignments.ChannelIds = resourceIDs - b, err := json.Marshal(unassignments) - if err != nil { - return nil, NewAppError("UnassignAccessControlPolicies", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIDeleteBytes(ctx, c.accessControlPolicyRoute(policyID)+"/unassign", b) + r, err := c.DoAPIDeleteJSON(ctx, c.accessControlPolicyRoute(policyID)+"/unassign", unassignments) if err != nil { return BuildResponse(r), err } @@ -9869,36 +7619,22 @@ func (c *Client4) UnassignAccessControlPolicies(ctx context.Context, policyID st } func (c *Client4) GetChannelsForAccessControlPolicy(ctx context.Context, policyID string, after string, limit int) (*ChannelsWithCount, *Response, error) { - r, err := c.DoAPIGet(ctx, c.accessControlPolicyRoute(policyID)+"/resources/channels?after="+after+"&limit="+strconv.Itoa(limit), "") + values := url.Values{} + values.Set("after", after) + values.Set("limit", strconv.Itoa(limit)) + r, err := c.DoAPIGet(ctx, c.accessControlPolicyRoute(policyID)+"/resources/channels?"+values.Encode(), "") if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var channels ChannelsWithCount - if err := json.NewDecoder(r.Body).Decode(&channels); err != nil { - return nil, nil, NewAppError("GetChannelsForAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return &channels, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelsWithCount](r) } func (c *Client4) SearchChannelsForAccessControlPolicy(ctx context.Context, policyID string, options ChannelSearch) (*ChannelsWithCount, *Response, error) { - b, err := json.Marshal(options) - if err != nil { - return nil, nil, NewAppError("SearchChannelsForAccessControlPolicy", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - r, err := c.DoAPIPostBytes(ctx, c.accessControlPolicyRoute(policyID)+"/resources/channels/search", b) + r, err := c.DoAPIPostJSON(ctx, c.accessControlPolicyRoute(policyID)+"/resources/channels/search", options) if err != nil { return nil, BuildResponse(r), err } defer closeBody(r) - - var channels ChannelsWithCount - if err := json.NewDecoder(r.Body).Decode(&channels); err != nil { - return nil, nil, NewAppError("SearchChannelsForAccessControlPolicy", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - return &channels, BuildResponse(r), nil + return DecodeJSONFromResponse[*ChannelsWithCount](r) } diff --git a/server/public/model/client4_test.go b/server/public/model/client4_test.go index 542dd279da9..5fb2ac25001 100644 --- a/server/public/model/client4_test.go +++ b/server/public/model/client4_test.go @@ -17,6 +17,7 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // https://github.com/mattermost/mattermost-plugin-starter-template/issues/115 @@ -380,3 +381,43 @@ func ExampleClient4_GetUsers() { page++ } } + +func TestBuildResponse(t *testing.T) { + t.Run("handles nil http.Response", func(t *testing.T) { + response := model.BuildResponse(nil) + assert.Nil(t, response) + }) + t.Run("builds response from http.Response", func(t *testing.T) { + httpResp := &http.Response{ + StatusCode: http.StatusOK, + Header: make(http.Header), + } + httpResp.Header.Set(model.HeaderRequestId, "test-request-id") + httpResp.Header.Set(model.HeaderEtagServer, "test-etag") + httpResp.Header.Set(model.HeaderVersionId, "test-version") + + response := model.BuildResponse(httpResp) + require.NotNil(t, response) + + assert.Equal(t, http.StatusOK, response.StatusCode) + assert.Equal(t, "test-request-id", response.RequestId) + assert.Equal(t, "test-etag", response.Etag) + assert.Equal(t, "test-version", response.ServerVersion) + assert.Equal(t, httpResp.Header, response.Header) + }) + t.Run("handles response with empty headers", func(t *testing.T) { + httpResp := &http.Response{ + StatusCode: http.StatusNoContent, + Header: http.Header{}, + } + + response := model.BuildResponse(httpResp) + require.NotNil(t, response) + + assert.Equal(t, http.StatusNoContent, response.StatusCode) + assert.Empty(t, response.RequestId) + assert.Empty(t, response.Etag) + assert.Empty(t, response.ServerVersion) + assert.Equal(t, httpResp.Header, response.Header) + }) +}