diff --git a/client/accounts.go b/client/accounts.go index 438e8cc55..330cc5237 100644 --- a/client/accounts.go +++ b/client/accounts.go @@ -5,7 +5,7 @@ import ( ) type Account interface { - GetAccountChannels(req *account.AccountChannelsRequest) (*account.AccountChannelsResponse, XRPLResponse, error) + GetAccountChannels(req *account.AccountChannelsRequest, params XRPLPaginatedParams) ([]account.AccountChannelsResponse, []XRPLResponse, error) GetAccountInfo(req *account.AccountInfoRequest) (*account.AccountInfoResponse, XRPLResponse, error) } @@ -13,17 +13,37 @@ type accountImpl struct { client Client } -func (a *accountImpl) GetAccountChannels(req *account.AccountChannelsRequest) (*account.AccountChannelsResponse, XRPLResponse, error) { - res, err := a.client.SendRequest(req) +func (a *accountImpl) GetAccountChannels(req *account.AccountChannelsRequest, params XRPLPaginatedParams) ([]account.AccountChannelsResponse, []XRPLResponse, error) { + + err := req.Validate() if err != nil { return nil, nil, err } - var acr account.AccountChannelsResponse - err = res.GetResult(&acr) + + XRPLResponse, err := a.client.SendRequestPaginated(req, params.Limit, params.Paginated) if err != nil { return nil, nil, err } - return &acr, res, nil + + XRPLResponsePages := XRPLResponse.GetXRPLPages() + + acrPages := []account.AccountChannelsResponse{} + + // loop through pages and get result + for _, page := range XRPLResponsePages { + + var acr account.AccountChannelsResponse + + err = page.GetResult(&acr) + if err != nil { + return nil, nil, err + } + + // append result to array + acrPages = append(acrPages, acr) + } + + return acrPages, XRPLResponsePages, nil } func (a *accountImpl) GetAccountInfo(req *account.AccountInfoRequest) (*account.AccountInfoResponse, XRPLResponse, error) { diff --git a/client/accounts_test.go b/client/accounts_test.go index 9e18638b8..b62609b99 100644 --- a/client/accounts_test.go +++ b/client/accounts_test.go @@ -8,18 +8,29 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/xyield/xrpl-go/model/client/account" - "github.com/xyield/xrpl-go/model/client/common" ) type mockClient struct { mock.Mock } +type mockPaginatedResponse struct { + Pages []mockClientXrplResponse +} + +func (r mockPaginatedResponse) GetXRPLPages() []XRPLResponse { + res := make([]XRPLResponse, len(r.Pages)) + for i, page := range r.Pages { + res[i] = page + } + return res +} + type mockClientXrplResponse struct { Result map[string]any } -func (m *mockClientXrplResponse) GetResult(v any) error { +func (m mockClientXrplResponse) GetResult(v any) error { dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{TagName: "json", Result: &v}) if err != nil { return err @@ -31,18 +42,28 @@ func (m *mockClientXrplResponse) GetResult(v any) error { return nil } +func (m mockClientXrplResponse) GetMarker() any { + return nil +} + func (m *mockClient) SendRequest(req XRPLRequest) (XRPLResponse, error) { args := m.Called(req) return args.Get(0).(XRPLResponse), args.Error(1) } +func (m *mockClient) SendRequestPaginated(reqParams XRPLPaginatedRequest, limit int, pagination bool) (XRPLPaginatedResponse, error) { + args := m.Called(reqParams, limit, pagination) + return args.Get(0).(XRPLPaginatedResponse), args.Error(1) +} + func TestGetAccountChannels(t *testing.T) { tt := []struct { description string input account.AccountChannelsRequest - sendRequestResult mockClientXrplResponse - output account.AccountChannelsResponse + paginationParams XRPLPaginatedParams + sendRequestResult mockPaginatedResponse + output []account.AccountChannelsResponse expectedErr error }{ { @@ -51,13 +72,15 @@ func TestGetAccountChannels(t *testing.T) { Account: "rLHmBn4fT92w4F6ViyYbjoizLTo83tHTHu", DestinationAccount: "rnZvsWuLem5Ha46AZs61jLWR9R5esinkG3", }, - sendRequestResult: mockClientXrplResponse{ - Result: map[string]any{ - "account": 123, - "destination_account": "rnZvsWuLem5Ha46AZs61jLWR9R5esinkG3", - }, + sendRequestResult: mockPaginatedResponse{ + Pages: []mockClientXrplResponse{{ + Result: map[string]any{ + "account": 123, + "destination_account": "rnZvsWuLem5Ha46AZs61jLWR9R5esinkG3", + }, + }}, }, - output: account.AccountChannelsResponse{}, + output: []account.AccountChannelsResponse{}, expectedErr: errors.New("1 error(s) decoding:\n\n* 'account' expected type 'types.Address', got unconvertible type 'int', value: '123'"), }, { @@ -65,29 +88,55 @@ func TestGetAccountChannels(t *testing.T) { input: account.AccountChannelsRequest{ Account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", DestinationAccount: "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", - LedgerIndex: common.VALIDATED, }, - sendRequestResult: mockClientXrplResponse{ - Result: map[string]any{ - "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "channels": []any{ - map[string]any{ - "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", - "amount": "1000", - "balance": "0", - "channel_id": "C7F634794B79DB40E87179A9D1BF05D05797AE7E92DF8E93FD6656E8C4BE3AE7", - "destination_account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", - "public_key": "aBR7mdD75Ycs8DRhMgQ4EMUEmBArF8SEh1hfjrT2V9DQTLNbJVqw", - "public_key_hex": "03CFD18E689434F032A4E84C63E2A3A6472D684EAF4FD52CA67742F3E24BAE81B2", - "settle_delay": 60, + paginationParams: XRPLPaginatedParams{ + Limit: 0, + Paginated: true, + }, + sendRequestResult: mockPaginatedResponse{ + Pages: []mockClientXrplResponse{{ + Result: map[string]any{ + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "channels": []any{ + map[string]any{ + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "amount": "1000", + "balance": "0", + "channel_id": "C7F634794B79DB40E87179A9D1BF05D05797AE7E92DF8E93FD6656E8C4BE3AE7", + "destination_account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "public_key": "aBR7mdD75Ycs8DRhMgQ4EMUEmBArF8SEh1hfjrT2V9DQTLNbJVqw", + "public_key_hex": "03CFD18E689434F032A4E84C63E2A3A6472D684EAF4FD52CA67742F3E24BAE81B2", + "settle_delay": 60, + }, }, + "ledger_hash": "1EDBBA3C793863366DF5B31C2174B6B5E6DF6DB89A7212B86838489148E2A581", + "ledger_index": 71766314, + "validated": true, + "marker": "pageMarker1", }, - "ledger_hash": "1EDBBA3C793863366DF5B31C2174B6B5E6DF6DB89A7212B86838489148E2A581", - "ledger_index": 71766314, - "validated": true, }, + { + Result: map[string]any{ + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "channels": []any{ + map[string]any{ + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "amount": "1000", + "balance": "0", + "channel_id": "C7F634794B79DB40E87179A9D1BF05D05797AE7E92DF8E93FD6656E8C4BE3AE7", + "destination_account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "public_key": "aBR7mdD75Ycs8DRhMgQ4EMUEmBArF8SEh1hfjrT2V9DQTLNbJVqw", + "public_key_hex": "03CFD18E689434F032A4E84C63E2A3A6472D684EAF4FD52CA67742F3E24BAE81B2", + "settle_delay": 60, + }, + }, + "ledger_hash": "1EDBBA3C793863366DF5B31C2174B6B5E6DF6DB89A7212B86838489148E2A581", + "ledger_index": 71766314, + "validated": true, + }, + }}, }, - output: account.AccountChannelsResponse{ + output: []account.AccountChannelsResponse{{ Account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", LedgerIndex: 71766314, LedgerHash: "1EDBBA3C793863366DF5B31C2174B6B5E6DF6DB89A7212B86838489148E2A581", @@ -104,7 +153,26 @@ func TestGetAccountChannels(t *testing.T) { }, }, Validated: true, + Marker: "pageMarker1", }, + { + Account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + LedgerIndex: 71766314, + LedgerHash: "1EDBBA3C793863366DF5B31C2174B6B5E6DF6DB89A7212B86838489148E2A581", + Channels: []account.ChannelResult{ + { + Account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + Amount: "1000", + Balance: "0", + ChannelID: "C7F634794B79DB40E87179A9D1BF05D05797AE7E92DF8E93FD6656E8C4BE3AE7", + DestinationAccount: "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + PublicKey: "aBR7mdD75Ycs8DRhMgQ4EMUEmBArF8SEh1hfjrT2V9DQTLNbJVqw", + PublicKeyHex: "03CFD18E689434F032A4E84C63E2A3A6472D684EAF4FD52CA67742F3E24BAE81B2", + SettleDelay: 60, + }, + }, + Validated: true, + }}, expectedErr: nil, }, } @@ -115,17 +183,15 @@ func TestGetAccountChannels(t *testing.T) { cl := new(mockClient) a := &accountImpl{client: cl} + cl.On("SendRequestPaginated", &tc.input, tc.paginationParams.Limit, tc.paginationParams.Paginated).Return(tc.sendRequestResult, nil) - cl.On("SendRequest", &tc.input).Return(&tc.sendRequestResult, nil) - - res, _, err := a.GetAccountChannels(&tc.input) + res, _, err := a.GetAccountChannels(&tc.input, tc.paginationParams) if tc.expectedErr != nil { require.EqualError(t, err, tc.expectedErr.Error()) } else { - require.Equal(t, &tc.output, res) + require.Equal(t, tc.output, res) } - }) } } diff --git a/client/client.go b/client/client.go index 4bb48522b..d58e0a528 100644 --- a/client/client.go +++ b/client/client.go @@ -2,6 +2,7 @@ package client type Client interface { SendRequest(req XRPLRequest) (XRPLResponse, error) + SendRequestPaginated(reqParams XRPLPaginatedRequest, limit int, pagination bool) (XRPLPaginatedResponse, error) } type XRPLClient struct { @@ -16,6 +17,22 @@ type XRPLRequest interface { type XRPLResponse interface { GetResult(v any) error + GetMarker() any +} + +type XRPLPaginatedParams struct { + Limit int + Paginated bool +} + +type XRPLPaginatedRequest interface { + Method() string + Validate() error + SetMarker(m any) +} + +type XRPLPaginatedResponse interface { + GetXRPLPages() []XRPLResponse } type XRPLResponseWarning struct { diff --git a/client/jsonrpc/jsonrpc_client.go b/client/jsonrpc/jsonrpc_client.go index 769c17018..080d0eaf4 100644 --- a/client/jsonrpc/jsonrpc_client.go +++ b/client/jsonrpc/jsonrpc_client.go @@ -186,3 +186,80 @@ func CheckForError(res *http.Response) (jsonrpcmodels.JsonRpcResponse, error) { return jr, nil } + +func (c *JsonRpcClient) SendRequestPaginated(reqParams client.XRPLPaginatedRequest, limit int, pagination bool) (client.XRPLPaginatedResponse, error) { + + responsePages := []jsonrpcmodels.JsonRpcResponse{} + + if !pagination { + + res, err := c.SendRequest(reqParams) + if err != nil { + return nil, err + } + jr, ok := res.(*jsonrpcmodels.JsonRpcResponse) + if !ok { + return nil, errors.New("problem casting XRPLResponse to JsonRpcResponse") + } + + responsePages = append(responsePages, *jr) + + } else { + + // set default limit if nothing passed in + if limit == 0 { + limit = 10 + } + + err := GetPages(c, reqParams, &responsePages, limit, 0) + if err != nil { + return nil, err + } + } + + res := jsonrpcmodels.JsonRpcPaginationResponse{ + Pages: responsePages, + } + + return res, nil +} + +func GetPages(c *JsonRpcClient, reqParams client.XRPLPaginatedRequest, responsePages *[]jsonrpcmodels.JsonRpcResponse, limit int, counter int) error { + + if limit == counter { + return nil + } + + // get first page of results + result, err := c.SendRequest(reqParams) + if err != nil { + return err + } + + fmt.Printf("Paginated response %v : ", result) + + // cast to JsonRpcResponse + jr, ok := result.(*jsonrpcmodels.JsonRpcResponse) + if !ok { + return errors.New("problem casting XRPLResponse to JsonRpcResponse") + } + + // add result to array + *responsePages = append(*responsePages, *jr) + + // check for marker + marker := jr.GetMarker() + if marker != nil { + + // set marker in request to get next page + reqParams.SetMarker(marker) + + // increase counter + counter++ + + // make next request + return GetPages(c, reqParams, responsePages, limit, counter) + } + + return nil +} diff --git a/client/jsonrpc/jsonrpc_client_test.go b/client/jsonrpc/jsonrpc_client_test.go index d153a9c63..b2e84cf30 100644 --- a/client/jsonrpc/jsonrpc_client_test.go +++ b/client/jsonrpc/jsonrpc_client_test.go @@ -10,7 +10,7 @@ import ( "time" jsoniter "github.com/json-iterator/go" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/xyield/xrpl-go/client" jsonrpcmodels "github.com/xyield/xrpl-go/client/jsonrpc/models" "github.com/xyield/xrpl-go/model/client/account" @@ -26,7 +26,7 @@ func TestJsonRpcClientCreation(t *testing.T) { jsonRpcClient := NewJsonRpcClient(cfg) - assert.Equal(t, &JsonRpcClient{Config: cfg}, jsonRpcClient) + require.Equal(t, &JsonRpcClient{Config: cfg}, jsonRpcClient) }) } @@ -54,9 +54,9 @@ func TestCheckForError(t *testing.T) { } bodyBytes, err := CheckForError(res) - assert.NotNil(t, bodyBytes) + require.NotNil(t, bodyBytes) expError := &JsonRpcClientError{ErrorString: "ledgerIndexMalformed"} - assert.Equal(t, expError, err) + require.Equal(t, expError, err) }) t.Run("Error Response with error code", func(t *testing.T) { @@ -70,9 +70,9 @@ func TestCheckForError(t *testing.T) { } bodyBytes, err := CheckForError(res) - assert.NotNil(t, bodyBytes) + require.NotNil(t, bodyBytes) expErrpr := &JsonRpcClientError{ErrorString: "Null Method"} - assert.Equal(t, expErrpr, err) + require.Equal(t, expErrpr, err) }) t.Run("No error Response", func(t *testing.T) { @@ -107,8 +107,8 @@ func TestCheckForError(t *testing.T) { bodyBytes, err := CheckForError(res) - assert.Nil(t, err) - assert.NotNil(t, bodyBytes) + require.Nil(t, err) + require.NotNil(t, bodyBytes) }) } @@ -129,11 +129,11 @@ func TestCreateRequest(t *testing.T) { byteRequest, err := CreateRequest(req) - assert.NoError(t, err) - // assert bytes equal - assert.Equal(t, expectedRequestBytes, byteRequest) - // assert json equal - assert.Equal(t, string(expectedRequestBytes), string(byteRequest)) + require.NoError(t, err) + // require bytes equal + require.Equal(t, expectedRequestBytes, byteRequest) + // require json equal + require.Equal(t, string(expectedRequestBytes), string(byteRequest)) }) t.Run("Create request - no parameters with using pointer declaration", func(t *testing.T) { @@ -146,11 +146,11 @@ func TestCreateRequest(t *testing.T) { byteRequest, err := CreateRequest(req) - assert.NoError(t, err) - // assert bytes equal - assert.Equal(t, expectedRequestBytes, byteRequest) - // assert json equal - assert.Equal(t, string(expectedRequestBytes), string(byteRequest)) + require.NoError(t, err) + // require bytes equal + require.Equal(t, expectedRequestBytes, byteRequest) + // require json equal + require.Equal(t, string(expectedRequestBytes), string(byteRequest)) }) t.Run("Create request - no parameters with struct initialisation", func(t *testing.T) { @@ -164,11 +164,11 @@ func TestCreateRequest(t *testing.T) { byteRequest, err := CreateRequest(req) - assert.NoError(t, err) - // assert bytes equal - assert.Equal(t, expectedRequestBytes, byteRequest) - // assert json equal - assert.Equal(t, string(expectedRequestBytes), string(byteRequest)) + require.NoError(t, err) + // require bytes equal + require.Equal(t, expectedRequestBytes, byteRequest) + // require json equal + require.Equal(t, string(expectedRequestBytes), string(byteRequest)) }) } @@ -188,17 +188,17 @@ func TestSendRequest(t *testing.T) { } cfg, err := client.NewJsonRpcConfig("http://testnode/", client.WithHttpClient(mc)) - assert.NoError(t, err) + require.NoError(t, err) jsonRpcClient := NewJsonRpcClient(cfg) _, err = jsonRpcClient.SendRequest(req) - assert.NotNil(t, capturedRequest) - assert.NoError(t, err) - assert.Equal(t, "POST", capturedRequest.Method) - assert.Equal(t, "http://testnode/", capturedRequest.URL.String()) - assert.Equal(t, "application/json", capturedRequest.Header.Get("Content-Type")) + require.NotNil(t, capturedRequest) + require.NoError(t, err) + require.Equal(t, "POST", capturedRequest.Method) + require.Equal(t, "http://testnode/", capturedRequest.URL.String()) + require.Equal(t, "application/json", capturedRequest.Header.Get("Content-Type")) }) t.Run("SendRequest - sucessful response", func(t *testing.T) { @@ -240,7 +240,7 @@ func TestSendRequest(t *testing.T) { mc.DoFunc = mockResponse(response, 200, mc) cfg, err := client.NewJsonRpcConfig("http://testnode/", client.WithHttpClient(mc)) - assert.NoError(t, err) + require.NoError(t, err) jsonRpcClient := NewJsonRpcClient(cfg) @@ -282,13 +282,13 @@ func TestSendRequest(t *testing.T) { LedgerHash: "1EDBBA3C793863366DF5B31C2174B6B5E6DF6DB89A7212B86838489148E2A581", } - assert.NoError(t, err) + require.NoError(t, err) - assert.Equal(t, expectedXrplResponse, xrplResponse) + require.Equal(t, expectedXrplResponse, xrplResponse) - assert.Equal(t, expected.Account, channelsResponse.Account) - assert.Equal(t, expected.LedgerIndex, channelsResponse.LedgerIndex) - assert.Equal(t, expected.LedgerHash, channelsResponse.LedgerHash) + require.Equal(t, expected.Account, channelsResponse.Account) + require.Equal(t, expected.LedgerIndex, channelsResponse.LedgerIndex) + require.Equal(t, expected.LedgerHash, channelsResponse.LedgerHash) }) t.Run("SendRequest - error response", func(t *testing.T) { @@ -313,13 +313,13 @@ func TestSendRequest(t *testing.T) { mc.DoFunc = mockResponse(response, 200, mc) cfg, err := client.NewJsonRpcConfig("http://testnode/", client.WithHttpClient(mc)) - assert.NoError(t, err) + require.NoError(t, err) jsonRpcClient := NewJsonRpcClient(cfg) _, err = jsonRpcClient.SendRequest(req) - assert.EqualError(t, err, "ledgerIndexMalformed") + require.EqualError(t, err, "ledgerIndexMalformed") }) t.Run("SendRequest - 503 response", func(t *testing.T) { @@ -336,15 +336,15 @@ func TestSendRequest(t *testing.T) { } cfg, err := client.NewJsonRpcConfig("http://testnode/", client.WithHttpClient(mc)) - assert.NoError(t, err) + require.NoError(t, err) jsonRpcClient := NewJsonRpcClient(cfg) _, err = jsonRpcClient.SendRequest(req) // Check that 3 extra requests were made - assert.Equal(t, 4, mc.RequestCount) - assert.EqualError(t, err, "Server is overloaded, rate limit exceeded") + require.Equal(t, 4, mc.RequestCount) + require.EqualError(t, err, "Server is overloaded, rate limit exceeded") }) @@ -359,7 +359,7 @@ func TestSendRequest(t *testing.T) { "ledger_hash": "27F530E5C93ED5C13994812787C1ED073C822BAEC7597964608F2C049C2ACD2D", "ledger_index": 71766343 } - }` + }` mc := &mockClient{} mc.DoFunc = func(req *http.Request) (*http.Response, error) { @@ -373,7 +373,7 @@ func TestSendRequest(t *testing.T) { } cfg, err := client.NewJsonRpcConfig("http://testnode/", client.WithHttpClient(mc)) - assert.NoError(t, err) + require.NoError(t, err) jsonRpcClient := NewJsonRpcClient(cfg) @@ -389,14 +389,13 @@ func TestSendRequest(t *testing.T) { } // Check that only 2 extra requests were made - assert.Equal(t, 3, mc.RequestCount) + require.Equal(t, 3, mc.RequestCount) - assert.NoError(t, err) - assert.Equal(t, expected.Account, channelsResponse.Account) - assert.Equal(t, expected.LedgerIndex, channelsResponse.LedgerIndex) - assert.Equal(t, expected.LedgerHash, channelsResponse.LedgerHash) + require.NoError(t, err) + require.Equal(t, expected.Account, channelsResponse.Account) + require.Equal(t, expected.LedgerIndex, channelsResponse.LedgerIndex) + require.Equal(t, expected.LedgerHash, channelsResponse.LedgerHash) }) - t.Run("SendRequest - timeout", func(t *testing.T) { req := &account.AccountChannelsRequest{ Account: "rLHmBn4fT92w4F6ViyYbjoizLTo83tHTHu", @@ -410,14 +409,178 @@ func TestSendRequest(t *testing.T) { } cfg, err := client.NewJsonRpcConfig("http://testnode/", client.WithHttpClient(mc)) - assert.NoError(t, err) + require.NoError(t, err) jsonRpcClient := NewJsonRpcClient(cfg) _, err = jsonRpcClient.SendRequest(req) // Check that the expected timeout error occurred - assert.Error(t, err) - assert.Contains(t, err.Error(), "timeout") + require.Error(t, err) + require.Contains(t, err.Error(), "timeout") + }) +} + +func TestSendRequestPagination(t *testing.T) { + + req1 := account.AccountChannelsRequest{ + Account: "rLHmBn4fT92w4F6ViyYbjoizLTo83tHTHu", + } + paginatedParams := client.XRPLPaginatedParams{ + Limit: 3, + Paginated: true, + } + + markerResponse1 := `{ + "result": { + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_index": 71766343, + "marker": "pageMarker1" + } + }` + markerResponse2 := `{ + "result": { + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_index": 71766343, + "marker": "pageMarker2" + } + }` + noMarkerResponse := `{ + "result": { + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_index": 71766343 + } + }` + + t.Run("Pagination calls", func(t *testing.T) { + + expectedRes := []account.AccountChannelsResponse{ + { + Account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + LedgerIndex: 71766343, + Marker: "pageMarker1", + }, + { + Account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + LedgerIndex: 71766343, + Marker: "pageMarker2", + }, + { + Account: "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + LedgerIndex: 71766343, + }, + } + + mc := &mockClient{} + mc.DoFunc = func(req *http.Request) (*http.Response, error) { + if mc.RequestCount < 1 { + // Return marker for first + mc.RequestCount++ + return mockResponse(markerResponse1, 200, mc)(req) + } + if mc.RequestCount < 2 { + // Return marker for second + mc.RequestCount++ + return mockResponse(markerResponse2, 200, mc)(req) + } + // Return no marker + return mockResponse(noMarkerResponse, 200, mc)(req) + } + cfg, err := client.NewJsonRpcConfig("http://testnode/", client.WithHttpClient(mc)) + require.NoError(t, err) + jsonRpcClient := NewJsonRpcClient(cfg) + + res, err := jsonRpcClient.SendRequestPaginated(&req1, paginatedParams.Limit, paginatedParams.Paginated) + require.NoError(t, err) + + expectedFirstPage := jsonrpcmodels.JsonRpcResponse{ + Result: jsonrpcmodels.AnyJson{ + "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_index": json.Number("71766343"), + "marker": "pageMarker1", + }} + + pages := res.GetXRPLPages() + firstPage := pages[0] + require.Equal(t, expectedFirstPage, firstPage) + + // unmarshall into specified type + acrPages := []account.AccountChannelsResponse{} + + for _, page := range pages { + + var acr account.AccountChannelsResponse + + err = page.GetResult(&acr) + require.NoError(t, err) + + acrPages = append(acrPages, acr) + } + + require.Equal(t, expectedRes, acrPages) + }) + + t.Run("No Pagination", func(t *testing.T) { + + mc := &mockClient{} + mc.DoFunc = func(req *http.Request) (*http.Response, error) { + // Return no marker + return mockResponse(markerResponse1, 200, mc)(req) + } + + cfg, err := client.NewJsonRpcConfig("http://testnode/", client.WithHttpClient(mc)) + require.NoError(t, err) + jsonRpcClient := NewJsonRpcClient(cfg) + + res, err := jsonRpcClient.SendRequestPaginated(&req1, 10, false) + pages := res.GetXRPLPages() + require.NoError(t, err) + require.Equal(t, 1, len(pages)) + }) + + t.Run("Limit set", func(t *testing.T) { + + mc := &mockClient{} + mc.DoFunc = func(req *http.Request) (*http.Response, error) { + if mc.RequestCount < 1 { + // Return marker for first + mc.RequestCount++ + return mockResponse(markerResponse1, 200, mc)(req) + } + if mc.RequestCount < 2 { + // Return marker for second + mc.RequestCount++ + return mockResponse(markerResponse2, 200, mc)(req) + } + // Return no marker + return mockResponse(noMarkerResponse, 200, mc)(req) + } + + cfg, err := client.NewJsonRpcConfig("http://testnode/", client.WithHttpClient(mc)) + require.NoError(t, err) + jsonRpcClient := NewJsonRpcClient(cfg) + + res, err := jsonRpcClient.SendRequestPaginated(&req1, 2, true) + pages := res.GetXRPLPages() + require.NoError(t, err) + require.Equal(t, 2, len(pages)) + }) + + t.Run("Default limit", func(t *testing.T) { + mc := &mockClient{} + + mc.DoFunc = func(req *http.Request) (*http.Response, error) { + // Return no marker + return mockResponse(markerResponse1, 200, mc)(req) + } + + cfg, err := client.NewJsonRpcConfig("http://testnode/", client.WithHttpClient(mc)) + require.NoError(t, err) + jsonRpcClient := NewJsonRpcClient(cfg) + + res, err := jsonRpcClient.SendRequestPaginated(&req1, 0, true) + pages := res.GetXRPLPages() + require.NoError(t, err) + require.Equal(t, 10, len(pages)) }) } diff --git a/client/jsonrpc/models/jsonrpc_response.go b/client/jsonrpc/models/jsonrpc_response.go index 03c3377a2..3e93e381a 100644 --- a/client/jsonrpc/models/jsonrpc_response.go +++ b/client/jsonrpc/models/jsonrpc_response.go @@ -33,3 +33,24 @@ func (r JsonRpcResponse) GetResult(v any) error { } return nil } + +func (r JsonRpcResponse) GetMarker() any { + if _, ok := r.Result["marker"]; ok { + return r.Result["marker"] + } + return nil +} + +type JsonRpcPaginationResponse struct { + Pages []JsonRpcResponse +} + +func (r JsonRpcPaginationResponse) GetXRPLPages() []client.XRPLResponse { + + res := make([]client.XRPLResponse, len(r.Pages)) + for i, page := range r.Pages { + res[i] = page + } + + return res +} diff --git a/client/jsonrpc/models/jsonrpc_response_test.go b/client/jsonrpc/models/jsonrpc_response_test.go index de9dbbd50..ce31ae9f4 100644 --- a/client/jsonrpc/models/jsonrpc_response_test.go +++ b/client/jsonrpc/models/jsonrpc_response_test.go @@ -5,7 +5,7 @@ import ( "strconv" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/xyield/xrpl-go/client" "github.com/xyield/xrpl-go/model/client/account" ) @@ -36,8 +36,8 @@ func TestGetResult(t *testing.T) { var acr account.AccountChannelsResponse err := jr.GetResult(&acr) - assert.NoError(t, err) - assert.Equal(t, expected, acr) + require.NoError(t, err) + require.Equal(t, expected, acr) }) t.Run("throws error for incorrect mapping", func(t *testing.T) { @@ -58,6 +58,6 @@ func TestGetResult(t *testing.T) { var acr account.AccountChannelsResponse err := jr.GetResult(&acr) - assert.Error(t, err) + require.Error(t, err) }) } diff --git a/client/jsonrpc_config_test.go b/client/jsonrpc_config_test.go index 2fe1e2c06..5aa55acb9 100644 --- a/client/jsonrpc_config_test.go +++ b/client/jsonrpc_config_test.go @@ -4,7 +4,7 @@ import ( "net/http" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type customHttpClient struct{} @@ -21,14 +21,14 @@ func TestConfigCreation(t *testing.T) { req, err := http.NewRequest(http.MethodPost, "http://s1.ripple.com:51234/", nil) req.Header = cfg.Headers - assert.Equal(t, "http://s1.ripple.com:51234/", cfg.Url) - assert.NoError(t, err) + require.Equal(t, "http://s1.ripple.com:51234/", cfg.Url) + require.NoError(t, err) }) t.Run("No port + IP provided", func(t *testing.T) { cfg, err := NewJsonRpcConfig("") - assert.Nil(t, cfg) - assert.EqualError(t, err, "empty port and IP provided") + require.Nil(t, cfg) + require.EqualError(t, err, "empty port and IP provided") }) t.Run("Format root path - add /", func(t *testing.T) { cfg, _ := NewJsonRpcConfig("http://s1.ripple.com:51234") @@ -36,8 +36,8 @@ func TestConfigCreation(t *testing.T) { req, err := http.NewRequest(http.MethodPost, "http://s1.ripple.com:51234/", nil) req.Header = cfg.Headers - assert.Equal(t, "http://s1.ripple.com:51234/", cfg.Url) - assert.NoError(t, err) + require.Equal(t, "http://s1.ripple.com:51234/", cfg.Url) + require.NoError(t, err) }) t.Run("Pass in custom HTTP client", func(t *testing.T) { @@ -49,7 +49,7 @@ func TestConfigCreation(t *testing.T) { "Content-Type": {"application/json"}, } req.Header = cfg.Headers - assert.Equal(t, &JsonRpcConfig{HTTPClient: customHttpClient{}, Url: "http://s1.ripple.com:51234/", Headers: headers}, cfg) - assert.NoError(t, err) + require.Equal(t, &JsonRpcConfig{HTTPClient: customHttpClient{}, Url: "http://s1.ripple.com:51234/", Headers: headers}, cfg) + require.NoError(t, err) }) } diff --git a/client/websocket/websocket_client.go b/client/websocket/websocket_client.go index ae25e4484..99b319fe3 100644 --- a/client/websocket/websocket_client.go +++ b/client/websocket/websocket_client.go @@ -23,6 +23,11 @@ type WebsocketClient struct { idCounter atomic.Uint32 } +func (c *WebsocketClient) SendRequestPaginated(reqParams client.XRPLPaginatedRequest, limit int, pagination bool) (client.XRPLPaginatedResponse, error) { + // TODO: impl this method + return nil, nil +} + func (c *WebsocketClient) SendRequest(req client.XRPLRequest) (client.XRPLResponse, error) { err := req.Validate() if err != nil { diff --git a/client/websocket/websocket_client_response.go b/client/websocket/websocket_client_response.go index 696bb45ab..039979a90 100644 --- a/client/websocket/websocket_client_response.go +++ b/client/websocket/websocket_client_response.go @@ -45,3 +45,8 @@ func (r *WebSocketClientXrplResponse) CheckError() error { } return nil } + +func (r WebSocketClientXrplResponse) GetMarker() any { + // TODO: impl this method for use in pagination method + return nil +} diff --git a/examples/jsonrpc-client/jsonrpc_client_example.go b/examples/jsonrpc-client/jsonrpc_client_example.go index ac52bb96f..54b3c701f 100644 --- a/examples/jsonrpc-client/jsonrpc_client_example.go +++ b/examples/jsonrpc-client/jsonrpc_client_example.go @@ -17,12 +17,18 @@ func main() { log.Panicln(err) } + paginatedParams := client.XRPLPaginatedParams{ + Limit: 3, + Paginated: true, + } + // Initialise new json client with json config client := jsonrpcclient.NewClient(cfg) // call the desired method var req *account.AccountChannelsRequest - ac, xrplRes, err := client.Account.GetAccountChannels(req) + + ac, xrplRes, err := client.Account.GetAccountChannels(req, paginatedParams) if err != nil { fmt.Println(err.Error()) } diff --git a/model/client/account/account_channels_request.go b/model/client/account/account_channels_request.go index 8c5f46d58..e696b479a 100644 --- a/model/client/account/account_channels_request.go +++ b/model/client/account/account_channels_request.go @@ -21,6 +21,11 @@ func (*AccountChannelsRequest) Method() string { return "account_channels" } +// Below mean struct satisfies paginated response interface +func (a *AccountChannelsRequest) SetMarker(m any) { + a.Marker = m +} + // Validate method to be added to each request struct func (a *AccountChannelsRequest) Validate() error { if a.Account == "" { diff --git a/model/client/account/account_channels_response.go b/model/client/account/account_channels_response.go index 7b865938b..8c6c6d467 100644 --- a/model/client/account/account_channels_response.go +++ b/model/client/account/account_channels_response.go @@ -14,3 +14,12 @@ type AccountChannelsResponse struct { Limit int `json:"limit,omitempty"` Marker any `json:"marker,omitempty"` } + +// below to satisfy the paginated response struct +func (a *AccountChannelsResponse) GetMarker() any { + if a.Marker != nil { + return a.Marker + } else { + return nil + } +} diff --git a/model/client/account/account_channels_test.go b/model/client/account/account_channels_test.go index 7fd8b9d19..cdcf59804 100644 --- a/model/client/account/account_channels_test.go +++ b/model/client/account/account_channels_test.go @@ -3,7 +3,7 @@ package account import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/xyield/xrpl-go/model/client/common" "github.com/xyield/xrpl-go/test" ) @@ -72,5 +72,5 @@ func TestValidate(t *testing.T) { err := s.Validate() - assert.EqualError(t, err, "no account ID specified") + require.EqualError(t, err, "no account ID specified") }