diff --git a/Makefile b/Makefile index 5b159471d0..08c3896881 100644 --- a/Makefile +++ b/Makefile @@ -154,7 +154,7 @@ govet: ## Runs govet against all packages. env GO111MODULE=off $(GO) get golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow $(GO) vet $(GOFLAGS) $(ALL_PACKAGES) || exit 1 $(GO) vet -vettool=$(GOPATH)/bin/shadow $(GOFLAGS) $(ALL_PACKAGES) || exit 1 - $(GO) run $(GOFLAGS) plugin/checker/main.go + $(GO) run $(GOFLAGS) ./plugin/checker gofmt: ## Runs gofmt against all packages. @echo Running GOFMT @@ -173,17 +173,15 @@ gofmt: ## Runs gofmt against all packages. done @echo "gofmt success"; \ -golangci-lint: +golangci-lint: ## Run golangci-lint on codebasis # https://stackoverflow.com/a/677212/1027058 (check if a command exists or not) -# https://github.com/golangci/golangci-lint#binary-release -# It is recommended to NOT use go get, but instead use a binary release pinned to a version. - @if ! [ -x "$$(command -v golangci-lint)" ]; then \ - echo "golangci-lint is not installed. Please run: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(GOPATH)/bin v1.21.0"; \ + @if ! [ -x "$$(command -v golangci-lintt)" ]; then \ + echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install for installation instructions."; \ exit 1; \ fi; \ @echo Running golangci-lint - $(GOPATH)/bin/golangci-lint run + golangci-lint run megacheck: ## Run megacheck on codebasis env GO111MODULE=off go get -u honnef.co/go/tools/cmd/megacheck diff --git a/api4/emoji_test.go b/api4/emoji_test.go index 0769cb7561..68b05bafb8 100644 --- a/api4/emoji_test.go +++ b/api4/emoji_test.go @@ -9,12 +9,12 @@ import ( _ "image/gif" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestCreateEmoji(t *testing.T) { @@ -46,9 +46,7 @@ func TestCreateEmoji(t *testing.T) { // try to create a valid gif emoji when they're enabled newEmoji, resp := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif") CheckNoError(t, resp) - if newEmoji.Name != emoji.Name { - t.Fatal("create with wrong name") - } + require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name") // try to create an emoji with a duplicate name emoji2 := &model.Emoji{ @@ -67,9 +65,7 @@ func TestCreateEmoji(t *testing.T) { newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 10, 10, 10), "image.gif") CheckNoError(t, resp) - if newEmoji.Name != emoji.Name { - t.Fatal("create with wrong name") - } + require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name") // try to create a valid jpeg emoji emoji = &model.Emoji{ @@ -79,9 +75,7 @@ func TestCreateEmoji(t *testing.T) { newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestJpeg(t, 10, 10), "image.gif") CheckNoError(t, resp) - if newEmoji.Name != emoji.Name { - t.Fatal("create with wrong name") - } + require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name") // try to create a valid png emoji emoji = &model.Emoji{ @@ -91,9 +85,7 @@ func TestCreateEmoji(t *testing.T) { newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestPng(t, 10, 10), "image.gif") CheckNoError(t, resp) - if newEmoji.Name != emoji.Name { - t.Fatal("create with wrong name") - } + require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name") // try to create an emoji that's too wide emoji = &model.Emoji{ @@ -103,9 +95,7 @@ func TestCreateEmoji(t *testing.T) { newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 1000, 10), "image.gif") CheckNoError(t, resp) - if newEmoji.Name != emoji.Name { - t.Fatal("create with wrong name") - } + require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name") // try to create an emoji that's too wide emoji = &model.Emoji{ @@ -114,9 +104,7 @@ func TestCreateEmoji(t *testing.T) { } newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, app.MaxEmojiOriginalWidth+1), "image.gif") - if resp.Error == nil { - t.Fatal("should fail - emoji is too wide") - } + require.Error(t, resp.Error, "should fail - emoji is too wide") // try to create an emoji that's too tall emoji = &model.Emoji{ @@ -125,9 +113,7 @@ func TestCreateEmoji(t *testing.T) { } newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, app.MaxEmojiOriginalHeight+1, 10), "image.gif") - if resp.Error == nil { - t.Fatal("should fail - emoji is too tall") - } + require.Error(t, resp.Error, "should fail - emoji is too tall") // try to create an emoji that's too large emoji = &model.Emoji{ @@ -136,9 +122,7 @@ func TestCreateEmoji(t *testing.T) { } _, resp = Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 100, 100, 10000), "image.gif") - if resp.Error == nil { - t.Fatal("should fail - emoji is too big") - } + require.Error(t, resp.Error, "should fail - emoji is too big") // try to create an emoji with data that isn't an image emoji = &model.Emoji{ @@ -224,9 +208,7 @@ func TestGetEmojiList(t *testing.T) { break } } - if !found { - t.Fatalf("failed to get emoji with id %v, %v", emoji.Id, len(listEmoji)) - } + require.Truef(t, found, "failed to get emoji with id %v, %v", emoji.Id, len(listEmoji)) } _, resp = Client.DeleteEmoji(emojis[0].Id) @@ -245,16 +227,12 @@ func TestGetEmojiList(t *testing.T) { listEmoji, resp = Client.GetEmojiList(0, 1) CheckNoError(t, resp) - if len(listEmoji) != 1 { - t.Fatal("should only return 1") - } + require.Len(t, listEmoji, 1, "should only return 1") listEmoji, resp = Client.GetSortedEmojiList(0, 100, model.EMOJI_SORT_BY_NAME) CheckNoError(t, resp) - if len(listEmoji) == 0 { - t.Fatal("should return more than 0") - } + require.Greater(t, len(listEmoji), 0, "should return more than 0") } func TestDeleteEmoji(t *testing.T) { @@ -283,14 +261,11 @@ func TestDeleteEmoji(t *testing.T) { ok, resp := Client.DeleteEmoji(newEmoji.Id) CheckNoError(t, resp) - if !ok { - t.Fatal("should return true") - } else { - _, err := Client.GetEmoji(newEmoji.Id) - if err == nil { - t.Fatal("should not return the emoji it was deleted") - } - } + require.True(t, ok, "delete did not return OK") + + _, resp = Client.GetEmoji(newEmoji.Id) + require.NotNil(t, resp, "nil response") + require.Error(t, resp.Error, "expected error fetching deleted emoji") //Admin can delete other users emoji newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif") @@ -298,14 +273,11 @@ func TestDeleteEmoji(t *testing.T) { ok, resp = th.SystemAdminClient.DeleteEmoji(newEmoji.Id) CheckNoError(t, resp) - if !ok { - t.Fatal("should return true") - } else { - _, err := th.SystemAdminClient.GetEmoji(newEmoji.Id) - if err == nil { - t.Fatal("should not return the emoji it was deleted") - } - } + require.True(t, ok, "delete did not return OK") + + _, resp = th.SystemAdminClient.GetEmoji(newEmoji.Id) + require.NotNil(t, resp, "nil response") + require.Error(t, resp.Error, "expected error fetching deleted emoji") // Try to delete just deleted emoji _, resp = Client.DeleteEmoji(newEmoji.Id) @@ -445,9 +417,7 @@ func TestGetEmoji(t *testing.T) { emoji, resp = Client.GetEmoji(newEmoji.Id) CheckNoError(t, resp) - if emoji.Id != newEmoji.Id { - t.Fatal("wrong emoji was returned") - } + require.Equal(t, newEmoji.Id, emoji.Id, "wrong emoji was returned") _, resp = Client.GetEmoji(model.NewId()) CheckNotFoundStatus(t, resp) @@ -506,15 +476,11 @@ func TestGetEmojiImage(t *testing.T) { emojiImage, resp := Client.GetEmojiImage(emoji1.Id) CheckNoError(t, resp) - if len(emojiImage) <= 0 { - t.Fatal("should return the image") - } + require.Greater(t, len(emojiImage), 0, "should return the image") + _, imageType, err := image.DecodeConfig(bytes.NewReader(emojiImage)) - if err != nil { - t.Fatalf("unable to identify received image: %v", err.Error()) - } else if imageType != "gif" { - t.Fatal("should've received gif data") - } + require.NoError(t, err) + require.Equal(t, imageType, "gif", "expected gif") emoji2 := &model.Emoji{ CreatorId: th.BasicUser.Id, @@ -526,15 +492,11 @@ func TestGetEmojiImage(t *testing.T) { emojiImage, resp = Client.GetEmojiImage(emoji2.Id) CheckNoError(t, resp) - if len(emojiImage) <= 0 { - t.Fatal("should return the image") - } + require.Greater(t, len(emojiImage), 0, "no image returned") + _, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage)) - if err != nil { - t.Fatalf("unable to identify received image: %v", err.Error()) - } else if imageType != "gif" { - t.Fatal("should've received gif data") - } + require.NoError(t, err, "unable to indentify received image") + require.Equal(t, imageType, "gif", "expected gif") emoji3 := &model.Emoji{ CreatorId: th.BasicUser.Id, @@ -545,15 +507,11 @@ func TestGetEmojiImage(t *testing.T) { emojiImage, resp = Client.GetEmojiImage(emoji3.Id) CheckNoError(t, resp) - if len(emojiImage) <= 0 { - t.Fatal("should return the image") - } + require.Greater(t, len(emojiImage), 0, "no image returned") + _, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage)) - if err != nil { - t.Fatalf("unable to identify received image: %v", err.Error()) - } else if imageType != "jpeg" { - t.Fatal("should've received gif data") - } + require.NoError(t, err, "unable to indentify received image") + require.Equal(t, imageType, "jpeg", "expected jpeg") emoji4 := &model.Emoji{ CreatorId: th.BasicUser.Id, @@ -564,15 +522,11 @@ func TestGetEmojiImage(t *testing.T) { emojiImage, resp = Client.GetEmojiImage(emoji4.Id) CheckNoError(t, resp) - if len(emojiImage) <= 0 { - t.Fatal("should return the image") - } + require.Greater(t, len(emojiImage), 0, "no image returned") + _, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage)) - if err != nil { - t.Fatalf("unable to identify received image: %v", err.Error()) - } else if imageType != "png" { - t.Fatal("should've received gif data") - } + require.NoError(t, err, "unable to idenitify received image") + require.Equal(t, imageType, "png", "expected png") _, resp = Client.DeleteEmoji(emoji4.Id) CheckNoError(t, resp) diff --git a/api4/integration_action_test.go b/api4/integration_action_test.go index 7b629f4a04..c543d851d2 100644 --- a/api4/integration_action_test.go +++ b/api4/integration_action_test.go @@ -27,8 +27,11 @@ func (th *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { assert.NotEmpty(th.t, string(bb)) poir := model.PostActionIntegrationRequestFromJson(bytes.NewReader(bb)) assert.NotEmpty(th.t, poir.UserId) + assert.NotEmpty(th.t, poir.UserName) assert.NotEmpty(th.t, poir.ChannelId) - assert.Empty(th.t, poir.TeamId) + assert.NotEmpty(th.t, poir.ChannelName) + assert.NotEmpty(th.t, poir.TeamId) + assert.NotEmpty(th.t, poir.TeamName) assert.NotEmpty(th.t, poir.PostId) assert.NotEmpty(th.t, poir.TriggerId) assert.Equal(th.t, "button", poir.Type) diff --git a/api4/license_test.go b/api4/license_test.go index a71b0ae72e..97c7c24501 100644 --- a/api4/license_test.go +++ b/api4/license_test.go @@ -4,6 +4,8 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" + "github.com/mattermost/mattermost-server/model" ) @@ -15,22 +17,22 @@ func TestGetOldClientLicense(t *testing.T) { license, resp := Client.GetOldClientLicense("") CheckNoError(t, resp) - if len(license["IsLicensed"]) == 0 { - t.Fatal("license not returned correctly") - } + require.NotEqual(t, license["IsLicensed"], "", "license not returned correctly") Client.Logout() _, resp = Client.GetOldClientLicense("") CheckNoError(t, resp) - if _, err := Client.DoApiGet("/license/client", ""); err == nil || err.StatusCode != http.StatusNotImplemented { - t.Fatal("should have errored with 501") - } + _, err := Client.DoApiGet("/license/client", "") + require.Error(t, err, "get /license/client did not return an error") + require.Equal(t, err.StatusCode, http.StatusNotImplemented, + "expected 501 Not Implemented") - if _, err := Client.DoApiGet("/license/client?format=junk", ""); err == nil || err.StatusCode != http.StatusBadRequest { - t.Fatal("should have errored with 400") - } + _, err = Client.DoApiGet("/license/client?format=junk", "") + require.Error(t, err, "get /license/client?format=junk did not return an error") + require.Equal(t, err.StatusCode, http.StatusBadRequest, + "expected 400 Bad Request") license, resp = th.SystemAdminClient.GetOldClientLicense("") CheckNoError(t, resp) diff --git a/api4/oauth_test.go b/api4/oauth_test.go index 184e146b43..4d4c5e5ced 100644 --- a/api4/oauth_test.go +++ b/api4/oauth_test.go @@ -6,10 +6,11 @@ package api4 import ( "io/ioutil" "net/http" - "strconv" "testing" "github.com/mattermost/mattermost-server/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestCreateOAuthApp(t *testing.T) { @@ -35,21 +36,14 @@ func TestCreateOAuthApp(t *testing.T) { rapp, resp := AdminClient.CreateOAuthApp(oapp) CheckNoError(t, resp) CheckCreatedStatus(t, resp) - - if rapp.Name != oapp.Name { - t.Fatal("names did not match") - } - - if rapp.IsTrusted != oapp.IsTrusted { - t.Fatal("trusted did no match") - } + assert.Equal(t, oapp.Name, rapp.Name, "names did not match") + assert.Equal(t, oapp.IsTrusted, rapp.IsTrusted, "trusted did no match") // Revoke permission from regular users. th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) _, resp = Client.CreateOAuthApp(oapp) CheckForbiddenStatus(t, resp) - // Grant permission to regular users. th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) @@ -57,23 +51,15 @@ func TestCreateOAuthApp(t *testing.T) { CheckNoError(t, resp) CheckCreatedStatus(t, resp) - if rapp.IsTrusted { - t.Fatal("trusted should be false - created by non admin") - } + assert.False(t, rapp.IsTrusted, "trusted should be false - created by non admin") oapp.Name = "" _, resp = AdminClient.CreateOAuthApp(oapp) CheckBadRequestStatus(t, resp) - if r, err := Client.DoApiPost("/oauth/apps", "garbage"); err == nil { - t.Fatal("should have failed") - } else { - if r.StatusCode != http.StatusBadRequest { - t.Log("actual: " + strconv.Itoa(r.StatusCode)) - t.Log("expected: " + strconv.Itoa(http.StatusBadRequest)) - t.Fatal("wrong status code") - } - } + r, err := Client.DoApiPost("/oauth/apps", "garbage") + require.Error(t, err, "expected error from garbage post") + assert.Equal(t, http.StatusBadRequest, r.StatusCode) Client.Logout() _, resp = Client.CreateOAuthApp(oapp) @@ -122,54 +108,22 @@ func TestUpdateOAuthApp(t *testing.T) { updatedApp, resp := AdminClient.UpdateOAuthApp(oapp) CheckNoError(t, resp) - - if updatedApp.Id != oapp.Id { - t.Fatal("Id should have not updated") - } - - if updatedApp.CreatorId != oapp.CreatorId { - t.Fatal("CreatorId should have not updated") - } - - if updatedApp.CreateAt != oapp.CreateAt { - t.Fatal("CreateAt should have not updated") - } - - if updatedApp.UpdateAt == oapp.UpdateAt { - t.Fatal("UpdateAt should have updated") - } - - if updatedApp.ClientSecret != oapp.ClientSecret { - t.Fatal("ClientSecret should have not updated") - } - - if updatedApp.Name != oapp.Name { - t.Fatal("Name should have updated") - } - - if updatedApp.Description != oapp.Description { - t.Fatal("Description should have updated") - } - - if updatedApp.IconURL != oapp.IconURL { - t.Fatal("IconURL should have updated") - } + assert.Equal(t, oapp.Id, updatedApp.Id, "Id should have not updated") + assert.Equal(t, oapp.CreatorId, updatedApp.CreatorId, "CreatorId should have not updated") + assert.Equal(t, oapp.CreateAt, updatedApp.CreateAt, "CreateAt should have not updated") + assert.NotEqual(t, oapp.UpdateAt, updatedApp.UpdateAt, "UpdateAt should have updated") + assert.Equal(t, oapp.ClientSecret, updatedApp.ClientSecret, "ClientSecret should have not updated") + assert.Equal(t, oapp.Name, updatedApp.Name, "Name should have updated") + assert.Equal(t, oapp.Description, updatedApp.Description, "Description should have updated") + assert.Equal(t, oapp.IconURL, updatedApp.IconURL, "IconURL should have updated") if len(updatedApp.CallbackUrls) == len(oapp.CallbackUrls) { for i, callbackUrl := range updatedApp.CallbackUrls { - if callbackUrl != oapp.CallbackUrls[i] { - t.Fatal("Description should have updated") - } + assert.Equal(t, oapp.CallbackUrls[i], callbackUrl, "Description should have updated") } } - - if updatedApp.Homepage != oapp.Homepage { - t.Fatal("Homepage should have updated") - } - - if updatedApp.IsTrusted != oapp.IsTrusted { - t.Fatal("IsTrusted should have updated") - } + assert.Equal(t, oapp.Homepage, updatedApp.Homepage, "Homepage should have updated") + assert.Equal(t, oapp.IsTrusted, updatedApp.IsTrusted, "IsTrusted should have updated") th.LoginBasic2() updatedApp.CreatorId = th.BasicUser2.Id @@ -241,24 +195,16 @@ func TestGetOAuthApps(t *testing.T) { found2 = true } } - - if !found1 || !found2 { - t.Fatal("missing oauth app") - } + assert.Truef(t, found1, "missing oauth app %v", rapp.Id) + assert.Truef(t, found2, "missing oauth app %v", rapp2.Id) apps, resp = AdminClient.GetOAuthApps(1, 1) CheckNoError(t, resp) - - if len(apps) != 1 { - t.Fatal("paging failed") - } + require.Equal(t, 1, len(apps), "paging failed") apps, resp = Client.GetOAuthApps(0, 1000) CheckNoError(t, resp) - - if len(apps) != 1 && apps[0].Id != rapp2.Id { - t.Fatal("wrong apps returned") - } + require.True(t, len(apps) == 1 || apps[0].Id == rapp2.Id, "wrong apps returned") // Revoke permission from regular users. th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID) @@ -304,25 +250,13 @@ func TestGetOAuthApp(t *testing.T) { rrapp, resp := AdminClient.GetOAuthApp(rapp.Id) CheckNoError(t, resp) - - if rapp.Id != rrapp.Id { - t.Fatal("wrong app") - } - - if rrapp.ClientSecret == "" { - t.Fatal("should not be sanitized") - } + assert.Equal(t, rapp.Id, rrapp.Id, "wrong app") + assert.NotEqual(t, "", rrapp.ClientSecret, "should not be sanitized") rrapp2, resp := AdminClient.GetOAuthApp(rapp2.Id) CheckNoError(t, resp) - - if rapp2.Id != rrapp2.Id { - t.Fatal("wrong app") - } - - if rrapp2.ClientSecret == "" { - t.Fatal("should not be sanitized") - } + assert.Equal(t, rapp2.Id, rrapp2.Id, "wrong app") + assert.NotEqual(t, "", rrapp2.ClientSecret, "should not be sanitized") _, resp = Client.GetOAuthApp(rapp2.Id) CheckNoError(t, resp) @@ -380,25 +314,13 @@ func TestGetOAuthAppInfo(t *testing.T) { rrapp, resp := AdminClient.GetOAuthAppInfo(rapp.Id) CheckNoError(t, resp) - - if rapp.Id != rrapp.Id { - t.Fatal("wrong app") - } - - if rrapp.ClientSecret != "" { - t.Fatal("should be sanitized") - } + assert.Equal(t, rapp.Id, rrapp.Id, "wrong app") + assert.Equal(t, "", rrapp.ClientSecret, "should be sanitized") rrapp2, resp := AdminClient.GetOAuthAppInfo(rapp2.Id) CheckNoError(t, resp) - - if rapp2.Id != rrapp2.Id { - t.Fatal("wrong app") - } - - if rrapp2.ClientSecret != "" { - t.Fatal("should be sanitized") - } + assert.Equal(t, rapp2.Id, rrapp2.Id, "wrong app") + assert.Equal(t, "", rrapp2.ClientSecret, "should be sanitized") _, resp = Client.GetOAuthAppInfo(rapp2.Id) CheckNoError(t, resp) @@ -456,10 +378,7 @@ func TestDeleteOAuthApp(t *testing.T) { pass, resp := AdminClient.DeleteOAuthApp(rapp.Id) CheckNoError(t, resp) - - if !pass { - t.Fatal("should have passed") - } + assert.True(t, pass, "should have passed") _, resp = AdminClient.DeleteOAuthApp(rapp2.Id) CheckNoError(t, resp) @@ -526,14 +445,8 @@ func TestRegenerateOAuthAppSecret(t *testing.T) { rrapp, resp := AdminClient.RegenerateOAuthAppSecret(rapp.Id) CheckNoError(t, resp) - - if rrapp.Id != rapp.Id { - t.Fatal("wrong app") - } - - if rrapp.ClientSecret == rapp.ClientSecret { - t.Fatal("secret didn't change") - } + assert.Equal(t, rrapp.Id, rapp.Id, "wrong app") + assert.NotEqual(t, rapp.ClientSecret, rrapp.ClientSecret, "secret didn't change") _, resp = AdminClient.RegenerateOAuthAppSecret(rapp2.Id) CheckNoError(t, resp) @@ -608,15 +521,9 @@ func TestGetAuthorizedOAuthAppsForUser(t *testing.T) { if a.Id == rapp.Id { found = true } - - if a.ClientSecret != "" { - t.Fatal("not sanitized") - } - } - - if !found { - t.Fatal("missing app") + assert.Equal(t, "", a.ClientSecret, "not sanitized") } + require.True(t, found, "missing app") _, resp = Client.GetAuthorizedOAuthAppsForUser(th.BasicUser2.Id, 0, 1000) CheckForbiddenStatus(t, resp) diff --git a/api4/openGraph_test.go b/api4/openGraph_test.go index db4fb29160..c4be33f25b 100644 --- a/api4/openGraph_test.go +++ b/api4/openGraph_test.go @@ -7,10 +7,10 @@ import ( "fmt" "net/http" "net/http/httptest" - "strings" - "testing" + "github.com/stretchr/testify/require" + "github.com/mattermost/mattermost-server/model" ) @@ -61,19 +61,12 @@ func TestGetOpenGraphMetadata(t *testing.T) { openGraph, resp := Client.OpenGraph(ts.URL + data["path"].(string)) CheckNoError(t, resp) - if strings.Compare(openGraph["title"], data["title"].(string)) != 0 { - t.Fatal(fmt.Sprintf( - "OG data title mismatch for path \"%s\". Expected title: \"%s\". Actual title: \"%s\"", - data["path"].(string), data["title"].(string), openGraph["title"], - )) - } - if ogDataCacheMissCount != data["cacheMissCount"].(int) { - t.Fatal(fmt.Sprintf( - "Cache miss count didn't match. Expected value %d. Actual value %d.", - data["cacheMissCount"].(int), ogDataCacheMissCount, - )) - } + require.Equalf(t, openGraph["title"], data["title"].(string), + "OG data title mismatch for path \"%s\".") + + require.Equal(t, ogDataCacheMissCount, data["cacheMissCount"].(int), + "Cache miss count didn't match.") } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableLinkPreviews = false }) diff --git a/api4/user.go b/api4/user.go index 81fda670f5..06c44975fc 100644 --- a/api4/user.go +++ b/api4/user.go @@ -1399,7 +1399,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAuditWithUserId(user.Id, "authenticated") - session, err := c.App.DoLogin(w, r, user, deviceId) + err = c.App.DoLogin(w, r, user, deviceId) if err != nil { c.Err = err return @@ -1408,7 +1408,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAuditWithUserId(user.Id, "success") if r.Header.Get(model.HEADER_REQUESTED_WITH) == model.HEADER_REQUESTED_WITH_XML { - c.App.AttachSessionCookies(w, r, session) + c.App.AttachSessionCookies(w, r) } userTermsOfService, err := c.App.GetUserTermsOfService(user.Id) diff --git a/api4/webhook_test.go b/api4/webhook_test.go index ac7b85fae5..0ffaf44776 100644 --- a/api4/webhook_test.go +++ b/api4/webhook_test.go @@ -33,17 +33,9 @@ func TestCreateIncomingWebhook(t *testing.T) { rhook, resp := th.SystemAdminClient.CreateIncomingWebhook(hook) CheckNoError(t, resp) - if rhook.ChannelId != hook.ChannelId { - t.Fatal("channel ids didn't match") - } - - if rhook.UserId != th.SystemAdminUser.Id { - t.Fatal("user ids didn't match") - } - - if rhook.TeamId != th.BasicTeam.Id { - t.Fatal("team ids didn't match") - } + require.Equal(t, hook.ChannelId, rhook.ChannelId, "channel ids didn't match") + require.Equal(t, th.SystemAdminUser.Id, rhook.UserId, "user ids didn't match") + require.Equal(t, th.BasicTeam.Id, rhook.TeamId, "team ids didn't match") hook.ChannelId = "junk" _, resp = th.SystemAdminClient.CreateIncomingWebhook(hook) @@ -136,16 +128,12 @@ func TestGetIncomingWebhooks(t *testing.T) { } } - if !found { - t.Fatal("missing hook") - } + require.True(t, found, "missing hook") hooks, resp = th.SystemAdminClient.GetIncomingWebhooks(0, 1, "") CheckNoError(t, resp) - if len(hooks) != 1 { - t.Fatal("should only be 1") - } + require.Len(t, hooks, 1, "should only be 1 hook") hooks, resp = th.SystemAdminClient.GetIncomingWebhooksForTeam(th.BasicTeam.Id, 0, 1000, "") CheckNoError(t, resp) @@ -157,16 +145,12 @@ func TestGetIncomingWebhooks(t *testing.T) { } } - if !found { - t.Fatal("missing hook") - } + require.True(t, found, "missing hook") hooks, resp = th.SystemAdminClient.GetIncomingWebhooksForTeam(model.NewId(), 0, 1000, "") CheckNoError(t, resp) - if len(hooks) != 0 { - t.Fatal("no hooks should be returned") - } + require.Len(t, hooks, 0, "no hooks should be returned") _, resp = Client.GetIncomingWebhooks(0, 1000, "") CheckForbiddenStatus(t, resp) @@ -335,11 +319,10 @@ func TestDeleteIncomingWebhook(t *testing.T) { rhook, resp = Client.CreateIncomingWebhook(hook) CheckNoError(t, resp) - if status, resp = Client.DeleteIncomingWebhook(rhook.Id); !status { - t.Fatal("Delete should have succeeded") - } else { - CheckOKStatus(t, resp) - } + status, resp = Client.DeleteIncomingWebhook(rhook.Id) + require.True(t, status, "Delete should have succeeded") + + CheckOKStatus(t, resp) // Get now should not return this deleted hook _, resp = Client.GetIncomingWebhook(rhook.Id, "") @@ -378,13 +361,9 @@ func TestCreateOutgoingWebhook(t *testing.T) { rhook, resp := th.SystemAdminClient.CreateOutgoingWebhook(hook) CheckNoError(t, resp) - if rhook.ChannelId != hook.ChannelId { - t.Fatal("channel ids didn't match") - } else if rhook.CreatorId != th.SystemAdminUser.Id { - t.Fatal("user ids didn't match") - } else if rhook.TeamId != th.BasicChannel.TeamId { - t.Fatal("team ids didn't match") - } + assert.Equal(t, hook.ChannelId, rhook.ChannelId, "channel ids didn't match") + assert.Equal(t, th.SystemAdminUser.Id, rhook.CreatorId, "user ids didn't match") + assert.Equal(t, th.BasicChannel.TeamId, rhook.TeamId, "team ids didn't match") hook.ChannelId = "junk" _, resp = th.SystemAdminClient.CreateOutgoingWebhook(hook) @@ -436,16 +415,12 @@ func TestGetOutgoingWebhooks(t *testing.T) { } } - if !found { - t.Fatal("missing hook") - } + require.True(t, found, "missing hook") hooks, resp = th.SystemAdminClient.GetOutgoingWebhooks(0, 1, "") CheckNoError(t, resp) - if len(hooks) != 1 { - t.Fatal("should only be 1") - } + require.Len(t, hooks, 1, "should only be 1 hook") hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForTeam(th.BasicTeam.Id, 0, 1000, "") CheckNoError(t, resp) @@ -457,16 +432,12 @@ func TestGetOutgoingWebhooks(t *testing.T) { } } - if !found { - t.Fatal("missing hook") - } + require.True(t, found, "missing hook") hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForTeam(model.NewId(), 0, 1000, "") CheckNoError(t, resp) - if len(hooks) != 0 { - t.Fatal("no hooks should be returned") - } + require.Len(t, hooks, 0, "no hooks should be returned") hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForChannel(th.BasicChannel.Id, 0, 1000, "") CheckNoError(t, resp) @@ -478,9 +449,7 @@ func TestGetOutgoingWebhooks(t *testing.T) { } } - if !found { - t.Fatal("missing hook") - } + require.True(t, found, "missing hook") _, resp = th.SystemAdminClient.GetOutgoingWebhooksForChannel(model.NewId(), 0, 1000, "") CheckForbiddenStatus(t, resp) @@ -647,9 +616,8 @@ func TestGetOutgoingWebhook(t *testing.T) { getHook, resp := th.SystemAdminClient.GetOutgoingWebhook(rhook.Id) CheckNoError(t, resp) - if getHook.Id != rhook.Id { - t.Fatal("failed to retrieve the correct outgoing hook") - } + + require.Equal(t, getHook.Id, rhook.Id, "failed to retrieve the correct outgoing hook") _, resp = Client.GetOutgoingWebhook(rhook.Id) CheckForbiddenStatus(t, resp) @@ -694,29 +662,13 @@ func TestUpdateIncomingHook(t *testing.T) { updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook) CheckNoError(t, resp) - if updatedHook != nil { - if updatedHook.DisplayName != "hook2" { - t.Fatal("Hook name is not updated") - } - if updatedHook.Description != "description" { - t.Fatal("Hook description is not updated") - } - - if updatedHook.ChannelId != th.BasicChannel2.Id { - t.Fatal("Hook channel is not updated") - } - - if updatedHook.Username != "" { - t.Fatal("Hook username was incorrectly updated") - } - - if updatedHook.IconURL != "" { - t.Fatal("Hook icon was incorrectly updated") - } - } else { - t.Fatal("should not be nil") - } + require.NotNil(t, updatedHook, "should not be nil") + require.Exactly(t, "hook2", updatedHook.DisplayName, "Hook name is not updated") + require.Exactly(t, "description", updatedHook.Description, "Hook description is not updated") + require.Equal(t, updatedHook.ChannelId, th.BasicChannel2.Id, "Hook channel is not updated") + require.Empty(t, updatedHook.Username, "Hook username was incorrectly updated") + require.Empty(t, updatedHook.IconURL, "Hook icon was incorrectly updated") //updatedHook, _ = th.App.GetIncomingWebhook(createdHook.Id) assert.Equal(t, updatedHook.ChannelId, createdHook.ChannelId) @@ -734,29 +686,13 @@ func TestUpdateIncomingHook(t *testing.T) { updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook) CheckNoError(t, resp) - if updatedHook != nil { - if updatedHook.DisplayName != "hook2" { - t.Fatal("Hook name is not updated") - } - if updatedHook.Description != "description" { - t.Fatal("Hook description is not updated") - } - - if updatedHook.ChannelId != th.BasicChannel2.Id { - t.Fatal("Hook channel is not updated") - } - - if updatedHook.Username != "username" { - t.Fatal("Hook username is not updated") - } - - if updatedHook.IconURL != "icon" { - t.Fatal("Hook icon is not updated") - } - } else { - t.Fatal("should not be nil") - } + require.NotNil(t, updatedHook, "should not be nil") + require.Exactly(t, "hook2", updatedHook.DisplayName, "Hook name is not updated") + require.Exactly(t, "description", updatedHook.Description, "Hook description is not updated") + require.Equal(t, updatedHook.ChannelId, th.BasicChannel2.Id, "Hook channel is not updated") + require.Exactly(t, "username", updatedHook.Username, "Hook username is not updated") + require.Exactly(t, "icon", updatedHook.IconURL, "Hook icon is not updated") //updatedHook, _ = th.App.GetIncomingWebhook(createdHook.Id) assert.Equal(t, updatedHook.ChannelId, createdHook.ChannelId) @@ -781,13 +717,8 @@ func TestUpdateIncomingHook(t *testing.T) { updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook) CheckNoError(t, resp) - if updatedHook != nil { - if updatedHook.UpdateAt == createdHook.UpdateAt { - t.Fatal("failed - hook updateAt is not updated") - } - } else { - t.Fatal("should not be nil") - } + require.NotNil(t, updatedHook, "should not be nil") + require.NotEqual(t, createdHook.UpdateAt, updatedHook.UpdateAt, "failed - hook updateAt is not updated") }) t.Run("UpdateNonExistentHook", func(t *testing.T) { @@ -837,9 +768,7 @@ func TestUpdateIncomingHook(t *testing.T) { t.Run("UpdateByDifferentUser", func(t *testing.T) { updatedHook, resp := Client.UpdateIncomingWebhook(createdHook) CheckNoError(t, resp) - if updatedHook.UserId == th.BasicUser2.Id { - t.Fatal("Hook's creator userId is not retained") - } + require.NotEqual(t, th.BasicUser2.Id, updatedHook.UserId, "Hook's creator userId is not retained") }) t.Run("IncomingHooksDisabled", func(t *testing.T) { @@ -932,9 +861,7 @@ func TestRegenOutgoingHookToken(t *testing.T) { regenHookToken, resp := th.SystemAdminClient.RegenOutgoingHookToken(rhook.Id) CheckNoError(t, resp) - if regenHookToken.Token == rhook.Token { - t.Fatal("regen didn't work properly") - } + require.NotEqual(t, rhook.Token, regenHookToken.Token, "regen didn't work properly") _, resp = Client.RegenOutgoingHookToken(rhook.Id) CheckForbiddenStatus(t, resp) @@ -969,12 +896,9 @@ func TestUpdateOutgoingHook(t *testing.T) { updatedHook, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook) CheckNoError(t, resp) - if updatedHook.DisplayName != "Cats" { - t.Fatal("did not update") - } - if updatedHook.Description != "Get me some cats" { - t.Fatal("did not update") - } + + require.Exactly(t, "Cats", updatedHook.DisplayName, "did not update") + require.Exactly(t, "Get me some cats", updatedHook.Description, "did not update") }) t.Run("OutgoingHooksDisabled", func(t *testing.T) { @@ -995,9 +919,7 @@ func TestUpdateOutgoingHook(t *testing.T) { updatedHook2, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook2) CheckNoError(t, resp) - if updatedHook2.CreateAt != createdHook2.CreateAt { - t.Fatal("failed - hook create at should not be changed") - } + require.Equal(t, createdHook2.CreateAt, updatedHook2.CreateAt, "failed - hook create at should not be changed") }) t.Run("ModifyUpdateAt", func(t *testing.T) { @@ -1006,9 +928,7 @@ func TestUpdateOutgoingHook(t *testing.T) { updatedHook2, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook) CheckNoError(t, resp) - if updatedHook2.UpdateAt == createdHook.UpdateAt { - t.Fatal("failed - hook updateAt is not updated") - } + require.NotEqual(t, createdHook.UpdateAt, updatedHook2.UpdateAt, "failed - hook updateAt is not updated") }) t.Run("UpdateNonExistentHook", func(t *testing.T) { @@ -1048,12 +968,9 @@ func TestUpdateOutgoingHook(t *testing.T) { createdHook.DisplayName = "Basic user 2" updatedHook, resp := Client.UpdateOutgoingWebhook(createdHook) CheckNoError(t, resp) - if updatedHook.DisplayName != "Basic user 2" { - t.Fatal("should apply the change") - } - if updatedHook.CreatorId != th.SystemAdminUser.Id { - t.Fatal("hook creator should not be changed") - } + + require.Exactly(t, "Basic user 2", updatedHook.DisplayName, "should apply the change") + require.Equal(t, th.SystemAdminUser.Id, updatedHook.CreatorId, "hook creator should not be changed") }) t.Run("UpdateToExistingTriggerWordAndCallback", func(t *testing.T) { @@ -1167,11 +1084,10 @@ func TestDeleteOutgoingHook(t *testing.T) { rhook, resp = Client.CreateOutgoingWebhook(hook) CheckNoError(t, resp) - if status, resp = Client.DeleteOutgoingWebhook(rhook.Id); !status { - t.Fatal("Delete should have succeeded") - } else { - CheckOKStatus(t, resp) - } + status, resp = Client.DeleteOutgoingWebhook(rhook.Id) + + require.True(t, status, "Delete should have succeeded") + CheckOKStatus(t, resp) // Get now should not return this deleted hook _, resp = Client.GetIncomingWebhook(rhook.Id, "") diff --git a/app/bot_test.go b/app/bot_test.go index 2b35ff4a9f..b8a563047a 100644 --- a/app/bot_test.go +++ b/app/bot_test.go @@ -45,6 +45,20 @@ func TestCreateBot(t *testing.T) { require.NotNil(t, err) require.Equal(t, "model.bot.is_valid.description.app_error", err.Id) }) + + t.Run("username contains . character", func(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + bot, err := th.App.CreateBot(&model.Bot{ + Username: "username.", + Description: "a bot", + OwnerId: th.BasicUser.Id, + }) + require.NotNil(t, err) + require.Nil(t, bot) + require.Equal(t, "model.user.is_valid.email.app_error", err.Id) + }) }) t.Run("create bot", func(t *testing.T) { diff --git a/app/command_invite.go b/app/command_invite.go index da747a9b95..7aa6bf91d8 100644 --- a/app/command_invite.go +++ b/app/command_invite.go @@ -143,6 +143,10 @@ func (me *InviteProvider) DoCommand(a *App, args *model.CommandArgs, message str var text string if err.Id == "api.channel.add_members.user_denied" { text = args.T("api.command_invite.group_constrained_user_denied") + } else if err.Id == "store.sql_team.get_member.missing.app_error" { + text = args.T("api.command_invite.user_not_in_team.app_error", map[string]interface{}{ + "Username": userProfile.Username, + }) } else { text = args.T("api.command_invite.fail.app_error") } diff --git a/app/command_invite_test.go b/app/command_invite_test.go index 71eb582154..354b373b77 100644 --- a/app/command_invite_test.go +++ b/app/command_invite_test.go @@ -94,7 +94,7 @@ func TestInviteProvider(t *testing.T) { }, { desc: "try to add a user which is not part of the team", - expected: "api.command_invite.fail.app_error", + expected: "api.command_invite.user_not_in_team.app_error", msg: basicUser4.Username, }, { diff --git a/app/command_test.go b/app/command_test.go index 03e0bda7f3..e43133b974 100644 --- a/app/command_test.go +++ b/app/command_test.go @@ -186,6 +186,7 @@ func TestHandleCommandResponsePost(t *testing.T) { post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn) assert.Nil(t, err) assert.Equal(t, "@channel", post.Message) + assert.Equal(t, "true", post.Props["from_webhook"]) // Test Slack attachments text conversion. resp.Attachments = []*model.SlackAttachment{ @@ -196,7 +197,11 @@ func TestHandleCommandResponsePost(t *testing.T) { post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn) assert.Nil(t, err) - assert.Equal(t, "@here", resp.Attachments[0].Text) + assert.Equal(t, "@channel", post.Message) + if assert.Len(t, post.Attachments(), 1) { + assert.Equal(t, "@here", post.Attachments()[0].Text) + } + assert.Equal(t, "true", post.Props["from_webhook"]) channel = th.CreatePrivateChannel(th.BasicTeam) resp.ChannelId = channel.Id diff --git a/app/import_functions.go b/app/import_functions.go index a5043ae9fd..add7e37212 100644 --- a/app/import_functions.go +++ b/app/import_functions.go @@ -334,7 +334,7 @@ func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError { authData = nil } else { // If no AuthData or Password is specified, we must generate a password. - password = model.NewId() + password = model.GeneratePassword(*a.Config().PasswordSettings.MinimumLength) authData = nil } diff --git a/app/integration_action.go b/app/integration_action.go index 98309bfe34..5c95cf6377 100644 --- a/app/integration_action.go +++ b/app/integration_action.go @@ -76,6 +76,13 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st close(cchan) }() + userChan := make(chan store.StoreResult, 1) + go func() { + user, err := a.Srv.Store.User().Get(upstreamRequest.UserId) + userChan <- store.StoreResult{Data: user, Err: err} + close(userChan) + }() + result := <-pchan if result.Err != nil { if cookie == nil { @@ -89,7 +96,14 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st return "", model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "postId doesn't match", http.StatusBadRequest) } + channel, err := a.Srv.Store.Channel().Get(cookie.ChannelId, true) + if err != nil { + return "", err + } + upstreamRequest.ChannelId = cookie.ChannelId + upstreamRequest.ChannelName = channel.Name + upstreamRequest.TeamId = channel.TeamId upstreamRequest.Type = cookie.Type upstreamRequest.Context = cookie.Integration.Context datasource = cookie.DataSource @@ -112,6 +126,7 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st } upstreamRequest.ChannelId = post.ChannelId + upstreamRequest.ChannelName = channel.Name upstreamRequest.TeamId = channel.TeamId upstreamRequest.Type = action.Type upstreamRequest.Context = action.Integration.Context @@ -140,6 +155,27 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st upstreamURL = action.Integration.URL } + teamChan := make(chan store.StoreResult, 1) + go func() { + team, err := a.Srv.Store.Team().Get(upstreamRequest.TeamId) + teamChan <- store.StoreResult{Data: team, Err: err} + close(teamChan) + }() + + ur := <-userChan + if ur.Err != nil { + return "", ur.Err + } + user := ur.Data.(*model.User) + upstreamRequest.UserName = user.Username + + tr := <-teamChan + if tr.Err != nil { + return "", tr.Err + } + team := tr.Data.(*model.Team) + upstreamRequest.TeamName = team.Name + if upstreamRequest.Type == model.POST_ACTION_TYPE_SELECT { if selectedOption != "" { if upstreamRequest.Context == nil { diff --git a/app/integration_action_test.go b/app/integration_action_test.go index 6474a16b29..fb5727f7ae 100644 --- a/app/integration_action_test.go +++ b/app/integration_action_test.go @@ -80,8 +80,11 @@ func TestPostAction(t *testing.T) { assert.NotNil(t, request) assert.Equal(t, request.UserId, th.BasicUser.Id) + assert.Equal(t, request.UserName, th.BasicUser.Username) assert.Equal(t, request.ChannelId, th.BasicChannel.Id) + assert.Equal(t, request.ChannelName, th.BasicChannel.Name) assert.Equal(t, request.TeamId, th.BasicTeam.Id) + assert.Equal(t, request.TeamName, th.BasicTeam.Name) assert.True(t, len(request.TriggerId) > 0) if request.Type == model.POST_ACTION_TYPE_SELECT { assert.Equal(t, request.DataSource, "some_source") diff --git a/app/login.go b/app/login.go index 31f1126fdd..80584e6b51 100644 --- a/app/login.go +++ b/app/login.go @@ -110,7 +110,7 @@ func (a *App) GetUserForLogin(id, loginId string) (*model.User, *model.AppError) return nil, model.NewAppError("GetUserForLogin", "store.sql_user.get_for_login.app_error", nil, "", http.StatusBadRequest) } -func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) (*model.Session, *model.AppError) { +func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) *model.AppError { if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { var rejectionReason string pluginContext := a.PluginContext() @@ -120,7 +120,7 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, }, plugin.UserWillLogInId) if rejectionReason != "" { - return nil, model.NewAppError("DoLogin", "Login rejected by plugin: "+rejectionReason, nil, "", http.StatusBadRequest) + return model.NewAppError("DoLogin", "Login rejected by plugin: "+rejectionReason, nil, "", http.StatusBadRequest) } } @@ -133,7 +133,7 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, // A special case where we logout of all other sessions with the same Id if err := a.RevokeSessionsForDeviceId(user.Id, deviceId, ""); err != nil { err.StatusCode = http.StatusInternalServerError - return nil, err + return err } } else { session.SetExpireInDays(*a.Config().ServiceSettings.SessionLengthWebInDays) @@ -158,10 +158,11 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, var err *model.AppError if session, err = a.CreateSession(session); err != nil { err.StatusCode = http.StatusInternalServerError - return nil, err + return err } w.Header().Set(model.HEADER_TOKEN, session.Token) + a.Session = *session if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { @@ -174,10 +175,10 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, }) } - return session, nil + return nil } -func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, session *model.Session) { +func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request) { secure := false if GetProtocol(r) == "https" { secure = true @@ -190,7 +191,7 @@ func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, sessi expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0) sessionCookie := &http.Cookie{ Name: model.SESSION_COOKIE_TOKEN, - Value: session.Token, + Value: a.Session.Token, Path: subpath, MaxAge: maxAge, Expires: expiresAt, @@ -201,7 +202,7 @@ func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, sessi userCookie := &http.Cookie{ Name: model.SESSION_COOKIE_USER, - Value: session.UserId, + Value: a.Session.UserId, Path: subpath, MaxAge: maxAge, Expires: expiresAt, @@ -211,7 +212,7 @@ func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, sessi csrfCookie := &http.Cookie{ Name: model.SESSION_COOKIE_CSRF, - Value: session.GetCSRF(), + Value: a.Session.GetCSRF(), Path: subpath, MaxAge: maxAge, Expires: expiresAt, diff --git a/app/plugin_api_test.go b/app/plugin_api_test.go index dc5fab7d3e..a353e5ce74 100644 --- a/app/plugin_api_test.go +++ b/app/plugin_api_test.go @@ -1440,6 +1440,17 @@ func TestPluginAPIGetUnsanitizedConfig(t *testing.T) { } } +func TestPluginCallLogAPI(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + pluginID := "com.mattermost.sample" + path, _ := fileutils.FindDir("mattermost-server/app/plugin_api_test") + pluginCode, err := ioutil.ReadFile(filepath.Join(path, "plugin_using_log_api.go")) + assert.NoError(t, err) + setupPluginApiTest(t, string(pluginCode), + `{"id": "com.mattermost.sample", "server": {"executable": "backend.exe"}, "settings_schema": {"settings": []}}`, pluginID, th.App) +} + func TestPluginAddUserToChannel(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() diff --git a/app/plugin_api_test/plugin_using_log_api.go b/app/plugin_api_test/plugin_using_log_api.go new file mode 100644 index 0000000000..159ec7f3b3 --- /dev/null +++ b/app/plugin_api_test/plugin_using_log_api.go @@ -0,0 +1,29 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package main + +import ( + "github.com/mattermost/mattermost-server/plugin" + "github.com/pkg/errors" +) + +type PluginUsingLogAPI struct { + plugin.MattermostPlugin +} + +type Foo struct { + bar float64 +} + +func main() { + plugin.ClientMain(&PluginUsingLogAPI{}) +} + +func (p *PluginUsingLogAPI) OnActivate() error { + p.API.LogDebug("LogDebug", "one", 1, "two", "two", "foo", Foo{bar: 3.1416}) + p.API.LogInfo("LogInfo", "one", 1, "two", "two", "foo", Foo{bar: 3.1416}) + p.API.LogWarn("LogWarn", "one", 1, "two", "two", "foo", Foo{bar: 3.1416}) + p.API.LogError("LogError", "error", errors.WithStack(errors.New("boom!"))) + return nil +} diff --git a/app/plugin_hooks_test.go b/app/plugin_hooks_test.go index 2feabc26de..d591a8d136 100644 --- a/app/plugin_hooks_test.go +++ b/app/plugin_hooks_test.go @@ -694,7 +694,7 @@ func TestUserWillLogIn_Blocked(t *testing.T) { r := &http.Request{} w := httptest.NewRecorder() - _, err = th.App.DoLogin(w, r, th.BasicUser, "") + err = th.App.DoLogin(w, r, th.BasicUser, "") assert.Contains(t, err.Id, "Login rejected by plugin", "Expected Login rejected by plugin, got %s", err.Id) } @@ -733,10 +733,10 @@ func TestUserWillLogInIn_Passed(t *testing.T) { r := &http.Request{} w := httptest.NewRecorder() - session, err := th.App.DoLogin(w, r, th.BasicUser, "") + err = th.App.DoLogin(w, r, th.BasicUser, "") assert.Nil(t, err, "Expected nil, got %s", err) - assert.Equal(t, session.UserId, th.BasicUser.Id) + assert.Equal(t, th.App.Session.UserId, th.BasicUser.Id) } func TestUserHasLoggedIn(t *testing.T) { @@ -774,7 +774,7 @@ func TestUserHasLoggedIn(t *testing.T) { r := &http.Request{} w := httptest.NewRecorder() - _, err = th.App.DoLogin(w, r, th.BasicUser, "") + err = th.App.DoLogin(w, r, th.BasicUser, "") assert.Nil(t, err, "Expected nil, got %s", err) diff --git a/build/docker-compose.common.yml b/build/docker-compose.common.yml index 1c120f3bd4..ad8e9326cd 100644 --- a/build/docker-compose.common.yml +++ b/build/docker-compose.common.yml @@ -52,7 +52,3 @@ services: http.host: "0.0.0.0" transport.host: "127.0.0.1" ES_JAVA_OPTS: "-Xms512m -Xmx512m" - redis: - image: redis - networks: - - mm-test diff --git a/build/docker-compose.yml b/build/docker-compose.yml index ad738ec50d..5e589e0fcb 100644 --- a/build/docker-compose.yml +++ b/build/docker-compose.yml @@ -28,10 +28,6 @@ services: extends: file: docker-compose.common.yml service: elasticsearch - redis: - extends: - file: docker-compose.common.yml - service: redis start_dependencies: image: mattermost/mattermost-wait-for-dep:latest @@ -44,8 +40,7 @@ services: - inbucket - openldap - elasticsearch - - redis - command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200 redis:6379 + command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200 networks: mm-test: diff --git a/build/legacy.mk b/build/legacy.mk index 42d4e08868..4ef04dfe96 100644 --- a/build/legacy.mk +++ b/build/legacy.mk @@ -55,9 +55,3 @@ clean-old-docker: docker stop mattermost-elasticsearch > /dev/null; \ docker rm -v mattermost-elasticsearch > /dev/null; \ fi - - @if [ $(shell docker ps -a | grep -ci mattermost-redis) -eq 1 ]; then \ - echo removing mattermost-redis; \ - docker stop mattermost-redis > /dev/null; \ - docker rm -v mattermost-redis > /dev/null; \ - fi diff --git a/docker-compose.yaml b/docker-compose.yaml index e2ad909628..b089998dde 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -46,13 +46,6 @@ services: extends: file: build/docker-compose.common.yml service: elasticsearch - redis: - container_name: mattermost-redis - ports: - - "6379:6379" - extends: - file: build/docker-compose.common.yml - service: redis start_dependencies: image: mattermost/mattermost-wait-for-dep:latest networks: @@ -64,8 +57,7 @@ services: - inbucket - openldap - elasticsearch - - redis - command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200 redis:6379 + command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200 networks: mm-test: diff --git a/go.mod b/go.mod index f01c778fbf..2c1e6b3078 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/fortytw2/leaktest v1.3.0 // indirect github.com/fsnotify/fsnotify v1.4.7 github.com/go-gorp/gorp v2.0.0+incompatible // indirect - github.com/go-redis/redis v6.15.5+incompatible github.com/go-sql-driver/mysql v1.4.1 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/google/uuid v1.1.1 // indirect diff --git a/go.sum b/go.sum index 4064e2c18d..9c3a83c5c7 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-redis/redis v6.15.5+incompatible h1:pLky8I0rgiblWfa8C1EV7fPEUv0aH6vKRaYHc/YRHVk= -github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= diff --git a/i18n/en.json b/i18n/en.json index eb29f5cc57..1a74119472 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -798,6 +798,10 @@ "id": "api.command_invite.user_already_in_channel.app_error", "translation": "{{.User}} is already in the channel." }, + { + "id": "api.command_invite.user_not_in_team.app_error", + "translation": "@{{.Username}} is not a member of the team." + }, { "id": "api.command_invite_people.permission.app_error", "translation": "You don't have permission to invite new users to this server." diff --git a/jobs/schedulers.go b/jobs/schedulers.go index d415c85e1f..52df4eebea 100644 --- a/jobs/schedulers.go +++ b/jobs/schedulers.go @@ -86,11 +86,13 @@ func (schedulers *Schedulers) Start() *Schedulers { } for { + timer := time.NewTimer(1 * time.Minute) select { case <-schedulers.stop: mlog.Debug("Schedulers received stop signal.") + timer.Stop() return - case now = <-time.After(1 * time.Minute): + case now = <-timer.C: cfg := schedulers.jobs.Config() for idx, nextTime := range schedulers.nextRunTimes { @@ -128,6 +130,7 @@ func (schedulers *Schedulers) Start() *Schedulers { } } } + timer.Stop() } }) }() diff --git a/model/bot.go b/model/bot.go index 079e1b157b..18d64fec53 100644 --- a/model/bot.go +++ b/model/bot.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "net/http" - "strings" "unicode/utf8" ) @@ -167,7 +166,7 @@ func UserFromBot(b *Bot) *User { return &User{ Id: b.UserId, Username: b.Username, - Email: fmt.Sprintf("%s@localhost", strings.ToLower(b.Username)), + Email: NormalizeEmail(fmt.Sprintf("%s@localhost", b.Username)), FirstName: b.DisplayName, Roles: SYSTEM_USER_ROLE_ID, } diff --git a/model/integration_action.go b/model/integration_action.go index cef7256abb..64898e242b 100644 --- a/model/integration_action.go +++ b/model/integration_action.go @@ -157,14 +157,17 @@ type PostActionIntegration struct { } type PostActionIntegrationRequest struct { - UserId string `json:"user_id"` - ChannelId string `json:"channel_id"` - TeamId string `json:"team_id"` - PostId string `json:"post_id"` - TriggerId string `json:"trigger_id"` - Type string `json:"type"` - DataSource string `json:"data_source"` - Context map[string]interface{} `json:"context,omitempty"` + UserId string `json:"user_id"` + UserName string `json:"user_name"` + ChannelId string `json:"channel_id"` + ChannelName string `json:"channel_name"` + TeamId string `json:"team_id"` + TeamName string `json:"team_domain"` + PostId string `json:"post_id"` + TriggerId string `json:"trigger_id"` + Type string `json:"type"` + DataSource string `json:"data_source"` + Context map[string]interface{} `json:"context,omitempty"` } type PostActionIntegrationResponse struct { diff --git a/model/user.go b/model/user.go index 30baef167f..29ed72a5e7 100644 --- a/model/user.go +++ b/model/user.go @@ -9,10 +9,12 @@ import ( "fmt" "io" "io/ioutil" + "math/rand" "net/http" "regexp" "sort" "strings" + "time" "unicode/utf8" "github.com/mattermost/mattermost-server/services/timezones" @@ -851,3 +853,27 @@ func UsersWithGroupsAndCountFromJson(data io.Reader) *UsersWithGroupsAndCount { json.Unmarshal(bodyBytes, uwg) return uwg } + +var passwordRandomSource = rand.NewSource(time.Now().Unix()) +var passwordSpecialChars = "!$%^&*(),." +var passwordNumbers = "0123456789" +var passwordUpperCaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +var passwordLowerCaseLetters = "abcdefghijklmnopqrstuvwxyz" +var passwordAllChars = passwordSpecialChars + passwordNumbers + passwordUpperCaseLetters + passwordLowerCaseLetters + +func GeneratePassword(minimumLength int) string { + r := rand.New(passwordRandomSource) + + // Make sure we are guaranteed at least one of each type to meet any possible password complexity requirements. + password := string([]rune(passwordUpperCaseLetters)[r.Intn(len(passwordUpperCaseLetters))]) + + string([]rune(passwordNumbers)[r.Intn(len(passwordNumbers))]) + + string([]rune(passwordLowerCaseLetters)[r.Intn(len(passwordLowerCaseLetters))]) + + string([]rune(passwordSpecialChars)[r.Intn(len(passwordSpecialChars))]) + + for len(password) < minimumLength { + i := r.Intn(len(passwordAllChars)) + password = password + string([]rune(passwordAllChars)[i]) + } + + return password +} diff --git a/model/user_test.go b/model/user_test.go index 5fe39917c6..97e513894d 100644 --- a/model/user_test.go +++ b/model/user_test.go @@ -5,6 +5,7 @@ package model import ( "fmt" + "math/rand" "net/http" "strings" "testing" @@ -350,3 +351,25 @@ func TestUserSlice(t *testing.T) { assert.Equal(t, 1, len(nonBotUsers)) }) } + +func TestGeneratePassword(t *testing.T) { + passwordRandomSource = rand.NewSource(12345) + + t.Run("Should be the minimum length or 4, whichever is less", func(t *testing.T) { + password1 := GeneratePassword(5) + assert.Len(t, password1, 5) + password2 := GeneratePassword(10) + assert.Len(t, password2, 10) + password3 := GeneratePassword(1) + assert.Len(t, password3, 4) + }) + + t.Run("Should contain at least one of symbols, upper case, lower case and numbers", func(t *testing.T) { + password := GeneratePassword(4) + require.Len(t, password, 4) + assert.Contains(t, []rune(passwordUpperCaseLetters), []rune(password)[0]) + assert.Contains(t, []rune(passwordNumbers), []rune(password)[1]) + assert.Contains(t, []rune(passwordLowerCaseLetters), []rune(password)[2]) + assert.Contains(t, []rune(passwordSpecialChars), []rune(password)[3]) + }) +} diff --git a/plugin/api.go b/plugin/api.go index 49ac0ebf8f..6a2ce229c3 100644 --- a/plugin/api.go +++ b/plugin/api.go @@ -663,7 +663,6 @@ type API interface { // LogDebug writes a log message to the Mattermost server log file. // Appropriate context such as the plugin name will already be added as fields so plugins // do not need to add that info. - // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob // // Minimum server version: 5.2 LogDebug(msg string, keyValuePairs ...interface{}) @@ -671,7 +670,6 @@ type API interface { // LogInfo writes a log message to the Mattermost server log file. // Appropriate context such as the plugin name will already be added as fields so plugins // do not need to add that info. - // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob // // Minimum server version: 5.2 LogInfo(msg string, keyValuePairs ...interface{}) @@ -679,7 +677,6 @@ type API interface { // LogError writes a log message to the Mattermost server log file. // Appropriate context such as the plugin name will already be added as fields so plugins // do not need to add that info. - // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob // // Minimum server version: 5.2 LogError(msg string, keyValuePairs ...interface{}) @@ -687,7 +684,6 @@ type API interface { // LogWarn writes a log message to the Mattermost server log file. // Appropriate context such as the plugin name will already be added as fields so plugins // do not need to add that info. - // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob // // Minimum server version: 5.2 LogWarn(msg string, keyValuePairs ...interface{}) diff --git a/plugin/checker/check_api.go b/plugin/checker/check_api.go new file mode 100644 index 0000000000..e5b9ec716f --- /dev/null +++ b/plugin/checker/check_api.go @@ -0,0 +1,50 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package main + +import ( + "fmt" + "go/ast" + "go/token" + + "github.com/mattermost/mattermost-server/plugin/checker/internal/asthelpers" + "github.com/mattermost/mattermost-server/plugin/checker/internal/version" +) + +func checkAPIVersionComments(pkgPath string) (result, error) { + pkg, err := asthelpers.GetPackage(pkgPath) + if err != nil { + return result{}, err + } + + apiInterface, err := asthelpers.FindInterface("API", pkg.Syntax) + if err != nil { + return result{}, err + } + + invalidMethods := findInvalidMethods(apiInterface.Methods.List) + return result{Errors: renderErrors(pkg.Fset, invalidMethods)}, nil +} + +func findInvalidMethods(methods []*ast.Field) []*ast.Field { + var invalid []*ast.Field + for _, m := range methods { + if !hasValidMinimumVersionComment(m.Doc.Text()) { + invalid = append(invalid, m) + } + } + return invalid +} + +func hasValidMinimumVersionComment(s string) bool { + return version.ExtractMinimumVersionFromComment(s) != "" +} + +func renderErrors(fset *token.FileSet, methods []*ast.Field) []string { + var out []string + for _, m := range methods { + out = append(out, renderWithFilePosition(fset, m.Pos(), fmt.Sprintf("missing a minimum server version comment on method %s", m.Names[0].Name))) + } + return out +} diff --git a/plugin/checker/main_test.go b/plugin/checker/check_api_test.go similarity index 69% rename from plugin/checker/main_test.go rename to plugin/checker/check_api_test.go index c05819b850..529da48947 100644 --- a/plugin/checker/main_test.go +++ b/plugin/checker/check_api_test.go @@ -10,29 +10,32 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRunCheck(t *testing.T) { +func TestCheckAPIVersionComments(t *testing.T) { testCases := []struct { name, pkgPath, err string + expected result }{ { name: "valid comments", - pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/valid", + pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/valid", err: "", }, { name: "invalid comments", - pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/invalid", - err: "test/invalid/invalid.go:15:2: missing a minimum server version comment\n", + pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/invalid", + expected: result{ + Errors: []string{"internal/test/invalid/invalid.go:15:2: missing a minimum server version comment on method InvalidMethod"}, + }, }, { name: "missing API interface", - pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/missing", - err: "could not find API interface in package github.com/mattermost/mattermost-server/plugin/checker/test/missing", + pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/missing", + err: "could not find API interface", }, { name: "non-existent package path", - pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/does_not_exist", - err: "could not find API interface in package github.com/mattermost/mattermost-server/plugin/checker/test/does_not_exist", + pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/does_not_exist", + err: "could not find API interface", }, } @@ -43,7 +46,8 @@ func TestRunCheck(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := runCheck(tc.pkgPath) + res, err := checkAPIVersionComments(tc.pkgPath) + assert.Equal(t, res, tc.expected) if tc.err != "" { assert.EqualError(t, err, tc.err) diff --git a/plugin/checker/check_helpers.go b/plugin/checker/check_helpers.go new file mode 100644 index 0000000000..8c2de7f5d1 --- /dev/null +++ b/plugin/checker/check_helpers.go @@ -0,0 +1,137 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package main + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + + "github.com/mattermost/mattermost-server/plugin/checker/internal/asthelpers" + "github.com/mattermost/mattermost-server/plugin/checker/internal/version" + + "github.com/pkg/errors" +) + +func checkHelpersVersionComments(pkgPath string) (result, error) { + pkg, err := asthelpers.GetPackage(pkgPath) + if err != nil { + return result{}, err + } + + api, apiIdent, err := asthelpers.FindInterfaceWithIdent("API", pkg.Syntax) + if err != nil { + return result{}, err + } + + apiObj := pkg.TypesInfo.ObjectOf(apiIdent) + if apiObj == nil { + return result{}, errors.New("could not find type object for API interface") + } + + helpers, err := asthelpers.FindInterface("Helpers", pkg.Syntax) + if err != nil { + return result{}, err + } + + apiVersions := mapMinimumVersionsByMethodName(api.Methods.List) + + helpersPositions := mapPositionsByMethodName(helpers.Methods.List) + helpersVersions := mapMinimumVersionsByMethodName(helpers.Methods.List) + + implMethods := asthelpers.FindReceiverMethods("HelpersImpl", pkg.Syntax) + implVersions := mapEffectiveVersionByMethod(pkg.TypesInfo, apiObj.Type(), apiVersions, implMethods) + + return validateMethods(pkg.Fset, helpersPositions, helpersVersions, implVersions), nil +} + +func validateMethods( + fset *token.FileSet, + helpersPositions map[string]token.Pos, + helpersVersions map[string]version.V, + implVersions map[string]version.V, +) result { + var res result + + for name, helperVer := range helpersVersions { + pos := helpersPositions[name] + + implVer, ok := implVersions[name] + if !ok { + res.Errors = append(res.Errors, renderWithFilePosition( + fset, + pos, + fmt.Sprintf("missing implementation for method %s", name)), + ) + continue + } + + if helperVer == "" { + res.Errors = append(res.Errors, renderWithFilePosition( + fset, + pos, + fmt.Sprintf("missing a minimum server version comment on method %s", name)), + ) + continue + } + + if helperVer == implVer { + continue + } + + if helperVer.LessThan(implVer) { + res.Errors = append(res.Errors, renderWithFilePosition( + fset, + pos, + fmt.Sprintf("documented minimum server version too low on method %s", name)), + ) + } else { + res.Warnings = append(res.Warnings, renderWithFilePosition( + fset, + pos, + fmt.Sprintf("documented minimum server version too high on method %s", name)), + ) + } + } + + return res +} + +func mapEffectiveVersionByMethod(info *types.Info, apiType types.Type, versions map[string]version.V, methods []*ast.FuncDecl) map[string]version.V { + effectiveVersions := map[string]version.V{} + for _, m := range methods { + apiMethodsCalled := asthelpers.FindMethodsCalledOnType(info, apiType, m) + effectiveVersions[m.Name.Name] = getEffectiveMinimumVersion(versions, apiMethodsCalled) + } + return effectiveVersions +} + +func mapMinimumVersionsByMethodName(methods []*ast.Field) map[string]version.V { + versions := map[string]version.V{} + for _, m := range methods { + versions[m.Names[0].Name] = version.V(version.ExtractMinimumVersionFromComment(m.Doc.Text())) + } + return versions +} + +func mapPositionsByMethodName(methods []*ast.Field) map[string]token.Pos { + pos := map[string]token.Pos{} + for _, m := range methods { + pos[m.Names[0].Name] = m.Pos() + } + return pos +} + +func getEffectiveMinimumVersion(info map[string]version.V, methods []string) version.V { + var highest version.V + for _, m := range methods { + if current, ok := info[m]; ok { + if current.GreaterThanOrEqualTo(highest) { + highest = current + } + } + } + return highest +} diff --git a/plugin/checker/check_helpers_test.go b/plugin/checker/check_helpers_test.go new file mode 100644 index 0000000000..d87d976d41 --- /dev/null +++ b/plugin/checker/check_helpers_test.go @@ -0,0 +1,48 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCheckHelpersVersionComments(t *testing.T) { + testCases := []struct { + name, pkgPath string + expected result + err string + }{ + { + name: "valid versions", + pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/valid", + expected: result{}, + }, + { + name: "invalid versions", + pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/invalid", + expected: result{ + Errors: []string{"internal/test/invalid/invalid.go:20:2: documented minimum server version too low on method LowerVersionMethod"}, + Warnings: []string{"internal/test/invalid/invalid.go:23:2: documented minimum server version too high on method HigherVersionMethod"}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert := assert.New(t) + + res, err := checkHelpersVersionComments(tc.pkgPath) + assert.Equal(tc.expected, res) + + if tc.err != "" { + assert.EqualError(err, tc.err) + } else { + assert.NoError(err) + } + + }) + } +} diff --git a/plugin/checker/internal/asthelpers/helpers.go b/plugin/checker/internal/asthelpers/helpers.go new file mode 100644 index 0000000000..1caf72e78d --- /dev/null +++ b/plugin/checker/internal/asthelpers/helpers.go @@ -0,0 +1,129 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package asthelpers + +import ( + "go/ast" + "go/types" + + "github.com/pkg/errors" + "golang.org/x/tools/go/packages" +) + +func GetPackage(pkgPath string) (*packages.Package, error) { + cfg := &packages.Config{ + Mode: packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo, + } + pkgs, err := packages.Load(cfg, pkgPath) + if err != nil { + return nil, err + } + + if len(pkgs) == 0 { + return nil, errors.Errorf("could not find package %s", pkgPath) + } + return pkgs[0], nil +} + +func FindInterface(name string, files []*ast.File) (*ast.InterfaceType, error) { + iface, _, err := FindInterfaceWithIdent(name, files) + return iface, err +} + +func FindInterfaceWithIdent(name string, files []*ast.File) (*ast.InterfaceType, *ast.Ident, error) { + var ( + ident *ast.Ident + iface *ast.InterfaceType + ) + + for _, f := range files { + ast.Inspect(f, func(n ast.Node) bool { + if t, ok := n.(*ast.TypeSpec); ok { + if iface != nil { + return false + } + + if i, ok := t.Type.(*ast.InterfaceType); ok && t.Name.Name == name { + ident = t.Name + iface = i + return false + } + } + return true + }) + + if iface != nil { + return iface, ident, nil + } + } + return nil, nil, errors.Errorf("could not find %s interface", name) +} + +func FindMethodsCalledOnType(info *types.Info, typ types.Type, caller *ast.FuncDecl) []string { + var methods []string + + ast.Inspect(caller, func(n ast.Node) bool { + if s, ok := n.(*ast.SelectorExpr); ok { + + var receiver *ast.Ident + switch r := s.X.(type) { + case *ast.Ident: + // Left-hand side of the selector is an identifier, eg: + // + // a := p.API + // a.GetTeams() + // + receiver = r + case *ast.SelectorExpr: + // Left-hand side of the selector is a selector, eg: + // + // p.API.GetTeams() + // + receiver = r.Sel + } + + if receiver != nil { + obj := info.ObjectOf(receiver) + if obj != nil && types.Identical(obj.Type(), typ) { + methods = append(methods, s.Sel.Name) + } + return false + } + + } + return true + }) + + return methods +} + +func FindReceiverMethods(receiverName string, files []*ast.File) []*ast.FuncDecl { + var fns []*ast.FuncDecl + for _, f := range files { + ast.Inspect(f, func(n ast.Node) bool { + if fn, ok := n.(*ast.FuncDecl); ok { + r := extractReceiverTypeName(fn) + if r == receiverName { + fns = append(fns, fn) + } + } + return true + }) + } + return fns +} + +func extractReceiverTypeName(fn *ast.FuncDecl) string { + if fn.Recv != nil { + t := fn.Recv.List[0].Type + // Unwrap the pointer type (a star expression) + if se, ok := t.(*ast.StarExpr); ok { + t = se.X + } + if id, ok := t.(*ast.Ident); ok { + return id.Name + } + } + return "" +} diff --git a/plugin/checker/test/invalid/invalid.go b/plugin/checker/internal/test/invalid/invalid.go similarity index 57% rename from plugin/checker/test/invalid/invalid.go rename to plugin/checker/internal/test/invalid/invalid.go index dc7ec0cbbc..56f413713c 100644 --- a/plugin/checker/test/invalid/invalid.go +++ b/plugin/checker/internal/test/invalid/invalid.go @@ -14,3 +14,23 @@ type API interface { // plugin comment checker with an invalid comment. InvalidMethod() } + +type Helpers interface { + // Minimum server version: 1.1 + LowerVersionMethod() + + // Minimum server version: 1.3 + HigherVersionMethod() +} + +type HelpersImpl struct { + api API +} + +func (h *HelpersImpl) LowerVersionMethod() { + h.api.ValidMethod() +} + +func (h *HelpersImpl) HigherVersionMethod() { + h.api.ValidMethod() +} diff --git a/plugin/checker/test/missing/missing.go b/plugin/checker/internal/test/missing/missing.go similarity index 100% rename from plugin/checker/test/missing/missing.go rename to plugin/checker/internal/test/missing/missing.go diff --git a/plugin/checker/internal/test/valid/valid.go b/plugin/checker/internal/test/valid/valid.go new file mode 100644 index 0000000000..ecdb379d6b --- /dev/null +++ b/plugin/checker/internal/test/valid/valid.go @@ -0,0 +1,44 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package valid + +type API interface { + // ValidMethod is a fake method for testing the + // plugin comment checker with a valid comment. + // + // Minimum server version: 1.2.3 + ValidMethod() + + // Minimum server version: 1.5 + NewerValidMethod() +} + +type Helpers interface { + // Minimum server version: 1.2.3 + ValidHelperMethod() + + // Minimum server version: 1.5 + NewerValidHelperMethod() + + // Minimum server version: 1.5 + IndirectReferenceMethod() +} + +type HelpersImpl struct { + api API +} + +func (h *HelpersImpl) ValidHelperMethod() { + h.api.ValidMethod() +} + +func (h *HelpersImpl) NewerValidHelperMethod() { + h.api.NewerValidMethod() + h.api.ValidMethod() +} + +func (h *HelpersImpl) IndirectReferenceMethod() { + a := h.api + a.NewerValidMethod() +} diff --git a/plugin/checker/internal/version/comments.go b/plugin/checker/internal/version/comments.go new file mode 100644 index 0000000000..ac003d3070 --- /dev/null +++ b/plugin/checker/internal/version/comments.go @@ -0,0 +1,22 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package version + +import ( + "regexp" + "strings" +) + +var versionCommentRE = regexp.MustCompile(`^Minimum server version: (\d+\.\d+(?:\.\d+[\w-]*)?)$`) + +func ExtractMinimumVersionFromComment(s string) string { + lines := strings.Split(strings.TrimSpace(s), "\n") + if len(lines) > 0 { + lastLine := lines[len(lines)-1] + if m := versionCommentRE.FindStringSubmatch(lastLine); len(m) >= 1 { + return m[1] + } + } + return "" +} diff --git a/plugin/checker/internal/version/comments_test.go b/plugin/checker/internal/version/comments_test.go new file mode 100644 index 0000000000..4d71f71499 --- /dev/null +++ b/plugin/checker/internal/version/comments_test.go @@ -0,0 +1,49 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package version + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExtractVersionFromComment(t *testing.T) { + testCases := []struct { + input string + expected string + }{ + { + input: "This is a comment.\n\nMinimum server version: 1.2.3-rc1\n", + expected: "1.2.3-rc1", + }, + { + input: "This is a comment.\n\nMinimum server version: 1.2.3\n", + expected: "1.2.3", + }, + { + input: "This is a comment.\n\nMinimum server version: 1.2\n", + expected: "1.2", + }, + { + input: "This is a comment.\n\nMinimum server version: 1\n", + expected: "", + }, + { + input: "This is a comment.\n", + expected: "", + }, + { + input: "", + expected: "", + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%+v", tc), func(t *testing.T) { + assert.Equal(t, tc.expected, ExtractMinimumVersionFromComment(tc.input)) + }) + } +} diff --git a/plugin/checker/internal/version/version.go b/plugin/checker/internal/version/version.go new file mode 100644 index 0000000000..997179250f --- /dev/null +++ b/plugin/checker/internal/version/version.go @@ -0,0 +1,80 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package version + +import ( + "regexp" + "strconv" + "strings" +) + +type V string + +func (v V) GreaterThanOrEqualTo(other V) bool { + return !v.LessThan(other) +} + +func (v V) LessThan(other V) bool { + leftParts, leftCount := split(v) + rightParts, rightCount := split(other) + + var length int + if leftCount < rightCount { + length = rightCount + } else { + length = leftCount + } + + for i := 0; i < length; i++ { + var left, right string + + if i < leftCount { + left = leftParts[i] + } + + if i < rightCount { + right = rightParts[i] + } + + if left == right { + continue + } + + leftInt := parseInt(left) + rightInt := parseInt(right) + + isNumericalComparison := leftInt != nil && rightInt != nil + + if isNumericalComparison { + return *leftInt < *rightInt + } + + return left < right + } + + return false +} + +func split(v V) ([]string, int) { + var chunks []string + + for _, part := range strings.Split(string(v), ".") { + chunks = append(chunks, splitNumericalChunks(part)...) + } + + return chunks, len(chunks) +} + +var numericalOrAlphaRE = regexp.MustCompile(`(\d+|\D+)`) + +func splitNumericalChunks(s string) []string { + return numericalOrAlphaRE.FindAllString(s, -1) +} + +func parseInt(s string) *int64 { + if n, err := strconv.ParseInt(s, 10, 64); err == nil { + return &n + } + return nil +} diff --git a/plugin/checker/internal/version/version_test.go b/plugin/checker/internal/version/version_test.go new file mode 100644 index 0000000000..54bdf65b5c --- /dev/null +++ b/plugin/checker/internal/version/version_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package version + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVersionComparison(t *testing.T) { + testCases := []struct { + a, b V + }{ + { + a: V("1.2"), + b: V("1.10"), + }, + { + a: V("1.2.1"), + b: V("1.2.3"), + }, + { + a: V("1.2"), + b: V("1.2.3"), + }, + { + a: V("1.2.1"), + b: V("1.2.3"), + }, + { + a: V("1.1"), + b: V("1.2.3"), + }, + { + a: V("1.2.3"), + b: V("1.3"), + }, + { + a: V("1.2.1-rc2"), + b: V("1.2.1-rc10"), + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%+v", tc), func(t *testing.T) { + assert.True(t, tc.a.LessThan(tc.b)) + assert.False(t, tc.b.LessThan(tc.a)) + + assert.True(t, tc.b.GreaterThanOrEqualTo(tc.a)) + assert.False(t, tc.a.GreaterThanOrEqualTo(tc.b)) + }) + } + + assert.True(t, V("1.2").GreaterThanOrEqualTo("1.2")) +} diff --git a/plugin/checker/main.go b/plugin/checker/main.go index 724bf18278..d7e956426e 100644 --- a/plugin/checker/main.go +++ b/plugin/checker/main.go @@ -4,123 +4,69 @@ package main import ( - "bytes" "fmt" "os" - "path/filepath" - "regexp" + "sort" "strings" - - "go/ast" - - "golang.org/x/tools/go/packages" - - "github.com/pkg/errors" ) const pluginPackagePath = "github.com/mattermost/mattermost-server/plugin" +type result struct { + Warnings []string + Errors []string +} + +type checkFn func(pkgPath string) (result, error) + +var checks = []checkFn{ + checkAPIVersionComments, + checkHelpersVersionComments, +} + func main() { - if err := runCheck(pluginPackagePath); err != nil { + var res result + for _, check := range checks { + res = runCheck(res, check) + } + + var msgs []string + msgs = append(msgs, res.Errors...) + msgs = append(msgs, res.Warnings...) + sort.Strings(msgs) + + if len(msgs) > 0 { fmt.Fprintln(os.Stderr, "#", pluginPackagePath) - fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(os.Stderr, strings.Join(msgs, "\n")) + } + + if len(res.Errors) > 0 { os.Exit(1) } } -func runCheck(pkgPath string) error { - pkg, err := getPackage(pkgPath) +func runCheck(prev result, fn checkFn) result { + res, err := fn(pluginPackagePath) if err != nil { - return err + prev.Errors = append(prev.Errors, err.Error()) + return prev } - apiInterface := findAPIInterface(pkg.Syntax) - if apiInterface == nil { - return errors.Errorf("could not find API interface in package %s", pkgPath) + if len(res.Warnings) > 0 { + prev.Warnings = append(prev.Warnings, mapWarnings(res.Warnings)...) } - invalidMethods := findInvalidMethods(apiInterface.Methods.List) - if len(invalidMethods) > 0 { - return errors.New(renderErrorMessage(pkg, invalidMethods)) + if len(res.Errors) > 0 { + prev.Errors = append(prev.Errors, res.Errors...) } - return nil + + return prev } -func getPackage(pkgPath string) (*packages.Package, error) { - cfg := &packages.Config{ - Mode: packages.NeedName | packages.NeedTypes | packages.NeedSyntax, +func mapWarnings(ss []string) []string { + var out []string + for _, s := range ss { + out = append(out, "[warn] "+s) } - pkgs, err := packages.Load(cfg, pkgPath) - if err != nil { - return nil, err - } - - if len(pkgs) == 0 { - return nil, errors.Errorf("could not find package %s", pkgPath) - } - return pkgs[0], nil -} - -func findAPIInterface(files []*ast.File) *ast.InterfaceType { - for _, f := range files { - var iface *ast.InterfaceType - - ast.Inspect(f, func(n ast.Node) bool { - if t, ok := n.(*ast.TypeSpec); ok { - if i, ok := t.Type.(*ast.InterfaceType); ok && t.Name.Name == "API" { - iface = i - return false - } - } - return true - }) - - if iface != nil { - return iface - } - } - return nil -} - -func findInvalidMethods(methods []*ast.Field) []*ast.Field { - var invalid []*ast.Field - for _, m := range methods { - if !hasValidMinimumVersionComment(m.Doc.Text()) { - invalid = append(invalid, m) - } - } - return invalid -} - -var versionRequirementRE = regexp.MustCompile(`^Minimum server version: \d+\.\d+(\.\d+)?$`) - -func hasValidMinimumVersionComment(s string) bool { - lines := strings.Split(strings.TrimSpace(s), "\n") - if len(lines) > 0 { - lastLine := lines[len(lines)-1] - return versionRequirementRE.MatchString(lastLine) - } - return false -} - -func renderErrorMessage(pkg *packages.Package, methods []*ast.Field) string { - cwd, _ := os.Getwd() - out := &bytes.Buffer{} - - for _, m := range methods { - pos := pkg.Fset.Position(m.Pos()) - filename, err := filepath.Rel(cwd, pos.Filename) - if err != nil { - // If deriving a relative path fails for some reason, - // we prefer to still print the absolute path to the file. - filename = pos.Filename - } - fmt.Fprintf(out, - "%s:%d:%d: missing a minimum server version comment\n", - filename, - pos.Line, - pos.Column, - ) - } - return out.String() + return out } diff --git a/plugin/checker/render.go b/plugin/checker/render.go new file mode 100644 index 0000000000..885c09e986 --- /dev/null +++ b/plugin/checker/render.go @@ -0,0 +1,29 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package main + +import ( + "fmt" + "go/token" + "os" + "path/filepath" +) + +func renderWithFilePosition(fset *token.FileSet, pos token.Pos, msg string) string { + var cwd string + if d, err := os.Getwd(); err == nil { + cwd = d + } + + fpos := fset.Position(pos) + + filename, err := filepath.Rel(cwd, fpos.Filename) + if err != nil { + // If deriving a relative path fails for some reason, + // we prefer to still print the absolute path to the file. + filename = fpos.Filename + } + + return fmt.Sprintf("%s:%d:%d: %s", filename, fpos.Line, fpos.Column, msg) +} diff --git a/plugin/checker/test/valid/valid.go b/plugin/checker/test/valid/valid.go deleted file mode 100644 index d1fceafcac..0000000000 --- a/plugin/checker/test/valid/valid.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package valid - -type API interface { - // ValidMethod is a fake method for testing the - // plugin comment checker with a valid comment. - // - // Minimum server version: 1.2.3 - ValidMethod() -} diff --git a/plugin/client_rpc.go b/plugin/client_rpc.go index 1bf1f4e435..0ef0709358 100644 --- a/plugin/client_rpc.go +++ b/plugin/client_rpc.go @@ -19,7 +19,7 @@ import ( "reflect" "github.com/dyatlov/go-opengraph/opengraph" - plugin "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin" "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" ) @@ -531,3 +531,118 @@ func (s *hooksRPCServer) MessageWillBeUpdated(args *Z_MessageWillBeUpdatedArgs, } return nil } + +type Z_LogDebugArgs struct { + A string + B []interface{} +} + +type Z_LogDebugReturns struct { +} + +func (g *apiRPCClient) LogDebug(msg string, keyValuePairs ...interface{}) { + stringifiedPairs := stringifyToObjects(keyValuePairs) + _args := &Z_LogDebugArgs{msg, stringifiedPairs} + _returns := &Z_LogDebugReturns{} + if err := g.client.Call("Plugin.LogDebug", _args, _returns); err != nil { + log.Printf("RPC call to LogDebug API failed: %s", err.Error()) + } + +} + +func (s *apiRPCServer) LogDebug(args *Z_LogDebugArgs, returns *Z_LogDebugReturns) error { + if hook, ok := s.impl.(interface { + LogDebug(msg string, keyValuePairs ...interface{}) + }); ok { + hook.LogDebug(args.A, args.B...) + } else { + return encodableError(fmt.Errorf("API LogDebug called but not implemented.")) + } + return nil +} + +type Z_LogInfoArgs struct { + A string + B []interface{} +} + +type Z_LogInfoReturns struct { +} + +func (g *apiRPCClient) LogInfo(msg string, keyValuePairs ...interface{}) { + stringifiedPairs := stringifyToObjects(keyValuePairs) + _args := &Z_LogInfoArgs{msg, stringifiedPairs} + _returns := &Z_LogInfoReturns{} + if err := g.client.Call("Plugin.LogInfo", _args, _returns); err != nil { + log.Printf("RPC call to LogInfo API failed: %s", err.Error()) + } + +} + +func (s *apiRPCServer) LogInfo(args *Z_LogInfoArgs, returns *Z_LogInfoReturns) error { + if hook, ok := s.impl.(interface { + LogInfo(msg string, keyValuePairs ...interface{}) + }); ok { + hook.LogInfo(args.A, args.B...) + } else { + return encodableError(fmt.Errorf("API LogInfo called but not implemented.")) + } + return nil +} + +type Z_LogWarnArgs struct { + A string + B []interface{} +} + +type Z_LogWarnReturns struct { +} + +func (g *apiRPCClient) LogWarn(msg string, keyValuePairs ...interface{}) { + stringifiedPairs := stringifyToObjects(keyValuePairs) + _args := &Z_LogWarnArgs{msg, stringifiedPairs} + _returns := &Z_LogWarnReturns{} + if err := g.client.Call("Plugin.LogWarn", _args, _returns); err != nil { + log.Printf("RPC call to LogWarn API failed: %s", err.Error()) + } + +} + +func (s *apiRPCServer) LogWarn(args *Z_LogWarnArgs, returns *Z_LogWarnReturns) error { + if hook, ok := s.impl.(interface { + LogWarn(msg string, keyValuePairs ...interface{}) + }); ok { + hook.LogWarn(args.A, args.B...) + } else { + return encodableError(fmt.Errorf("API LogWarn called but not implemented.")) + } + return nil +} + +type Z_LogErrorArgs struct { + A string + B []interface{} +} + +type Z_LogErrorReturns struct { +} + +func (g *apiRPCClient) LogError(msg string, keyValuePairs ...interface{}) { + stringifiedPairs := stringifyToObjects(keyValuePairs) + _args := &Z_LogErrorArgs{msg, stringifiedPairs} + _returns := &Z_LogErrorReturns{} + if err := g.client.Call("Plugin.LogError", _args, _returns); err != nil { + log.Printf("RPC call to LogError API failed: %s", err.Error()) + } +} + +func (s *apiRPCServer) LogError(args *Z_LogErrorArgs, returns *Z_LogErrorReturns) error { + if hook, ok := s.impl.(interface { + LogError(msg string, keyValuePairs ...interface{}) + }); ok { + hook.LogError(args.A, args.B...) + } else { + return encodableError(fmt.Errorf("API LogError called but not implemented.")) + } + return nil +} diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go index d91ae36db6..37e6587321 100644 --- a/plugin/client_rpc_generated.go +++ b/plugin/client_rpc_generated.go @@ -3989,118 +3989,6 @@ func (s *apiRPCServer) HasPermissionToChannel(args *Z_HasPermissionToChannelArgs return nil } -type Z_LogDebugArgs struct { - A string - B []interface{} -} - -type Z_LogDebugReturns struct { -} - -func (g *apiRPCClient) LogDebug(msg string, keyValuePairs ...interface{}) { - _args := &Z_LogDebugArgs{msg, keyValuePairs} - _returns := &Z_LogDebugReturns{} - if err := g.client.Call("Plugin.LogDebug", _args, _returns); err != nil { - log.Printf("RPC call to LogDebug API failed: %s", err.Error()) - } - -} - -func (s *apiRPCServer) LogDebug(args *Z_LogDebugArgs, returns *Z_LogDebugReturns) error { - if hook, ok := s.impl.(interface { - LogDebug(msg string, keyValuePairs ...interface{}) - }); ok { - hook.LogDebug(args.A, args.B...) - } else { - return encodableError(fmt.Errorf("API LogDebug called but not implemented.")) - } - return nil -} - -type Z_LogInfoArgs struct { - A string - B []interface{} -} - -type Z_LogInfoReturns struct { -} - -func (g *apiRPCClient) LogInfo(msg string, keyValuePairs ...interface{}) { - _args := &Z_LogInfoArgs{msg, keyValuePairs} - _returns := &Z_LogInfoReturns{} - if err := g.client.Call("Plugin.LogInfo", _args, _returns); err != nil { - log.Printf("RPC call to LogInfo API failed: %s", err.Error()) - } - -} - -func (s *apiRPCServer) LogInfo(args *Z_LogInfoArgs, returns *Z_LogInfoReturns) error { - if hook, ok := s.impl.(interface { - LogInfo(msg string, keyValuePairs ...interface{}) - }); ok { - hook.LogInfo(args.A, args.B...) - } else { - return encodableError(fmt.Errorf("API LogInfo called but not implemented.")) - } - return nil -} - -type Z_LogErrorArgs struct { - A string - B []interface{} -} - -type Z_LogErrorReturns struct { -} - -func (g *apiRPCClient) LogError(msg string, keyValuePairs ...interface{}) { - _args := &Z_LogErrorArgs{msg, keyValuePairs} - _returns := &Z_LogErrorReturns{} - if err := g.client.Call("Plugin.LogError", _args, _returns); err != nil { - log.Printf("RPC call to LogError API failed: %s", err.Error()) - } - -} - -func (s *apiRPCServer) LogError(args *Z_LogErrorArgs, returns *Z_LogErrorReturns) error { - if hook, ok := s.impl.(interface { - LogError(msg string, keyValuePairs ...interface{}) - }); ok { - hook.LogError(args.A, args.B...) - } else { - return encodableError(fmt.Errorf("API LogError called but not implemented.")) - } - return nil -} - -type Z_LogWarnArgs struct { - A string - B []interface{} -} - -type Z_LogWarnReturns struct { -} - -func (g *apiRPCClient) LogWarn(msg string, keyValuePairs ...interface{}) { - _args := &Z_LogWarnArgs{msg, keyValuePairs} - _returns := &Z_LogWarnReturns{} - if err := g.client.Call("Plugin.LogWarn", _args, _returns); err != nil { - log.Printf("RPC call to LogWarn API failed: %s", err.Error()) - } - -} - -func (s *apiRPCServer) LogWarn(args *Z_LogWarnArgs, returns *Z_LogWarnReturns) error { - if hook, ok := s.impl.(interface { - LogWarn(msg string, keyValuePairs ...interface{}) - }); ok { - hook.LogWarn(args.A, args.B...) - } else { - return encodableError(fmt.Errorf("API LogWarn called but not implemented.")) - } - return nil -} - type Z_SendMailArgs struct { A string B string diff --git a/plugin/helpers.go b/plugin/helpers.go index 5f96bf4da1..94d66c0683 100644 --- a/plugin/helpers.go +++ b/plugin/helpers.go @@ -8,9 +8,13 @@ import "github.com/mattermost/mattermost-server/model" type Helpers interface { // EnsureBot either returns an existing bot user matching the given bot, or creates a bot user from the given bot. // Returns the id of the resulting bot. + // + // Minimum server version: 5.10 EnsureBot(bot *model.Bot) (string, error) // KVSetJSON stores a key-value pair, unique per plugin, marshalling the given value as a JSON string. + // + // Minimum server version: 5.2 KVSetJSON(key string, value interface{}) error // KVCompareAndSetJSON updates a key-value pair, unique per plugin, but only if the current value matches the given oldValue after marshalling as a JSON string. @@ -31,6 +35,8 @@ type Helpers interface { KVCompareAndDeleteJSON(key string, oldValue interface{}) (bool, error) // KVGetJSON retrieves a value based on the key, unique per plugin, unmarshalling the previously set JSON string into the given value. Returns true if the key exists. + // + // Minimum server version: 5.2 KVGetJSON(key string, value interface{}) (bool, error) // KVSetWithExpiryJSON stores a key-value pair with an expiry time, unique per plugin, marshalling the given value as a JSON string. diff --git a/plugin/interface_generator/main.go b/plugin/interface_generator/main.go index 4afb254436..e3a70fbd08 100644 --- a/plugin/interface_generator/main.go +++ b/plugin/interface_generator/main.go @@ -399,6 +399,10 @@ func removeExcluded(info *PluginInterfaceInfo) *PluginInterfaceInfo { "FileWillBeUploaded", "MessageWillBePosted", "MessageWillBeUpdated", + "LogDebug", + "LogInfo", + "LogWarn", + "LogError", } for _, exclusion := range excluded { if exclusion == item { diff --git a/plugin/stringifier.go b/plugin/stringifier.go new file mode 100644 index 0000000000..1455fb4e63 --- /dev/null +++ b/plugin/stringifier.go @@ -0,0 +1,31 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package plugin + +import ( + "fmt" +) + +func stringify(objects []interface{}) []string { + stringified := make([]string, len(objects), len(objects)) + for i, object := range objects { + stringified[i] = fmt.Sprintf("%+v", object) + } + return stringified +} + +func toObjects(strings []string) []interface{} { + if strings == nil { + return nil + } + objects := make([]interface{}, len(strings)) + for i, string := range strings { + objects[i] = string + } + return objects +} + +func stringifyToObjects(objects []interface{}) []interface{} { + return toObjects(stringify(objects)) +} diff --git a/plugin/stringifier_test.go b/plugin/stringifier_test.go new file mode 100644 index 0000000000..58ae7e44d1 --- /dev/null +++ b/plugin/stringifier_test.go @@ -0,0 +1,93 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package plugin + +import ( + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestStringify(t *testing.T) { + t.Run("NilShouldReturnEmpty", func(t *testing.T) { + strings := stringify(nil) + assert.Empty(t, strings) + }) + t.Run("EmptyShouldReturnEmpty", func(t *testing.T) { + strings := stringify(make([]interface{}, 0, 0)) + assert.Empty(t, strings) + }) + t.Run("PrimitivesAndCompositesShouldReturnCorrectValues", func(t *testing.T) { + strings := stringify([]interface{}{ + 1234, + 3.14159265358979323846264338327950288419716939937510, + true, + "foo", + nil, + []string{"foo", "bar"}, + map[string]int{"one": 1, "two": 2}, + &WithString{}, + &WithoutString{}, + &WithStringAndError{}, + }) + assert.Equal(t, []string{ + "1234", + "3.141592653589793", + "true", + "foo", + "", + "[foo bar]", + "map[one:1 two:2]", + "string", + "&{}", + "error", + }, strings) + }) + t.Run("ErrorShouldReturnFormattedStack", func(t *testing.T) { + strings := stringify([]interface{}{ + errors.New("error"), + errors.WithStack(errors.New("error")), + }) + stackRegexp := "error\n.*plugin.TestStringify.func\\d+\n\t.*plugin/stringifier_test.go:\\d+\ntesting.tRunner\n\t.*testing.go:\\d+.*" + assert.Len(t, strings, 2) + assert.Regexp(t, stackRegexp, strings[0]) + assert.Regexp(t, stackRegexp, strings[1]) + }) +} + +type WithString struct { +} + +func (*WithString) String() string { + return "string" +} + +type WithoutString struct { +} + +type WithStringAndError struct { +} + +func (*WithStringAndError) String() string { + return "string" +} + +func (*WithStringAndError) Error() string { + return "error" +} + +func TestToObjects(t *testing.T) { + t.Run("NilShouldReturnNil", func(t *testing.T) { + objects := toObjects(nil) + assert.Nil(t, objects) + }) + t.Run("EmptyShouldReturnEmpty", func(t *testing.T) { + objects := toObjects(make([]string, 0, 0)) + assert.Empty(t, objects) + }) + t.Run("ShouldReturnSliceOfObjects", func(t *testing.T) { + objects := toObjects([]string{"foo", "bar"}) + assert.Equal(t, []interface{}{"foo", "bar"}, objects) + }) +} diff --git a/store/layered_store.go b/store/layered_store.go index 6316b2b3e2..51672fbd4b 100644 --- a/store/layered_store.go +++ b/store/layered_store.go @@ -7,11 +7,6 @@ import ( "context" "github.com/mattermost/mattermost-server/einterfaces" - "github.com/mattermost/mattermost-server/mlog" -) - -const ( - ENABLE_EXPERIMENTAL_REDIS = false ) type LayeredStoreDatabaseLayer interface { @@ -23,7 +18,6 @@ type LayeredStore struct { TmpContext context.Context DatabaseLayer LayeredStoreDatabaseLayer LocalCacheLayer *LocalCacheSupplier - RedisLayer *RedisSupplier LayerChainHead LayeredStoreSupplier } @@ -35,15 +29,8 @@ func NewLayeredStore(db LayeredStoreDatabaseLayer, metrics einterfaces.MetricsIn } // Setup the chain - if ENABLE_EXPERIMENTAL_REDIS { - mlog.Debug("Experimental redis enabled.") - store.RedisLayer = NewRedisSupplier() - store.RedisLayer.SetChainNext(store.DatabaseLayer) - store.LayerChainHead = store.RedisLayer - } else { - store.LocalCacheLayer.SetChainNext(store.DatabaseLayer) - store.LayerChainHead = store.LocalCacheLayer - } + store.LocalCacheLayer.SetChainNext(store.DatabaseLayer) + store.LayerChainHead = store.LocalCacheLayer return store } diff --git a/store/redis_supplier.go b/store/redis_supplier.go deleted file mode 100644 index ce8cb0f0d9..0000000000 --- a/store/redis_supplier.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package store - -import ( - "bytes" - "encoding/gob" - - "time" - - "github.com/go-redis/redis" - "github.com/mattermost/mattermost-server/mlog" -) - -const REDIS_EXPIRY_TIME = 30 * time.Minute - -type RedisSupplier struct { - next LayeredStoreSupplier - client *redis.Client -} - -func GetBytes(key interface{}) ([]byte, error) { - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - err := enc.Encode(key) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -func DecodeBytes(input []byte, thing interface{}) error { - dec := gob.NewDecoder(bytes.NewReader(input)) - return dec.Decode(thing) -} - -func NewRedisSupplier() *RedisSupplier { - supplier := &RedisSupplier{} - - supplier.client = redis.NewClient(&redis.Options{ - Addr: "localhost:6379", - Password: "", - DB: 0, - }) - - if _, err := supplier.client.Ping().Result(); err != nil { - mlog.Error("Unable to ping redis server: " + err.Error()) - return nil - } - - return supplier -} - -func (s *RedisSupplier) save(key string, value interface{}, expiry time.Duration) error { - if bytes, err := GetBytes(value); err != nil { - return err - } else { - if err := s.client.Set(key, bytes, expiry).Err(); err != nil { - return err - } - } - return nil -} - -func (s *RedisSupplier) load(key string, writeTo interface{}) (bool, error) { - if data, err := s.client.Get(key).Bytes(); err != nil { - if err == redis.Nil { - return false, nil - } else { - return false, err - } - } else { - if err := DecodeBytes(data, writeTo); err != nil { - return false, err - } - } - return true, nil -} - -func (s *RedisSupplier) SetChainNext(next LayeredStoreSupplier) { - s.next = next -} - -func (s *RedisSupplier) Next() LayeredStoreSupplier { - return s.next -} diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go index 6bf9f09436..29b88be307 100644 --- a/store/storetest/channel_store.go +++ b/store/storetest/channel_store.go @@ -24,9 +24,7 @@ type SqlSupplier interface { func cleanupChannels(t *testing.T, ss store.Store) { list, err := ss.Channel().GetAllChannels(0, 100000, store.ChannelSearchOpts{IncludeDeleted: true}) - if err != nil { - t.Fatalf("error cleaning all channels: %v", err) - } + require.Nilf(t, err, "error cleaning all channels: %v", err) for _, channel := range *list { ss.Channel().PermanentDelete(channel.Id) } @@ -100,25 +98,21 @@ func testChannelStoreSave(t *testing.T, ss store.Store) { o1.Name = "zz" + model.NewId() + "b" o1.Type = model.CHANNEL_OPEN - if _, err := ss.Channel().Save(&o1, -1); err != nil { - t.Fatal("couldn't save item", err) - } + _, err := ss.Channel().Save(&o1, -1) + require.Nil(t, err, "couldn't save item", err) - if _, err := ss.Channel().Save(&o1, -1); err == nil { - t.Fatal("shouldn't be able to update from save") - } + _, err = ss.Channel().Save(&o1, -1) + require.NotNil(t, err, "shouldn't be able to update from save") o1.Id = "" - if _, err := ss.Channel().Save(&o1, -1); err == nil { - t.Fatal("should be unique name") - } + _, err = ss.Channel().Save(&o1, -1) + require.NotNil(t, err, "should be unique name") o1.Id = "" o1.Name = "zz" + model.NewId() + "b" o1.Type = model.CHANNEL_DIRECT - if _, err := ss.Channel().Save(&o1, -1); err == nil { - t.Fatal("Should not be able to save direct channel") - } + _, err = ss.Channel().Save(&o1, -1) + require.NotNil(t, err, "should not be able to save direct channel") } func testChannelStoreSaveDirectChannel(t *testing.T, ss store.Store, s SqlSupplier) { @@ -156,19 +150,15 @@ func testChannelStoreSaveDirectChannel(t *testing.T, ss store.Store, s SqlSuppli m2.UserId = u2.Id m2.NotifyProps = model.GetDefaultChannelNotifyProps() - if _, err = ss.Channel().SaveDirectChannel(&o1, &m1, &m2); err != nil { - t.Fatal("couldn't save direct channel", err) - } + _, err = ss.Channel().SaveDirectChannel(&o1, &m1, &m2) + require.Nil(t, err, "couldn't save direct channel", err) members, err := ss.Channel().GetMembers(o1.Id, 0, 100) require.Nil(t, err) - if len(*members) != 2 { - t.Fatal("should have saved 2 members") - } + require.Len(t, *members, 2, "should have saved 2 members") - if _, err = ss.Channel().SaveDirectChannel(&o1, &m1, &m2); err == nil { - t.Fatal("shouldn't be able to update from save") - } + _, err = ss.Channel().SaveDirectChannel(&o1, &m1, &m2) + require.NotNil(t, err, "shoudn't be a able to update from save") // Attempt to save a direct channel that already exists o1a := model.Channel{ @@ -179,36 +169,28 @@ func testChannelStoreSaveDirectChannel(t *testing.T, ss store.Store, s SqlSuppli } returnedChannel, err := ss.Channel().SaveDirectChannel(&o1a, &m1, &m2) - if err == nil { - t.Fatal("should've failed to save a duplicate direct channel") - } else if err.Id != store.CHANNEL_EXISTS_ERROR { - t.Fatal("should've returned CHANNEL_EXISTS_ERROR") - } else if returnedChannel.Id != o1.Id { - t.Fatal("should've returned original channel when saving a duplicate direct channel") - } + require.NotNil(t, err, "should've failed to save a duplicate direct channel") + require.Equal(t, store.CHANNEL_EXISTS_ERROR, err.Id, "should've returned CHANNEL_EXISTS_ERROR") + require.Equal(t, o1.Id, returnedChannel.Id, "should've failed to save a duplicate direct channel") // Attempt to save a non-direct channel o1.Id = "" o1.Name = "zz" + model.NewId() + "b" o1.Type = model.CHANNEL_OPEN - if _, err = ss.Channel().SaveDirectChannel(&o1, &m1, &m2); err == nil { - t.Fatal("Should not be able to save non-direct channel") - } + _, err = ss.Channel().SaveDirectChannel(&o1, &m1, &m2) + require.NotNil(t, err, "Should not be able to save non-direct channel") // Save yourself Direct Message o1.Id = "" o1.DisplayName = "Myself" o1.Name = "zz" + model.NewId() + "b" o1.Type = model.CHANNEL_DIRECT - if _, err = ss.Channel().SaveDirectChannel(&o1, &m1, &m1); err != nil { - t.Fatal("couldn't save direct channel", err) - } + _, err = ss.Channel().SaveDirectChannel(&o1, &m1, &m1) + require.Nil(t, err, "couldn't save direct channel", err) members, err = ss.Channel().GetMembers(o1.Id, 0, 100) require.Nil(t, err) - if len(*members) != 1 { - t.Fatal("should have saved just 1 member") - } + require.Len(t, *members, 1, "should have saved just 1 member") // Manually truncate Channels table until testlib can handle cleanups s.GetMaster().Exec("TRUNCATE Channels") @@ -232,9 +214,7 @@ func testChannelStoreCreateDirectChannel(t *testing.T, ss store.Store) { require.Nil(t, err) c1, err := ss.Channel().CreateDirectChannel(u1, u2) - if err != nil { - t.Fatal("couldn't create direct channel", err) - } + require.Nil(t, err, "couldn't create direct channel", err) defer func() { ss.Channel().PermanentDeleteMembersByChannel(c1.Id) ss.Channel().PermanentDelete(c1.Id) @@ -242,9 +222,7 @@ func testChannelStoreCreateDirectChannel(t *testing.T, ss store.Store) { members, err := ss.Channel().GetMembers(c1.Id, 0, 100) require.Nil(t, err) - if len(*members) != 2 { - t.Fatal("should have saved 2 members") - } + require.Len(t, *members, 2, "should have saved 2 members") } func testChannelStoreUpdate(t *testing.T, ss store.Store) { @@ -268,30 +246,25 @@ func testChannelStoreUpdate(t *testing.T, ss store.Store) { time.Sleep(100 * time.Millisecond) - if _, err := ss.Channel().Update(&o1); err != nil { - t.Fatal(err) - } + _, err = ss.Channel().Update(&o1) + require.Nil(t, err, err) o1.DeleteAt = 100 - if _, err := ss.Channel().Update(&o1); err == nil { - t.Fatal("Update should have failed because channel is archived") - } + _, err = ss.Channel().Update(&o1) + require.NotNil(t, err, "update should have failed because channel is archived") o1.DeleteAt = 0 o1.Id = "missing" - if _, err := ss.Channel().Update(&o1); err == nil { - t.Fatal("Update should have failed because of missing key") - } + _, err = ss.Channel().Update(&o1) + require.NotNil(t, err, "Update should have failed because of missing key") o1.Id = model.NewId() - if _, err := ss.Channel().Update(&o1); err == nil { - t.Fatal("Update should have faile because id change") - } + _, err = ss.Channel().Update(&o1) + require.NotNil(t, err, "update should have failed because id change") o2.Name = o1.Name - if _, err := ss.Channel().Update(&o2); err == nil { - t.Fatal("Update should have failed because of existing name") - } + _, err = ss.Channel().Update(&o2) + require.NotNil(t, err, "update should have failed because of existing name") } func testGetChannelUnread(t *testing.T, ss store.Store) { @@ -326,50 +299,23 @@ func testGetChannelUnread(t *testing.T, ss store.Store) { require.Nil(t, err) // Check for Channel 1 - if ch, err := ss.Channel().GetChannelUnread(c1.Id, uid); err != nil { - t.Fatal(err) - } else { - if c1.Id != ch.ChannelId { - t.Fatal("wrong channel id") - } + ch, err := ss.Channel().GetChannelUnread(c1.Id, uid) - if teamId1 != ch.TeamId { - t.Fatal("wrong team id for channel 1") - } - - if ch.NotifyProps == nil { - t.Fatal("wrong props for channel 1") - } - - if ch.MentionCount != 0 { - t.Fatal("wrong MentionCount for channel 1") - } - - if ch.MsgCount != 10 { - t.Fatal("wrong MsgCount for channel 1") - } - } + require.Nil(t, err, err) + require.Equal(t, c1.Id, ch.ChannelId, "Wrong channel id") + require.Equal(t, teamId1, ch.TeamId, "Wrong team id for channel 1") + require.NotNil(t, ch.NotifyProps, "wrong props for channel 1") + require.EqualValues(t, 0, ch.MentionCount, "wrong MentionCount for channel 1") + require.EqualValues(t, 10, ch.MsgCount, "wrong MsgCount for channel 1") // Check for Channel 2 - if ch2, err := ss.Channel().GetChannelUnread(c2.Id, uid); err != nil { - t.Fatal(err) - } else { - if c2.Id != ch2.ChannelId { - t.Fatal("wrong channel id") - } + ch2, err := ss.Channel().GetChannelUnread(c2.Id, uid) - if teamId2 != ch2.TeamId { - t.Fatal("wrong team id") - } - - if ch2.MentionCount != 5 { - t.Fatal("wrong MentionCount for channel 2") - } - - if ch2.MsgCount != 10 { - t.Fatal("wrong MsgCount for channel 2") - } - } + require.Nil(t, err, err) + require.Equal(t, c2.Id, ch2.ChannelId, "Wrong channel id") + require.Equal(t, teamId2, ch2.TeamId, "Wrong team id") + require.EqualValues(t, 5, ch2.MentionCount, "wrong MentionCount for channel 2") + require.EqualValues(t, 10, ch2.MsgCount, "wrong MsgCount for channel 2") } func testChannelStoreGet(t *testing.T, ss store.Store, s SqlSupplier) { @@ -382,17 +328,12 @@ func testChannelStoreGet(t *testing.T, ss store.Store, s SqlSupplier) { require.Nil(t, err) c1 := &model.Channel{} - if c1, err = ss.Channel().Get(o1.Id, false); err != nil { - t.Fatal(err) - } else { - if c1.ToJson() != o1.ToJson() { - t.Fatal("invalid returned channel") - } - } + c1, err = ss.Channel().Get(o1.Id, false) + require.Nil(t, err, err) + require.Equal(t, o1.ToJson(), c1.ToJson(), "invalid returned channel") - if _, err = ss.Channel().Get("", false); err == nil { - t.Fatal("Missing id should have failed") - } + _, err = ss.Channel().Get("", false) + require.NotNil(t, err, "missing id should have failed") u1 := &model.User{} u1.Email = MakeEmail() @@ -429,37 +370,22 @@ func testChannelStoreGet(t *testing.T, ss store.Store, s SqlSupplier) { _, err = ss.Channel().SaveDirectChannel(&o2, &m1, &m2) require.Nil(t, err) - if c2, err := ss.Channel().Get(o2.Id, false); err != nil { - t.Fatal(err) - } else { - if c2.ToJson() != o2.ToJson() { - t.Fatal("invalid returned channel") - } - } + c2, err := ss.Channel().Get(o2.Id, false) + require.Nil(t, err, err) + require.Equal(t, o2.ToJson(), c2.ToJson(), "invalid returned channel") - if c4, err := ss.Channel().Get(o2.Id, true); err != nil { - t.Fatal(err) - } else { - if c4.ToJson() != o2.ToJson() { - t.Fatal("invalid returned channel") - } - } + c4, err := ss.Channel().Get(o2.Id, true) + require.Nil(t, err, err) + require.Equal(t, o2.ToJson(), c4.ToJson(), "invalid returned channel") - if channels, chanErr := ss.Channel().GetAll(o1.TeamId); chanErr != nil { - t.Fatal(chanErr) - } else { - if len(channels) == 0 { - t.Fatal("too little") - } - } + channels, chanErr := ss.Channel().GetAll(o1.TeamId) + require.Nil(t, chanErr, chanErr) + require.Greater(t, len(channels), 0, "too little") + + channelsTeam, err := ss.Channel().GetTeamChannels(o1.TeamId) + require.Nil(t, err, err) + require.Greater(t, len(*channelsTeam), 0, "too little") - if channels, err := ss.Channel().GetTeamChannels(o1.TeamId); err != nil { - t.Fatal(err) - } else { - if len(*channels) == 0 { - t.Fatal("too little") - } - } // Manually truncate Channels table until testlib can handle cleanups s.GetMaster().Exec("TRUNCATE Channels") } @@ -508,31 +434,17 @@ func testChannelStoreGetChannelsByIds(t *testing.T, ss store.Store) { _, err = ss.Channel().SaveDirectChannel(&o2, &m1, &m2) require.Nil(t, err) - if r1, err := ss.Channel().GetChannelsByIds([]string{o1.Id, o2.Id}); err != nil { - t.Fatal(err) - } else { - if len(r1) != 2 { - t.Fatal("invalid returned channels, expected 2 and got " + strconv.Itoa(len(r1))) - } - if r1[0].ToJson() != o1.ToJson() { - t.Fatal("invalid returned channel") - } - if r1[1].ToJson() != o2.ToJson() { - t.Fatal("invalid returned channel") - } - } + r1, err := ss.Channel().GetChannelsByIds([]string{o1.Id, o2.Id}) + require.Nil(t, err, err) + require.Len(t, r1, 2, "invalid returned channels, exepected 2 and got "+strconv.Itoa(len(r1))) + require.Equal(t, o1.ToJson(), r1[0].ToJson()) + require.Equal(t, o2.ToJson(), r1[1].ToJson()) nonexistentId := "abcd1234" - if r2, err := ss.Channel().GetChannelsByIds([]string{o1.Id, nonexistentId}); err != nil { - t.Fatal(err) - } else { - if len(r2) != 1 { - t.Fatal("invalid returned channels, expected 1 and got " + strconv.Itoa(len(r2))) - } - if r2[0].ToJson() != o1.ToJson() { - t.Fatal("invalid returned channel") - } - } + r2, err := ss.Channel().GetChannelsByIds([]string{o1.Id, nonexistentId}) + require.Nil(t, err, err) + require.Len(t, r2, 1, "invalid returned channels, expected 1 and got "+strconv.Itoa(len(r2))) + require.Equal(t, o1.ToJson(), r2[0].ToJson(), "invalid returned channel") } func testChannelStoreGetForPost(t *testing.T, ss store.Store) { @@ -553,11 +465,9 @@ func testChannelStoreGetForPost(t *testing.T, ss store.Store) { }) require.Nil(t, err) - if channel, chanErr := ss.Channel().GetForPost(p1.Id); chanErr != nil { - t.Fatal(chanErr) - } else if channel.Id != o1.Id { - t.Fatal("incorrect channel returned") - } + channel, chanErr := ss.Channel().GetForPost(p1.Id) + require.Nil(t, chanErr, chanErr) + require.Equal(t, o1.Id, channel.Id, "incorrect channel returned") } func testChannelStoreRestore(t *testing.T, ss store.Store) { @@ -569,22 +479,17 @@ func testChannelStoreRestore(t *testing.T, ss store.Store) { _, err := ss.Channel().Save(&o1, -1) require.Nil(t, err) - if err := ss.Channel().Delete(o1.Id, model.GetMillis()); err != nil { - t.Fatal(err) - } + err = ss.Channel().Delete(o1.Id, model.GetMillis()) + require.Nil(t, err, err) - if c, _ := ss.Channel().Get(o1.Id, false); c.DeleteAt == 0 { - t.Fatal("should have been deleted") - } + c, _ := ss.Channel().Get(o1.Id, false) + require.NotEqual(t, 0, c.DeleteAt, "should have been deleted") - if err := ss.Channel().Restore(o1.Id, model.GetMillis()); err != nil { - t.Fatal(err) - } - - if c, _ := ss.Channel().Get(o1.Id, false); c.DeleteAt != 0 { - t.Fatal("should have been restored") - } + err = ss.Channel().Restore(o1.Id, model.GetMillis()) + require.Nil(t, err, err) + c, _ = ss.Channel().Get(o1.Id, false) + require.EqualValues(t, 0, c.DeleteAt, "should have been restored") } func testChannelStoreDelete(t *testing.T, ss store.Store) { @@ -634,31 +539,22 @@ func testChannelStoreDelete(t *testing.T, ss store.Store) { _, err = ss.Channel().SaveMember(&m2) require.Nil(t, err) - if err = ss.Channel().Delete(o1.Id, model.GetMillis()); err != nil { - t.Fatal(err) - } + err = ss.Channel().Delete(o1.Id, model.GetMillis()) + require.Nil(t, err, err) - if c, _ := ss.Channel().Get(o1.Id, false); c.DeleteAt == 0 { - t.Fatal("should have been deleted") - } + c, _ := ss.Channel().Get(o1.Id, false) + require.NotEqual(t, 0, c.DeleteAt, "should have been deleted") - if err = ss.Channel().Delete(o3.Id, model.GetMillis()); err != nil { - t.Fatal(err) - } + err = ss.Channel().Delete(o3.Id, model.GetMillis()) + require.Nil(t, err, err) list, err := ss.Channel().GetChannels(o1.TeamId, m1.UserId, false) require.Nil(t, err) - - if len(*list) != 1 { - t.Fatal("invalid number of channels") - } + require.Len(t, *list, 1, "invalid number of channels") list, err = ss.Channel().GetMoreChannels(o1.TeamId, m1.UserId, 0, 100) require.Nil(t, err) - - if len(*list) != 1 { - t.Fatal("invalid number of channels") - } + require.Len(t, *list, 1, "invalid number of channels") cresult := ss.Channel().PermanentDelete(o2.Id) require.Nil(t, cresult) @@ -670,9 +566,8 @@ func testChannelStoreDelete(t *testing.T, ss store.Store) { require.Equal(t, &model.ChannelList{}, list) } - if err = ss.Channel().PermanentDeleteByTeam(o1.TeamId); err != nil { - t.Fatal(err) - } + err = ss.Channel().PermanentDeleteByTeam(o1.TeamId) + require.Nil(t, err, err) } func testChannelStoreGetByName(t *testing.T, ss store.Store) { @@ -799,17 +694,9 @@ func testChannelStoreGetDeleted(t *testing.T, ss store.Store) { require.Nil(t, err, "channel should have been deleted") list, err := ss.Channel().GetDeleted(o1.TeamId, 0, 100) - if err != nil { - t.Fatal(err) - } - - if len(*list) != 1 { - t.Fatal("wrong list") - } - - if (*list)[0].Name != o1.Name { - t.Fatal("missing channel") - } + require.Nil(t, err, err) + require.Len(t, *list, 1, "wrong list") + require.Equal(t, o1.Name, (*list)[0].Name, "missing channel") o2 := model.Channel{} o2.TeamId = o1.TeamId @@ -820,13 +707,8 @@ func testChannelStoreGetDeleted(t *testing.T, ss store.Store) { require.Nil(t, err) list, err = ss.Channel().GetDeleted(o1.TeamId, 0, 100) - if err != nil { - t.Fatal(err) - } - - if len(*list) != 1 { - t.Fatal("wrong list") - } + require.Nil(t, err, err) + require.Len(t, *list, 1, "wrong list") o3 := model.Channel{} o3.TeamId = o1.TeamId @@ -841,31 +723,16 @@ func testChannelStoreGetDeleted(t *testing.T, ss store.Store) { require.Nil(t, err, "channel should have been deleted") list, err = ss.Channel().GetDeleted(o1.TeamId, 0, 100) - if err != nil { - t.Fatal(err) - } - - if len(*list) != 2 { - t.Fatal("wrong list length") - } + require.Nil(t, err, err) + require.Len(t, *list, 2, "wrong list length") list, err = ss.Channel().GetDeleted(o1.TeamId, 0, 1) - if err != nil { - t.Fatal(err) - } - - if len(*list) != 1 { - t.Fatal("wrong list length") - } + require.Nil(t, err, err) + require.Len(t, *list, 1, "wrong list length") list, err = ss.Channel().GetDeleted(o1.TeamId, 1, 1) - if err != nil { - t.Fatal(err) - } - - if len(*list) != 1 { - t.Fatal("wrong list length") - } + require.Nil(t, err, err) + require.Len(t, *list, 1, "wrong list length") } @@ -916,50 +783,42 @@ func testChannelMemberStore(t *testing.T, ss store.Store) { count, err := ss.Channel().GetMemberCount(o1.ChannelId, true) require.Nil(t, err) - if count != 2 { - t.Fatal("should have saved 2 members") - } + require.EqualValues(t, 2, count, "should have saved 2 members") count, err = ss.Channel().GetMemberCount(o1.ChannelId, true) require.Nil(t, err) - if count != 2 { - t.Fatal("should have saved 2 members") - } + require.EqualValues(t, 2, count, "should have saved 2 members") + require.EqualValues( + t, + 2, + ss.Channel().GetMemberCountFromCache(o1.ChannelId), + "should have saved 2 members") - if ss.Channel().GetMemberCountFromCache(o1.ChannelId) != 2 { - t.Fatal("should have saved 2 members") - } - - if ss.Channel().GetMemberCountFromCache("junk") != 0 { - t.Fatal("should have saved 0 members") - } + require.EqualValues( + t, + 0, + ss.Channel().GetMemberCountFromCache("junk"), + "should have saved 0 members") count, err = ss.Channel().GetMemberCount(o1.ChannelId, false) require.Nil(t, err) - if count != 2 { - t.Fatal("should have saved 2 members") - } + require.EqualValues(t, 2, count, "should have saved 2 members") err = ss.Channel().RemoveMember(o2.ChannelId, o2.UserId) require.Nil(t, err) count, err = ss.Channel().GetMemberCount(o1.ChannelId, false) require.Nil(t, err) - if count != 1 { - t.Fatal("should have removed 1 member") - } + require.EqualValues(t, 1, count, "should have removed 1 member") c1t3, _ := ss.Channel().Get(c1.Id, false) assert.EqualValues(t, 0, c1t3.ExtraUpdateAt, "ExtraUpdateAt should be 0") member, _ := ss.Channel().GetMember(o1.ChannelId, o1.UserId) - if member.ChannelId != o1.ChannelId { - t.Fatal("should have go member") - } + require.Equal(t, o1.ChannelId, member.ChannelId, "should have go member") - if _, err := ss.Channel().SaveMember(&o1); err == nil { - t.Fatal("Should have been a duplicate") - } + _, err = ss.Channel().SaveMember(&o1) + require.NotNil(t, err, "should have been a duplicate") c1t4, _ := ss.Channel().Get(c1.Id, false) assert.EqualValues(t, 0, c1t4.ExtraUpdateAt, "ExtraUpdateAt should be 0") @@ -1012,28 +871,21 @@ func testChannelDeleteMemberStore(t *testing.T, ss store.Store) { count, err := ss.Channel().GetMemberCount(o1.ChannelId, false) require.Nil(t, err) - if count != 2 { - t.Fatal("should have saved 2 members") - } + require.EqualValues(t, 2, count, "should have saved 2 members") err = ss.Channel().PermanentDeleteMembersByUser(o2.UserId) require.Nil(t, err) count, err = ss.Channel().GetMemberCount(o1.ChannelId, false) require.Nil(t, err) - if count != 1 { - t.Fatal("should have removed 1 member") - } + require.EqualValues(t, 1, count, "should have removed 1 member") - if err = ss.Channel().PermanentDeleteMembersByChannel(o1.ChannelId); err != nil { - t.Fatal(err) - } + err = ss.Channel().PermanentDeleteMembersByChannel(o1.ChannelId) + require.Nil(t, err, err) count, err = ss.Channel().GetMemberCount(o1.ChannelId, false) require.Nil(t, err) - if count != 0 { - t.Fatal("should have removed all members") - } + require.EqualValues(t, 0, count, "should have removed all members") } func testChannelStoreGetChannels(t *testing.T, ss store.Store) { @@ -1076,41 +928,37 @@ func testChannelStoreGetChannels(t *testing.T, ss store.Store) { list, err := ss.Channel().GetChannels(o1.TeamId, m1.UserId, false) require.Nil(t, err) - - if (*list)[0].Id != o1.Id { - t.Fatal("missing channel") - } + require.Equal(t, o1.Id, (*list)[0].Id, "missing channel") ids, _ := ss.Channel().GetAllChannelMembersForUser(m1.UserId, false, false) - if _, ok := ids[o1.Id]; !ok { - t.Fatal("missing channel") - } + _, ok := ids[o1.Id] + require.True(t, ok, "missing channel") ids2, _ := ss.Channel().GetAllChannelMembersForUser(m1.UserId, true, false) - if _, ok := ids2[o1.Id]; !ok { - t.Fatal("missing channel") - } + _, ok = ids2[o1.Id] + require.True(t, ok, "missing channel") ids3, _ := ss.Channel().GetAllChannelMembersForUser(m1.UserId, true, false) - if _, ok := ids3[o1.Id]; !ok { - t.Fatal("missing channel") - } + _, ok = ids3[o1.Id] + require.True(t, ok, "missing channel") + require.True( + t, + ss.Channel().IsUserInChannelUseCache(m1.UserId, o1.Id), + "missing channel") + require.False( + t, + ss.Channel().IsUserInChannelUseCache(m1.UserId, o2.Id), + "missing channel") - if !ss.Channel().IsUserInChannelUseCache(m1.UserId, o1.Id) { - t.Fatal("missing channel") - } + require.False( + t, + ss.Channel().IsUserInChannelUseCache(m1.UserId, "blahblah"), + "missing channel") - if ss.Channel().IsUserInChannelUseCache(m1.UserId, o2.Id) { - t.Fatal("missing channel") - } - - if ss.Channel().IsUserInChannelUseCache(m1.UserId, "blahblah") { - t.Fatal("missing channel") - } - - if ss.Channel().IsUserInChannelUseCache("blahblah", "blahblah") { - t.Fatal("missing channel") - } + require.False( + t, + ss.Channel().IsUserInChannelUseCache("blahblah", "blahblah"), + "missing channel") ss.Channel().InvalidateAllChannelMembersForUser(m1.UserId) } @@ -1192,10 +1040,10 @@ func testChannelStoreGetAllChannels(t *testing.T, ss store.Store, s SqlSupplier) list, err := ss.Channel().GetAllChannels(0, 10, store.ChannelSearchOpts{}) require.Nil(t, err) assert.Len(t, *list, 2) - assert.Equal(t, (*list)[0].Id, c1.Id) - assert.Equal(t, (*list)[0].TeamDisplayName, "Name") - assert.Equal(t, (*list)[1].Id, c3.Id) - assert.Equal(t, (*list)[1].TeamDisplayName, "Name2") + assert.Equal(t, c1.Id, (*list)[0].Id) + assert.Equal(t, "Name", (*list)[0].TeamDisplayName) + assert.Equal(t, c3.Id, (*list)[1].Id) + assert.Equal(t, "Name2", (*list)[1].TeamDisplayName) count1, err := ss.Channel().GetAllChannelsCount(store.ChannelSearchOpts{}) require.Nil(t, err) @@ -1203,10 +1051,10 @@ func testChannelStoreGetAllChannels(t *testing.T, ss store.Store, s SqlSupplier) list, err = ss.Channel().GetAllChannels(0, 10, store.ChannelSearchOpts{IncludeDeleted: true}) require.Nil(t, err) assert.Len(t, *list, 3) - assert.Equal(t, (*list)[0].Id, c1.Id) - assert.Equal(t, (*list)[0].TeamDisplayName, "Name") - assert.Equal(t, (*list)[1].Id, c2.Id) - assert.Equal(t, (*list)[2].Id, c3.Id) + assert.Equal(t, c1.Id, (*list)[0].Id) + assert.Equal(t, "Name", (*list)[0].TeamDisplayName) + assert.Equal(t, c2.Id, (*list)[1].Id) + assert.Equal(t, c3.Id, (*list)[2].Id) count2, err := ss.Channel().GetAllChannelsCount(store.ChannelSearchOpts{IncludeDeleted: true}) require.Nil(t, err) @@ -1217,8 +1065,8 @@ func testChannelStoreGetAllChannels(t *testing.T, ss store.Store, s SqlSupplier) list, err = ss.Channel().GetAllChannels(0, 1, store.ChannelSearchOpts{IncludeDeleted: true}) require.Nil(t, err) assert.Len(t, *list, 1) - assert.Equal(t, (*list)[0].Id, c1.Id) - assert.Equal(t, (*list)[0].TeamDisplayName, "Name") + assert.Equal(t, c1.Id, (*list)[0].Id) + assert.Equal(t, "Name", (*list)[0].TeamDisplayName) // Not associated to group list, err = ss.Channel().GetAllChannels(0, 10, store.ChannelSearchOpts{NotAssociatedToGroup: group.Id}) @@ -1551,7 +1399,7 @@ func testChannelStoreGetPublicChannelsByIdsForTeam(t *testing.T, ss store.Store) t.Run("random channel id should not be found as a public channel in the team", func(t *testing.T) { _, err := ss.Channel().GetPublicChannelsByIdsForTeam(teamId, []string{model.NewId()}) require.NotNil(t, err) - require.Equal(t, err.Id, "store.sql_channel.get_channels_by_ids.not_found.app_error") + require.Equal(t, "store.sql_channel.get_channels_by_ids.not_found.app_error", err.Id) }) } @@ -1595,13 +1443,8 @@ func testChannelStoreGetChannelCounts(t *testing.T, ss store.Store) { counts, _ := ss.Channel().GetChannelCounts(o1.TeamId, m1.UserId) - if len(counts.Counts) != 1 { - t.Fatal("wrong number of counts") - } - - if len(counts.UpdateTimes) != 1 { - t.Fatal("wrong number of update times") - } + require.Len(t, counts.Counts, 1, "wrong number of counts") + require.Len(t, counts.UpdateTimes, 1, "wrong number of update times") } func testChannelStoreGetMembersForUser(t *testing.T, ss store.Store) { @@ -1785,33 +1628,28 @@ func testChannelStoreUpdateLastViewedAt(t *testing.T, ss store.Store) { require.Nil(t, err) var times map[string]int64 - if times, err = ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId}, m1.UserId); err != nil { - t.Fatal("failed to update", err) - } else if times[o1.Id] != o1.LastPostAt { - t.Fatal("last viewed at time incorrect") - } + times, err = ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId}, m1.UserId) + require.Nil(t, err, "failed to update ", err) + require.Equal(t, o1.LastPostAt, times[o1.Id], "last viewed at time incorrect") - if times, err = ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId, m2.ChannelId}, m1.UserId); err != nil { - t.Fatal("failed to update", err) - } else if times[o2.Id] != o2.LastPostAt { - t.Fatal("last viewed at time incorrect") - } + times, err = ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId, m2.ChannelId}, m1.UserId) + require.Nil(t, err, "failed to update ", err) + require.Equal(t, o2.LastPostAt, times[o2.Id], "last viewed at time incorrect") rm1, err := ss.Channel().GetMember(m1.ChannelId, m1.UserId) assert.Nil(t, err) - assert.Equal(t, rm1.LastViewedAt, o1.LastPostAt) - assert.Equal(t, rm1.LastUpdateAt, o1.LastPostAt) - assert.Equal(t, rm1.MsgCount, o1.TotalMsgCount) + assert.Equal(t, o1.LastPostAt, rm1.LastViewedAt) + assert.Equal(t, o1.LastPostAt, rm1.LastUpdateAt) + assert.Equal(t, o1.TotalMsgCount, rm1.MsgCount) rm2, err := ss.Channel().GetMember(m2.ChannelId, m2.UserId) assert.Nil(t, err) - assert.Equal(t, rm2.LastViewedAt, o2.LastPostAt) - assert.Equal(t, rm2.LastUpdateAt, o2.LastPostAt) - assert.Equal(t, rm2.MsgCount, o2.TotalMsgCount) + assert.Equal(t, o2.LastPostAt, rm2.LastViewedAt) + assert.Equal(t, o2.LastPostAt, rm2.LastUpdateAt) + assert.Equal(t, o2.TotalMsgCount, rm2.MsgCount) - if _, err := ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId}, "missing id"); err != nil { - t.Fatal("failed to update") - } + _, err = ss.Channel().UpdateLastViewedAt([]string{m1.ChannelId}, "missing id") + require.Nil(t, err, "failed to update") } func testChannelStoreIncrementMentionCount(t *testing.T, ss store.Store) { @@ -1832,24 +1670,16 @@ func testChannelStoreIncrementMentionCount(t *testing.T, ss store.Store) { require.Nil(t, err) err = ss.Channel().IncrementMentionCount(m1.ChannelId, m1.UserId) - if err != nil { - t.Fatal("failed to update") - } + require.Nil(t, err, "failed to update") err = ss.Channel().IncrementMentionCount(m1.ChannelId, "missing id") - if err != nil { - t.Fatal("failed to update") - } + require.Nil(t, err, "failed to update") err = ss.Channel().IncrementMentionCount("missing id", m1.UserId) - if err != nil { - t.Fatal("failed to update") - } + require.Nil(t, err, "failed to update") err = ss.Channel().IncrementMentionCount("missing id", "missing id") - if err != nil { - t.Fatal("failed to update") - } + require.Nil(t, err, "failed to update") } func testUpdateChannelMember(t *testing.T, ss store.Store) { @@ -1873,14 +1703,12 @@ func testUpdateChannelMember(t *testing.T, ss store.Store) { require.Nil(t, err) m1.NotifyProps["test"] = "sometext" - if _, err := ss.Channel().UpdateMember(m1); err != nil { - t.Fatal(err) - } + _, err = ss.Channel().UpdateMember(m1) + require.Nil(t, err, err) m1.UserId = "" - if _, err := ss.Channel().UpdateMember(m1); err == nil { - t.Fatal("bad user id - should fail") - } + _, err = ss.Channel().UpdateMember(m1) + require.NotNil(t, err, "bad user id - should fail") } func testGetMember(t *testing.T, ss store.Store) { @@ -1920,42 +1748,29 @@ func testGetMember(t *testing.T, ss store.Store) { _, err = ss.Channel().SaveMember(m2) require.Nil(t, err) - if _, err := ss.Channel().GetMember(model.NewId(), userId); err == nil { - t.Fatal("should've failed to get member for non-existent channel") - } + _, err = ss.Channel().GetMember(model.NewId(), userId) + require.NotNil(t, err, "should've failed to get member for non-existent channel") - if _, err := ss.Channel().GetMember(c1.Id, model.NewId()); err == nil { - t.Fatal("should've failed to get member for non-existent user") - } + _, err = ss.Channel().GetMember(c1.Id, model.NewId()) + require.NotNil(t, err, "should've failed to get member for non-existent user") - if member, err := ss.Channel().GetMember(c1.Id, userId); err != nil { - t.Fatal("shouldn't have errored when getting member", err) - } else if member.ChannelId != c1.Id { - t.Fatal("should've gotten member of channel 1") - } else if member.UserId != userId { - t.Fatal("should've gotten member for user") - } + member, err := ss.Channel().GetMember(c1.Id, userId) + require.Nil(t, err, "shouldn't have errored when getting member", err) + require.Equal(t, c1.Id, member.ChannelId, "should've gotten member of channel 1") + require.Equal(t, userId, member.UserId, "should've have gotten member for user") - if member, err := ss.Channel().GetMember(c2.Id, userId); err != nil { - t.Fatal("shouldn't have errored when getting member", err) - } else if member.ChannelId != c2.Id { - t.Fatal("should've gotten member of channel 2") - } else if member.UserId != userId { - t.Fatal("should've gotten member for user") - } + member, err = ss.Channel().GetMember(c2.Id, userId) + require.Nil(t, err, "should'nt have errored when getting member", err) + require.Equal(t, c2.Id, member.ChannelId, "should've gotten member of channel 2") + require.Equal(t, userId, member.UserId, "should've gotten member for user") - if props, err := ss.Channel().GetAllChannelMembersNotifyPropsForChannel(c2.Id, false); err != nil { - t.Fatal(err) - } else if len(props) == 0 { - t.Fatal("should not be empty") - } + props, err := ss.Channel().GetAllChannelMembersNotifyPropsForChannel(c2.Id, false) + require.Nil(t, err, err) + require.NotEqual(t, 0, len(props), "should not be empty") - if props, err := ss.Channel().GetAllChannelMembersNotifyPropsForChannel(c2.Id, true); err != nil { - t.Fatal(err) - } else if len(props) == 0 { - t.Fatal("should not be empty") - - } + props, err = ss.Channel().GetAllChannelMembersNotifyPropsForChannel(c2.Id, true) + require.Nil(t, err, err) + require.NotEqual(t, 0, len(props), "should not be empty") ss.Channel().InvalidateCacheForChannelMembersNotifyProps(c2.Id) } @@ -1985,15 +1800,12 @@ func testChannelStoreGetMemberForPost(t *testing.T, ss store.Store) { }) require.Nil(t, err) - if r1, err := ss.Channel().GetMemberForPost(p1.Id, m1.UserId); err != nil { - t.Fatal(err) - } else if r1.ToJson() != m1.ToJson() { - t.Fatal("invalid returned channel member") - } + r1, err := ss.Channel().GetMemberForPost(p1.Id, m1.UserId) + require.Nil(t, err, err) + require.Equal(t, m1.ToJson(), r1.ToJson(), "invalid returned channel member") - if _, err := ss.Channel().GetMemberForPost(p1.Id, model.NewId()); err == nil { - t.Fatal("shouldn't have returned a member") - } + _, err = ss.Channel().GetMemberForPost(p1.Id, model.NewId()) + require.NotNil(t, err, "shouldn't have returned a member") } func testGetMemberCount(t *testing.T, ss store.Store) { @@ -2034,11 +1846,9 @@ func testGetMemberCount(t *testing.T, ss store.Store) { _, err = ss.Channel().SaveMember(&m1) require.Nil(t, err) - if count, channelErr := ss.Channel().GetMemberCount(c1.Id, false); channelErr != nil { - t.Fatalf("failed to get member count: %v", channelErr) - } else if count != 1 { - t.Fatalf("got incorrect member count %v", count) - } + count, channelErr := ss.Channel().GetMemberCount(c1.Id, false) + require.Nilf(t, channelErr, "failed to get member count: %v", channelErr) + require.EqualValuesf(t, 1, count, "got incorrect member count %v", count) u2 := model.User{ Email: MakeEmail(), @@ -2057,11 +1867,9 @@ func testGetMemberCount(t *testing.T, ss store.Store) { _, err = ss.Channel().SaveMember(&m2) require.Nil(t, err) - if count, channelErr := ss.Channel().GetMemberCount(c1.Id, false); channelErr != nil { - t.Fatalf("failed to get member count: %v", channelErr) - } else if count != 2 { - t.Fatalf("got incorrect member count %v", count) - } + count, channelErr = ss.Channel().GetMemberCount(c1.Id, false) + require.Nilf(t, channelErr, "failed to get member count: %v", channelErr) + require.EqualValuesf(t, 2, count, "got incorrect member count %v", count) // make sure members of other channels aren't counted u3 := model.User{ @@ -2081,11 +1889,9 @@ func testGetMemberCount(t *testing.T, ss store.Store) { _, err = ss.Channel().SaveMember(&m3) require.Nil(t, err) - if count, channelErr := ss.Channel().GetMemberCount(c1.Id, false); channelErr != nil { - t.Fatalf("failed to get member count: %v", channelErr) - } else if count != 2 { - t.Fatalf("got incorrect member count %v", count) - } + count, channelErr = ss.Channel().GetMemberCount(c1.Id, false) + require.Nilf(t, channelErr, "failed to get member count: %v", channelErr) + require.EqualValuesf(t, 2, count, "got incorrect member count %v", count) // make sure inactive users aren't counted u4 := &model.User{ @@ -2105,11 +1911,9 @@ func testGetMemberCount(t *testing.T, ss store.Store) { _, err = ss.Channel().SaveMember(&m4) require.Nil(t, err) - if count, err := ss.Channel().GetMemberCount(c1.Id, false); err != nil { - t.Fatalf("failed to get member count: %v", err) - } else if count != 2 { - t.Fatalf("got incorrect member count %v", count) - } + count, err = ss.Channel().GetMemberCount(c1.Id, false) + require.Nilf(t, err, "failed to get member count: %v", err) + require.EqualValuesf(t, 2, count, "got incorrect member count %v", count) } func testGetGuestCount(t *testing.T, ss store.Store) { @@ -2155,7 +1959,7 @@ func testGetGuestCount(t *testing.T, ss store.Store) { count, channelErr := ss.Channel().GetGuestCount(c1.Id, false) require.Nil(t, channelErr) - require.Equal(t, count, int64(0)) + require.Equal(t, int64(0), count) }) t.Run("Guest member does count", func(t *testing.T) { @@ -2180,7 +1984,7 @@ func testGetGuestCount(t *testing.T, ss store.Store) { count, channelErr := ss.Channel().GetGuestCount(c1.Id, false) require.Nil(t, channelErr) - require.Equal(t, count, int64(1)) + require.Equal(t, int64(1), count) }) t.Run("make sure members of other channels aren't counted", func(t *testing.T) { @@ -2205,7 +2009,7 @@ func testGetGuestCount(t *testing.T, ss store.Store) { count, channelErr := ss.Channel().GetGuestCount(c1.Id, false) require.Nil(t, channelErr) - require.Equal(t, count, int64(1)) + require.Equal(t, int64(1), count) }) t.Run("make sure inactive users aren't counted", func(t *testing.T) { @@ -2230,7 +2034,7 @@ func testGetGuestCount(t *testing.T, ss store.Store) { count, channelErr := ss.Channel().GetGuestCount(c1.Id, false) require.Nil(t, channelErr) - require.Equal(t, count, int64(1)) + require.Equal(t, int64(1), count) }) } @@ -2906,7 +2710,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, ss store.Store) { require.Nil(t, err) require.Equal(t, len(*testCase.ExpectedResults), len(*channels)) for i, expected := range *testCase.ExpectedResults { - require.Equal(t, (*channels)[i].Id, expected.Id) + require.Equal(t, expected.Id, (*channels)[i].Id) } }) } @@ -3059,35 +2863,23 @@ func testChannelStoreGetMembersByIds(t *testing.T, ss store.Store) { require.Nil(t, err) var members *model.ChannelMembers - if members, err = ss.Channel().GetMembersByIds(m1.ChannelId, []string{m1.UserId}); err != nil { - t.Fatal(err) - } else { - rm1 := (*members)[0] + members, err = ss.Channel().GetMembersByIds(m1.ChannelId, []string{m1.UserId}) + rm1 := (*members)[0] - if rm1.ChannelId != m1.ChannelId { - t.Fatal("bad team id") - } - - if rm1.UserId != m1.UserId { - t.Fatal("bad user id") - } - } + require.Nil(t, err, err) + require.Equal(t, m1.ChannelId, rm1.ChannelId, "bad team id") + require.Equal(t, m1.UserId, rm1.UserId, "bad user id") m2 := &model.ChannelMember{ChannelId: o1.Id, UserId: model.NewId(), NotifyProps: model.GetDefaultChannelNotifyProps()} _, err = ss.Channel().SaveMember(m2) require.Nil(t, err) - if members, err = ss.Channel().GetMembersByIds(m1.ChannelId, []string{m1.UserId, m2.UserId, model.NewId()}); err != nil { - t.Fatal(err) - } else { - if len(*members) != 2 { - t.Fatal("return wrong number of results") - } - } + members, err = ss.Channel().GetMembersByIds(m1.ChannelId, []string{m1.UserId, m2.UserId, model.NewId()}) + require.Nil(t, err, err) + require.Len(t, *members, 2, "return wrong number of results") - if _, err = ss.Channel().GetMembersByIds(m1.ChannelId, []string{}); err == nil { - t.Fatal("empty user ids - should have failed") - } + _, err = ss.Channel().GetMembersByIds(m1.ChannelId, []string{}) + require.NotNil(t, err, "empty user ids - should have failed") } func testChannelStoreSearchGroupChannels(t *testing.T, ss store.Store) { @@ -3282,28 +3074,23 @@ func testChannelStoreAnalyticsDeletedTypeCount(t *testing.T, ss store.Store) { require.Nil(t, err) d4, err := ss.Channel().CreateDirectChannel(u1, u2) - if err != nil { - t.Fatalf(err.Error()) - } + require.Nil(t, err) defer func() { ss.Channel().PermanentDeleteMembersByChannel(d4.Id) ss.Channel().PermanentDelete(d4.Id) }() var openStartCount int64 - if openStartCount, err = ss.Channel().AnalyticsDeletedTypeCount("", "O"); err != nil { - t.Fatal(err) - } + openStartCount, err = ss.Channel().AnalyticsDeletedTypeCount("", "O") + require.Nil(t, err, err) var privateStartCount int64 - if privateStartCount, err = ss.Channel().AnalyticsDeletedTypeCount("", "P"); err != nil { - t.Fatal(err) - } + privateStartCount, err = ss.Channel().AnalyticsDeletedTypeCount("", "P") + require.Nil(t, err, err) var directStartCount int64 - if directStartCount, err = ss.Channel().AnalyticsDeletedTypeCount("", "D"); err != nil { - t.Fatal(err) - } + directStartCount, err = ss.Channel().AnalyticsDeletedTypeCount("", "D") + require.Nil(t, err, err) err = ss.Channel().Delete(o1.Id, model.GetMillis()) require.Nil(t, err, "channel should have been deleted") @@ -3316,19 +3103,16 @@ func testChannelStoreAnalyticsDeletedTypeCount(t *testing.T, ss store.Store) { var count int64 - if count, err = ss.Channel().AnalyticsDeletedTypeCount("", "O"); err != nil { - t.Fatal(err) - } + count, err = ss.Channel().AnalyticsDeletedTypeCount("", "O") + require.Nil(t, err, err) assert.Equal(t, openStartCount+2, count, "Wrong open channel deleted count.") - if count, err = ss.Channel().AnalyticsDeletedTypeCount("", "P"); err != nil { - t.Fatal(err) - } + count, err = ss.Channel().AnalyticsDeletedTypeCount("", "P") + require.Nil(t, err, err) assert.Equal(t, privateStartCount+1, count, "Wrong private channel deleted count.") - if count, err = ss.Channel().AnalyticsDeletedTypeCount("", "D"); err != nil { - t.Fatal(err) - } + count, err = ss.Channel().AnalyticsDeletedTypeCount("", "D") + require.Nil(t, err, err) assert.Equal(t, directStartCount+1, count, "Wrong direct channel deleted count.") } @@ -3351,11 +3135,9 @@ func testChannelStoreGetPinnedPosts(t *testing.T, ss store.Store) { }) require.Nil(t, err) - if pl, errGet := ss.Channel().GetPinnedPosts(o1.Id); errGet != nil { - t.Fatal(errGet) - } else if pl.Posts[p1.Id] == nil { - t.Fatal("didn't return relevant pinned posts") - } + pl, errGet := ss.Channel().GetPinnedPosts(o1.Id) + require.Nil(t, errGet, errGet) + require.NotNil(t, pl.Posts[p1.Id], "didn't return relevant pinned posts") ch2 := &model.Channel{ TeamId: model.NewId(), @@ -3374,11 +3156,9 @@ func testChannelStoreGetPinnedPosts(t *testing.T, ss store.Store) { }) require.Nil(t, err) - if pl, errGet := ss.Channel().GetPinnedPosts(o2.Id); errGet != nil { - t.Fatal(errGet) - } else if len(pl.Posts) != 0 { - t.Fatal("wasn't supposed to return posts") - } + pl, errGet = ss.Channel().GetPinnedPosts(o2.Id) + require.Nil(t, errGet, errGet) + require.Len(t, pl.Posts, 0, "wasn't supposed to return posts") } func testChannelStoreGetPinnedPostCount(t *testing.T, ss store.Store) { @@ -3408,15 +3188,15 @@ func testChannelStoreGetPinnedPostCount(t *testing.T, ss store.Store) { }) require.Nil(t, err) - if count, errGet := ss.Channel().GetPinnedPostCount(o1.Id, true); errGet != nil { - t.Fatal(errGet) - } else if count != 2 { - t.Fatal("didn't return right count") - } + count, errGet := ss.Channel().GetPinnedPostCount(o1.Id, true) + require.Nil(t, errGet, errGet) + require.EqualValues(t, 2, count, "didn't return right count") - if ss.Channel().GetPinnedPostCountFromCache(o1.Id) != 2 { - t.Fatal("should have saved 2 pinned post count ") - } + require.EqualValues( + t, + 2, + ss.Channel().GetPinnedPostCountFromCache(o1.Id), + "should have saved 2 pinned post count") ch2 := &model.Channel{ TeamId: model.NewId(), @@ -3442,15 +3222,15 @@ func testChannelStoreGetPinnedPostCount(t *testing.T, ss store.Store) { }) require.Nil(t, err) - if count, errGet := ss.Channel().GetPinnedPostCount(o2.Id, true); errGet != nil { - t.Fatal(errGet) - } else if count != 0 { - t.Fatal("should return 0") - } + count, errGet = ss.Channel().GetPinnedPostCount(o2.Id, true) + require.Nil(t, errGet, errGet) + require.EqualValues(t, 0, count, "should return 0") - if ss.Channel().GetPinnedPostCountFromCache(o2.Id) != 0 { - t.Fatal("should have saved 0 pinned post count ") - } + require.EqualValues( + t, + 0, + ss.Channel().GetPinnedPostCountFromCache(o2.Id), + "should have saved 0 pinned post count") } func testChannelStoreMaxChannelsPerTeam(t *testing.T, ss store.Store) { @@ -3461,8 +3241,8 @@ func testChannelStoreMaxChannelsPerTeam(t *testing.T, ss store.Store) { Type: model.CHANNEL_OPEN, } _, err := ss.Channel().Save(channel, 0) - assert.NotEqual(t, nil, err) - assert.Equal(t, err.Id, "store.sql_channel.save_channel.limit.app_error") + assert.NotNil(t, err) + assert.Equal(t, "store.sql_channel.save_channel.limit.app_error", err.Id) channel.Id = "" _, err = ss.Channel().Save(channel, 1) @@ -4035,7 +3815,7 @@ func testChannelStoreRemoveAllDeactivatedMembers(t *testing.T, ss store.Store) { d2, err := ss.Channel().GetMembers(c1.Id, 0, 1000) assert.Nil(t, err) assert.Len(t, *d2, 1) - assert.Equal(t, (*d2)[0].UserId, u3.Id) + assert.Equal(t, u3.Id, (*d2)[0].UserId) } func testChannelStoreExportAllDirectChannels(t *testing.T, ss store.Store, s SqlSupplier) { @@ -4088,7 +3868,7 @@ func testChannelStoreExportAllDirectChannels(t *testing.T, ss store.Store, s Sql d1, err := ss.Channel().GetAllDirectChannelsForExportAfter(10000, strings.Repeat("0", 26)) assert.Nil(t, err) - assert.Equal(t, 2, len(d1)) + assert.Len(t, d1, 2) assert.ElementsMatch(t, []string{o1.DisplayName, o2.DisplayName}, []string{d1[0].DisplayName, d1[1].DisplayName}) // Manually truncate Channels table until testlib can handle cleanups @@ -4150,7 +3930,7 @@ func testChannelStoreExportAllDirectChannelsExcludePrivateAndPublic(t *testing.T d1, err := ss.Channel().GetAllDirectChannelsForExportAfter(10000, strings.Repeat("0", 26)) assert.Nil(t, err) - assert.Equal(t, 1, len(d1)) + assert.Len(t, d1, 1) assert.Equal(t, o1.DisplayName, d1[0].DisplayName) // Manually truncate Channels table until testlib can handle cleanups diff --git a/store/storetest/oauth_store.go b/store/storetest/oauth_store.go index 6d3298c718..3649e79729 100644 --- a/store/storetest/oauth_store.go +++ b/store/storetest/oauth_store.go @@ -382,9 +382,8 @@ func testOAuthStoreDeleteApp(t *testing.T, ss store.Store) { err = ss.OAuth().DeleteApp(a1.Id) require.Nil(t, err) - if _, err = ss.Session().Get(s1.Token); err == nil { - t.Fatal("should error - session should be deleted") - } + _, err = ss.Session().Get(s1.Token) + require.NotNil(t, err, "should error - session should be deleted") _, err = ss.OAuth().GetAccessData(s1.Token) require.NotNil(t, err, "should error - access data should be deleted") diff --git a/vendor/github.com/go-redis/redis/.gitignore b/vendor/github.com/go-redis/redis/.gitignore deleted file mode 100644 index ebfe903bcd..0000000000 --- a/vendor/github.com/go-redis/redis/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.rdb -testdata/*/ diff --git a/vendor/github.com/go-redis/redis/.travis.yml b/vendor/github.com/go-redis/redis/.travis.yml deleted file mode 100644 index 06d7897b4e..0000000000 --- a/vendor/github.com/go-redis/redis/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -sudo: false -language: go - -services: - - redis-server - -go: - - 1.9.x - - 1.10.x - - 1.11.x - - 1.12.x - - tip - -matrix: - allow_failures: - - go: tip - -install: - - go get github.com/onsi/ginkgo - - go get github.com/onsi/gomega diff --git a/vendor/github.com/go-redis/redis/CHANGELOG.md b/vendor/github.com/go-redis/redis/CHANGELOG.md deleted file mode 100644 index 19645661a4..0000000000 --- a/vendor/github.com/go-redis/redis/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -# Changelog - -## Unreleased - -- Cluster and Ring pipelines process commands for each node in its own goroutine. - -## 6.14 - -- Added Options.MinIdleConns. -- Added Options.MaxConnAge. -- PoolStats.FreeConns is renamed to PoolStats.IdleConns. -- Add Client.Do to simplify creating custom commands. -- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. -- Lower memory usage. - -## v6.13 - -- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards. -- Cluster client was optimized to use much less memory when reloading cluster state. -- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead. -- Dialer.KeepAlive is set to 5 minutes by default. - -## v6.12 - -- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup diff --git a/vendor/github.com/go-redis/redis/LICENSE b/vendor/github.com/go-redis/redis/LICENSE deleted file mode 100644 index 298bed9bea..0000000000 --- a/vendor/github.com/go-redis/redis/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2013 The github.com/go-redis/redis Authors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/go-redis/redis/Makefile b/vendor/github.com/go-redis/redis/Makefile deleted file mode 100644 index fa3b4e004f..0000000000 --- a/vendor/github.com/go-redis/redis/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -all: testdeps - go test ./... - go test ./... -short -race - env GOOS=linux GOARCH=386 go test ./... - go vet - go get github.com/gordonklaus/ineffassign - ineffassign . - -testdeps: testdata/redis/src/redis-server - -bench: testdeps - go test ./... -test.run=NONE -test.bench=. -test.benchmem - -.PHONY: all test testdeps bench - -testdata/redis: - mkdir -p $@ - wget -qO- https://github.com/antirez/redis/archive/5.0.tar.gz | tar xvz --strip-components=1 -C $@ - -testdata/redis/src/redis-server: testdata/redis - sed -i.bak 's/libjemalloc.a/libjemalloc.a -lrt/g' $ -} - -func ExampleClient() { - err := client.Set("key", "value", 0).Err() - if err != nil { - panic(err) - } - - val, err := client.Get("key").Result() - if err != nil { - panic(err) - } - fmt.Println("key", val) - - val2, err := client.Get("key2").Result() - if err == redis.Nil { - fmt.Println("key2 does not exist") - } else if err != nil { - panic(err) - } else { - fmt.Println("key2", val2) - } - // Output: key value - // key2 does not exist -} -``` - -## Howto - -Please go through [examples](https://godoc.org/github.com/go-redis/redis#pkg-examples) to get an idea how to use this package. - -## Look and feel - -Some corner cases: - -```go -// SET key value EX 10 NX -set, err := client.SetNX("key", "value", 10*time.Second).Result() - -// SORT list LIMIT 0 2 ASC -vals, err := client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result() - -// ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2 -vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ - Min: "-inf", - Max: "+inf", - Offset: 0, - Count: 2, -}).Result() - -// ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM -vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result() - -// EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello" -vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result() -``` - -## Benchmark - -go-redis vs redigo: - -``` -BenchmarkSetGoRedis10Conns64Bytes-4 200000 7621 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns64Bytes-4 200000 7554 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis10Conns1KB-4 200000 7697 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns1KB-4 200000 7688 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis10Conns10KB-4 200000 9214 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns10KB-4 200000 9181 ns/op 210 B/op 6 allocs/op -BenchmarkSetGoRedis10Conns1MB-4 2000 583242 ns/op 2337 B/op 6 allocs/op -BenchmarkSetGoRedis100Conns1MB-4 2000 583089 ns/op 2338 B/op 6 allocs/op -BenchmarkSetRedigo10Conns64Bytes-4 200000 7576 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo100Conns64Bytes-4 200000 7782 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo10Conns1KB-4 200000 7958 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo100Conns1KB-4 200000 7725 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo10Conns10KB-4 100000 18442 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo100Conns10KB-4 100000 18818 ns/op 208 B/op 7 allocs/op -BenchmarkSetRedigo10Conns1MB-4 2000 668829 ns/op 226 B/op 7 allocs/op -BenchmarkSetRedigo100Conns1MB-4 2000 679542 ns/op 226 B/op 7 allocs/op -``` - -Redis Cluster: - -``` -BenchmarkRedisPing-4 200000 6983 ns/op 116 B/op 4 allocs/op -BenchmarkRedisClusterPing-4 100000 11535 ns/op 117 B/op 4 allocs/op -``` - -## See also - -- [Golang PostgreSQL ORM](https://github.com/go-pg/pg) -- [Golang msgpack](https://github.com/vmihailenco/msgpack) -- [Golang message task queue](https://github.com/vmihailenco/taskq) diff --git a/vendor/github.com/go-redis/redis/cluster.go b/vendor/github.com/go-redis/redis/cluster.go deleted file mode 100644 index ab2c76f05e..0000000000 --- a/vendor/github.com/go-redis/redis/cluster.go +++ /dev/null @@ -1,1627 +0,0 @@ -package redis - -import ( - "context" - "crypto/tls" - "fmt" - "math" - "math/rand" - "net" - "runtime" - "sort" - "sync" - "sync/atomic" - "time" - - "github.com/go-redis/redis/internal" - "github.com/go-redis/redis/internal/hashtag" - "github.com/go-redis/redis/internal/pool" - "github.com/go-redis/redis/internal/proto" -) - -var errClusterNoNodes = fmt.Errorf("redis: cluster has no nodes") - -// ClusterOptions are used to configure a cluster client and should be -// passed to NewClusterClient. -type ClusterOptions struct { - // A seed list of host:port addresses of cluster nodes. - Addrs []string - - // The maximum number of retries before giving up. Command is retried - // on network errors and MOVED/ASK redirects. - // Default is 8 retries. - MaxRedirects int - - // Enables read-only commands on slave nodes. - ReadOnly bool - // Allows routing read-only commands to the closest master or slave node. - // It automatically enables ReadOnly. - RouteByLatency bool - // Allows routing read-only commands to the random master or slave node. - // It automatically enables ReadOnly. - RouteRandomly bool - - // Optional function that returns cluster slots information. - // It is useful to manually create cluster of standalone Redis servers - // and load-balance read/write operations between master and slaves. - // It can use service like ZooKeeper to maintain configuration information - // and Cluster.ReloadState to manually trigger state reloading. - ClusterSlots func() ([]ClusterSlot, error) - - // Optional hook that is called when a new node is created. - OnNewNode func(*Client) - - // Following options are copied from Options struct. - - OnConnect func(*Conn) error - - Password string - - MaxRetries int - MinRetryBackoff time.Duration - MaxRetryBackoff time.Duration - - DialTimeout time.Duration - ReadTimeout time.Duration - WriteTimeout time.Duration - - // PoolSize applies per cluster node and not for the whole cluster. - PoolSize int - MinIdleConns int - MaxConnAge time.Duration - PoolTimeout time.Duration - IdleTimeout time.Duration - IdleCheckFrequency time.Duration - - TLSConfig *tls.Config -} - -func (opt *ClusterOptions) init() { - if opt.MaxRedirects == -1 { - opt.MaxRedirects = 0 - } else if opt.MaxRedirects == 0 { - opt.MaxRedirects = 8 - } - - if (opt.RouteByLatency || opt.RouteRandomly) && opt.ClusterSlots == nil { - opt.ReadOnly = true - } - - if opt.PoolSize == 0 { - opt.PoolSize = 5 * runtime.NumCPU() - } - - switch opt.ReadTimeout { - case -1: - opt.ReadTimeout = 0 - case 0: - opt.ReadTimeout = 3 * time.Second - } - switch opt.WriteTimeout { - case -1: - opt.WriteTimeout = 0 - case 0: - opt.WriteTimeout = opt.ReadTimeout - } - - switch opt.MinRetryBackoff { - case -1: - opt.MinRetryBackoff = 0 - case 0: - opt.MinRetryBackoff = 8 * time.Millisecond - } - switch opt.MaxRetryBackoff { - case -1: - opt.MaxRetryBackoff = 0 - case 0: - opt.MaxRetryBackoff = 512 * time.Millisecond - } -} - -func (opt *ClusterOptions) clientOptions() *Options { - const disableIdleCheck = -1 - - return &Options{ - OnConnect: opt.OnConnect, - - MaxRetries: opt.MaxRetries, - MinRetryBackoff: opt.MinRetryBackoff, - MaxRetryBackoff: opt.MaxRetryBackoff, - Password: opt.Password, - readOnly: opt.ReadOnly, - - DialTimeout: opt.DialTimeout, - ReadTimeout: opt.ReadTimeout, - WriteTimeout: opt.WriteTimeout, - - PoolSize: opt.PoolSize, - MinIdleConns: opt.MinIdleConns, - MaxConnAge: opt.MaxConnAge, - PoolTimeout: opt.PoolTimeout, - IdleTimeout: opt.IdleTimeout, - IdleCheckFrequency: disableIdleCheck, - - TLSConfig: opt.TLSConfig, - } -} - -//------------------------------------------------------------------------------ - -type clusterNode struct { - Client *Client - - latency uint32 // atomic - generation uint32 // atomic - loading uint32 // atomic -} - -func newClusterNode(clOpt *ClusterOptions, addr string) *clusterNode { - opt := clOpt.clientOptions() - opt.Addr = addr - node := clusterNode{ - Client: NewClient(opt), - } - - node.latency = math.MaxUint32 - if clOpt.RouteByLatency { - go node.updateLatency() - } - - if clOpt.OnNewNode != nil { - clOpt.OnNewNode(node.Client) - } - - return &node -} - -func (n *clusterNode) String() string { - return n.Client.String() -} - -func (n *clusterNode) Close() error { - return n.Client.Close() -} - -func (n *clusterNode) updateLatency() { - const probes = 10 - - var latency uint32 - for i := 0; i < probes; i++ { - start := time.Now() - n.Client.Ping() - probe := uint32(time.Since(start) / time.Microsecond) - latency = (latency + probe) / 2 - } - atomic.StoreUint32(&n.latency, latency) -} - -func (n *clusterNode) Latency() time.Duration { - latency := atomic.LoadUint32(&n.latency) - return time.Duration(latency) * time.Microsecond -} - -func (n *clusterNode) MarkAsLoading() { - atomic.StoreUint32(&n.loading, uint32(time.Now().Unix())) -} - -func (n *clusterNode) Loading() bool { - const minute = int64(time.Minute / time.Second) - - loading := atomic.LoadUint32(&n.loading) - if loading == 0 { - return false - } - if time.Now().Unix()-int64(loading) < minute { - return true - } - atomic.StoreUint32(&n.loading, 0) - return false -} - -func (n *clusterNode) Generation() uint32 { - return atomic.LoadUint32(&n.generation) -} - -func (n *clusterNode) SetGeneration(gen uint32) { - for { - v := atomic.LoadUint32(&n.generation) - if gen < v || atomic.CompareAndSwapUint32(&n.generation, v, gen) { - break - } - } -} - -//------------------------------------------------------------------------------ - -type clusterNodes struct { - opt *ClusterOptions - - mu sync.RWMutex - allAddrs []string - allNodes map[string]*clusterNode - clusterAddrs []string - closed bool - - _generation uint32 // atomic -} - -func newClusterNodes(opt *ClusterOptions) *clusterNodes { - return &clusterNodes{ - opt: opt, - - allAddrs: opt.Addrs, - allNodes: make(map[string]*clusterNode), - } -} - -func (c *clusterNodes) Close() error { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed { - return nil - } - c.closed = true - - var firstErr error - for _, node := range c.allNodes { - if err := node.Client.Close(); err != nil && firstErr == nil { - firstErr = err - } - } - - c.allNodes = nil - c.clusterAddrs = nil - - return firstErr -} - -func (c *clusterNodes) Addrs() ([]string, error) { - var addrs []string - c.mu.RLock() - closed := c.closed - if !closed { - if len(c.clusterAddrs) > 0 { - addrs = c.clusterAddrs - } else { - addrs = c.allAddrs - } - } - c.mu.RUnlock() - - if closed { - return nil, pool.ErrClosed - } - if len(addrs) == 0 { - return nil, errClusterNoNodes - } - return addrs, nil -} - -func (c *clusterNodes) NextGeneration() uint32 { - return atomic.AddUint32(&c._generation, 1) -} - -// GC removes unused nodes. -func (c *clusterNodes) GC(generation uint32) { - var collected []*clusterNode - c.mu.Lock() - for addr, node := range c.allNodes { - if node.Generation() >= generation { - continue - } - - c.clusterAddrs = remove(c.clusterAddrs, addr) - delete(c.allNodes, addr) - collected = append(collected, node) - } - c.mu.Unlock() - - for _, node := range collected { - _ = node.Client.Close() - } -} - -func (c *clusterNodes) Get(addr string) (*clusterNode, error) { - var node *clusterNode - var err error - c.mu.RLock() - if c.closed { - err = pool.ErrClosed - } else { - node = c.allNodes[addr] - } - c.mu.RUnlock() - return node, err -} - -func (c *clusterNodes) GetOrCreate(addr string) (*clusterNode, error) { - node, err := c.Get(addr) - if err != nil { - return nil, err - } - if node != nil { - return node, nil - } - - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed { - return nil, pool.ErrClosed - } - - node, ok := c.allNodes[addr] - if ok { - return node, err - } - - node = newClusterNode(c.opt, addr) - - c.allAddrs = appendIfNotExists(c.allAddrs, addr) - c.clusterAddrs = append(c.clusterAddrs, addr) - c.allNodes[addr] = node - - return node, err -} - -func (c *clusterNodes) All() ([]*clusterNode, error) { - c.mu.RLock() - defer c.mu.RUnlock() - - if c.closed { - return nil, pool.ErrClosed - } - - cp := make([]*clusterNode, 0, len(c.allNodes)) - for _, node := range c.allNodes { - cp = append(cp, node) - } - return cp, nil -} - -func (c *clusterNodes) Random() (*clusterNode, error) { - addrs, err := c.Addrs() - if err != nil { - return nil, err - } - - n := rand.Intn(len(addrs)) - return c.GetOrCreate(addrs[n]) -} - -//------------------------------------------------------------------------------ - -type clusterSlot struct { - start, end int - nodes []*clusterNode -} - -type clusterSlotSlice []*clusterSlot - -func (p clusterSlotSlice) Len() int { - return len(p) -} - -func (p clusterSlotSlice) Less(i, j int) bool { - return p[i].start < p[j].start -} - -func (p clusterSlotSlice) Swap(i, j int) { - p[i], p[j] = p[j], p[i] -} - -type clusterState struct { - nodes *clusterNodes - Masters []*clusterNode - Slaves []*clusterNode - - slots []*clusterSlot - - generation uint32 - createdAt time.Time -} - -func newClusterState( - nodes *clusterNodes, slots []ClusterSlot, origin string, -) (*clusterState, error) { - c := clusterState{ - nodes: nodes, - - slots: make([]*clusterSlot, 0, len(slots)), - - generation: nodes.NextGeneration(), - createdAt: time.Now(), - } - - originHost, _, _ := net.SplitHostPort(origin) - isLoopbackOrigin := isLoopback(originHost) - - for _, slot := range slots { - var nodes []*clusterNode - for i, slotNode := range slot.Nodes { - addr := slotNode.Addr - if !isLoopbackOrigin { - addr = replaceLoopbackHost(addr, originHost) - } - - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return nil, err - } - - node.SetGeneration(c.generation) - nodes = append(nodes, node) - - if i == 0 { - c.Masters = appendUniqueNode(c.Masters, node) - } else { - c.Slaves = appendUniqueNode(c.Slaves, node) - } - } - - c.slots = append(c.slots, &clusterSlot{ - start: slot.Start, - end: slot.End, - nodes: nodes, - }) - } - - sort.Sort(clusterSlotSlice(c.slots)) - - time.AfterFunc(time.Minute, func() { - nodes.GC(c.generation) - }) - - return &c, nil -} - -func replaceLoopbackHost(nodeAddr, originHost string) string { - nodeHost, nodePort, err := net.SplitHostPort(nodeAddr) - if err != nil { - return nodeAddr - } - - nodeIP := net.ParseIP(nodeHost) - if nodeIP == nil { - return nodeAddr - } - - if !nodeIP.IsLoopback() { - return nodeAddr - } - - // Use origin host which is not loopback and node port. - return net.JoinHostPort(originHost, nodePort) -} - -func isLoopback(host string) bool { - ip := net.ParseIP(host) - if ip == nil { - return true - } - return ip.IsLoopback() -} - -func (c *clusterState) slotMasterNode(slot int) (*clusterNode, error) { - nodes := c.slotNodes(slot) - if len(nodes) > 0 { - return nodes[0], nil - } - return c.nodes.Random() -} - -func (c *clusterState) slotSlaveNode(slot int) (*clusterNode, error) { - nodes := c.slotNodes(slot) - switch len(nodes) { - case 0: - return c.nodes.Random() - case 1: - return nodes[0], nil - case 2: - if slave := nodes[1]; !slave.Loading() { - return slave, nil - } - return nodes[0], nil - default: - var slave *clusterNode - for i := 0; i < 10; i++ { - n := rand.Intn(len(nodes)-1) + 1 - slave = nodes[n] - if !slave.Loading() { - return slave, nil - } - } - - // All slaves are loading - use master. - return nodes[0], nil - } -} - -func (c *clusterState) slotClosestNode(slot int) (*clusterNode, error) { - const threshold = time.Millisecond - - nodes := c.slotNodes(slot) - if len(nodes) == 0 { - return c.nodes.Random() - } - - var node *clusterNode - for _, n := range nodes { - if n.Loading() { - continue - } - if node == nil || node.Latency()-n.Latency() > threshold { - node = n - } - } - return node, nil -} - -func (c *clusterState) slotRandomNode(slot int) *clusterNode { - nodes := c.slotNodes(slot) - n := rand.Intn(len(nodes)) - return nodes[n] -} - -func (c *clusterState) slotNodes(slot int) []*clusterNode { - i := sort.Search(len(c.slots), func(i int) bool { - return c.slots[i].end >= slot - }) - if i >= len(c.slots) { - return nil - } - x := c.slots[i] - if slot >= x.start && slot <= x.end { - return x.nodes - } - return nil -} - -//------------------------------------------------------------------------------ - -type clusterStateHolder struct { - load func() (*clusterState, error) - - state atomic.Value - reloading uint32 // atomic -} - -func newClusterStateHolder(fn func() (*clusterState, error)) *clusterStateHolder { - return &clusterStateHolder{ - load: fn, - } -} - -func (c *clusterStateHolder) Reload() (*clusterState, error) { - state, err := c.load() - if err != nil { - return nil, err - } - c.state.Store(state) - return state, nil -} - -func (c *clusterStateHolder) LazyReload() { - if !atomic.CompareAndSwapUint32(&c.reloading, 0, 1) { - return - } - go func() { - defer atomic.StoreUint32(&c.reloading, 0) - - _, err := c.Reload() - if err != nil { - return - } - time.Sleep(100 * time.Millisecond) - }() -} - -func (c *clusterStateHolder) Get() (*clusterState, error) { - v := c.state.Load() - if v != nil { - state := v.(*clusterState) - if time.Since(state.createdAt) > time.Minute { - c.LazyReload() - } - return state, nil - } - return c.Reload() -} - -func (c *clusterStateHolder) ReloadOrGet() (*clusterState, error) { - state, err := c.Reload() - if err == nil { - return state, nil - } - return c.Get() -} - -//------------------------------------------------------------------------------ - -// ClusterClient is a Redis Cluster client representing a pool of zero -// or more underlying connections. It's safe for concurrent use by -// multiple goroutines. -type ClusterClient struct { - cmdable - - ctx context.Context - - opt *ClusterOptions - nodes *clusterNodes - state *clusterStateHolder - cmdsInfoCache *cmdsInfoCache - - process func(Cmder) error - processPipeline func([]Cmder) error - processTxPipeline func([]Cmder) error -} - -// NewClusterClient returns a Redis Cluster client as described in -// http://redis.io/topics/cluster-spec. -func NewClusterClient(opt *ClusterOptions) *ClusterClient { - opt.init() - - c := &ClusterClient{ - opt: opt, - nodes: newClusterNodes(opt), - } - c.state = newClusterStateHolder(c.loadState) - c.cmdsInfoCache = newCmdsInfoCache(c.cmdsInfo) - - c.process = c.defaultProcess - c.processPipeline = c.defaultProcessPipeline - c.processTxPipeline = c.defaultProcessTxPipeline - - c.init() - if opt.IdleCheckFrequency > 0 { - go c.reaper(opt.IdleCheckFrequency) - } - - return c -} - -func (c *ClusterClient) init() { - c.cmdable.setProcessor(c.Process) -} - -// ReloadState reloads cluster state. If available it calls ClusterSlots func -// to get cluster slots information. -func (c *ClusterClient) ReloadState() error { - _, err := c.state.Reload() - return err -} - -func (c *ClusterClient) Context() context.Context { - if c.ctx != nil { - return c.ctx - } - return context.Background() -} - -func (c *ClusterClient) WithContext(ctx context.Context) *ClusterClient { - if ctx == nil { - panic("nil context") - } - c2 := c.clone() - c2.ctx = ctx - return c2 -} - -func (c *ClusterClient) clone() *ClusterClient { - cp := *c - cp.init() - return &cp -} - -// Options returns read-only Options that were used to create the client. -func (c *ClusterClient) Options() *ClusterOptions { - return c.opt -} - -func (c *ClusterClient) retryBackoff(attempt int) time.Duration { - return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) -} - -func (c *ClusterClient) cmdsInfo() (map[string]*CommandInfo, error) { - addrs, err := c.nodes.Addrs() - if err != nil { - return nil, err - } - - var firstErr error - for _, addr := range addrs { - node, err := c.nodes.Get(addr) - if err != nil { - return nil, err - } - if node == nil { - continue - } - - info, err := node.Client.Command().Result() - if err == nil { - return info, nil - } - if firstErr == nil { - firstErr = err - } - } - return nil, firstErr -} - -func (c *ClusterClient) cmdInfo(name string) *CommandInfo { - cmdsInfo, err := c.cmdsInfoCache.Get() - if err != nil { - return nil - } - - info := cmdsInfo[name] - if info == nil { - internal.Logf("info for cmd=%s not found", name) - } - return info -} - -func cmdSlot(cmd Cmder, pos int) int { - if pos == 0 { - return hashtag.RandomSlot() - } - firstKey := cmd.stringArg(pos) - return hashtag.Slot(firstKey) -} - -func (c *ClusterClient) cmdSlot(cmd Cmder) int { - args := cmd.Args() - if args[0] == "cluster" && args[1] == "getkeysinslot" { - return args[2].(int) - } - - cmdInfo := c.cmdInfo(cmd.Name()) - return cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) -} - -func (c *ClusterClient) cmdSlotAndNode(cmd Cmder) (int, *clusterNode, error) { - state, err := c.state.Get() - if err != nil { - return 0, nil, err - } - - cmdInfo := c.cmdInfo(cmd.Name()) - slot := c.cmdSlot(cmd) - - if c.opt.ReadOnly && cmdInfo != nil && cmdInfo.ReadOnly { - if c.opt.RouteByLatency { - node, err := state.slotClosestNode(slot) - return slot, node, err - } - - if c.opt.RouteRandomly { - node := state.slotRandomNode(slot) - return slot, node, nil - } - - node, err := state.slotSlaveNode(slot) - return slot, node, err - } - - node, err := state.slotMasterNode(slot) - return slot, node, err -} - -func (c *ClusterClient) slotMasterNode(slot int) (*clusterNode, error) { - state, err := c.state.Get() - if err != nil { - return nil, err - } - - nodes := state.slotNodes(slot) - if len(nodes) > 0 { - return nodes[0], nil - } - return c.nodes.Random() -} - -func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error { - if len(keys) == 0 { - return fmt.Errorf("redis: Watch requires at least one key") - } - - slot := hashtag.Slot(keys[0]) - for _, key := range keys[1:] { - if hashtag.Slot(key) != slot { - err := fmt.Errorf("redis: Watch requires all keys to be in the same slot") - return err - } - } - - node, err := c.slotMasterNode(slot) - if err != nil { - return err - } - - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - err = node.Client.Watch(fn, keys...) - if err == nil { - break - } - if err != Nil { - c.state.LazyReload() - } - - moved, ask, addr := internal.IsMovedError(err) - if moved || ask { - node, err = c.nodes.GetOrCreate(addr) - if err != nil { - return err - } - continue - } - - if err == pool.ErrClosed || internal.IsReadOnlyError(err) { - node, err = c.slotMasterNode(slot) - if err != nil { - return err - } - continue - } - - if internal.IsRetryableError(err, true) { - continue - } - - return err - } - - return err -} - -// Close closes the cluster client, releasing any open resources. -// -// It is rare to Close a ClusterClient, as the ClusterClient is meant -// to be long-lived and shared between many goroutines. -func (c *ClusterClient) Close() error { - return c.nodes.Close() -} - -// Do creates a Cmd from the args and processes the cmd. -func (c *ClusterClient) Do(args ...interface{}) *Cmd { - cmd := NewCmd(args...) - c.Process(cmd) - return cmd -} - -func (c *ClusterClient) WrapProcess( - fn func(oldProcess func(Cmder) error) func(Cmder) error, -) { - c.process = fn(c.process) -} - -func (c *ClusterClient) Process(cmd Cmder) error { - return c.process(cmd) -} - -func (c *ClusterClient) defaultProcess(cmd Cmder) error { - var node *clusterNode - var ask bool - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - if node == nil { - var err error - _, node, err = c.cmdSlotAndNode(cmd) - if err != nil { - cmd.setErr(err) - break - } - } - - var err error - if ask { - pipe := node.Client.Pipeline() - _ = pipe.Process(NewCmd("ASKING")) - _ = pipe.Process(cmd) - _, err = pipe.Exec() - _ = pipe.Close() - ask = false - } else { - err = node.Client.Process(cmd) - } - - // If there is no error - we are done. - if err == nil { - break - } - if err != Nil { - c.state.LazyReload() - } - - // If slave is loading - pick another node. - if c.opt.ReadOnly && internal.IsLoadingError(err) { - node.MarkAsLoading() - node = nil - continue - } - - var moved bool - var addr string - moved, ask, addr = internal.IsMovedError(err) - if moved || ask { - node, err = c.nodes.GetOrCreate(addr) - if err != nil { - break - } - continue - } - - if err == pool.ErrClosed || internal.IsReadOnlyError(err) { - node = nil - continue - } - - if internal.IsRetryableError(err, true) { - // First retry the same node. - if attempt == 0 { - continue - } - - // Second try random node. - node, err = c.nodes.Random() - if err != nil { - break - } - continue - } - - break - } - - return cmd.Err() -} - -// ForEachMaster concurrently calls the fn on each master node in the cluster. -// It returns the first error if any. -func (c *ClusterClient) ForEachMaster(fn func(client *Client) error) error { - state, err := c.state.ReloadOrGet() - if err != nil { - return err - } - - var wg sync.WaitGroup - errCh := make(chan error, 1) - for _, master := range state.Masters { - wg.Add(1) - go func(node *clusterNode) { - defer wg.Done() - err := fn(node.Client) - if err != nil { - select { - case errCh <- err: - default: - } - } - }(master) - } - wg.Wait() - - select { - case err := <-errCh: - return err - default: - return nil - } -} - -// ForEachSlave concurrently calls the fn on each slave node in the cluster. -// It returns the first error if any. -func (c *ClusterClient) ForEachSlave(fn func(client *Client) error) error { - state, err := c.state.ReloadOrGet() - if err != nil { - return err - } - - var wg sync.WaitGroup - errCh := make(chan error, 1) - for _, slave := range state.Slaves { - wg.Add(1) - go func(node *clusterNode) { - defer wg.Done() - err := fn(node.Client) - if err != nil { - select { - case errCh <- err: - default: - } - } - }(slave) - } - wg.Wait() - - select { - case err := <-errCh: - return err - default: - return nil - } -} - -// ForEachNode concurrently calls the fn on each known node in the cluster. -// It returns the first error if any. -func (c *ClusterClient) ForEachNode(fn func(client *Client) error) error { - state, err := c.state.ReloadOrGet() - if err != nil { - return err - } - - var wg sync.WaitGroup - errCh := make(chan error, 1) - worker := func(node *clusterNode) { - defer wg.Done() - err := fn(node.Client) - if err != nil { - select { - case errCh <- err: - default: - } - } - } - - for _, node := range state.Masters { - wg.Add(1) - go worker(node) - } - for _, node := range state.Slaves { - wg.Add(1) - go worker(node) - } - - wg.Wait() - select { - case err := <-errCh: - return err - default: - return nil - } -} - -// PoolStats returns accumulated connection pool stats. -func (c *ClusterClient) PoolStats() *PoolStats { - var acc PoolStats - - state, _ := c.state.Get() - if state == nil { - return &acc - } - - for _, node := range state.Masters { - s := node.Client.connPool.Stats() - acc.Hits += s.Hits - acc.Misses += s.Misses - acc.Timeouts += s.Timeouts - - acc.TotalConns += s.TotalConns - acc.IdleConns += s.IdleConns - acc.StaleConns += s.StaleConns - } - - for _, node := range state.Slaves { - s := node.Client.connPool.Stats() - acc.Hits += s.Hits - acc.Misses += s.Misses - acc.Timeouts += s.Timeouts - - acc.TotalConns += s.TotalConns - acc.IdleConns += s.IdleConns - acc.StaleConns += s.StaleConns - } - - return &acc -} - -func (c *ClusterClient) loadState() (*clusterState, error) { - if c.opt.ClusterSlots != nil { - slots, err := c.opt.ClusterSlots() - if err != nil { - return nil, err - } - return newClusterState(c.nodes, slots, "") - } - - addrs, err := c.nodes.Addrs() - if err != nil { - return nil, err - } - - var firstErr error - for _, addr := range addrs { - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - if firstErr == nil { - firstErr = err - } - continue - } - - slots, err := node.Client.ClusterSlots().Result() - if err != nil { - if firstErr == nil { - firstErr = err - } - continue - } - - return newClusterState(c.nodes, slots, node.Client.opt.Addr) - } - - return nil, firstErr -} - -// reaper closes idle connections to the cluster. -func (c *ClusterClient) reaper(idleCheckFrequency time.Duration) { - ticker := time.NewTicker(idleCheckFrequency) - defer ticker.Stop() - - for range ticker.C { - nodes, err := c.nodes.All() - if err != nil { - break - } - - for _, node := range nodes { - _, err := node.Client.connPool.(*pool.ConnPool).ReapStaleConns() - if err != nil { - internal.Logf("ReapStaleConns failed: %s", err) - } - } - } -} - -func (c *ClusterClient) Pipeline() Pipeliner { - pipe := Pipeline{ - exec: c.processPipeline, - } - pipe.statefulCmdable.setProcessor(pipe.Process) - return &pipe -} - -func (c *ClusterClient) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { - return c.Pipeline().Pipelined(fn) -} - -func (c *ClusterClient) WrapProcessPipeline( - fn func(oldProcess func([]Cmder) error) func([]Cmder) error, -) { - c.processPipeline = fn(c.processPipeline) - c.processTxPipeline = fn(c.processTxPipeline) -} - -func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error { - cmdsMap := newCmdsMap() - err := c.mapCmdsByNode(cmds, cmdsMap) - if err != nil { - setCmdsErr(cmds, err) - return err - } - - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - failedCmds := newCmdsMap() - var wg sync.WaitGroup - - for node, cmds := range cmdsMap.m { - wg.Add(1) - go func(node *clusterNode, cmds []Cmder) { - defer wg.Done() - - cn, err := node.Client.getConn() - if err != nil { - if err == pool.ErrClosed { - c.mapCmdsByNode(cmds, failedCmds) - } else { - setCmdsErr(cmds, err) - } - return - } - - err = c.pipelineProcessCmds(node, cn, cmds, failedCmds) - node.Client.releaseConnStrict(cn, err) - }(node, cmds) - } - - wg.Wait() - if len(failedCmds.m) == 0 { - break - } - cmdsMap = failedCmds - } - - return cmdsFirstErr(cmds) -} - -type cmdsMap struct { - mu sync.Mutex - m map[*clusterNode][]Cmder -} - -func newCmdsMap() *cmdsMap { - return &cmdsMap{ - m: make(map[*clusterNode][]Cmder), - } -} - -func (c *ClusterClient) mapCmdsByNode(cmds []Cmder, cmdsMap *cmdsMap) error { - state, err := c.state.Get() - if err != nil { - setCmdsErr(cmds, err) - return err - } - - cmdsAreReadOnly := c.cmdsAreReadOnly(cmds) - for _, cmd := range cmds { - var node *clusterNode - var err error - if cmdsAreReadOnly { - _, node, err = c.cmdSlotAndNode(cmd) - } else { - slot := c.cmdSlot(cmd) - node, err = state.slotMasterNode(slot) - } - if err != nil { - return err - } - cmdsMap.mu.Lock() - cmdsMap.m[node] = append(cmdsMap.m[node], cmd) - cmdsMap.mu.Unlock() - } - return nil -} - -func (c *ClusterClient) cmdsAreReadOnly(cmds []Cmder) bool { - for _, cmd := range cmds { - cmdInfo := c.cmdInfo(cmd.Name()) - if cmdInfo == nil || !cmdInfo.ReadOnly { - return false - } - } - return true -} - -func (c *ClusterClient) pipelineProcessCmds( - node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap, -) error { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return writeCmd(wr, cmds...) - }) - if err != nil { - setCmdsErr(cmds, err) - failedCmds.mu.Lock() - failedCmds.m[node] = cmds - failedCmds.mu.Unlock() - return err - } - - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - return c.pipelineReadCmds(node, rd, cmds, failedCmds) - }) - return err -} - -func (c *ClusterClient) pipelineReadCmds( - node *clusterNode, rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap, -) error { - var firstErr error - for _, cmd := range cmds { - err := cmd.readReply(rd) - if err == nil { - continue - } - - if c.checkMovedErr(cmd, err, failedCmds) { - continue - } - - if internal.IsRedisError(err) { - continue - } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], cmd) - failedCmds.mu.Unlock() - if firstErr == nil { - firstErr = err - } - } - return firstErr -} - -func (c *ClusterClient) checkMovedErr( - cmd Cmder, err error, failedCmds *cmdsMap, -) bool { - moved, ask, addr := internal.IsMovedError(err) - - if moved { - c.state.LazyReload() - - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return false - } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], cmd) - failedCmds.mu.Unlock() - return true - } - - if ask { - node, err := c.nodes.GetOrCreate(addr) - if err != nil { - return false - } - - failedCmds.mu.Lock() - failedCmds.m[node] = append(failedCmds.m[node], NewCmd("ASKING"), cmd) - failedCmds.mu.Unlock() - return true - } - - return false -} - -// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. -func (c *ClusterClient) TxPipeline() Pipeliner { - pipe := Pipeline{ - exec: c.processTxPipeline, - } - pipe.statefulCmdable.setProcessor(pipe.Process) - return &pipe -} - -func (c *ClusterClient) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { - return c.TxPipeline().Pipelined(fn) -} - -func (c *ClusterClient) defaultProcessTxPipeline(cmds []Cmder) error { - state, err := c.state.Get() - if err != nil { - return err - } - - cmdsMap := c.mapCmdsBySlot(cmds) - for slot, cmds := range cmdsMap { - node, err := state.slotMasterNode(slot) - if err != nil { - setCmdsErr(cmds, err) - continue - } - cmdsMap := map[*clusterNode][]Cmder{node: cmds} - - for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { - if attempt > 0 { - time.Sleep(c.retryBackoff(attempt)) - } - - failedCmds := newCmdsMap() - var wg sync.WaitGroup - - for node, cmds := range cmdsMap { - wg.Add(1) - go func(node *clusterNode, cmds []Cmder) { - defer wg.Done() - - cn, err := node.Client.getConn() - if err != nil { - if err == pool.ErrClosed { - c.mapCmdsByNode(cmds, failedCmds) - } else { - setCmdsErr(cmds, err) - } - return - } - - err = c.txPipelineProcessCmds(node, cn, cmds, failedCmds) - node.Client.releaseConnStrict(cn, err) - }(node, cmds) - } - - wg.Wait() - if len(failedCmds.m) == 0 { - break - } - cmdsMap = failedCmds.m - } - } - - return cmdsFirstErr(cmds) -} - -func (c *ClusterClient) mapCmdsBySlot(cmds []Cmder) map[int][]Cmder { - cmdsMap := make(map[int][]Cmder) - for _, cmd := range cmds { - slot := c.cmdSlot(cmd) - cmdsMap[slot] = append(cmdsMap[slot], cmd) - } - return cmdsMap -} - -func (c *ClusterClient) txPipelineProcessCmds( - node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap, -) error { - err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error { - return txPipelineWriteMulti(wr, cmds) - }) - if err != nil { - setCmdsErr(cmds, err) - failedCmds.mu.Lock() - failedCmds.m[node] = cmds - failedCmds.mu.Unlock() - return err - } - - err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error { - err := c.txPipelineReadQueued(rd, cmds, failedCmds) - if err != nil { - setCmdsErr(cmds, err) - return err - } - return pipelineReadCmds(rd, cmds) - }) - return err -} - -func (c *ClusterClient) txPipelineReadQueued( - rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap, -) error { - // Parse queued replies. - var statusCmd StatusCmd - if err := statusCmd.readReply(rd); err != nil { - return err - } - - for _, cmd := range cmds { - err := statusCmd.readReply(rd) - if err == nil { - continue - } - - if c.checkMovedErr(cmd, err, failedCmds) || internal.IsRedisError(err) { - continue - } - - return err - } - - // Parse number of replies. - line, err := rd.ReadLine() - if err != nil { - if err == Nil { - err = TxFailedErr - } - return err - } - - switch line[0] { - case proto.ErrorReply: - err := proto.ParseErrorReply(line) - for _, cmd := range cmds { - if !c.checkMovedErr(cmd, err, failedCmds) { - break - } - } - return err - case proto.ArrayReply: - // ok - default: - err := fmt.Errorf("redis: expected '*', but got line %q", line) - return err - } - - return nil -} - -func (c *ClusterClient) pubSub() *PubSub { - var node *clusterNode - pubsub := &PubSub{ - opt: c.opt.clientOptions(), - - newConn: func(channels []string) (*pool.Conn, error) { - if node != nil { - panic("node != nil") - } - - var err error - if len(channels) > 0 { - slot := hashtag.Slot(channels[0]) - node, err = c.slotMasterNode(slot) - } else { - node, err = c.nodes.Random() - } - if err != nil { - return nil, err - } - - cn, err := node.Client.newConn() - if err != nil { - node = nil - - return nil, err - } - - return cn, nil - }, - closeConn: func(cn *pool.Conn) error { - err := node.Client.connPool.CloseConn(cn) - node = nil - return err - }, - } - pubsub.init() - - return pubsub -} - -// Subscribe subscribes the client to the specified channels. -// Channels can be omitted to create empty subscription. -func (c *ClusterClient) Subscribe(channels ...string) *PubSub { - pubsub := c.pubSub() - if len(channels) > 0 { - _ = pubsub.Subscribe(channels...) - } - return pubsub -} - -// PSubscribe subscribes the client to the given patterns. -// Patterns can be omitted to create empty subscription. -func (c *ClusterClient) PSubscribe(channels ...string) *PubSub { - pubsub := c.pubSub() - if len(channels) > 0 { - _ = pubsub.PSubscribe(channels...) - } - return pubsub -} - -func appendUniqueNode(nodes []*clusterNode, node *clusterNode) []*clusterNode { - for _, n := range nodes { - if n == node { - return nodes - } - } - return append(nodes, node) -} - -func appendIfNotExists(ss []string, es ...string) []string { -loop: - for _, e := range es { - for _, s := range ss { - if s == e { - continue loop - } - } - ss = append(ss, e) - } - return ss -} - -func remove(ss []string, es ...string) []string { - if len(es) == 0 { - return ss[:0] - } - for _, e := range es { - for i, s := range ss { - if s == e { - ss = append(ss[:i], ss[i+1:]...) - break - } - } - } - return ss -} diff --git a/vendor/github.com/go-redis/redis/cluster_commands.go b/vendor/github.com/go-redis/redis/cluster_commands.go deleted file mode 100644 index dff62c902d..0000000000 --- a/vendor/github.com/go-redis/redis/cluster_commands.go +++ /dev/null @@ -1,22 +0,0 @@ -package redis - -import "sync/atomic" - -func (c *ClusterClient) DBSize() *IntCmd { - cmd := NewIntCmd("dbsize") - var size int64 - err := c.ForEachMaster(func(master *Client) error { - n, err := master.DBSize().Result() - if err != nil { - return err - } - atomic.AddInt64(&size, n) - return nil - }) - if err != nil { - cmd.setErr(err) - return cmd - } - cmd.val = size - return cmd -} diff --git a/vendor/github.com/go-redis/redis/command.go b/vendor/github.com/go-redis/redis/command.go deleted file mode 100644 index dde513be2d..0000000000 --- a/vendor/github.com/go-redis/redis/command.go +++ /dev/null @@ -1,1966 +0,0 @@ -package redis - -import ( - "fmt" - "net" - "strconv" - "strings" - "time" - - "github.com/go-redis/redis/internal" - "github.com/go-redis/redis/internal/proto" -) - -type Cmder interface { - Name() string - Args() []interface{} - stringArg(int) string - - readReply(rd *proto.Reader) error - setErr(error) - - readTimeout() *time.Duration - - Err() error -} - -func setCmdsErr(cmds []Cmder, e error) { - for _, cmd := range cmds { - if cmd.Err() == nil { - cmd.setErr(e) - } - } -} - -func cmdsFirstErr(cmds []Cmder) error { - for _, cmd := range cmds { - if err := cmd.Err(); err != nil { - return err - } - } - return nil -} - -func writeCmd(wr *proto.Writer, cmds ...Cmder) error { - for _, cmd := range cmds { - err := wr.WriteArgs(cmd.Args()) - if err != nil { - return err - } - } - return nil -} - -func cmdString(cmd Cmder, val interface{}) string { - var ss []string - for _, arg := range cmd.Args() { - ss = append(ss, fmt.Sprint(arg)) - } - s := strings.Join(ss, " ") - if err := cmd.Err(); err != nil { - return s + ": " + err.Error() - } - if val != nil { - switch vv := val.(type) { - case []byte: - return s + ": " + string(vv) - default: - return s + ": " + fmt.Sprint(val) - } - } - return s - -} - -func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { - switch cmd.Name() { - case "eval", "evalsha": - if cmd.stringArg(2) != "0" { - return 3 - } - - return 0 - case "publish": - return 1 - } - if info == nil { - return 0 - } - return int(info.FirstKeyPos) -} - -//------------------------------------------------------------------------------ - -type baseCmd struct { - _args []interface{} - err error - - _readTimeout *time.Duration -} - -var _ Cmder = (*Cmd)(nil) - -func (cmd *baseCmd) Err() error { - return cmd.err -} - -func (cmd *baseCmd) Args() []interface{} { - return cmd._args -} - -func (cmd *baseCmd) stringArg(pos int) string { - if pos < 0 || pos >= len(cmd._args) { - return "" - } - s, _ := cmd._args[pos].(string) - return s -} - -func (cmd *baseCmd) Name() string { - if len(cmd._args) > 0 { - // Cmd name must be lower cased. - s := internal.ToLower(cmd.stringArg(0)) - cmd._args[0] = s - return s - } - return "" -} - -func (cmd *baseCmd) readTimeout() *time.Duration { - return cmd._readTimeout -} - -func (cmd *baseCmd) setReadTimeout(d time.Duration) { - cmd._readTimeout = &d -} - -func (cmd *baseCmd) setErr(e error) { - cmd.err = e -} - -//------------------------------------------------------------------------------ - -type Cmd struct { - baseCmd - - val interface{} -} - -func NewCmd(args ...interface{}) *Cmd { - return &Cmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *Cmd) Val() interface{} { - return cmd.val -} - -func (cmd *Cmd) Result() (interface{}, error) { - return cmd.val, cmd.err -} - -func (cmd *Cmd) String() (string, error) { - if cmd.err != nil { - return "", cmd.err - } - switch val := cmd.val.(type) { - case string: - return val, nil - default: - err := fmt.Errorf("redis: unexpected type=%T for String", val) - return "", err - } -} - -func (cmd *Cmd) Int() (int, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return int(val), nil - case string: - return strconv.Atoi(val) - default: - err := fmt.Errorf("redis: unexpected type=%T for Int", val) - return 0, err - } -} - -func (cmd *Cmd) Int64() (int64, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return val, nil - case string: - return strconv.ParseInt(val, 10, 64) - default: - err := fmt.Errorf("redis: unexpected type=%T for Int64", val) - return 0, err - } -} - -func (cmd *Cmd) Uint64() (uint64, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return uint64(val), nil - case string: - return strconv.ParseUint(val, 10, 64) - default: - err := fmt.Errorf("redis: unexpected type=%T for Uint64", val) - return 0, err - } -} - -func (cmd *Cmd) Float32() (float32, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return float32(val), nil - case string: - f, err := strconv.ParseFloat(val, 32) - if err != nil { - return 0, err - } - return float32(f), nil - default: - err := fmt.Errorf("redis: unexpected type=%T for Float32", val) - return 0, err - } -} - -func (cmd *Cmd) Float64() (float64, error) { - if cmd.err != nil { - return 0, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return float64(val), nil - case string: - return strconv.ParseFloat(val, 64) - default: - err := fmt.Errorf("redis: unexpected type=%T for Float64", val) - return 0, err - } -} - -func (cmd *Cmd) Bool() (bool, error) { - if cmd.err != nil { - return false, cmd.err - } - switch val := cmd.val.(type) { - case int64: - return val != 0, nil - case string: - return strconv.ParseBool(val) - default: - err := fmt.Errorf("redis: unexpected type=%T for Bool", val) - return false, err - } -} - -func (cmd *Cmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadReply(sliceParser) - return cmd.err -} - -// Implements proto.MultiBulkParse -func sliceParser(rd *proto.Reader, n int64) (interface{}, error) { - vals := make([]interface{}, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(sliceParser) - if err != nil { - if err == Nil { - vals = append(vals, nil) - continue - } - if err, ok := err.(proto.RedisError); ok { - vals = append(vals, err) - continue - } - return nil, err - } - - switch v := v.(type) { - case string: - vals = append(vals, v) - default: - vals = append(vals, v) - } - } - return vals, nil -} - -//------------------------------------------------------------------------------ - -type SliceCmd struct { - baseCmd - - val []interface{} -} - -var _ Cmder = (*SliceCmd)(nil) - -func NewSliceCmd(args ...interface{}) *SliceCmd { - return &SliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *SliceCmd) Val() []interface{} { - return cmd.val -} - -func (cmd *SliceCmd) Result() ([]interface{}, error) { - return cmd.val, cmd.err -} - -func (cmd *SliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *SliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(sliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]interface{}) - return nil -} - -//------------------------------------------------------------------------------ - -type StatusCmd struct { - baseCmd - - val string -} - -var _ Cmder = (*StatusCmd)(nil) - -func NewStatusCmd(args ...interface{}) *StatusCmd { - return &StatusCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StatusCmd) Val() string { - return cmd.val -} - -func (cmd *StatusCmd) Result() (string, error) { - return cmd.val, cmd.err -} - -func (cmd *StatusCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StatusCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadString() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type IntCmd struct { - baseCmd - - val int64 -} - -var _ Cmder = (*IntCmd)(nil) - -func NewIntCmd(args ...interface{}) *IntCmd { - return &IntCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *IntCmd) Val() int64 { - return cmd.val -} - -func (cmd *IntCmd) Result() (int64, error) { - return cmd.val, cmd.err -} - -func (cmd *IntCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *IntCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadIntReply() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type DurationCmd struct { - baseCmd - - val time.Duration - precision time.Duration -} - -var _ Cmder = (*DurationCmd)(nil) - -func NewDurationCmd(precision time.Duration, args ...interface{}) *DurationCmd { - return &DurationCmd{ - baseCmd: baseCmd{_args: args}, - precision: precision, - } -} - -func (cmd *DurationCmd) Val() time.Duration { - return cmd.val -} - -func (cmd *DurationCmd) Result() (time.Duration, error) { - return cmd.val, cmd.err -} - -func (cmd *DurationCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *DurationCmd) readReply(rd *proto.Reader) error { - var n int64 - n, cmd.err = rd.ReadIntReply() - if cmd.err != nil { - return cmd.err - } - cmd.val = time.Duration(n) * cmd.precision - return nil -} - -//------------------------------------------------------------------------------ - -type TimeCmd struct { - baseCmd - - val time.Time -} - -var _ Cmder = (*TimeCmd)(nil) - -func NewTimeCmd(args ...interface{}) *TimeCmd { - return &TimeCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *TimeCmd) Val() time.Time { - return cmd.val -} - -func (cmd *TimeCmd) Result() (time.Time, error) { - return cmd.val, cmd.err -} - -func (cmd *TimeCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *TimeCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(timeParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(time.Time) - return nil -} - -// Implements proto.MultiBulkParse -func timeParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d elements, expected 2", n) - } - - sec, err := rd.ReadInt() - if err != nil { - return nil, err - } - - microsec, err := rd.ReadInt() - if err != nil { - return nil, err - } - - return time.Unix(sec, microsec*1000), nil -} - -//------------------------------------------------------------------------------ - -type BoolCmd struct { - baseCmd - - val bool -} - -var _ Cmder = (*BoolCmd)(nil) - -func NewBoolCmd(args ...interface{}) *BoolCmd { - return &BoolCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *BoolCmd) Val() bool { - return cmd.val -} - -func (cmd *BoolCmd) Result() (bool, error) { - return cmd.val, cmd.err -} - -func (cmd *BoolCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *BoolCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadReply(nil) - // `SET key value NX` returns nil when key already exists. But - // `SETNX key value` returns bool (0/1). So convert nil to bool. - // TODO: is this okay? - if cmd.err == Nil { - cmd.val = false - cmd.err = nil - return nil - } - if cmd.err != nil { - return cmd.err - } - switch v := v.(type) { - case int64: - cmd.val = v == 1 - return nil - case string: - cmd.val = v == "OK" - return nil - default: - cmd.err = fmt.Errorf("got %T, wanted int64 or string", v) - return cmd.err - } -} - -//------------------------------------------------------------------------------ - -type StringCmd struct { - baseCmd - - val string -} - -var _ Cmder = (*StringCmd)(nil) - -func NewStringCmd(args ...interface{}) *StringCmd { - return &StringCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringCmd) Val() string { - return cmd.val -} - -func (cmd *StringCmd) Result() (string, error) { - return cmd.Val(), cmd.err -} - -func (cmd *StringCmd) Bytes() ([]byte, error) { - return []byte(cmd.val), cmd.err -} - -func (cmd *StringCmd) Int() (int, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.Atoi(cmd.Val()) -} - -func (cmd *StringCmd) Int64() (int64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseInt(cmd.Val(), 10, 64) -} - -func (cmd *StringCmd) Uint64() (uint64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseUint(cmd.Val(), 10, 64) -} - -func (cmd *StringCmd) Float32() (float32, error) { - if cmd.err != nil { - return 0, cmd.err - } - f, err := strconv.ParseFloat(cmd.Val(), 32) - if err != nil { - return 0, err - } - return float32(f), nil -} - -func (cmd *StringCmd) Float64() (float64, error) { - if cmd.err != nil { - return 0, cmd.err - } - return strconv.ParseFloat(cmd.Val(), 64) -} - -func (cmd *StringCmd) Scan(val interface{}) error { - if cmd.err != nil { - return cmd.err - } - return proto.Scan([]byte(cmd.val), val) -} - -func (cmd *StringCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadString() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type FloatCmd struct { - baseCmd - - val float64 -} - -var _ Cmder = (*FloatCmd)(nil) - -func NewFloatCmd(args ...interface{}) *FloatCmd { - return &FloatCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *FloatCmd) Val() float64 { - return cmd.val -} - -func (cmd *FloatCmd) Result() (float64, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *FloatCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *FloatCmd) readReply(rd *proto.Reader) error { - cmd.val, cmd.err = rd.ReadFloatReply() - return cmd.err -} - -//------------------------------------------------------------------------------ - -type StringSliceCmd struct { - baseCmd - - val []string -} - -var _ Cmder = (*StringSliceCmd)(nil) - -func NewStringSliceCmd(args ...interface{}) *StringSliceCmd { - return &StringSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringSliceCmd) Val() []string { - return cmd.val -} - -func (cmd *StringSliceCmd) Result() ([]string, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *StringSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringSliceCmd) ScanSlice(container interface{}) error { - return proto.ScanSlice(cmd.Val(), container) -} - -func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]string) - return nil -} - -// Implements proto.MultiBulkParse -func stringSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ss := make([]string, 0, n) - for i := int64(0); i < n; i++ { - switch s, err := rd.ReadString(); { - case err == Nil: - ss = append(ss, "") - case err != nil: - return nil, err - default: - ss = append(ss, s) - } - } - return ss, nil -} - -//------------------------------------------------------------------------------ - -type BoolSliceCmd struct { - baseCmd - - val []bool -} - -var _ Cmder = (*BoolSliceCmd)(nil) - -func NewBoolSliceCmd(args ...interface{}) *BoolSliceCmd { - return &BoolSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *BoolSliceCmd) Val() []bool { - return cmd.val -} - -func (cmd *BoolSliceCmd) Result() ([]bool, error) { - return cmd.val, cmd.err -} - -func (cmd *BoolSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *BoolSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(boolSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]bool) - return nil -} - -// Implements proto.MultiBulkParse -func boolSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - bools := make([]bool, 0, n) - for i := int64(0); i < n; i++ { - n, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - bools = append(bools, n == 1) - } - return bools, nil -} - -//------------------------------------------------------------------------------ - -type StringStringMapCmd struct { - baseCmd - - val map[string]string -} - -var _ Cmder = (*StringStringMapCmd)(nil) - -func NewStringStringMapCmd(args ...interface{}) *StringStringMapCmd { - return &StringStringMapCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringStringMapCmd) Val() map[string]string { - return cmd.val -} - -func (cmd *StringStringMapCmd) Result() (map[string]string, error) { - return cmd.val, cmd.err -} - -func (cmd *StringStringMapCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringStringMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringStringMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]string) - return nil -} - -// Implements proto.MultiBulkParse -func stringStringMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]string, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - value, err := rd.ReadString() - if err != nil { - return nil, err - } - - m[key] = value - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type StringIntMapCmd struct { - baseCmd - - val map[string]int64 -} - -var _ Cmder = (*StringIntMapCmd)(nil) - -func NewStringIntMapCmd(args ...interface{}) *StringIntMapCmd { - return &StringIntMapCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringIntMapCmd) Val() map[string]int64 { - return cmd.val -} - -func (cmd *StringIntMapCmd) Result() (map[string]int64, error) { - return cmd.val, cmd.err -} - -func (cmd *StringIntMapCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringIntMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringIntMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]int64) - return nil -} - -// Implements proto.MultiBulkParse -func stringIntMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]int64, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - n, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - m[key] = n - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type StringStructMapCmd struct { - baseCmd - - val map[string]struct{} -} - -var _ Cmder = (*StringStructMapCmd)(nil) - -func NewStringStructMapCmd(args ...interface{}) *StringStructMapCmd { - return &StringStructMapCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *StringStructMapCmd) Val() map[string]struct{} { - return cmd.val -} - -func (cmd *StringStructMapCmd) Result() (map[string]struct{}, error) { - return cmd.val, cmd.err -} - -func (cmd *StringStructMapCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *StringStructMapCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(stringStructMapParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]struct{}) - return nil -} - -// Implements proto.MultiBulkParse -func stringStructMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]struct{}, n) - for i := int64(0); i < n; i++ { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - m[key] = struct{}{} - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type XMessage struct { - ID string - Values map[string]interface{} -} - -type XMessageSliceCmd struct { - baseCmd - - val []XMessage -} - -var _ Cmder = (*XMessageSliceCmd)(nil) - -func NewXMessageSliceCmd(args ...interface{}) *XMessageSliceCmd { - return &XMessageSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XMessageSliceCmd) Val() []XMessage { - return cmd.val -} - -func (cmd *XMessageSliceCmd) Result() ([]XMessage, error) { - return cmd.val, cmd.err -} - -func (cmd *XMessageSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(xMessageSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]XMessage) - return nil -} - -// Implements proto.MultiBulkParse -func xMessageSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - msgs := make([]XMessage, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - id, err := rd.ReadString() - if err != nil { - return nil, err - } - - v, err := rd.ReadArrayReply(stringInterfaceMapParser) - if err != nil { - return nil, err - } - - msgs = append(msgs, XMessage{ - ID: id, - Values: v.(map[string]interface{}), - }) - return nil, nil - }) - if err != nil { - return nil, err - } - } - return msgs, nil -} - -// Implements proto.MultiBulkParse -func stringInterfaceMapParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]interface{}, n/2) - for i := int64(0); i < n; i += 2 { - key, err := rd.ReadString() - if err != nil { - return nil, err - } - - value, err := rd.ReadString() - if err != nil { - return nil, err - } - - m[key] = value - } - return m, nil -} - -//------------------------------------------------------------------------------ - -type XStream struct { - Stream string - Messages []XMessage -} - -type XStreamSliceCmd struct { - baseCmd - - val []XStream -} - -var _ Cmder = (*XStreamSliceCmd)(nil) - -func NewXStreamSliceCmd(args ...interface{}) *XStreamSliceCmd { - return &XStreamSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XStreamSliceCmd) Val() []XStream { - return cmd.val -} - -func (cmd *XStreamSliceCmd) Result() ([]XStream, error) { - return cmd.val, cmd.err -} - -func (cmd *XStreamSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XStreamSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(xStreamSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]XStream) - return nil -} - -// Implements proto.MultiBulkParse -func xStreamSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ret := make([]XStream, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } - - stream, err := rd.ReadString() - if err != nil { - return nil, err - } - - v, err := rd.ReadArrayReply(xMessageSliceParser) - if err != nil { - return nil, err - } - - ret = append(ret, XStream{ - Stream: stream, - Messages: v.([]XMessage), - }) - return nil, nil - }) - if err != nil { - return nil, err - } - } - return ret, nil -} - -//------------------------------------------------------------------------------ - -type XPending struct { - Count int64 - Lower string - Higher string - Consumers map[string]int64 -} - -type XPendingCmd struct { - baseCmd - val *XPending -} - -var _ Cmder = (*XPendingCmd)(nil) - -func NewXPendingCmd(args ...interface{}) *XPendingCmd { - return &XPendingCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XPendingCmd) Val() *XPending { - return cmd.val -} - -func (cmd *XPendingCmd) Result() (*XPending, error) { - return cmd.val, cmd.err -} - -func (cmd *XPendingCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XPendingCmd) readReply(rd *proto.Reader) error { - var info interface{} - info, cmd.err = rd.ReadArrayReply(xPendingParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = info.(*XPending) - return nil -} - -func xPendingParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) - } - - count, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - lower, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - higher, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - pending := &XPending{ - Count: count, - Lower: lower, - Higher: higher, - } - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - for i := int64(0); i < n; i++ { - _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 2 { - return nil, fmt.Errorf("got %d, wanted 2", n) - } - - consumerName, err := rd.ReadString() - if err != nil { - return nil, err - } - - consumerPending, err := rd.ReadInt() - if err != nil { - return nil, err - } - - if pending.Consumers == nil { - pending.Consumers = make(map[string]int64) - } - pending.Consumers[consumerName] = consumerPending - - return nil, nil - }) - if err != nil { - return nil, err - } - } - return nil, nil - }) - if err != nil && err != Nil { - return nil, err - } - - return pending, nil -} - -//------------------------------------------------------------------------------ - -type XPendingExt struct { - Id string - Consumer string - Idle time.Duration - RetryCount int64 -} - -type XPendingExtCmd struct { - baseCmd - val []XPendingExt -} - -var _ Cmder = (*XPendingExtCmd)(nil) - -func NewXPendingExtCmd(args ...interface{}) *XPendingExtCmd { - return &XPendingExtCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *XPendingExtCmd) Val() []XPendingExt { - return cmd.val -} - -func (cmd *XPendingExtCmd) Result() ([]XPendingExt, error) { - return cmd.val, cmd.err -} - -func (cmd *XPendingExtCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error { - var info interface{} - info, cmd.err = rd.ReadArrayReply(xPendingExtSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = info.([]XPendingExt) - return nil -} - -func xPendingExtSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - ret := make([]XPendingExt, 0, n) - for i := int64(0); i < n; i++ { - _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { - if n != 4 { - return nil, fmt.Errorf("got %d, wanted 4", n) - } - - id, err := rd.ReadString() - if err != nil { - return nil, err - } - - consumer, err := rd.ReadString() - if err != nil && err != Nil { - return nil, err - } - - idle, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - retryCount, err := rd.ReadIntReply() - if err != nil && err != Nil { - return nil, err - } - - ret = append(ret, XPendingExt{ - Id: id, - Consumer: consumer, - Idle: time.Duration(idle) * time.Millisecond, - RetryCount: retryCount, - }) - return nil, nil - }) - if err != nil { - return nil, err - } - } - return ret, nil -} - -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ - -type ZSliceCmd struct { - baseCmd - - val []Z -} - -var _ Cmder = (*ZSliceCmd)(nil) - -func NewZSliceCmd(args ...interface{}) *ZSliceCmd { - return &ZSliceCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *ZSliceCmd) Val() []Z { - return cmd.val -} - -func (cmd *ZSliceCmd) Result() ([]Z, error) { - return cmd.val, cmd.err -} - -func (cmd *ZSliceCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(zSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]Z) - return nil -} - -// Implements proto.MultiBulkParse -func zSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - zz := make([]Z, n/2) - for i := int64(0); i < n; i += 2 { - var err error - - z := &zz[i/2] - - z.Member, err = rd.ReadString() - if err != nil { - return nil, err - } - - z.Score, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - return zz, nil -} - -//------------------------------------------------------------------------------ - -type ZWithKeyCmd struct { - baseCmd - - val ZWithKey -} - -var _ Cmder = (*ZWithKeyCmd)(nil) - -func NewZWithKeyCmd(args ...interface{}) *ZWithKeyCmd { - return &ZWithKeyCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *ZWithKeyCmd) Val() ZWithKey { - return cmd.val -} - -func (cmd *ZWithKeyCmd) Result() (ZWithKey, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *ZWithKeyCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *ZWithKeyCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(zWithKeyParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(ZWithKey) - return nil -} - -// Implements proto.MultiBulkParse -func zWithKeyParser(rd *proto.Reader, n int64) (interface{}, error) { - if n != 3 { - return nil, fmt.Errorf("got %d elements, expected 3", n) - } - - var z ZWithKey - var err error - - z.Key, err = rd.ReadString() - if err != nil { - return nil, err - } - z.Member, err = rd.ReadString() - if err != nil { - return nil, err - } - z.Score, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - return z, nil -} - -//------------------------------------------------------------------------------ - -type ScanCmd struct { - baseCmd - - page []string - cursor uint64 - - process func(cmd Cmder) error -} - -var _ Cmder = (*ScanCmd)(nil) - -func NewScanCmd(process func(cmd Cmder) error, args ...interface{}) *ScanCmd { - return &ScanCmd{ - baseCmd: baseCmd{_args: args}, - process: process, - } -} - -func (cmd *ScanCmd) Val() (keys []string, cursor uint64) { - return cmd.page, cmd.cursor -} - -func (cmd *ScanCmd) Result() (keys []string, cursor uint64, err error) { - return cmd.page, cmd.cursor, cmd.err -} - -func (cmd *ScanCmd) String() string { - return cmdString(cmd, cmd.page) -} - -func (cmd *ScanCmd) readReply(rd *proto.Reader) error { - cmd.page, cmd.cursor, cmd.err = rd.ReadScanReply() - return cmd.err -} - -// Iterator creates a new ScanIterator. -func (cmd *ScanCmd) Iterator() *ScanIterator { - return &ScanIterator{ - cmd: cmd, - } -} - -//------------------------------------------------------------------------------ - -type ClusterNode struct { - Id string - Addr string -} - -type ClusterSlot struct { - Start int - End int - Nodes []ClusterNode -} - -type ClusterSlotsCmd struct { - baseCmd - - val []ClusterSlot -} - -var _ Cmder = (*ClusterSlotsCmd)(nil) - -func NewClusterSlotsCmd(args ...interface{}) *ClusterSlotsCmd { - return &ClusterSlotsCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *ClusterSlotsCmd) Val() []ClusterSlot { - return cmd.val -} - -func (cmd *ClusterSlotsCmd) Result() ([]ClusterSlot, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *ClusterSlotsCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *ClusterSlotsCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(clusterSlotsParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.([]ClusterSlot) - return nil -} - -// Implements proto.MultiBulkParse -func clusterSlotsParser(rd *proto.Reader, n int64) (interface{}, error) { - slots := make([]ClusterSlot, n) - for i := 0; i < len(slots); i++ { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n < 2 { - err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) - return nil, err - } - - start, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - end, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - - nodes := make([]ClusterNode, n-2) - for j := 0; j < len(nodes); j++ { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n != 2 && n != 3 { - err := fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", n) - return nil, err - } - - ip, err := rd.ReadString() - if err != nil { - return nil, err - } - - port, err := rd.ReadString() - if err != nil { - return nil, err - } - - nodes[j].Addr = net.JoinHostPort(ip, port) - - if n == 3 { - id, err := rd.ReadString() - if err != nil { - return nil, err - } - nodes[j].Id = id - } - } - - slots[i] = ClusterSlot{ - Start: int(start), - End: int(end), - Nodes: nodes, - } - } - return slots, nil -} - -//------------------------------------------------------------------------------ - -// GeoLocation is used with GeoAdd to add geospatial location. -type GeoLocation struct { - Name string - Longitude, Latitude, Dist float64 - GeoHash int64 -} - -// GeoRadiusQuery is used with GeoRadius to query geospatial index. -type GeoRadiusQuery struct { - Radius float64 - // Can be m, km, ft, or mi. Default is km. - Unit string - WithCoord bool - WithDist bool - WithGeoHash bool - Count int - // Can be ASC or DESC. Default is no sort order. - Sort string - Store string - StoreDist string -} - -type GeoLocationCmd struct { - baseCmd - - q *GeoRadiusQuery - locations []GeoLocation -} - -var _ Cmder = (*GeoLocationCmd)(nil) - -func NewGeoLocationCmd(q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd { - args = append(args, q.Radius) - if q.Unit != "" { - args = append(args, q.Unit) - } else { - args = append(args, "km") - } - if q.WithCoord { - args = append(args, "withcoord") - } - if q.WithDist { - args = append(args, "withdist") - } - if q.WithGeoHash { - args = append(args, "withhash") - } - if q.Count > 0 { - args = append(args, "count", q.Count) - } - if q.Sort != "" { - args = append(args, q.Sort) - } - if q.Store != "" { - args = append(args, "store") - args = append(args, q.Store) - } - if q.StoreDist != "" { - args = append(args, "storedist") - args = append(args, q.StoreDist) - } - return &GeoLocationCmd{ - baseCmd: baseCmd{_args: args}, - q: q, - } -} - -func (cmd *GeoLocationCmd) Val() []GeoLocation { - return cmd.locations -} - -func (cmd *GeoLocationCmd) Result() ([]GeoLocation, error) { - return cmd.locations, cmd.err -} - -func (cmd *GeoLocationCmd) String() string { - return cmdString(cmd, cmd.locations) -} - -func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(newGeoLocationSliceParser(cmd.q)) - if cmd.err != nil { - return cmd.err - } - cmd.locations = v.([]GeoLocation) - return nil -} - -func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse { - return func(rd *proto.Reader, n int64) (interface{}, error) { - var loc GeoLocation - var err error - - loc.Name, err = rd.ReadString() - if err != nil { - return nil, err - } - if q.WithDist { - loc.Dist, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - if q.WithGeoHash { - loc.GeoHash, err = rd.ReadIntReply() - if err != nil { - return nil, err - } - } - if q.WithCoord { - n, err := rd.ReadArrayLen() - if err != nil { - return nil, err - } - if n != 2 { - return nil, fmt.Errorf("got %d coordinates, expected 2", n) - } - - loc.Longitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - loc.Latitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - } - - return &loc, nil - } -} - -func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse { - return func(rd *proto.Reader, n int64) (interface{}, error) { - locs := make([]GeoLocation, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(newGeoLocationParser(q)) - if err != nil { - return nil, err - } - switch vv := v.(type) { - case string: - locs = append(locs, GeoLocation{ - Name: vv, - }) - case *GeoLocation: - locs = append(locs, *vv) - default: - return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) - } - } - return locs, nil - } -} - -//------------------------------------------------------------------------------ - -type GeoPos struct { - Longitude, Latitude float64 -} - -type GeoPosCmd struct { - baseCmd - - positions []*GeoPos -} - -var _ Cmder = (*GeoPosCmd)(nil) - -func NewGeoPosCmd(args ...interface{}) *GeoPosCmd { - return &GeoPosCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *GeoPosCmd) Val() []*GeoPos { - return cmd.positions -} - -func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *GeoPosCmd) String() string { - return cmdString(cmd, cmd.positions) -} - -func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(geoPosSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.positions = v.([]*GeoPos) - return nil -} - -func geoPosSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - positions := make([]*GeoPos, 0, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(geoPosParser) - if err != nil { - if err == Nil { - positions = append(positions, nil) - continue - } - return nil, err - } - switch v := v.(type) { - case *GeoPos: - positions = append(positions, v) - default: - return nil, fmt.Errorf("got %T, expected *GeoPos", v) - } - } - return positions, nil -} - -func geoPosParser(rd *proto.Reader, n int64) (interface{}, error) { - var pos GeoPos - var err error - - pos.Longitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - - pos.Latitude, err = rd.ReadFloatReply() - if err != nil { - return nil, err - } - - return &pos, nil -} - -//------------------------------------------------------------------------------ - -type CommandInfo struct { - Name string - Arity int8 - Flags []string - FirstKeyPos int8 - LastKeyPos int8 - StepCount int8 - ReadOnly bool -} - -type CommandsInfoCmd struct { - baseCmd - - val map[string]*CommandInfo -} - -var _ Cmder = (*CommandsInfoCmd)(nil) - -func NewCommandsInfoCmd(args ...interface{}) *CommandsInfoCmd { - return &CommandsInfoCmd{ - baseCmd: baseCmd{_args: args}, - } -} - -func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo { - return cmd.val -} - -func (cmd *CommandsInfoCmd) Result() (map[string]*CommandInfo, error) { - return cmd.Val(), cmd.Err() -} - -func (cmd *CommandsInfoCmd) String() string { - return cmdString(cmd, cmd.val) -} - -func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error { - var v interface{} - v, cmd.err = rd.ReadArrayReply(commandInfoSliceParser) - if cmd.err != nil { - return cmd.err - } - cmd.val = v.(map[string]*CommandInfo) - return nil -} - -// Implements proto.MultiBulkParse -func commandInfoSliceParser(rd *proto.Reader, n int64) (interface{}, error) { - m := make(map[string]*CommandInfo, n) - for i := int64(0); i < n; i++ { - v, err := rd.ReadReply(commandInfoParser) - if err != nil { - return nil, err - } - vv := v.(*CommandInfo) - m[vv.Name] = vv - - } - return m, nil -} - -func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { - var cmd CommandInfo - var err error - - if n != 6 { - return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6", n) - } - - cmd.Name, err = rd.ReadString() - if err != nil { - return nil, err - } - - arity, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.Arity = int8(arity) - - flags, err := rd.ReadReply(stringSliceParser) - if err != nil { - return nil, err - } - cmd.Flags = flags.([]string) - - firstKeyPos, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.FirstKeyPos = int8(firstKeyPos) - - lastKeyPos, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.LastKeyPos = int8(lastKeyPos) - - stepCount, err := rd.ReadIntReply() - if err != nil { - return nil, err - } - cmd.StepCount = int8(stepCount) - - for _, flag := range cmd.Flags { - if flag == "readonly" { - cmd.ReadOnly = true - break - } - } - - return &cmd, nil -} - -//------------------------------------------------------------------------------ - -type cmdsInfoCache struct { - fn func() (map[string]*CommandInfo, error) - - once internal.Once - cmds map[string]*CommandInfo -} - -func newCmdsInfoCache(fn func() (map[string]*CommandInfo, error)) *cmdsInfoCache { - return &cmdsInfoCache{ - fn: fn, - } -} - -func (c *cmdsInfoCache) Get() (map[string]*CommandInfo, error) { - err := c.once.Do(func() error { - cmds, err := c.fn() - if err != nil { - return err - } - c.cmds = cmds - return nil - }) - return c.cmds, err -} diff --git a/vendor/github.com/go-redis/redis/commands.go b/vendor/github.com/go-redis/redis/commands.go deleted file mode 100644 index 653e4abe96..0000000000 --- a/vendor/github.com/go-redis/redis/commands.go +++ /dev/null @@ -1,2583 +0,0 @@ -package redis - -import ( - "errors" - "io" - "time" - - "github.com/go-redis/redis/internal" -) - -func usePrecise(dur time.Duration) bool { - return dur < time.Second || dur%time.Second != 0 -} - -func formatMs(dur time.Duration) int64 { - if dur > 0 && dur < time.Millisecond { - internal.Logf( - "specified duration is %s, but minimal supported value is %s", - dur, time.Millisecond, - ) - } - return int64(dur / time.Millisecond) -} - -func formatSec(dur time.Duration) int64 { - if dur > 0 && dur < time.Second { - internal.Logf( - "specified duration is %s, but minimal supported value is %s", - dur, time.Second, - ) - } - return int64(dur / time.Second) -} - -func appendArgs(dst, src []interface{}) []interface{} { - if len(src) == 1 { - if ss, ok := src[0].([]string); ok { - for _, s := range ss { - dst = append(dst, s) - } - return dst - } - } - - for _, v := range src { - dst = append(dst, v) - } - return dst -} - -type Cmdable interface { - Pipeline() Pipeliner - Pipelined(fn func(Pipeliner) error) ([]Cmder, error) - - TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) - TxPipeline() Pipeliner - - Command() *CommandsInfoCmd - ClientGetName() *StringCmd - Echo(message interface{}) *StringCmd - Ping() *StatusCmd - Quit() *StatusCmd - Del(keys ...string) *IntCmd - Unlink(keys ...string) *IntCmd - Dump(key string) *StringCmd - Exists(keys ...string) *IntCmd - Expire(key string, expiration time.Duration) *BoolCmd - ExpireAt(key string, tm time.Time) *BoolCmd - Keys(pattern string) *StringSliceCmd - Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd - Move(key string, db int64) *BoolCmd - ObjectRefCount(key string) *IntCmd - ObjectEncoding(key string) *StringCmd - ObjectIdleTime(key string) *DurationCmd - Persist(key string) *BoolCmd - PExpire(key string, expiration time.Duration) *BoolCmd - PExpireAt(key string, tm time.Time) *BoolCmd - PTTL(key string) *DurationCmd - RandomKey() *StringCmd - Rename(key, newkey string) *StatusCmd - RenameNX(key, newkey string) *BoolCmd - Restore(key string, ttl time.Duration, value string) *StatusCmd - RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd - Sort(key string, sort *Sort) *StringSliceCmd - SortStore(key, store string, sort *Sort) *IntCmd - SortInterfaces(key string, sort *Sort) *SliceCmd - Touch(keys ...string) *IntCmd - TTL(key string) *DurationCmd - Type(key string) *StatusCmd - Scan(cursor uint64, match string, count int64) *ScanCmd - SScan(key string, cursor uint64, match string, count int64) *ScanCmd - HScan(key string, cursor uint64, match string, count int64) *ScanCmd - ZScan(key string, cursor uint64, match string, count int64) *ScanCmd - Append(key, value string) *IntCmd - BitCount(key string, bitCount *BitCount) *IntCmd - BitOpAnd(destKey string, keys ...string) *IntCmd - BitOpOr(destKey string, keys ...string) *IntCmd - BitOpXor(destKey string, keys ...string) *IntCmd - BitOpNot(destKey string, key string) *IntCmd - BitPos(key string, bit int64, pos ...int64) *IntCmd - Decr(key string) *IntCmd - DecrBy(key string, decrement int64) *IntCmd - Get(key string) *StringCmd - GetBit(key string, offset int64) *IntCmd - GetRange(key string, start, end int64) *StringCmd - GetSet(key string, value interface{}) *StringCmd - Incr(key string) *IntCmd - IncrBy(key string, value int64) *IntCmd - IncrByFloat(key string, value float64) *FloatCmd - MGet(keys ...string) *SliceCmd - MSet(pairs ...interface{}) *StatusCmd - MSetNX(pairs ...interface{}) *BoolCmd - Set(key string, value interface{}, expiration time.Duration) *StatusCmd - SetBit(key string, offset int64, value int) *IntCmd - SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd - SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd - SetRange(key string, offset int64, value string) *IntCmd - StrLen(key string) *IntCmd - HDel(key string, fields ...string) *IntCmd - HExists(key, field string) *BoolCmd - HGet(key, field string) *StringCmd - HGetAll(key string) *StringStringMapCmd - HIncrBy(key, field string, incr int64) *IntCmd - HIncrByFloat(key, field string, incr float64) *FloatCmd - HKeys(key string) *StringSliceCmd - HLen(key string) *IntCmd - HMGet(key string, fields ...string) *SliceCmd - HMSet(key string, fields map[string]interface{}) *StatusCmd - HSet(key, field string, value interface{}) *BoolCmd - HSetNX(key, field string, value interface{}) *BoolCmd - HVals(key string) *StringSliceCmd - BLPop(timeout time.Duration, keys ...string) *StringSliceCmd - BRPop(timeout time.Duration, keys ...string) *StringSliceCmd - BRPopLPush(source, destination string, timeout time.Duration) *StringCmd - LIndex(key string, index int64) *StringCmd - LInsert(key, op string, pivot, value interface{}) *IntCmd - LInsertBefore(key string, pivot, value interface{}) *IntCmd - LInsertAfter(key string, pivot, value interface{}) *IntCmd - LLen(key string) *IntCmd - LPop(key string) *StringCmd - LPush(key string, values ...interface{}) *IntCmd - LPushX(key string, value interface{}) *IntCmd - LRange(key string, start, stop int64) *StringSliceCmd - LRem(key string, count int64, value interface{}) *IntCmd - LSet(key string, index int64, value interface{}) *StatusCmd - LTrim(key string, start, stop int64) *StatusCmd - RPop(key string) *StringCmd - RPopLPush(source, destination string) *StringCmd - RPush(key string, values ...interface{}) *IntCmd - RPushX(key string, value interface{}) *IntCmd - SAdd(key string, members ...interface{}) *IntCmd - SCard(key string) *IntCmd - SDiff(keys ...string) *StringSliceCmd - SDiffStore(destination string, keys ...string) *IntCmd - SInter(keys ...string) *StringSliceCmd - SInterStore(destination string, keys ...string) *IntCmd - SIsMember(key string, member interface{}) *BoolCmd - SMembers(key string) *StringSliceCmd - SMembersMap(key string) *StringStructMapCmd - SMove(source, destination string, member interface{}) *BoolCmd - SPop(key string) *StringCmd - SPopN(key string, count int64) *StringSliceCmd - SRandMember(key string) *StringCmd - SRandMemberN(key string, count int64) *StringSliceCmd - SRem(key string, members ...interface{}) *IntCmd - SUnion(keys ...string) *StringSliceCmd - SUnionStore(destination string, keys ...string) *IntCmd - XAdd(a *XAddArgs) *StringCmd - XDel(stream string, ids ...string) *IntCmd - XLen(stream string) *IntCmd - XRange(stream, start, stop string) *XMessageSliceCmd - XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd - XRevRange(stream string, start, stop string) *XMessageSliceCmd - XRevRangeN(stream string, start, stop string, count int64) *XMessageSliceCmd - XRead(a *XReadArgs) *XStreamSliceCmd - XReadStreams(streams ...string) *XStreamSliceCmd - XGroupCreate(stream, group, start string) *StatusCmd - XGroupCreateMkStream(stream, group, start string) *StatusCmd - XGroupSetID(stream, group, start string) *StatusCmd - XGroupDestroy(stream, group string) *IntCmd - XGroupDelConsumer(stream, group, consumer string) *IntCmd - XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd - XAck(stream, group string, ids ...string) *IntCmd - XPending(stream, group string) *XPendingCmd - XPendingExt(a *XPendingExtArgs) *XPendingExtCmd - XClaim(a *XClaimArgs) *XMessageSliceCmd - XClaimJustID(a *XClaimArgs) *StringSliceCmd - XTrim(key string, maxLen int64) *IntCmd - XTrimApprox(key string, maxLen int64) *IntCmd - BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd - BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd - ZAdd(key string, members ...Z) *IntCmd - ZAddNX(key string, members ...Z) *IntCmd - ZAddXX(key string, members ...Z) *IntCmd - ZAddCh(key string, members ...Z) *IntCmd - ZAddNXCh(key string, members ...Z) *IntCmd - ZAddXXCh(key string, members ...Z) *IntCmd - ZIncr(key string, member Z) *FloatCmd - ZIncrNX(key string, member Z) *FloatCmd - ZIncrXX(key string, member Z) *FloatCmd - ZCard(key string) *IntCmd - ZCount(key, min, max string) *IntCmd - ZLexCount(key, min, max string) *IntCmd - ZIncrBy(key string, increment float64, member string) *FloatCmd - ZInterStore(destination string, store ZStore, keys ...string) *IntCmd - ZPopMax(key string, count ...int64) *ZSliceCmd - ZPopMin(key string, count ...int64) *ZSliceCmd - ZRange(key string, start, stop int64) *StringSliceCmd - ZRangeWithScores(key string, start, stop int64) *ZSliceCmd - ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd - ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd - ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd - ZRank(key, member string) *IntCmd - ZRem(key string, members ...interface{}) *IntCmd - ZRemRangeByRank(key string, start, stop int64) *IntCmd - ZRemRangeByScore(key, min, max string) *IntCmd - ZRemRangeByLex(key, min, max string) *IntCmd - ZRevRange(key string, start, stop int64) *StringSliceCmd - ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd - ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd - ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd - ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd - ZRevRank(key, member string) *IntCmd - ZScore(key, member string) *FloatCmd - ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd - PFAdd(key string, els ...interface{}) *IntCmd - PFCount(keys ...string) *IntCmd - PFMerge(dest string, keys ...string) *StatusCmd - BgRewriteAOF() *StatusCmd - BgSave() *StatusCmd - ClientKill(ipPort string) *StatusCmd - ClientKillByFilter(keys ...string) *IntCmd - ClientList() *StringCmd - ClientPause(dur time.Duration) *BoolCmd - ClientID() *IntCmd - ConfigGet(parameter string) *SliceCmd - ConfigResetStat() *StatusCmd - ConfigSet(parameter, value string) *StatusCmd - ConfigRewrite() *StatusCmd - DBSize() *IntCmd - FlushAll() *StatusCmd - FlushAllAsync() *StatusCmd - FlushDB() *StatusCmd - FlushDBAsync() *StatusCmd - Info(section ...string) *StringCmd - LastSave() *IntCmd - Save() *StatusCmd - Shutdown() *StatusCmd - ShutdownSave() *StatusCmd - ShutdownNoSave() *StatusCmd - SlaveOf(host, port string) *StatusCmd - Time() *TimeCmd - Eval(script string, keys []string, args ...interface{}) *Cmd - EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd - ScriptExists(hashes ...string) *BoolSliceCmd - ScriptFlush() *StatusCmd - ScriptKill() *StatusCmd - ScriptLoad(script string) *StringCmd - DebugObject(key string) *StringCmd - Publish(channel string, message interface{}) *IntCmd - PubSubChannels(pattern string) *StringSliceCmd - PubSubNumSub(channels ...string) *StringIntMapCmd - PubSubNumPat() *IntCmd - ClusterSlots() *ClusterSlotsCmd - ClusterNodes() *StringCmd - ClusterMeet(host, port string) *StatusCmd - ClusterForget(nodeID string) *StatusCmd - ClusterReplicate(nodeID string) *StatusCmd - ClusterResetSoft() *StatusCmd - ClusterResetHard() *StatusCmd - ClusterInfo() *StringCmd - ClusterKeySlot(key string) *IntCmd - ClusterGetKeysInSlot(slot int, count int) *StringSliceCmd - ClusterCountFailureReports(nodeID string) *IntCmd - ClusterCountKeysInSlot(slot int) *IntCmd - ClusterDelSlots(slots ...int) *StatusCmd - ClusterDelSlotsRange(min, max int) *StatusCmd - ClusterSaveConfig() *StatusCmd - ClusterSlaves(nodeID string) *StringSliceCmd - ClusterFailover() *StatusCmd - ClusterAddSlots(slots ...int) *StatusCmd - ClusterAddSlotsRange(min, max int) *StatusCmd - GeoAdd(key string, geoLocation ...*GeoLocation) *IntCmd - GeoPos(key string, members ...string) *GeoPosCmd - GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusRO(key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusByMember(key, member string, query *GeoRadiusQuery) *GeoLocationCmd - GeoRadiusByMemberRO(key, member string, query *GeoRadiusQuery) *GeoLocationCmd - GeoDist(key string, member1, member2, unit string) *FloatCmd - GeoHash(key string, members ...string) *StringSliceCmd - ReadOnly() *StatusCmd - ReadWrite() *StatusCmd - MemoryUsage(key string, samples ...int) *IntCmd -} - -type StatefulCmdable interface { - Cmdable - Auth(password string) *StatusCmd - Select(index int) *StatusCmd - SwapDB(index1, index2 int) *StatusCmd - ClientSetName(name string) *BoolCmd -} - -var _ Cmdable = (*Client)(nil) -var _ Cmdable = (*Tx)(nil) -var _ Cmdable = (*Ring)(nil) -var _ Cmdable = (*ClusterClient)(nil) - -type cmdable struct { - process func(cmd Cmder) error -} - -func (c *cmdable) setProcessor(fn func(Cmder) error) { - c.process = fn -} - -type statefulCmdable struct { - cmdable - process func(cmd Cmder) error -} - -func (c *statefulCmdable) setProcessor(fn func(Cmder) error) { - c.process = fn - c.cmdable.setProcessor(fn) -} - -//------------------------------------------------------------------------------ - -func (c *statefulCmdable) Auth(password string) *StatusCmd { - cmd := NewStatusCmd("auth", password) - c.process(cmd) - return cmd -} - -func (c *cmdable) Echo(message interface{}) *StringCmd { - cmd := NewStringCmd("echo", message) - c.process(cmd) - return cmd -} - -func (c *cmdable) Ping() *StatusCmd { - cmd := NewStatusCmd("ping") - c.process(cmd) - return cmd -} - -func (c *cmdable) Wait(numSlaves int, timeout time.Duration) *IntCmd { - cmd := NewIntCmd("wait", numSlaves, int(timeout/time.Millisecond)) - c.process(cmd) - return cmd -} - -func (c *cmdable) Quit() *StatusCmd { - panic("not implemented") -} - -func (c *statefulCmdable) Select(index int) *StatusCmd { - cmd := NewStatusCmd("select", index) - c.process(cmd) - return cmd -} - -func (c *statefulCmdable) SwapDB(index1, index2 int) *StatusCmd { - cmd := NewStatusCmd("swapdb", index1, index2) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) Command() *CommandsInfoCmd { - cmd := NewCommandsInfoCmd("command") - c.process(cmd) - return cmd -} - -func (c *cmdable) Del(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "del" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Unlink(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "unlink" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Dump(key string) *StringCmd { - cmd := NewStringCmd("dump", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Exists(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "exists" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Expire(key string, expiration time.Duration) *BoolCmd { - cmd := NewBoolCmd("expire", key, formatSec(expiration)) - c.process(cmd) - return cmd -} - -func (c *cmdable) ExpireAt(key string, tm time.Time) *BoolCmd { - cmd := NewBoolCmd("expireat", key, tm.Unix()) - c.process(cmd) - return cmd -} - -func (c *cmdable) Keys(pattern string) *StringSliceCmd { - cmd := NewStringSliceCmd("keys", pattern) - c.process(cmd) - return cmd -} - -func (c *cmdable) Migrate(host, port, key string, db int64, timeout time.Duration) *StatusCmd { - cmd := NewStatusCmd( - "migrate", - host, - port, - key, - db, - formatMs(timeout), - ) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) Move(key string, db int64) *BoolCmd { - cmd := NewBoolCmd("move", key, db) - c.process(cmd) - return cmd -} - -func (c *cmdable) ObjectRefCount(key string) *IntCmd { - cmd := NewIntCmd("object", "refcount", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) ObjectEncoding(key string) *StringCmd { - cmd := NewStringCmd("object", "encoding", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) ObjectIdleTime(key string) *DurationCmd { - cmd := NewDurationCmd(time.Second, "object", "idletime", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Persist(key string) *BoolCmd { - cmd := NewBoolCmd("persist", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) PExpire(key string, expiration time.Duration) *BoolCmd { - cmd := NewBoolCmd("pexpire", key, formatMs(expiration)) - c.process(cmd) - return cmd -} - -func (c *cmdable) PExpireAt(key string, tm time.Time) *BoolCmd { - cmd := NewBoolCmd( - "pexpireat", - key, - tm.UnixNano()/int64(time.Millisecond), - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) PTTL(key string) *DurationCmd { - cmd := NewDurationCmd(time.Millisecond, "pttl", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) RandomKey() *StringCmd { - cmd := NewStringCmd("randomkey") - c.process(cmd) - return cmd -} - -func (c *cmdable) Rename(key, newkey string) *StatusCmd { - cmd := NewStatusCmd("rename", key, newkey) - c.process(cmd) - return cmd -} - -func (c *cmdable) RenameNX(key, newkey string) *BoolCmd { - cmd := NewBoolCmd("renamenx", key, newkey) - c.process(cmd) - return cmd -} - -func (c *cmdable) Restore(key string, ttl time.Duration, value string) *StatusCmd { - cmd := NewStatusCmd( - "restore", - key, - formatMs(ttl), - value, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd { - cmd := NewStatusCmd( - "restore", - key, - formatMs(ttl), - value, - "replace", - ) - c.process(cmd) - return cmd -} - -type Sort struct { - By string - Offset, Count int64 - Get []string - Order string - Alpha bool -} - -func (sort *Sort) args(key string) []interface{} { - args := []interface{}{"sort", key} - if sort.By != "" { - args = append(args, "by", sort.By) - } - if sort.Offset != 0 || sort.Count != 0 { - args = append(args, "limit", sort.Offset, sort.Count) - } - for _, get := range sort.Get { - args = append(args, "get", get) - } - if sort.Order != "" { - args = append(args, sort.Order) - } - if sort.Alpha { - args = append(args, "alpha") - } - return args -} - -func (c *cmdable) Sort(key string, sort *Sort) *StringSliceCmd { - cmd := NewStringSliceCmd(sort.args(key)...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SortStore(key, store string, sort *Sort) *IntCmd { - args := sort.args(key) - if store != "" { - args = append(args, "store", store) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SortInterfaces(key string, sort *Sort) *SliceCmd { - cmd := NewSliceCmd(sort.args(key)...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Touch(keys ...string) *IntCmd { - args := make([]interface{}, len(keys)+1) - args[0] = "touch" - for i, key := range keys { - args[i+1] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) TTL(key string) *DurationCmd { - cmd := NewDurationCmd(time.Second, "ttl", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Type(key string) *StatusCmd { - cmd := NewStatusCmd("type", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) Scan(cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"scan", cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SScan(key string, cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"sscan", key, cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HScan(key string, cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"hscan", key, cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZScan(key string, cursor uint64, match string, count int64) *ScanCmd { - args := []interface{}{"zscan", key, cursor} - if match != "" { - args = append(args, "match", match) - } - if count > 0 { - args = append(args, "count", count) - } - cmd := NewScanCmd(c.process, args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) Append(key, value string) *IntCmd { - cmd := NewIntCmd("append", key, value) - c.process(cmd) - return cmd -} - -type BitCount struct { - Start, End int64 -} - -func (c *cmdable) BitCount(key string, bitCount *BitCount) *IntCmd { - args := []interface{}{"bitcount", key} - if bitCount != nil { - args = append( - args, - bitCount.Start, - bitCount.End, - ) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) bitOp(op, destKey string, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) - args[0] = "bitop" - args[1] = op - args[2] = destKey - for i, key := range keys { - args[3+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) BitOpAnd(destKey string, keys ...string) *IntCmd { - return c.bitOp("and", destKey, keys...) -} - -func (c *cmdable) BitOpOr(destKey string, keys ...string) *IntCmd { - return c.bitOp("or", destKey, keys...) -} - -func (c *cmdable) BitOpXor(destKey string, keys ...string) *IntCmd { - return c.bitOp("xor", destKey, keys...) -} - -func (c *cmdable) BitOpNot(destKey string, key string) *IntCmd { - return c.bitOp("not", destKey, key) -} - -func (c *cmdable) BitPos(key string, bit int64, pos ...int64) *IntCmd { - args := make([]interface{}, 3+len(pos)) - args[0] = "bitpos" - args[1] = key - args[2] = bit - switch len(pos) { - case 0: - case 1: - args[3] = pos[0] - case 2: - args[3] = pos[0] - args[4] = pos[1] - default: - panic("too many arguments") - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) Decr(key string) *IntCmd { - cmd := NewIntCmd("decr", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) DecrBy(key string, decrement int64) *IntCmd { - cmd := NewIntCmd("decrby", key, decrement) - c.process(cmd) - return cmd -} - -// Redis `GET key` command. It returns redis.Nil error when key does not exist. -func (c *cmdable) Get(key string) *StringCmd { - cmd := NewStringCmd("get", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) GetBit(key string, offset int64) *IntCmd { - cmd := NewIntCmd("getbit", key, offset) - c.process(cmd) - return cmd -} - -func (c *cmdable) GetRange(key string, start, end int64) *StringCmd { - cmd := NewStringCmd("getrange", key, start, end) - c.process(cmd) - return cmd -} - -func (c *cmdable) GetSet(key string, value interface{}) *StringCmd { - cmd := NewStringCmd("getset", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) Incr(key string) *IntCmd { - cmd := NewIntCmd("incr", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) IncrBy(key string, value int64) *IntCmd { - cmd := NewIntCmd("incrby", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) IncrByFloat(key string, value float64) *FloatCmd { - cmd := NewFloatCmd("incrbyfloat", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) MGet(keys ...string) *SliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "mget" - for i, key := range keys { - args[1+i] = key - } - cmd := NewSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) MSet(pairs ...interface{}) *StatusCmd { - args := make([]interface{}, 1, 1+len(pairs)) - args[0] = "mset" - args = appendArgs(args, pairs) - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) MSetNX(pairs ...interface{}) *BoolCmd { - args := make([]interface{}, 1, 1+len(pairs)) - args[0] = "msetnx" - args = appendArgs(args, pairs) - cmd := NewBoolCmd(args...) - c.process(cmd) - return cmd -} - -// Redis `SET key value [expiration]` command. -// -// Use expiration for `SETEX`-like behavior. -// Zero expiration means the key has no expiration time. -func (c *cmdable) Set(key string, value interface{}, expiration time.Duration) *StatusCmd { - args := make([]interface{}, 3, 4) - args[0] = "set" - args[1] = key - args[2] = value - if expiration > 0 { - if usePrecise(expiration) { - args = append(args, "px", formatMs(expiration)) - } else { - args = append(args, "ex", formatSec(expiration)) - } - } - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SetBit(key string, offset int64, value int) *IntCmd { - cmd := NewIntCmd( - "setbit", - key, - offset, - value, - ) - c.process(cmd) - return cmd -} - -// Redis `SET key value [expiration] NX` command. -// -// Zero expiration means the key has no expiration time. -func (c *cmdable) SetNX(key string, value interface{}, expiration time.Duration) *BoolCmd { - var cmd *BoolCmd - if expiration == 0 { - // Use old `SETNX` to support old Redis versions. - cmd = NewBoolCmd("setnx", key, value) - } else { - if usePrecise(expiration) { - cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "nx") - } else { - cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "nx") - } - } - c.process(cmd) - return cmd -} - -// Redis `SET key value [expiration] XX` command. -// -// Zero expiration means the key has no expiration time. -func (c *cmdable) SetXX(key string, value interface{}, expiration time.Duration) *BoolCmd { - var cmd *BoolCmd - if expiration == 0 { - cmd = NewBoolCmd("set", key, value, "xx") - } else { - if usePrecise(expiration) { - cmd = NewBoolCmd("set", key, value, "px", formatMs(expiration), "xx") - } else { - cmd = NewBoolCmd("set", key, value, "ex", formatSec(expiration), "xx") - } - } - c.process(cmd) - return cmd -} - -func (c *cmdable) SetRange(key string, offset int64, value string) *IntCmd { - cmd := NewIntCmd("setrange", key, offset, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) StrLen(key string) *IntCmd { - cmd := NewIntCmd("strlen", key) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) HDel(key string, fields ...string) *IntCmd { - args := make([]interface{}, 2+len(fields)) - args[0] = "hdel" - args[1] = key - for i, field := range fields { - args[2+i] = field - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HExists(key, field string) *BoolCmd { - cmd := NewBoolCmd("hexists", key, field) - c.process(cmd) - return cmd -} - -func (c *cmdable) HGet(key, field string) *StringCmd { - cmd := NewStringCmd("hget", key, field) - c.process(cmd) - return cmd -} - -func (c *cmdable) HGetAll(key string) *StringStringMapCmd { - cmd := NewStringStringMapCmd("hgetall", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) HIncrBy(key, field string, incr int64) *IntCmd { - cmd := NewIntCmd("hincrby", key, field, incr) - c.process(cmd) - return cmd -} - -func (c *cmdable) HIncrByFloat(key, field string, incr float64) *FloatCmd { - cmd := NewFloatCmd("hincrbyfloat", key, field, incr) - c.process(cmd) - return cmd -} - -func (c *cmdable) HKeys(key string) *StringSliceCmd { - cmd := NewStringSliceCmd("hkeys", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) HLen(key string) *IntCmd { - cmd := NewIntCmd("hlen", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) HMGet(key string, fields ...string) *SliceCmd { - args := make([]interface{}, 2+len(fields)) - args[0] = "hmget" - args[1] = key - for i, field := range fields { - args[2+i] = field - } - cmd := NewSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HMSet(key string, fields map[string]interface{}) *StatusCmd { - args := make([]interface{}, 2+len(fields)*2) - args[0] = "hmset" - args[1] = key - i := 2 - for k, v := range fields { - args[i] = k - args[i+1] = v - i += 2 - } - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) HSet(key, field string, value interface{}) *BoolCmd { - cmd := NewBoolCmd("hset", key, field, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) HSetNX(key, field string, value interface{}) *BoolCmd { - cmd := NewBoolCmd("hsetnx", key, field, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) HVals(key string) *StringSliceCmd { - cmd := NewStringSliceCmd("hvals", key) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) BLPop(timeout time.Duration, keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "blpop" - for i, key := range keys { - args[1+i] = key - } - args[len(args)-1] = formatSec(timeout) - cmd := NewStringSliceCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) BRPop(timeout time.Duration, keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "brpop" - for i, key := range keys { - args[1+i] = key - } - args[len(keys)+1] = formatSec(timeout) - cmd := NewStringSliceCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) BRPopLPush(source, destination string, timeout time.Duration) *StringCmd { - cmd := NewStringCmd( - "brpoplpush", - source, - destination, - formatSec(timeout), - ) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) LIndex(key string, index int64) *StringCmd { - cmd := NewStringCmd("lindex", key, index) - c.process(cmd) - return cmd -} - -func (c *cmdable) LInsert(key, op string, pivot, value interface{}) *IntCmd { - cmd := NewIntCmd("linsert", key, op, pivot, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LInsertBefore(key string, pivot, value interface{}) *IntCmd { - cmd := NewIntCmd("linsert", key, "before", pivot, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LInsertAfter(key string, pivot, value interface{}) *IntCmd { - cmd := NewIntCmd("linsert", key, "after", pivot, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LLen(key string) *IntCmd { - cmd := NewIntCmd("llen", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) LPop(key string) *StringCmd { - cmd := NewStringCmd("lpop", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) LPush(key string, values ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(values)) - args[0] = "lpush" - args[1] = key - args = appendArgs(args, values) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) LPushX(key string, value interface{}) *IntCmd { - cmd := NewIntCmd("lpushx", key, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LRange(key string, start, stop int64) *StringSliceCmd { - cmd := NewStringSliceCmd( - "lrange", - key, - start, - stop, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) LRem(key string, count int64, value interface{}) *IntCmd { - cmd := NewIntCmd("lrem", key, count, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LSet(key string, index int64, value interface{}) *StatusCmd { - cmd := NewStatusCmd("lset", key, index, value) - c.process(cmd) - return cmd -} - -func (c *cmdable) LTrim(key string, start, stop int64) *StatusCmd { - cmd := NewStatusCmd( - "ltrim", - key, - start, - stop, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPop(key string) *StringCmd { - cmd := NewStringCmd("rpop", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPopLPush(source, destination string) *StringCmd { - cmd := NewStringCmd("rpoplpush", source, destination) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPush(key string, values ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(values)) - args[0] = "rpush" - args[1] = key - args = appendArgs(args, values) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) RPushX(key string, value interface{}) *IntCmd { - cmd := NewIntCmd("rpushx", key, value) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) SAdd(key string, members ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(members)) - args[0] = "sadd" - args[1] = key - args = appendArgs(args, members) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SCard(key string) *IntCmd { - cmd := NewIntCmd("scard", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) SDiff(keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "sdiff" - for i, key := range keys { - args[1+i] = key - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SDiffStore(destination string, keys ...string) *IntCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "sdiffstore" - args[1] = destination - for i, key := range keys { - args[2+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SInter(keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "sinter" - for i, key := range keys { - args[1+i] = key - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SInterStore(destination string, keys ...string) *IntCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "sinterstore" - args[1] = destination - for i, key := range keys { - args[2+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SIsMember(key string, member interface{}) *BoolCmd { - cmd := NewBoolCmd("sismember", key, member) - c.process(cmd) - return cmd -} - -// Redis `SMEMBERS key` command output as a slice -func (c *cmdable) SMembers(key string) *StringSliceCmd { - cmd := NewStringSliceCmd("smembers", key) - c.process(cmd) - return cmd -} - -// Redis `SMEMBERS key` command output as a map -func (c *cmdable) SMembersMap(key string) *StringStructMapCmd { - cmd := NewStringStructMapCmd("smembers", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) SMove(source, destination string, member interface{}) *BoolCmd { - cmd := NewBoolCmd("smove", source, destination, member) - c.process(cmd) - return cmd -} - -// Redis `SPOP key` command. -func (c *cmdable) SPop(key string) *StringCmd { - cmd := NewStringCmd("spop", key) - c.process(cmd) - return cmd -} - -// Redis `SPOP key count` command. -func (c *cmdable) SPopN(key string, count int64) *StringSliceCmd { - cmd := NewStringSliceCmd("spop", key, count) - c.process(cmd) - return cmd -} - -// Redis `SRANDMEMBER key` command. -func (c *cmdable) SRandMember(key string) *StringCmd { - cmd := NewStringCmd("srandmember", key) - c.process(cmd) - return cmd -} - -// Redis `SRANDMEMBER key count` command. -func (c *cmdable) SRandMemberN(key string, count int64) *StringSliceCmd { - cmd := NewStringSliceCmd("srandmember", key, count) - c.process(cmd) - return cmd -} - -func (c *cmdable) SRem(key string, members ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(members)) - args[0] = "srem" - args[1] = key - args = appendArgs(args, members) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SUnion(keys ...string) *StringSliceCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "sunion" - for i, key := range keys { - args[1+i] = key - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) SUnionStore(destination string, keys ...string) *IntCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "sunionstore" - args[1] = destination - for i, key := range keys { - args[2+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -type XAddArgs struct { - Stream string - MaxLen int64 // MAXLEN N - MaxLenApprox int64 // MAXLEN ~ N - ID string - Values map[string]interface{} -} - -func (c *cmdable) XAdd(a *XAddArgs) *StringCmd { - args := make([]interface{}, 0, 6+len(a.Values)*2) - args = append(args, "xadd") - args = append(args, a.Stream) - if a.MaxLen > 0 { - args = append(args, "maxlen", a.MaxLen) - } else if a.MaxLenApprox > 0 { - args = append(args, "maxlen", "~", a.MaxLenApprox) - } - if a.ID != "" { - args = append(args, a.ID) - } else { - args = append(args, "*") - } - for k, v := range a.Values { - args = append(args, k) - args = append(args, v) - } - - cmd := NewStringCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XDel(stream string, ids ...string) *IntCmd { - args := []interface{}{"xdel", stream} - for _, id := range ids { - args = append(args, id) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XLen(stream string) *IntCmd { - cmd := NewIntCmd("xlen", stream) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRange(stream, start, stop string) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrange", stream, start, stop) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrange", stream, start, stop, "count", count) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRevRange(stream, start, stop string) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop) - c.process(cmd) - return cmd -} - -func (c *cmdable) XRevRangeN(stream, start, stop string, count int64) *XMessageSliceCmd { - cmd := NewXMessageSliceCmd("xrevrange", stream, start, stop, "count", count) - c.process(cmd) - return cmd -} - -type XReadArgs struct { - Streams []string - Count int64 - Block time.Duration -} - -func (c *cmdable) XRead(a *XReadArgs) *XStreamSliceCmd { - args := make([]interface{}, 0, 5+len(a.Streams)) - args = append(args, "xread") - if a.Count > 0 { - args = append(args, "count") - args = append(args, a.Count) - } - if a.Block >= 0 { - args = append(args, "block") - args = append(args, int64(a.Block/time.Millisecond)) - } - args = append(args, "streams") - for _, s := range a.Streams { - args = append(args, s) - } - - cmd := NewXStreamSliceCmd(args...) - if a.Block >= 0 { - cmd.setReadTimeout(a.Block) - } - c.process(cmd) - return cmd -} - -func (c *cmdable) XReadStreams(streams ...string) *XStreamSliceCmd { - return c.XRead(&XReadArgs{ - Streams: streams, - Block: -1, - }) -} - -func (c *cmdable) XGroupCreate(stream, group, start string) *StatusCmd { - cmd := NewStatusCmd("xgroup", "create", stream, group, start) - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupCreateMkStream(stream, group, start string) *StatusCmd { - cmd := NewStatusCmd("xgroup", "create", stream, group, start, "mkstream") - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupSetID(stream, group, start string) *StatusCmd { - cmd := NewStatusCmd("xgroup", "setid", stream, group, start) - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupDestroy(stream, group string) *IntCmd { - cmd := NewIntCmd("xgroup", "destroy", stream, group) - c.process(cmd) - return cmd -} - -func (c *cmdable) XGroupDelConsumer(stream, group, consumer string) *IntCmd { - cmd := NewIntCmd("xgroup", "delconsumer", stream, group, consumer) - c.process(cmd) - return cmd -} - -type XReadGroupArgs struct { - Group string - Consumer string - // List of streams and ids. - Streams []string - Count int64 - Block time.Duration - NoAck bool -} - -func (c *cmdable) XReadGroup(a *XReadGroupArgs) *XStreamSliceCmd { - args := make([]interface{}, 0, 8+len(a.Streams)) - args = append(args, "xreadgroup", "group", a.Group, a.Consumer) - if a.Count > 0 { - args = append(args, "count", a.Count) - } - if a.Block >= 0 { - args = append(args, "block", int64(a.Block/time.Millisecond)) - } - if a.NoAck { - args = append(args, "noack") - } - args = append(args, "streams") - for _, s := range a.Streams { - args = append(args, s) - } - - cmd := NewXStreamSliceCmd(args...) - if a.Block >= 0 { - cmd.setReadTimeout(a.Block) - } - c.process(cmd) - return cmd -} - -func (c *cmdable) XAck(stream, group string, ids ...string) *IntCmd { - args := []interface{}{"xack", stream, group} - for _, id := range ids { - args = append(args, id) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XPending(stream, group string) *XPendingCmd { - cmd := NewXPendingCmd("xpending", stream, group) - c.process(cmd) - return cmd -} - -type XPendingExtArgs struct { - Stream string - Group string - Start string - End string - Count int64 - Consumer string -} - -func (c *cmdable) XPendingExt(a *XPendingExtArgs) *XPendingExtCmd { - args := make([]interface{}, 0, 7) - args = append(args, "xpending", a.Stream, a.Group, a.Start, a.End, a.Count) - if a.Consumer != "" { - args = append(args, a.Consumer) - } - cmd := NewXPendingExtCmd(args...) - c.process(cmd) - return cmd -} - -type XClaimArgs struct { - Stream string - Group string - Consumer string - MinIdle time.Duration - Messages []string -} - -func (c *cmdable) XClaim(a *XClaimArgs) *XMessageSliceCmd { - args := xClaimArgs(a) - cmd := NewXMessageSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) XClaimJustID(a *XClaimArgs) *StringSliceCmd { - args := xClaimArgs(a) - args = append(args, "justid") - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func xClaimArgs(a *XClaimArgs) []interface{} { - args := make([]interface{}, 0, 4+len(a.Messages)) - args = append(args, - "xclaim", - a.Stream, - a.Group, a.Consumer, - int64(a.MinIdle/time.Millisecond)) - for _, id := range a.Messages { - args = append(args, id) - } - return args -} - -func (c *cmdable) XTrim(key string, maxLen int64) *IntCmd { - cmd := NewIntCmd("xtrim", key, "maxlen", maxLen) - c.process(cmd) - return cmd -} - -func (c *cmdable) XTrimApprox(key string, maxLen int64) *IntCmd { - cmd := NewIntCmd("xtrim", key, "maxlen", "~", maxLen) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -// Z represents sorted set member. -type Z struct { - Score float64 - Member interface{} -} - -// ZWithKey represents sorted set member including the name of the key where it was popped. -type ZWithKey struct { - Z - Key string -} - -// ZStore is used as an arg to ZInterStore and ZUnionStore. -type ZStore struct { - Weights []float64 - // Can be SUM, MIN or MAX. - Aggregate string -} - -// Redis `BZPOPMAX key [key ...] timeout` command. -func (c *cmdable) BZPopMax(timeout time.Duration, keys ...string) *ZWithKeyCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "bzpopmax" - for i, key := range keys { - args[1+i] = key - } - args[len(args)-1] = formatSec(timeout) - cmd := NewZWithKeyCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -// Redis `BZPOPMIN key [key ...] timeout` command. -func (c *cmdable) BZPopMin(timeout time.Duration, keys ...string) *ZWithKeyCmd { - args := make([]interface{}, 1+len(keys)+1) - args[0] = "bzpopmin" - for i, key := range keys { - args[1+i] = key - } - args[len(args)-1] = formatSec(timeout) - cmd := NewZWithKeyCmd(args...) - cmd.setReadTimeout(timeout) - c.process(cmd) - return cmd -} - -func (c *cmdable) zAdd(a []interface{}, n int, members ...Z) *IntCmd { - for i, m := range members { - a[n+2*i] = m.Score - a[n+2*i+1] = m.Member - } - cmd := NewIntCmd(a...) - c.process(cmd) - return cmd -} - -// Redis `ZADD key score member [score member ...]` command. -func (c *cmdable) ZAdd(key string, members ...Z) *IntCmd { - const n = 2 - a := make([]interface{}, n+2*len(members)) - a[0], a[1] = "zadd", key - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key NX score member [score member ...]` command. -func (c *cmdable) ZAddNX(key string, members ...Z) *IntCmd { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "nx" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key XX score member [score member ...]` command. -func (c *cmdable) ZAddXX(key string, members ...Z) *IntCmd { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "xx" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key CH score member [score member ...]` command. -func (c *cmdable) ZAddCh(key string, members ...Z) *IntCmd { - const n = 3 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2] = "zadd", key, "ch" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key NX CH score member [score member ...]` command. -func (c *cmdable) ZAddNXCh(key string, members ...Z) *IntCmd { - const n = 4 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2], a[3] = "zadd", key, "nx", "ch" - return c.zAdd(a, n, members...) -} - -// Redis `ZADD key XX CH score member [score member ...]` command. -func (c *cmdable) ZAddXXCh(key string, members ...Z) *IntCmd { - const n = 4 - a := make([]interface{}, n+2*len(members)) - a[0], a[1], a[2], a[3] = "zadd", key, "xx", "ch" - return c.zAdd(a, n, members...) -} - -func (c *cmdable) zIncr(a []interface{}, n int, members ...Z) *FloatCmd { - for i, m := range members { - a[n+2*i] = m.Score - a[n+2*i+1] = m.Member - } - cmd := NewFloatCmd(a...) - c.process(cmd) - return cmd -} - -// Redis `ZADD key INCR score member` command. -func (c *cmdable) ZIncr(key string, member Z) *FloatCmd { - const n = 3 - a := make([]interface{}, n+2) - a[0], a[1], a[2] = "zadd", key, "incr" - return c.zIncr(a, n, member) -} - -// Redis `ZADD key NX INCR score member` command. -func (c *cmdable) ZIncrNX(key string, member Z) *FloatCmd { - const n = 4 - a := make([]interface{}, n+2) - a[0], a[1], a[2], a[3] = "zadd", key, "incr", "nx" - return c.zIncr(a, n, member) -} - -// Redis `ZADD key XX INCR score member` command. -func (c *cmdable) ZIncrXX(key string, member Z) *FloatCmd { - const n = 4 - a := make([]interface{}, n+2) - a[0], a[1], a[2], a[3] = "zadd", key, "incr", "xx" - return c.zIncr(a, n, member) -} - -func (c *cmdable) ZCard(key string) *IntCmd { - cmd := NewIntCmd("zcard", key) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZCount(key, min, max string) *IntCmd { - cmd := NewIntCmd("zcount", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZLexCount(key, min, max string) *IntCmd { - cmd := NewIntCmd("zlexcount", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZIncrBy(key string, increment float64, member string) *FloatCmd { - cmd := NewFloatCmd("zincrby", key, increment, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZInterStore(destination string, store ZStore, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) - args[0] = "zinterstore" - args[1] = destination - args[2] = len(keys) - for i, key := range keys { - args[3+i] = key - } - if len(store.Weights) > 0 { - args = append(args, "weights") - for _, weight := range store.Weights { - args = append(args, weight) - } - } - if store.Aggregate != "" { - args = append(args, "aggregate", store.Aggregate) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZPopMax(key string, count ...int64) *ZSliceCmd { - args := []interface{}{ - "zpopmax", - key, - } - - switch len(count) { - case 0: - break - case 1: - args = append(args, count[0]) - default: - panic("too many arguments") - } - - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZPopMin(key string, count ...int64) *ZSliceCmd { - args := []interface{}{ - "zpopmin", - key, - } - - switch len(count) { - case 0: - break - case 1: - args = append(args, count[0]) - default: - panic("too many arguments") - } - - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) zRange(key string, start, stop int64, withScores bool) *StringSliceCmd { - args := []interface{}{ - "zrange", - key, - start, - stop, - } - if withScores { - args = append(args, "withscores") - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRange(key string, start, stop int64) *StringSliceCmd { - return c.zRange(key, start, stop, false) -} - -func (c *cmdable) ZRangeWithScores(key string, start, stop int64) *ZSliceCmd { - cmd := NewZSliceCmd("zrange", key, start, stop, "withscores") - c.process(cmd) - return cmd -} - -type ZRangeBy struct { - Min, Max string - Offset, Count int64 -} - -func (c *cmdable) zRangeBy(zcmd, key string, opt ZRangeBy, withScores bool) *StringSliceCmd { - args := []interface{}{zcmd, key, opt.Min, opt.Max} - if withScores { - args = append(args, "withscores") - } - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRangeBy("zrangebyscore", key, opt, false) -} - -func (c *cmdable) ZRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRangeBy("zrangebylex", key, opt, false) -} - -func (c *cmdable) ZRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { - args := []interface{}{"zrangebyscore", key, opt.Min, opt.Max, "withscores"} - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRank(key, member string) *IntCmd { - cmd := NewIntCmd("zrank", key, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRem(key string, members ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(members)) - args[0] = "zrem" - args[1] = key - args = appendArgs(args, members) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRemRangeByRank(key string, start, stop int64) *IntCmd { - cmd := NewIntCmd( - "zremrangebyrank", - key, - start, - stop, - ) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRemRangeByScore(key, min, max string) *IntCmd { - cmd := NewIntCmd("zremrangebyscore", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRemRangeByLex(key, min, max string) *IntCmd { - cmd := NewIntCmd("zremrangebylex", key, min, max) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRange(key string, start, stop int64) *StringSliceCmd { - cmd := NewStringSliceCmd("zrevrange", key, start, stop) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRangeWithScores(key string, start, stop int64) *ZSliceCmd { - cmd := NewZSliceCmd("zrevrange", key, start, stop, "withscores") - c.process(cmd) - return cmd -} - -func (c *cmdable) zRevRangeBy(zcmd, key string, opt ZRangeBy) *StringSliceCmd { - args := []interface{}{zcmd, key, opt.Max, opt.Min} - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewStringSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRangeByScore(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRevRangeBy("zrevrangebyscore", key, opt) -} - -func (c *cmdable) ZRevRangeByLex(key string, opt ZRangeBy) *StringSliceCmd { - return c.zRevRangeBy("zrevrangebylex", key, opt) -} - -func (c *cmdable) ZRevRangeByScoreWithScores(key string, opt ZRangeBy) *ZSliceCmd { - args := []interface{}{"zrevrangebyscore", key, opt.Max, opt.Min, "withscores"} - if opt.Offset != 0 || opt.Count != 0 { - args = append( - args, - "limit", - opt.Offset, - opt.Count, - ) - } - cmd := NewZSliceCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZRevRank(key, member string) *IntCmd { - cmd := NewIntCmd("zrevrank", key, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZScore(key, member string) *FloatCmd { - cmd := NewFloatCmd("zscore", key, member) - c.process(cmd) - return cmd -} - -func (c *cmdable) ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd { - args := make([]interface{}, 3+len(keys)) - args[0] = "zunionstore" - args[1] = dest - args[2] = len(keys) - for i, key := range keys { - args[3+i] = key - } - if len(store.Weights) > 0 { - args = append(args, "weights") - for _, weight := range store.Weights { - args = append(args, weight) - } - } - if store.Aggregate != "" { - args = append(args, "aggregate", store.Aggregate) - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) PFAdd(key string, els ...interface{}) *IntCmd { - args := make([]interface{}, 2, 2+len(els)) - args[0] = "pfadd" - args[1] = key - args = appendArgs(args, els) - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) PFCount(keys ...string) *IntCmd { - args := make([]interface{}, 1+len(keys)) - args[0] = "pfcount" - for i, key := range keys { - args[1+i] = key - } - cmd := NewIntCmd(args...) - c.process(cmd) - return cmd -} - -func (c *cmdable) PFMerge(dest string, keys ...string) *StatusCmd { - args := make([]interface{}, 2+len(keys)) - args[0] = "pfmerge" - args[1] = dest - for i, key := range keys { - args[2+i] = key - } - cmd := NewStatusCmd(args...) - c.process(cmd) - return cmd -} - -//------------------------------------------------------------------------------ - -func (c *cmdable) BgRewriteAOF() *StatusCmd { - cmd := NewStatusCmd("bgrewriteaof") - c.process(cmd) - return cmd -} - -func (c *cmdable) BgSave() *StatusCmd { - cmd := NewStatusCmd("bgsave") - c.process(cmd) - return cmd -} - -func (c *cmdable) ClientKill(ipPort string) *StatusCmd { - cmd := NewStatusCmd("client", "kill", ipPort) - c.process(cmd) - return cmd -} - -// ClientKillByFilter is new style synx, while the ClientKill is old -// CLIENT KILL