diff --git a/api/mojang/mojang.go b/api/mojang/mojang.go index 1f2f025..323cd95 100644 --- a/api/mojang/mojang.go +++ b/api/mojang/mojang.go @@ -28,6 +28,8 @@ type ProfileInfo struct { IsDemo bool `json:"demo,omitempty"` } +// Exchanges usernames array to array of uuids +// See https://wiki.vg/Mojang_API#Playernames_-.3E_UUIDs func UsernamesToUuids(usernames []string) ([]*ProfileInfo, error) { requestBody, _ := json.Marshal(usernames) request, err := http.NewRequest("POST", "https://api.mojang.com/profiles/minecraft", bytes.NewBuffer(requestBody)) @@ -43,8 +45,8 @@ func UsernamesToUuids(usernames []string) ([]*ProfileInfo, error) { } defer response.Body.Close() - if response.StatusCode == 429 { - return nil, &TooManyRequestsError{} + if responseErr := validateResponse(response); responseErr != nil { + return nil, responseErr } var result []*ProfileInfo @@ -55,6 +57,8 @@ func UsernamesToUuids(usernames []string) ([]*ProfileInfo, error) { return result, nil } +// Obtains textures information for provided uuid +// See https://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape func UuidToTextures(uuid string, signed bool) (*SignedTexturesResponse, error) { url := "https://sessionserver.mojang.com/session/minecraft/profile/" + uuid if signed { @@ -72,8 +76,8 @@ func UuidToTextures(uuid string, signed bool) (*SignedTexturesResponse, error) { } defer response.Body.Close() - if response.StatusCode == 429 { - return nil, &TooManyRequestsError{} + if responseErr := validateResponse(response); responseErr != nil { + return nil, responseErr } var result *SignedTexturesResponse @@ -84,9 +88,30 @@ func UuidToTextures(uuid string, signed bool) (*SignedTexturesResponse, error) { return result, nil } +func validateResponse(response *http.Response) error { + switch response.StatusCode { + case 204: + return &EmptyResponse{} + case 429: + return &TooManyRequestsError{} + } + + return nil +} + +// Mojang API doesn't return a 404 Not Found error for non-existent data identifiers +// Instead, they return 204 with an empty body +type EmptyResponse struct { +} + +func (*EmptyResponse) Error() string { + return "Empty Response" +} + +// When you exceed the set limit of requests, this error will be returned type TooManyRequestsError struct { } -func (e *TooManyRequestsError) Error() string { +func (*TooManyRequestsError) Error() string { return "Too Many Requests" } diff --git a/api/mojang/mojang_test.go b/api/mojang/mojang_test.go index e8a6f14..8bd809d 100644 --- a/api/mojang/mojang_test.go +++ b/api/mojang/mojang_test.go @@ -146,7 +146,27 @@ func TestUuidToTextures(t *testing.T) { } }) - t.Run("handle too many requests error", func(t *testing.T) { + t.Run("handle empty response", func(t *testing.T) { + assert := testify.New(t) + + defer gock.Off() + gock.New("https://sessionserver.mojang.com"). + Get("/session/minecraft/profile/4566e69fc90748ee8d71d7ba5aa00d20"). + Reply(204). + BodyString("") + + client := &http.Client{} + gock.InterceptClient(client) + + HttpClient = client + + result, err := UuidToTextures("4566e69fc90748ee8d71d7ba5aa00d20", false) + assert.Nil(result) + assert.IsType(&EmptyResponse{}, err) + assert.EqualError(err, "Empty Response") + }) + + t.Run("handle too many requests response", func(t *testing.T) { assert := testify.New(t) defer gock.Off() diff --git a/api/mojang/queue/queue.go b/api/mojang/queue/queue.go index 8739c9e..1511718 100644 --- a/api/mojang/queue/queue.go +++ b/api/mojang/queue/queue.go @@ -146,12 +146,12 @@ func (ctx *JobsQueue) getTextures(uuid string) *mojang.SignedTexturesResponse { shouldCache := true result, err := uuidToTextures(uuid, true) - if err != nil { - if _, ok := err.(*mojang.TooManyRequestsError); !ok { - panic(err) - } - + switch err.(type) { + case *mojang.EmptyResponse: + case *mojang.TooManyRequestsError: shouldCache = false + case error: + panic(err) } if shouldCache && result != nil { diff --git a/api/mojang/queue/queue_test.go b/api/mojang/queue/queue_test.go index f288edf..627c3c5 100644 --- a/api/mojang/queue/queue_test.go +++ b/api/mojang/queue/queue_test.go @@ -293,7 +293,7 @@ func (suite *QueueTestSuite) TestDoNothingWhenNoTasks() { suite.Iterate() } -func (suite *QueueTestSuite) TestHandle429ResponseWhenExchangingUsernamesToUuids() { +func (suite *QueueTestSuite) TestHandleTooManyRequestsResponseWhenExchangingUsernamesToUuids() { suite.Storage.On("GetUuid", "maksimkurb").Once().Return("", &ValueNotFound{}) // Storage.StoreUuid, Storage.GetTextures and Storage.StoreTextures shouldn't be called suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return(nil, &mojang.TooManyRequestsError{}) @@ -305,7 +305,27 @@ func (suite *QueueTestSuite) TestHandle429ResponseWhenExchangingUsernamesToUuids suite.Assert().Nil(<-resultChan) } -func (suite *QueueTestSuite) TestHandle429ResponseWhenRequestingUsersTextures() { +func (suite *QueueTestSuite) TestHandleEmptyResponseWhenRequestingUsersTextures() { + suite.Storage.On("GetUuid", "maksimkurb").Once().Return("", &ValueNotFound{}) + suite.Storage.On("StoreUuid", "maksimkurb", "0d252b7218b648bfb86c2ae476954d32").Once() + suite.Storage.On("GetTextures", "0d252b7218b648bfb86c2ae476954d32").Once().Return(nil, &ValueNotFound{}) + // Storage.StoreTextures shouldn't be called + suite.MojangApi.On("UsernameToUuids", []string{"maksimkurb"}).Once().Return([]*mojang.ProfileInfo{ + {Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"}, + }, nil) + suite.MojangApi.On("UuidToTextures", "0d252b7218b648bfb86c2ae476954d32", true).Once().Return( + nil, + &mojang.EmptyResponse{}, + ) + + resultChan := suite.Queue.GetTexturesForUsername("maksimkurb") + + suite.Iterate() + + suite.Assert().Nil(<-resultChan) +} + +func (suite *QueueTestSuite) TestHandleTooManyRequestsResponseWhenRequestingUsersTextures() { suite.Storage.On("GetUuid", "maksimkurb").Once().Return("", &ValueNotFound{}) suite.Storage.On("StoreUuid", "maksimkurb", "0d252b7218b648bfb86c2ae476954d32").Once() suite.Storage.On("GetTextures", "0d252b7218b648bfb86c2ae476954d32").Once().Return(nil, &ValueNotFound{})