Added paging methods, closes #538

This commit is contained in:
Jonas Dellinger 2020-12-28 17:23:59 +01:00
parent 8dd31420ea
commit 237925d51e
5 changed files with 245 additions and 10 deletions

View File

@ -0,0 +1,96 @@
using System.Reflection;
using System.Collections.Generic;
using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using SpotifyAPI.Web.Http;
namespace SpotifyAPI.Web.Tests
{
[TestFixture]
public class SpotifyClientTest
{
[Test]
public async Task NextPageForIPaginatable()
{
var api = new Mock<IAPIConnector>();
var config = SpotifyClientConfig.CreateDefault("FakeToken").WithAPIConnector(api.Object);
var spotify = new SpotifyClient(config);
var response = new SearchResponse
{
Albums = new Paging<SimpleAlbum, SearchResponse>
{
Next = "https://next-url",
}
};
await spotify.NextPage(response.Albums);
api.Verify(a => a.Get<SearchResponse>(new System.Uri("https://next-url")), Times.Once);
}
[Test]
public async Task NextPageForCursorPaging()
{
var api = new Mock<IAPIConnector>();
var config = SpotifyClientConfig.CreateDefault("FakeToken").WithAPIConnector(api.Object);
var spotify = new SpotifyClient(config);
var response = new CursorPaging<PlayHistoryItem>
{
Next = "https://next-url"
};
await spotify.NextPage(response);
api.Verify(a => a.Get<CursorPaging<PlayHistoryItem>>(new System.Uri("https://next-url")), Times.Once);
}
[Test]
public async Task NextPageForPaging()
{
var api = new Mock<IAPIConnector>();
var config = SpotifyClientConfig.CreateDefault("FakeToken").WithAPIConnector(api.Object);
var spotify = new SpotifyClient(config);
var response = new Paging<PlayHistoryItem>
{
Next = "https://next-url"
};
await spotify.NextPage(response);
api.Verify(a => a.Get<Paging<PlayHistoryItem>>(new System.Uri("https://next-url")), Times.Once);
}
[Test]
public async Task PreviousPageForPaging()
{
var api = new Mock<IAPIConnector>();
var config = SpotifyClientConfig.CreateDefault("FakeToken").WithAPIConnector(api.Object);
var spotify = new SpotifyClient(config);
var response = new Paging<PlayHistoryItem>
{
Previous = "https://previous-url"
};
await spotify.PreviousPage(response);
api.Verify(a => a.Get<Paging<PlayHistoryItem>>(new System.Uri("https://previous-url")), Times.Once);
}
[Test]
public async Task PreviousPageForCustomPaging()
{
var api = new Mock<IAPIConnector>();
var config = SpotifyClientConfig.CreateDefault("FakeToken").WithAPIConnector(api.Object);
var spotify = new SpotifyClient(config);
var response = new Paging<PlayHistoryItem, SearchResponse>
{
Previous = "https://previous-url"
};
await spotify.PreviousPage(response);
api.Verify(a => a.Get<SearchResponse>(new System.Uri("https://previous-url")), Times.Once);
}
}
}

View File

@ -167,5 +167,15 @@ namespace SpotifyAPI.Web
);
#endif
public Task<Paging<T>> NextPage<T>(Paging<T> paging);
public Task<CursorPaging<T>> NextPage<T>(CursorPaging<T> cursorPaging);
public Task<TNext> NextPage<T, TNext>(IPaginatable<T, TNext> paginatable);
public Task<Paging<T>> PreviousPage<T>(Paging<T> paging);
public Task<TNext> PreviousPage<T, TNext>(Paging<T, TNext> paging);
}
}

View File

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using SpotifyAPI.Web.Http;
using System.Runtime.CompilerServices;
using System.Threading;
namespace SpotifyAPI.Web
{
@ -29,14 +29,7 @@ namespace SpotifyAPI.Web
#pragma warning restore CA2208
}
_apiConnector = new APIConnector(
config.BaseAddress,
config.Authenticator,
config.JSONSerializer,
config.HTTPClient,
config.RetryHandler,
config.HTTPLogger
);
_apiConnector = config.BuildAPIConnector();
_apiConnector.ResponseReceived += (sender, response) =>
{
LastResponse = response;
@ -124,6 +117,79 @@ namespace SpotifyAPI.Web
return (paginator ?? DefaultPaginator).PaginateAll(firstPage, mapper, _apiConnector);
}
private Task<T> FetchPage<T>(string? nextUrl)
{
if (nextUrl == null)
{
throw new APIPagingException("The paging object has no next page");
}
return _apiConnector.Get<T>(new Uri(nextUrl, UriKind.Absolute));
}
/// <summary>
/// Fetches the next page of the paging object
/// </summary>
/// <param name="paging">A paging object which has a next page</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public Task<Paging<T>> NextPage<T>(Paging<T> paging)
{
Ensure.ArgumentNotNull(paging, nameof(paging));
return FetchPage<Paging<T>>(paging.Next);
}
/// <summary>
/// Fetches the next page of the cursor paging object
/// </summary>
/// <param name="cursorPaging">A cursor paging object which has a next page</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public Task<CursorPaging<T>> NextPage<T>(CursorPaging<T> cursorPaging)
{
Ensure.ArgumentNotNull(cursorPaging, nameof(cursorPaging));
return FetchPage<CursorPaging<T>>(cursorPaging.Next);
}
/// <summary>
/// Fetches the next page of the complex IPaginatable object.
/// </summary>
/// <param name="paginatable">A complex IPaginatable object with a next page</param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TNext">The type of the next page</typeparam>
/// <returns></returns>
public Task<TNext> NextPage<T, TNext>(IPaginatable<T, TNext> paginatable)
{
Ensure.ArgumentNotNull(paginatable, nameof(paginatable));
return FetchPage<TNext>(paginatable.Next);
}
/// <summary>
/// Fetches the previous page of the paging object.
/// </summary>
/// <param name="paging">A paging object with a previous page</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public Task<Paging<T>> PreviousPage<T>(Paging<T> paging)
{
Ensure.ArgumentNotNull(paging, nameof(paging));
return FetchPage<Paging<T>>(paging.Previous);
}
/// <summary>
/// Fetches the previous page of the complex paging object.
/// </summary>
/// <param name="paging">A complex paging object with a previous page</param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TNext">The type of the next page</typeparam>
/// <returns></returns>
public Task<TNext> PreviousPage<T, TNext>(Paging<T, TNext> paging)
{
Ensure.ArgumentNotNull(paging, nameof(paging));
return FetchPage<TNext>(paging.Previous);
}
#if NETSTANDARD2_1
/// <summary>

View File

@ -13,6 +13,7 @@ namespace SpotifyAPI.Web
public IHTTPLogger? HTTPLogger { get; private set; }
public IRetryHandler? RetryHandler { get; private set; }
public IPaginator DefaultPaginator { get; private set; }
public IAPIConnector? APIConnector { get; private set; }
/// <summary>
/// This config spefies the internal parts of the SpotifyClient.
@ -24,6 +25,7 @@ namespace SpotifyAPI.Web
/// <param name="retryHandler"></param>
/// <param name="httpLogger"></param>
/// <param name="defaultPaginator"></param>
/// <param name="apiConnector"></param>
public SpotifyClientConfig(
Uri baseAddress,
IAuthenticator? authenticator,
@ -31,7 +33,8 @@ namespace SpotifyAPI.Web
IHTTPClient httpClient,
IRetryHandler? retryHandler,
IHTTPLogger? httpLogger,
IPaginator defaultPaginator
IPaginator defaultPaginator,
IAPIConnector? apiConnector = null
)
{
BaseAddress = baseAddress;
@ -41,6 +44,7 @@ namespace SpotifyAPI.Web
RetryHandler = retryHandler;
HTTPLogger = httpLogger;
DefaultPaginator = defaultPaginator;
APIConnector = apiConnector;
}
public SpotifyClientConfig WithToken(string token, string tokenType = "Bearer")
@ -145,6 +149,34 @@ namespace SpotifyAPI.Web
);
}
public SpotifyClientConfig WithAPIConnector(IAPIConnector apiConnector)
{
Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector));
return new SpotifyClientConfig(
BaseAddress,
Authenticator,
JSONSerializer,
HTTPClient,
RetryHandler,
HTTPLogger,
DefaultPaginator,
apiConnector
);
}
public IAPIConnector BuildAPIConnector()
{
return APIConnector ?? new APIConnector(
BaseAddress,
Authenticator,
JSONSerializer,
HTTPClient,
RetryHandler,
HTTPLogger
);
}
public static SpotifyClientConfig CreateDefault(string token, string tokenType = "Bearer")
{
return CreateDefault().WithAuthenticator(new TokenAuthenticator(token, tokenType));

View File

@ -0,0 +1,31 @@
using System.Globalization;
using System.Runtime.Serialization;
using System;
using SpotifyAPI.Web.Http;
namespace SpotifyAPI.Web
{
[Serializable]
public class APIPagingException : APIException
{
public TimeSpan RetryAfter { get; }
public APIPagingException(IResponse response) : base(response)
{
Ensure.ArgumentNotNull(response, nameof(response));
if (response.Headers.TryGetValue("Retry-After", out string? retryAfter))
{
RetryAfter = TimeSpan.FromSeconds(int.Parse(retryAfter, CultureInfo.InvariantCulture));
}
}
public APIPagingException() { }
public APIPagingException(string message) : base(message) { }
public APIPagingException(string message, Exception innerException) : base(message, innerException) { }
protected APIPagingException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
}