mirror of
https://github.com/Sarsoo/Spotify.NET.git
synced 2024-12-23 22:56:25 +00:00
Added paging methods, closes #538
This commit is contained in:
parent
8dd31420ea
commit
237925d51e
96
SpotifyAPI.Web.Tests/Clients/SpotifyClientTest.cs
Normal file
96
SpotifyAPI.Web.Tests/Clients/SpotifyClientTest.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -167,5 +167,15 @@ namespace SpotifyAPI.Web
|
|||||||
);
|
);
|
||||||
|
|
||||||
#endif
|
#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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SpotifyAPI.Web.Http;
|
using SpotifyAPI.Web.Http;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace SpotifyAPI.Web
|
namespace SpotifyAPI.Web
|
||||||
{
|
{
|
||||||
@ -29,14 +29,7 @@ namespace SpotifyAPI.Web
|
|||||||
#pragma warning restore CA2208
|
#pragma warning restore CA2208
|
||||||
}
|
}
|
||||||
|
|
||||||
_apiConnector = new APIConnector(
|
_apiConnector = config.BuildAPIConnector();
|
||||||
config.BaseAddress,
|
|
||||||
config.Authenticator,
|
|
||||||
config.JSONSerializer,
|
|
||||||
config.HTTPClient,
|
|
||||||
config.RetryHandler,
|
|
||||||
config.HTTPLogger
|
|
||||||
);
|
|
||||||
_apiConnector.ResponseReceived += (sender, response) =>
|
_apiConnector.ResponseReceived += (sender, response) =>
|
||||||
{
|
{
|
||||||
LastResponse = response;
|
LastResponse = response;
|
||||||
@ -124,6 +117,79 @@ namespace SpotifyAPI.Web
|
|||||||
return (paginator ?? DefaultPaginator).PaginateAll(firstPage, mapper, _apiConnector);
|
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
|
#if NETSTANDARD2_1
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -13,6 +13,7 @@ namespace SpotifyAPI.Web
|
|||||||
public IHTTPLogger? HTTPLogger { get; private set; }
|
public IHTTPLogger? HTTPLogger { get; private set; }
|
||||||
public IRetryHandler? RetryHandler { get; private set; }
|
public IRetryHandler? RetryHandler { get; private set; }
|
||||||
public IPaginator DefaultPaginator { get; private set; }
|
public IPaginator DefaultPaginator { get; private set; }
|
||||||
|
public IAPIConnector? APIConnector { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This config spefies the internal parts of the SpotifyClient.
|
/// This config spefies the internal parts of the SpotifyClient.
|
||||||
@ -24,6 +25,7 @@ namespace SpotifyAPI.Web
|
|||||||
/// <param name="retryHandler"></param>
|
/// <param name="retryHandler"></param>
|
||||||
/// <param name="httpLogger"></param>
|
/// <param name="httpLogger"></param>
|
||||||
/// <param name="defaultPaginator"></param>
|
/// <param name="defaultPaginator"></param>
|
||||||
|
/// <param name="apiConnector"></param>
|
||||||
public SpotifyClientConfig(
|
public SpotifyClientConfig(
|
||||||
Uri baseAddress,
|
Uri baseAddress,
|
||||||
IAuthenticator? authenticator,
|
IAuthenticator? authenticator,
|
||||||
@ -31,7 +33,8 @@ namespace SpotifyAPI.Web
|
|||||||
IHTTPClient httpClient,
|
IHTTPClient httpClient,
|
||||||
IRetryHandler? retryHandler,
|
IRetryHandler? retryHandler,
|
||||||
IHTTPLogger? httpLogger,
|
IHTTPLogger? httpLogger,
|
||||||
IPaginator defaultPaginator
|
IPaginator defaultPaginator,
|
||||||
|
IAPIConnector? apiConnector = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
BaseAddress = baseAddress;
|
BaseAddress = baseAddress;
|
||||||
@ -41,6 +44,7 @@ namespace SpotifyAPI.Web
|
|||||||
RetryHandler = retryHandler;
|
RetryHandler = retryHandler;
|
||||||
HTTPLogger = httpLogger;
|
HTTPLogger = httpLogger;
|
||||||
DefaultPaginator = defaultPaginator;
|
DefaultPaginator = defaultPaginator;
|
||||||
|
APIConnector = apiConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpotifyClientConfig WithToken(string token, string tokenType = "Bearer")
|
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")
|
public static SpotifyClientConfig CreateDefault(string token, string tokenType = "Bearer")
|
||||||
{
|
{
|
||||||
return CreateDefault().WithAuthenticator(new TokenAuthenticator(token, tokenType));
|
return CreateDefault().WithAuthenticator(new TokenAuthenticator(token, tokenType));
|
||||||
|
31
SpotifyAPI.Web/Exceptions/APIPagingException.cs
Normal file
31
SpotifyAPI.Web/Exceptions/APIPagingException.cs
Normal 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) { }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user