Skip to content

Commit

Permalink
Fixes #54 - Better pagination support.
Browse files Browse the repository at this point in the history
  • Loading branch information
bchavez committed Mar 11, 2019
1 parent c546367 commit 34946dd
Show file tree
Hide file tree
Showing 18 changed files with 214 additions and 27 deletions.
3 changes: 3 additions & 0 deletions HISTORY.md
@@ -1,3 +1,6 @@
## v5.1.0
* Issue #54: Better support for Pagination with `client.GetNextPageAsync` helper.

## v5.0.8
* PR #53: Ensure all `.CreatedAt` and `.UpdatedAt` fields are nullable to prevent deserialization exception errors.

Expand Down
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -84,6 +84,23 @@ public async Task can_get_spotprice_of_ETHUSD()
* [`client.Users`](https://developers.coinbase.com/api/v2#users) - [Examples](https://github.com/bchavez/Coinbase/blob/master/Source/Coinbase.Tests/Endpoints/UserTests.cs)
* [`client.Withdrawals`](https://developers.coinbase.com/api/v2#withdrawals) - [Examples](https://github.com/bchavez/Coinbase/blob/master/Source/Coinbase.Tests/Endpoints/WithdrawlTests.cs)

### Pagination
Some Coinbase [APIs support pagination. See developer docs here](https://developers.coinbase.com/api/v2#pagination). APIs that support pagination can specify an extra `PaginationOptions` object used to specify item page `limit` and other various options. The following code shows how to enumerate the first 3 pages where each page contains 5 buy transactions for an account.

```csharp
var client = new CoinbaseClient(...);

var page1 = await client.Buys.ListBuysAsync("..accountId..",
new PaginationOptions{Limit = 5}); //Limit results to 5 items
var page2 = await client.GetNextPageAsync(page1); //Same pagination options used.
//Limit 5 items.
var page3 = await client.GetNextPageAsync(page2); //Same pagination options used.
//Limit 5 items.
```

Use the `.GetNextPageAsync` helper method on `CoinbaseClient` supplying the current page of data to get the next page of data.

### Authentication Details
##### OAuth Access and Refresh Tokens
Expand Down
52 changes: 52 additions & 0 deletions Source/Coinbase.Tests/Endpoints/PaginationTests.cs
@@ -0,0 +1,52 @@
using System.Threading.Tasks;
using Coinbase.Models;
using NUnit.Framework;
using static Coinbase.Tests.Examples;

namespace Coinbase.Tests.Endpoints
{
public class PaginationTests : OAuthServerTest
{
[Test]
public async Task page_accounts()
{
await client.Accounts.ListAccountsAsync(new PaginationOptions {Limit = 5, EndingBefore = "before", Order = "ooo", StartingAfter = "after"});

server.ShouldHaveExactCall("https://api.coinbase.com/v2/accounts?limit=5&order=ooo&starting_after=after&ending_before=before");
}

[Test]
public async Task page_addresses()
{
await client.Addresses.ListAddressesAsync("fff", new PaginationOptions { Limit = 5,Order = "ooo" });

server.ShouldHaveExactCall("https://api.coinbase.com/v2/accounts/fff/addresses?limit=5&order=ooo");
}

[Test]
public async Task can_get_next_page()
{
var p = new PagedResponse<Buy>()
{
Pagination = new Pagination { NextUri = "/v2/next/thing?limit=5" }
};

await client.GetNextPageAsync(p);

server.ShouldHaveExactCall("https://api.coinbase.com/v2/next/thing?limit=5");
}

//[Test]
//public async Task can_get_prev_page()
//{
// var p = new PagedResponse<Buy>()
// {
// Pagination = new Pagination { PreviousUri = "/v2/prev/thing?limit=5" }
// };

// await client.PreviousPageAsync(p);

// server.ShouldHaveExactCall("https://api.coinbase.com/v2/prev/thing?limit=5");
//}
}
}
31 changes: 29 additions & 2 deletions Source/Coinbase.Tests/Integration/IntegrationTests.cs
@@ -1,8 +1,13 @@
using System.Diagnostics;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Coinbase.Models;
using FluentAssertions;
using Flurl;
using Flurl.Http;
using Newtonsoft.Json;
using NUnit.Framework;
Expand Down Expand Up @@ -43,7 +48,7 @@ public IntegrationTests()

protected void ReadSecrets()
{
var json = File.ReadAllText("../../.secrets.txt");
var json = File.ReadAllText("../../../.secrets.txt");
this.secrets = JsonConvert.DeserializeObject<Secrets>(json);
}
}
Expand Down Expand Up @@ -154,5 +159,27 @@ public async Task test_state()
var ethAddress = ethAddresses.Data.FirstOrDefault();
var ethTransactions = await client.Transactions.ListTransactionsAsync(ethAccount.Id);
}


[Test]
public async Task test_paged_response()
{
var accounts = await client.Accounts.ListAccountsAsync();
var btcWallet = accounts.Data.First(a => a.Name.StartsWith("BTC Wallet"));

var page1 = await client.Addresses.ListAddressesAsync(btcWallet.Id, new PaginationOptions{Limit = 1});
page1.Dump();

var page2 = await client.GetNextPageAsync(page1);
page2.Dump();

var page3 = await client.GetNextPageAsync(page2);
page3.Dump();
//var prevPage = await client.PreviousPageAsync(page3);

//prevPage.Dump();

//prevPage.Should().BeEquivalentTo(page1);
}
}
}
5 changes: 3 additions & 2 deletions Source/Coinbase/CoinbaseClient.Accounts.cs
Expand Up @@ -12,7 +12,7 @@ public interface IAccountsEndpoint
/// <summary>
/// Lists current user’s accounts to which the authentication method has access to.
/// </summary>
Task<PagedResponse<Account>> ListAccountsAsync(CancellationToken cancellationToken = default);
Task<PagedResponse<Account>> ListAccountsAsync(PaginationOptions pagination = null, CancellationToken cancellationToken = default);

/// <summary>
/// Show current user’s account. To access the primary account for a given currency, a currency string (BTC or ETH) can be used instead of the account id in the URL.
Expand Down Expand Up @@ -47,9 +47,10 @@ public partial class CoinbaseClient : IAccountsEndpoint
/// <summary>
/// Lists current user’s accounts to which the authentication method has access to.
/// </summary>
Task<PagedResponse<Account>> IAccountsEndpoint.ListAccountsAsync(CancellationToken cancellationToken)
Task<PagedResponse<Account>> IAccountsEndpoint.ListAccountsAsync(PaginationOptions pagination, CancellationToken cancellationToken)
{
return this.AccountsEndpoint
.WithPagination(pagination)
.WithClient(this)
.GetJsonAsync<PagedResponse<Account>>(cancellationToken);
}
Expand Down
10 changes: 6 additions & 4 deletions Source/Coinbase/CoinbaseClient.Addresses.cs
Expand Up @@ -11,7 +11,7 @@ public interface IAddressesEndpoint
/// <summary>
/// Lists addresses for an account.
/// </summary>
Task<PagedResponse<AddressEntity>> ListAddressesAsync(string accountId, CancellationToken cancellationToken = default);
Task<PagedResponse<AddressEntity>> ListAddressesAsync(string accountId, PaginationOptions pagination = null, CancellationToken cancellationToken = default);

/// <summary>
/// Show an individual address for an account. A regular bitcoin, bitcoin cash, litecoin or ethereum address can be used in place of address_id but the address has to be associated to the correct account.
Expand All @@ -21,7 +21,7 @@ public interface IAddressesEndpoint
/// <summary>
/// List transactions that have been sent to a specific address. A regular bitcoin, bitcoin cash, litecoin or ethereum address can be used in place of address_id but the address has to be associated to the correct account.
/// </summary>
Task<PagedResponse<Transaction>> ListAddressTransactionsAsync(string accountId, string addressId, CancellationToken cancellationToken = default);
Task<PagedResponse<Transaction>> ListAddressTransactionsAsync(string accountId, string addressId, PaginationOptions pagination = null, CancellationToken cancellationToken = default);

/// <summary>
/// Creates a new address for an account. As all the arguments are optinal, it’s possible just to do a empty POST which will create a new address. This is handy if you need to create new receive addresses for an account on-demand.
Expand All @@ -36,10 +36,11 @@ public partial class CoinbaseClient : IAddressesEndpoint
public IAddressesEndpoint Addresses => this;

/// <inheritdoc />
Task<PagedResponse<AddressEntity>> IAddressesEndpoint.ListAddressesAsync(string accountId, CancellationToken cancellationToken)
Task<PagedResponse<AddressEntity>> IAddressesEndpoint.ListAddressesAsync(string accountId, PaginationOptions pagination, CancellationToken cancellationToken)
{
return this.AccountsEndpoint
.AppendPathSegments(accountId, "addresses")
.WithPagination(pagination)
.WithClient(this)
.GetJsonAsync<PagedResponse<AddressEntity>>(cancellationToken);
}
Expand All @@ -54,10 +55,11 @@ Task<Response<AddressEntity>> IAddressesEndpoint.GetAddressAsync(string accountI
}

/// <inheritdoc />
Task<PagedResponse<Transaction>> IAddressesEndpoint.ListAddressTransactionsAsync(string accountId, string addressId, CancellationToken cancellationToken)
Task<PagedResponse<Transaction>> IAddressesEndpoint.ListAddressTransactionsAsync(string accountId, string addressId, PaginationOptions pagination, CancellationToken cancellationToken)
{
return this.AccountsEndpoint
.AppendPathSegments(accountId, "addresses", addressId, "transactions")
.WithPagination(pagination)
.WithClient(this)
.GetJsonAsync<PagedResponse<Transaction>>(cancellationToken);
}
Expand Down
5 changes: 3 additions & 2 deletions Source/Coinbase/CoinbaseClient.Buys.cs
Expand Up @@ -11,7 +11,7 @@ public interface IBuysEndpoint
/// <summary>
/// Lists buys for an account.
/// </summary>
Task<PagedResponse<Buy>> ListBuysAsync(string accountId, CancellationToken cancellationToken = default);
Task<PagedResponse<Buy>> ListBuysAsync(string accountId, PaginationOptions pagination = null, CancellationToken cancellationToken = default);

/// <summary>
/// Get an individual buy.
Expand Down Expand Up @@ -43,10 +43,11 @@ public partial class CoinbaseClient : IBuysEndpoint
/// <summary>
/// Lists buys for an account.
/// </summary>
Task<PagedResponse<Buy>> IBuysEndpoint.ListBuysAsync(string accountId, CancellationToken cancellationToken)
Task<PagedResponse<Buy>> IBuysEndpoint.ListBuysAsync(string accountId, PaginationOptions pagination, CancellationToken cancellationToken)
{
return this.AccountsEndpoint
.AppendPathSegments(accountId, "buys")
.WithPagination(pagination)
.WithClient(this)
.GetJsonAsync<PagedResponse<Buy>>(cancellationToken);
}
Expand Down
5 changes: 3 additions & 2 deletions Source/Coinbase/CoinbaseClient.Data.cs
Expand Up @@ -40,7 +40,7 @@ public interface IDataEndpoint
/// <summary>
/// List known currencies. Currency codes will conform to the ISO 4217 standard where possible. Currencies which have or had no representation in ISO 4217 may use a custom code (e.g. BTC).
/// </summary>
Task<PagedResponse<Currency>> GetCurrenciesAsync(CancellationToken cancellationToken = default);
Task<PagedResponse<Currency>> GetCurrenciesAsync(PaginationOptions pagination = null, CancellationToken cancellationToken = default);

/// <summary>
/// Get the API server time.
Expand Down Expand Up @@ -118,9 +118,10 @@ Task<Response<ExchangeRates>> IDataEndpoint.GetExchangeRatesAsync(string currenc
/// <summary>
/// List known currencies. Currency codes will conform to the ISO 4217 standard where possible. Currencies which have or had no representation in ISO 4217 may use a custom code (e.g. BTC).
/// </summary>
Task<PagedResponse<Currency>> IDataEndpoint.GetCurrenciesAsync(CancellationToken cancellationToken)
Task<PagedResponse<Currency>> IDataEndpoint.GetCurrenciesAsync(PaginationOptions pagination, CancellationToken cancellationToken)
{
return this.CurrenciesEndpoint
.WithPagination(pagination)
.WithClient(this)
.GetJsonAsync<PagedResponse<Currency>>(cancellationToken);
}
Expand Down
5 changes: 3 additions & 2 deletions Source/Coinbase/CoinbaseClient.Deposits.cs
Expand Up @@ -12,7 +12,7 @@ public interface IDepositsEndpoint
/// <summary>
/// Lists deposits for an account.
/// </summary>
Task<PagedResponse<Deposit>> ListDepositsAsync(string accountId, CancellationToken cancellationToken = default);
Task<PagedResponse<Deposit>> ListDepositsAsync(string accountId, PaginationOptions pagination = null, CancellationToken cancellationToken = default);
/// <summary>
/// Show an individual deposit.
/// </summary>
Expand All @@ -34,10 +34,11 @@ public partial class CoinbaseClient : IDepositsEndpoint
private const string deposits = "deposits";

/// <inheritdoc />
Task<PagedResponse<Deposit>> IDepositsEndpoint.ListDepositsAsync(string accountId, CancellationToken cancellationToken)
Task<PagedResponse<Deposit>> IDepositsEndpoint.ListDepositsAsync(string accountId, PaginationOptions pagination, CancellationToken cancellationToken)
{
return this.AccountsEndpoint
.AppendPathSegments(accountId, deposits)
.WithPagination(pagination)
.WithClient(this)
.GetJsonAsync<PagedResponse<Deposit>>(cancellationToken);
}
Expand Down
5 changes: 3 additions & 2 deletions Source/Coinbase/CoinbaseClient.Notifications.cs
Expand Up @@ -12,7 +12,7 @@ public interface INotificationsEndpoint
/// <summary>
/// Lists current user’s payment methods.
/// </summary>
Task<PagedResponse<Notification>> ListNotificationsAsync(CancellationToken cancellationToken = default);
Task<PagedResponse<Notification>> ListNotificationsAsync(PaginationOptions pagination = null, CancellationToken cancellationToken = default);
/// <summary>
/// Show current user’s payment method.
/// </summary>
Expand All @@ -24,9 +24,10 @@ public partial class CoinbaseClient : INotificationsEndpoint
public INotificationsEndpoint Notifications => this;


Task<PagedResponse<Notification>> INotificationsEndpoint.ListNotificationsAsync(CancellationToken cancellationToken)
Task<PagedResponse<Notification>> INotificationsEndpoint.ListNotificationsAsync(PaginationOptions pagination, CancellationToken cancellationToken)
{
return this.NotificationsEndpoint
.WithPagination(pagination)
.WithClient(this)
.GetJsonAsync<PagedResponse<Notification>>(cancellationToken);
}
Expand Down
5 changes: 3 additions & 2 deletions Source/Coinbase/CoinbaseClient.PaymentMethods.cs
Expand Up @@ -12,7 +12,7 @@ public interface IPaymentMethodsEndpoint
/// <summary>
/// Lists current user’s payment methods.
/// </summary>
Task<PagedResponse<PaymentMethod>> ListPaymentMethodsAsync(CancellationToken cancellationToken = default);
Task<PagedResponse<PaymentMethod>> ListPaymentMethodsAsync(PaginationOptions pagination = null, CancellationToken cancellationToken = default);
/// <summary>
/// Show current user’s payment method.
/// </summary>
Expand All @@ -24,9 +24,10 @@ public partial class CoinbaseClient : IPaymentMethodsEndpoint
public IPaymentMethodsEndpoint PaymentMethods => this;


Task<PagedResponse<PaymentMethod>> IPaymentMethodsEndpoint.ListPaymentMethodsAsync(CancellationToken cancellationToken)
Task<PagedResponse<PaymentMethod>> IPaymentMethodsEndpoint.ListPaymentMethodsAsync(PaginationOptions pagination, CancellationToken cancellationToken)
{
return this.PaymentMethodsEndpoint
.WithPagination(pagination)
.WithClient(this)
.GetJsonAsync<PagedResponse<PaymentMethod>>(cancellationToken);
}
Expand Down
5 changes: 3 additions & 2 deletions Source/Coinbase/CoinbaseClient.Sells.cs
Expand Up @@ -11,7 +11,7 @@ public interface ISellsEndpoint
/// <summary>
/// Lists sells for an account.
/// </summary>
Task<PagedResponse<Sell>> ListSellsAsync(string accountId, CancellationToken cancellationToken = default);
Task<PagedResponse<Sell>> ListSellsAsync(string accountId, PaginationOptions pagination = null, CancellationToken cancellationToken = default);

/// <summary>
/// Get an individual sell.
Expand Down Expand Up @@ -43,10 +43,11 @@ public partial class CoinbaseClient : ISellsEndpoint
/// <summary>
/// Lists sells for an account.
/// </summary>
Task<PagedResponse<Sell>> ISellsEndpoint.ListSellsAsync(string accountId, CancellationToken cancellationToken)
Task<PagedResponse<Sell>> ISellsEndpoint.ListSellsAsync(string accountId, PaginationOptions pagination, CancellationToken cancellationToken)
{
return this.AccountsEndpoint
.AppendPathSegments(accountId, "sells")
.WithPagination(pagination)
.WithClient(this)
.GetJsonAsync<PagedResponse<Sell>>(cancellationToken);
}
Expand Down
5 changes: 3 additions & 2 deletions Source/Coinbase/CoinbaseClient.Transactions.cs
Expand Up @@ -12,7 +12,7 @@ public interface ITransactionsEndpoint
/// <summary>
/// Lists account’s transactions.
/// </summary>
Task<PagedResponse<Transaction>> ListTransactionsAsync(string accountId, CancellationToken cancellationToken = default);
Task<PagedResponse<Transaction>> ListTransactionsAsync(string accountId, PaginationOptions pagination = null, CancellationToken cancellationToken = default);

/// <summary>
/// Show an individual transaction for an account. See transaction resource for more information.
Expand Down Expand Up @@ -63,10 +63,11 @@ public partial class CoinbaseClient : ITransactionsEndpoint
/// <summary>
/// Lists account’s transactions.
/// </summary>
Task<PagedResponse<Transaction>> ITransactionsEndpoint.ListTransactionsAsync(string accountId, CancellationToken cancellationToken)
Task<PagedResponse<Transaction>> ITransactionsEndpoint.ListTransactionsAsync(string accountId, PaginationOptions pagination, CancellationToken cancellationToken)
{
return this.AccountsEndpoint
.AppendPathSegments(accountId, "transactions")
.WithPagination(pagination)
.WithClient(this)
.GetJsonAsync<PagedResponse<Transaction>>(cancellationToken);
}
Expand Down
5 changes: 3 additions & 2 deletions Source/Coinbase/CoinbaseClient.Withdrawals.cs
Expand Up @@ -11,7 +11,7 @@ public interface IWithdrawalsEndpoint
/// <summary>
/// Lists deposits for an account.
/// </summary>
Task<PagedResponse<Withdrawal>> ListWithdrawalsAsync(string accountId, CancellationToken cancellationToken = default);
Task<PagedResponse<Withdrawal>> ListWithdrawalsAsync(string accountId, PaginationOptions pagination = null, CancellationToken cancellationToken = default);
/// <summary>
/// Show an individual deposit.
/// </summary>
Expand All @@ -33,10 +33,11 @@ public partial class CoinbaseClient : IWithdrawalsEndpoint

private const string withdrawals = "withdrawals";

Task<PagedResponse<Withdrawal>> IWithdrawalsEndpoint.ListWithdrawalsAsync(string accountId, CancellationToken cancellationToken)
Task<PagedResponse<Withdrawal>> IWithdrawalsEndpoint.ListWithdrawalsAsync(string accountId, PaginationOptions pagination, CancellationToken cancellationToken)
{
return this.AccountsEndpoint
.AppendPathSegments(accountId, withdrawals)
.WithPagination(pagination)
.WithClient(this)
.GetJsonAsync<PagedResponse<Withdrawal>>(cancellationToken);
}
Expand Down

0 comments on commit 34946dd

Please sign in to comment.