Pagination implementation adapted - Less methods,more defaults #451

This commit is contained in:
Jonas Dellinger 2020-05-31 15:56:49 +02:00
parent 7445d3ca0e
commit 5ae126699c
11 changed files with 567 additions and 100 deletions

View File

@ -59,7 +59,7 @@ namespace Example.CLI.PersistentConfig
var me = await spotify.UserProfile.Current(); var me = await spotify.UserProfile.Current();
Console.WriteLine($"Welcome {me.DisplayName} ({me.Id}), your authenticated!"); Console.WriteLine($"Welcome {me.DisplayName} ({me.Id}), your authenticated!");
var playlists = await spotify.PaginateAll(() => spotify.Playlists.CurrentUsers()); var playlists = await spotify.PaginateAll(spotify.Playlists.CurrentUsers());
Console.WriteLine($"Total Playlists in your Account: {playlists.Count}"); Console.WriteLine($"Total Playlists in your Account: {playlists.Count}");
_server.Dispose(); _server.Dispose();

View File

@ -4,14 +4,60 @@ namespace SpotifyAPI.Web
{ {
public interface IShowsClient public interface IShowsClient
{ {
/// <summary>
/// Get Spotify catalog information for a single show identified by its unique Spotify ID.
/// </summary>
/// <param name="showId">The Spotify ID for the show.</param>
/// <remarks>
/// https://developer.spotify.com/documentation/web-api/reference-beta/#endpoint-get-a-show
/// </remarks>
/// <returns></returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716")]
Task<FullShow> Get(string showId); Task<FullShow> Get(string showId);
/// <summary>
/// Get Spotify catalog information for a single show identified by its unique Spotify ID.
/// </summary>
/// <param name="showId">The Spotify ID for the show.</param>
/// <param name="request">The request-model which contains required and optional parameters.</param>
/// <remarks>
/// https://developer.spotify.com/documentation/web-api/reference-beta/#endpoint-get-a-show
/// </remarks>
/// <returns></returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716")]
Task<FullShow> Get(string showId, ShowRequest request); Task<FullShow> Get(string showId, ShowRequest request);
/// <summary>
/// Get Spotify catalog information for several shows based on their Spotify IDs.
/// </summary>
/// <param name="request">The request-model which contains required and optional parameters.</param>
/// <remarks>
/// https://developer.spotify.com/documentation/web-api/reference-beta/#endpoint-get-multiple-shows
/// </remarks>
/// <returns></returns>
Task<ShowsResponse> GetSeveral(ShowsRequest request); Task<ShowsResponse> GetSeveral(ShowsRequest request);
/// <summary>
/// Get Spotify catalog information about an shows episodes.
/// Optional parameters can be used to limit the number of episodes returned.
/// </summary>
/// <param name="showId">The Spotify ID for the show.</param>
/// <remarks>
/// https://developer.spotify.com/documentation/web-api/reference-beta/#endpoint-get-a-shows-episodes
/// </remarks>
/// <returns></returns>
Task<Paging<SimpleEpisode>> GetEpisodes(string showId); Task<Paging<SimpleEpisode>> GetEpisodes(string showId);
/// <summary>
/// Get Spotify catalog information about an shows episodes.
/// Optional parameters can be used to limit the number of episodes returned.
/// </summary>
/// <param name="showId">The Spotify ID for the show.</param>
/// <param name="request">The request-model which contains required and optional parameters.</param>
/// <remarks>
/// https://developer.spotify.com/documentation/web-api/reference-beta/#endpoint-get-a-shows-episodes
/// </remarks>
/// <returns></returns>
Task<Paging<SimpleEpisode>> GetEpisodes(string showId, ShowEpisodesRequest request); Task<Paging<SimpleEpisode>> GetEpisodes(string showId, ShowEpisodesRequest request);
} }
} }

View File

@ -1,3 +1,4 @@
using System.Threading;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -7,56 +8,296 @@ namespace SpotifyAPI.Web
{ {
public interface ISpotifyClient public interface ISpotifyClient
{ {
/// <summary>
/// The default paginator used by the Paginator methods
/// </summary>
/// <value></value>
IPaginator DefaultPaginator { get; } IPaginator DefaultPaginator { get; }
/// <summary>
/// Operations related to Spotify User Profiles
/// </summary>
/// <value></value>
IUserProfileClient UserProfile { get; } IUserProfileClient UserProfile { get; }
/// <summary>
/// Operations related to Spotify Browse Endpoints
/// </summary>
/// <value></value>
IBrowseClient Browse { get; } IBrowseClient Browse { get; }
/// <summary>
/// Operations related to Spotify Shows
/// </summary>
/// <value></value>
IShowsClient Shows { get; } IShowsClient Shows { get; }
/// <summary>
/// Operations related to Spotify Playlists
/// </summary>
/// <value></value>
IPlaylistsClient Playlists { get; } IPlaylistsClient Playlists { get; }
/// <summary>
/// Operations related to Spotify Search
/// </summary>
/// <value></value>
ISearchClient Search { get; } ISearchClient Search { get; }
/// <summary>
/// Operations related to Spotify Follows
/// </summary>
/// <value></value>
IFollowClient Follow { get; } IFollowClient Follow { get; }
/// <summary>
/// Operations related to Spotify Tracks
/// </summary>
/// <value></value>
ITracksClient Tracks { get; } ITracksClient Tracks { get; }
/// <summary>
/// Operations related to Spotify Player Endpoints
/// </summary>
/// <value></value>
IPlayerClient Player { get; } IPlayerClient Player { get; }
/// <summary>
/// Operations related to Spotify Albums
/// </summary>
/// <value></value>
IAlbumsClient Albums { get; } IAlbumsClient Albums { get; }
/// <summary>
/// Operations related to Spotify Artists
/// </summary>
/// <value></value>
IArtistsClient Artists { get; } IArtistsClient Artists { get; }
/// <summary>
/// Operations related to Spotify Personalization Endpoints
/// </summary>
/// <value></value>
IPersonalizationClient Personalization { get; } IPersonalizationClient Personalization { get; }
/// <summary>
/// Operations related to Spotify Podcast Episodes
/// </summary>
/// <value></value>
IEpisodesClient Episodes { get; } IEpisodesClient Episodes { get; }
/// <summary>
/// Operations related to Spotify User Library
/// </summary>
/// <value></value>
ILibraryClient Library { get; } ILibraryClient Library { get; }
/// <summary>
/// Returns the last response received by an API call.
/// </summary>
/// <value></value>
IResponse? LastResponse { get; } IResponse? LastResponse { get; }
Task<IList<T>> PaginateAll<T>(Paging<T> firstPage); /// <summary>
Task<IList<T>> PaginateAll<T>(Paging<T> firstPage, IPaginator paginator); /// Fetches all pages and returns them grouped in a list.
Task<IList<T>> PaginateAll<T>(Func<Task<Paging<T>>> getFirstPage); /// The default paginator will fetch all available resources without a delay between requests.
Task<IList<T>> PaginateAll<T>(Func<Task<Paging<T>>> getFirstPage, IPaginator paginator); /// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPage">The first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>A list containing all fetched pages</returns>
Task<IList<T>> PaginateAll<T>(Paging<T> firstPage, IPaginator? paginator = default!);
Task<IList<T>> PaginateAll<T, TNext>(Paging<T, TNext> firstPage, Func<TNext, Paging<T, TNext>> mapper); /// <summary>
Task<IList<T>> PaginateAll<T, TNext>(Paging<T, TNext> firstPage, Func<TNext, Paging<T, TNext>> mapper, IPaginator paginator); /// Fetches all pages and returns them grouped in a list.
Task<IList<T>> PaginateAll<T, TNext>(Func<Task<Paging<T, TNext>>> getFirstPage, Func<TNext, Paging<T, TNext>> mapper); /// The default paginator will fetch all available resources without a delay between requests.
Task<IList<T>> PaginateAll<T, TNext>(Func<Task<Paging<T, TNext>>> getFirstPage, Func<TNext, Paging<T, TNext>> mapper, IPaginator paginator); /// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="getFirstPage">A function to retrive the first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>A list containing all fetched pages</returns>
Task<IList<T>> PaginateAll<T>(Func<Task<Paging<T>>> getFirstPage, IPaginator? paginator = default!);
/// <summary>
/// Fetches all pages and returns them grouped in a list.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPageTask">A task to retrive the first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>A list containing all fetched pages</returns>
Task<IList<T>> PaginateAll<T>(Task<Paging<T>> firstPageTask, IPaginator? paginator = default!);
/// <summary>
/// Fetches all pages and returns them grouped in a list.
/// Some responses (e.g search response) have the pagination nested in a JSON Property.
/// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPage">A first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>A list containing all fetched pages</returns>
Task<IList<T>> PaginateAll<T, TNext>(
Paging<T, TNext> firstPage,
Func<TNext, Paging<T, TNext>> mapper,
IPaginator? paginator = default!
);
/// <summary>
/// Fetches all pages and returns them grouped in a list.
/// Some responses (e.g search response) have the pagination nested in a JSON Property.
/// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="getFirstPage">A function to retrive the first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <typeparam name="TNext">The Response-Type</typeparam>
/// <returns></returns>
Task<IList<T>> PaginateAll<T, TNext>(
Func<Task<Paging<T, TNext>>> getFirstPage,
Func<TNext, Paging<T, TNext>> mapper,
IPaginator? paginator = default!
);
/// <summary>
/// Fetches all pages and returns them grouped in a list.
/// Some responses (e.g search response) have the pagination nested in a JSON Property.
/// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPageTask">A Task to retrive the first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <typeparam name="TNext">The Response-Type</typeparam>
/// <returns></returns>
Task<IList<T>> PaginateAll<T, TNext>(
Task<Paging<T, TNext>> firstPageTask,
Func<TNext, Paging<T, TNext>> mapper,
IPaginator? paginator = default!
);
#if NETSTANDARD2_1 #if NETSTANDARD2_1
IAsyncEnumerable<T> Paginate<T>(Paging<T> firstPage); /// <summary>
IAsyncEnumerable<T> Paginate<T>(Paging<T> firstPage, IPaginator paginator); /// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
IAsyncEnumerable<T> Paginate<T>(Func<Task<Paging<T>>> getFirstPage); /// The default paginator will fetch all available resources without a delay between requests.
IAsyncEnumerable<T> Paginate<T>(Func<Task<Paging<T>>> getFirstPage, IPaginator paginator); /// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPage">A first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>An iterable IAsyncEnumerable</returns>
IAsyncEnumerable<T> Paginate<T>(
Paging<T> firstPage,
IPaginator? paginator = default!,
CancellationToken cancellationToken = default!
);
IAsyncEnumerable<T> Paginate<T, TNext>(Paging<T, TNext> firstPage, Func<TNext, Paging<T, TNext>> mapper); /// <summary>
IAsyncEnumerable<T> Paginate<T, TNext>(Paging<T, TNext> firstPage, Func<TNext, Paging<T, TNext>> mapper, IPaginator paginator); /// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
IAsyncEnumerable<T> Paginate<T, TNext>(Func<Task<Paging<T, TNext>>> getFirstPage, Func<TNext, Paging<T, TNext>> mapper); /// The default paginator will fetch all available resources without a delay between requests.
IAsyncEnumerable<T> Paginate<T, TNext>(Func<Task<Paging<T, TNext>>> getFirstPage, Func<TNext, Paging<T, TNext>> mapper, IPaginator paginator); /// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="getFirstPage">A Function to retrive the first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>An iterable IAsyncEnumerable</returns>
IAsyncEnumerable<T> Paginate<T>(
Func<Task<Paging<T>>> getFirstPage,
IPaginator? paginator = default!,
CancellationToken cancellationToken = default!
);
/// <summary>
/// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPageTask">A Task to retrive the first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>An iterable IAsyncEnumerable</returns>
IAsyncEnumerable<T> Paginate<T>(
Task<Paging<T>> firstPageTask,
IPaginator? paginator = default!,
CancellationToken cancellationToken = default!
);
/// <summary>
/// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
/// Some responses (e.g search response) have the pagination nested in a JSON Property.
/// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPage">A first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <typeparam name="TNext">The Response-Type</typeparam>
/// <returns></returns>
IAsyncEnumerable<T> Paginate<T, TNext>(
Paging<T, TNext> firstPage,
Func<TNext, Paging<T, TNext>> mapper,
IPaginator? paginator = default!,
CancellationToken cancellationToken = default!
);
/// <summary>
/// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
/// Some responses (e.g search response) have the pagination nested in a JSON Property.
/// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="getFirstPage">A Function to retrive the first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <typeparam name="TNext">The Response-Type</typeparam>
/// <returns></returns>
IAsyncEnumerable<T> Paginate<T, TNext>(
Func<Task<Paging<T, TNext>>> getFirstPage,
Func<TNext, Paging<T, TNext>> mapper,
IPaginator? paginator = default!,
CancellationToken cancellationToken = default!
);
/// <summary>
/// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
/// Some responses (e.g search response) have the pagination nested in a JSON Property.
/// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPageTask">A Task to retrive the first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <typeparam name="TNext">The Response-Type</typeparam>
/// <returns></returns>
IAsyncEnumerable<T> Paginate<T, TNext>(
Task<Paging<T, TNext>> firstPageTask,
Func<TNext, Paging<T, TNext>> mapper,
IPaginator? paginator = default!,
CancellationToken cancellationToken = default!
);
#endif #endif
} }
} }

View File

@ -1,7 +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;
namespace SpotifyAPI.Web namespace SpotifyAPI.Web
{ {
@ -80,157 +82,287 @@ namespace SpotifyAPI.Web
public IResponse? LastResponse { get; private set; } public IResponse? LastResponse { get; private set; }
public Task<IList<T>> PaginateAll<T>(Paging<T> firstPage) /// <summary>
/// Fetches all pages and returns them grouped in a list.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPage">The first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>A list containing all fetched pages</returns>
public Task<IList<T>> PaginateAll<T>(Paging<T> firstPage, IPaginator? paginator = null)
{ {
return DefaultPaginator.PaginateAll(firstPage, _apiConnector); return (paginator ?? DefaultPaginator).PaginateAll(firstPage, _apiConnector);
} }
public Task<IList<T>> PaginateAll<T>(Paging<T> firstPage, IPaginator paginator) /// <summary>
{ /// Fetches all pages and returns them grouped in a list.
Ensure.ArgumentNotNull(paginator, nameof(paginator)); /// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
return paginator.PaginateAll(firstPage, _apiConnector); /// </summary>
} /// <param name="getFirstPage">A function to retrive the first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
public async Task<IList<T>> PaginateAll<T>(Func<Task<Paging<T>>> getFirstPage) /// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>A list containing all fetched pages</returns>
public async Task<IList<T>> PaginateAll<T>(Func<Task<Paging<T>>> getFirstPage, IPaginator? paginator = null)
{ {
Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage)); Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage));
return await DefaultPaginator.PaginateAll( var firstPage = await getFirstPage().ConfigureAwait(false);
await getFirstPage().ConfigureAwait(false), _apiConnector return await (paginator ?? DefaultPaginator).PaginateAll(firstPage, _apiConnector).ConfigureAwait(false);
).ConfigureAwait(false);
} }
public async Task<IList<T>> PaginateAll<T>(Func<Task<Paging<T>>> getFirstPage, IPaginator paginator) /// <summary>
/// Fetches all pages and returns them grouped in a list.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPageTask">A task to retrive the first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>A list containing all fetched pages</returns>
public async Task<IList<T>> PaginateAll<T>(Task<Paging<T>> firstPageTask, IPaginator? paginator = null)
{ {
Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage)); Ensure.ArgumentNotNull(firstPageTask, nameof(firstPageTask));
Ensure.ArgumentNotNull(paginator, nameof(paginator));
return await paginator.PaginateAll( var firstPage = await firstPageTask.ConfigureAwait(false);
await getFirstPage().ConfigureAwait(false), _apiConnector return await (paginator ?? DefaultPaginator).PaginateAll(firstPage, _apiConnector).ConfigureAwait(false);
).ConfigureAwait(false);
} }
public Task<IList<T>> PaginateAll<T, TNext>(
Paging<T, TNext> firstPage,
Func<TNext, Paging<T, TNext>> mapper
)
{
return DefaultPaginator.PaginateAll(firstPage, mapper, _apiConnector);
}
public async Task<IList<T>> PaginateAll<T, TNext>(
Func<Task<Paging<T, TNext>>> getFirstPage,
Func<TNext, Paging<T, TNext>> mapper
)
{
Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage));
return await DefaultPaginator.PaginateAll(await getFirstPage().ConfigureAwait(false), mapper, _apiConnector).ConfigureAwait(false);
}
/// <summary>
/// Fetches all pages and returns them grouped in a list.
/// Some responses (e.g search response) have the pagination nested in a JSON Property.
/// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPage">A first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>A list containing all fetched pages</returns>
public Task<IList<T>> PaginateAll<T, TNext>( public Task<IList<T>> PaginateAll<T, TNext>(
Paging<T, TNext> firstPage, Paging<T, TNext> firstPage,
Func<TNext, Paging<T, TNext>> mapper, Func<TNext, Paging<T, TNext>> mapper,
IPaginator paginator) IPaginator? paginator = null
)
{ {
Ensure.ArgumentNotNull(paginator, nameof(paginator)); return (paginator ?? DefaultPaginator).PaginateAll(firstPage, mapper, _apiConnector);
return paginator.PaginateAll(firstPage, mapper, _apiConnector);
} }
/// <summary>
/// Fetches all pages and returns them grouped in a list.
/// Some responses (e.g search response) have the pagination nested in a JSON Property.
/// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="getFirstPage">A function to retrive the first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <typeparam name="TNext">The Response-Type</typeparam>
/// <returns></returns>
public async Task<IList<T>> PaginateAll<T, TNext>( public async Task<IList<T>> PaginateAll<T, TNext>(
Func<Task<Paging<T, TNext>>> getFirstPage, Func<Task<Paging<T, TNext>>> getFirstPage,
Func<TNext, Paging<T, TNext>> mapper, Func<TNext, Paging<T, TNext>> mapper,
IPaginator paginator IPaginator? paginator = null
) )
{ {
Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage)); Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage));
Ensure.ArgumentNotNull(paginator, nameof(paginator));
return await paginator.PaginateAll( var firstPage = await getFirstPage().ConfigureAwait(false);
await getFirstPage().ConfigureAwait(false), mapper, _apiConnector return await (paginator ?? DefaultPaginator).PaginateAll(firstPage, mapper, _apiConnector).ConfigureAwait(false);
).ConfigureAwait(false);
} }
/// <summary>
/// Fetches all pages and returns them grouped in a list.
/// Some responses (e.g search response) have the pagination nested in a JSON Property.
/// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPageTask">A Task to retrive the first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <typeparam name="TNext">The Response-Type</typeparam>
/// <returns></returns>
public async Task<IList<T>> PaginateAll<T, TNext>(
Task<Paging<T, TNext>> firstPageTask,
Func<TNext, Paging<T, TNext>> mapper,
IPaginator? paginator = null
)
{
Ensure.ArgumentNotNull(firstPageTask, nameof(firstPageTask));
var firstPage = await firstPageTask.ConfigureAwait(false);
return await (paginator ?? DefaultPaginator).PaginateAll(firstPage, mapper, _apiConnector).ConfigureAwait(false);
}
#if NETSTANDARD2_1 #if NETSTANDARD2_1
public IAsyncEnumerable<T> Paginate<T>(Paging<T> firstPage)
/// <summary>
/// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPage">A first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>An iterable IAsyncEnumerable</returns>
public IAsyncEnumerable<T> Paginate<T>(
Paging<T> firstPage,
IPaginator? paginator = null,
CancellationToken cancellationToken = default
)
{ {
return DefaultPaginator.Paginate(firstPage, _apiConnector); return (paginator ?? DefaultPaginator).Paginate(firstPage, _apiConnector, cancellationToken);
} }
public IAsyncEnumerable<T> Paginate<T>(Paging<T> firstPage, IPaginator paginator) /// <summary>
/// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="getFirstPage">A Function to retrive the first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>An iterable IAsyncEnumerable</returns>
public async IAsyncEnumerable<T> Paginate<T>(
Func<Task<Paging<T>>> getFirstPage,
IPaginator? paginator = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default
)
{ {
Ensure.ArgumentNotNull(paginator, nameof(paginator));
return paginator.Paginate(firstPage, _apiConnector);
}
public async IAsyncEnumerable<T> Paginate<T>(Func<Task<Paging<T>>> getFirstPage)
{
Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage)); Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage));
var firstPage = await getFirstPage().ConfigureAwait(false); var firstPage = await getFirstPage().ConfigureAwait(false);
await foreach (var item in DefaultPaginator.Paginate(firstPage, _apiConnector)) await foreach (var item in (paginator ?? DefaultPaginator)
.Paginate(firstPage, _apiConnector)
.WithCancellation(cancellationToken)
)
{ {
yield return item; yield return item;
} }
} }
public async IAsyncEnumerable<T> Paginate<T>(Func<Task<Paging<T>>> getFirstPage, IPaginator paginator) /// <summary>
/// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPageTask">A Task to retrive the first page, will be included in the output list!</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <returns>An iterable IAsyncEnumerable</returns>
public async IAsyncEnumerable<T> Paginate<T>(
Task<Paging<T>> firstPageTask,
IPaginator? paginator = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{ {
Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage)); Ensure.ArgumentNotNull(firstPageTask, nameof(firstPageTask));
Ensure.ArgumentNotNull(paginator, nameof(paginator));
var firstPage = await getFirstPage().ConfigureAwait(false); var firstPage = await firstPageTask.ConfigureAwait(false);
await foreach (var item in DefaultPaginator.Paginate(firstPage, _apiConnector)) await foreach (var item in (paginator ?? DefaultPaginator)
.Paginate(firstPage, _apiConnector)
.WithCancellation(cancellationToken)
)
{ {
yield return item; yield return item;
} }
} }
public IAsyncEnumerable<T> Paginate<T, TNext>(Paging<T, TNext> firstPage, Func<TNext, Paging<T, TNext>> mapper) /// <summary>
{ /// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
return DefaultPaginator.Paginate(firstPage, mapper, _apiConnector); /// Some responses (e.g search response) have the pagination nested in a JSON Property.
} /// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPage">A first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <typeparam name="TNext">The Response-Type</typeparam>
/// <returns></returns>
public IAsyncEnumerable<T> Paginate<T, TNext>( public IAsyncEnumerable<T> Paginate<T, TNext>(
Paging<T, TNext> firstPage, Paging<T, TNext> firstPage,
Func<TNext, Paging<T, TNext>> mapper, Func<TNext, Paging<T, TNext>> mapper,
IPaginator paginator IPaginator? paginator = null,
CancellationToken cancellationToken = default
) )
{ {
Ensure.ArgumentNotNull(paginator, nameof(paginator)); return (paginator ?? DefaultPaginator).Paginate(firstPage, mapper, _apiConnector, cancellationToken);
return paginator.Paginate(firstPage, mapper, _apiConnector);
} }
/// <summary>
/// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
/// Some responses (e.g search response) have the pagination nested in a JSON Property.
/// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="getFirstPage">A Function to retrive the first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <typeparam name="TNext">The Response-Type</typeparam>
/// <returns></returns>
public async IAsyncEnumerable<T> Paginate<T, TNext>( public async IAsyncEnumerable<T> Paginate<T, TNext>(
Func<Task<Paging<T, TNext>>> getFirstPage, Func<Task<Paging<T, TNext>>> getFirstPage,
Func<TNext, Paging<T, TNext>> mapper Func<TNext, Paging<T, TNext>> mapper,
) IPaginator? paginator = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{ {
Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage)); Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage));
var firstPage = await getFirstPage().ConfigureAwait(false); var firstPage = await getFirstPage().ConfigureAwait(false);
await foreach (var item in DefaultPaginator.Paginate(firstPage, mapper, _apiConnector)) await foreach (var item in (paginator ?? DefaultPaginator)
.Paginate(firstPage, mapper, _apiConnector)
.WithCancellation(cancellationToken)
)
{ {
yield return item; yield return item;
} }
} }
/// <summary>
/// Paginate through pages by using IAsyncEnumerable, introduced in C# 8
/// Some responses (e.g search response) have the pagination nested in a JSON Property.
/// To workaround this limitation, the mapper is required and needs to point to the correct next pagination.
/// The default paginator will fetch all available resources without a delay between requests.
/// This can drain your request limit quite fast, so consider using a custom paginator with delays.
/// </summary>
/// <param name="firstPageTask">A Task to retrive the first page, will be included in the output list!</param>
/// <param name="mapper">A function which maps response objects to the next paging object</param>
/// <param name="paginator">Optional. If not supplied, DefaultPaginator will be used</param>
/// <param name="cancellationToken">An optional Cancellation Token</param>
/// <typeparam name="T">The Paging-Type</typeparam>
/// <typeparam name="TNext">The Response-Type</typeparam>
/// <returns></returns>
public async IAsyncEnumerable<T> Paginate<T, TNext>( public async IAsyncEnumerable<T> Paginate<T, TNext>(
Func<Task<Paging<T, TNext>>> getFirstPage, Task<Paging<T, TNext>> firstPageTask,
Func<TNext, Paging<T, TNext>> mapper, Func<TNext, Paging<T, TNext>> mapper,
IPaginator paginator IPaginator? paginator = null,
) [EnumeratorCancellation] CancellationToken cancellationToken = default)
{ {
Ensure.ArgumentNotNull(getFirstPage, nameof(getFirstPage)); Ensure.ArgumentNotNull(firstPageTask, nameof(firstPageTask));
Ensure.ArgumentNotNull(paginator, nameof(paginator));
var firstPage = await getFirstPage().ConfigureAwait(false); var firstPage = await firstPageTask.ConfigureAwait(false);
await foreach (var item in paginator.Paginate(firstPage, mapper, _apiConnector)) await foreach (var item in (paginator ?? DefaultPaginator)
.Paginate(firstPage, mapper, _apiConnector)
.WithCancellation(cancellationToken)
)
{ {
yield return item; yield return item;
} }

View File

@ -2,12 +2,30 @@ namespace SpotifyAPI.Web
{ {
public class ShowEpisodesRequest : RequestParams public class ShowEpisodesRequest : RequestParams
{ {
/// <summary>
/// The maximum number of episodes to return. Default: 20. Minimum: 1. Maximum: 50.
/// </summary>
/// <value></value>
[QueryParam("limit")] [QueryParam("limit")]
public int? Limit { get; set; } public int? Limit { get; set; }
/// <summary>
/// The index of the first episode to return.
/// Default: 0 (the first object). Use with limit to get the next set of episodes.
/// </summary>
/// <value></value>
[QueryParam("offset")] [QueryParam("offset")]
public int? Offset { get; set; } public int? Offset { get; set; }
/// <summary>
/// An ISO 3166-1 alpha-2 country code. If a country code is specified, only shows and episodes
/// that are available in that market will be returned.
/// If a valid user access token is specified in the request header,
/// the country associated with the user account will take priority over this parameter.
/// Note: If neither market or user country are provided, the content is considered unavailable for the client.
/// Users can view the country that is associated with their account in the account settings.
/// </summary>
/// <value></value>
[QueryParam("market")] [QueryParam("market")]
public string? Market { get; set; } public string? Market { get; set; }
} }

View File

@ -2,6 +2,16 @@ namespace SpotifyAPI.Web
{ {
public class ShowRequest : RequestParams public class ShowRequest : RequestParams
{ {
/// <summary>
/// An ISO 3166-1 alpha-2 country code. If a country code is specified,
/// only shows and episodes that are available in that market will be returned.
/// If a valid user access token is specified in the request header,
/// the country associated with the user account will take priority over this parameter.
/// Note: If neither market or user country are provided, the content
/// is considered unavailable for the client.
/// Users can view the country that is associated with their account in the account settings.
/// </summary>
/// <value></value>
[QueryParam("market")] [QueryParam("market")]
public string? Market { get; set; } public string? Market { get; set; }
} }

View File

@ -4,6 +4,12 @@ namespace SpotifyAPI.Web
{ {
public class ShowsRequest : RequestParams public class ShowsRequest : RequestParams
{ {
/// <summary>
/// Get Spotify catalog information for several shows based on their Spotify IDs.
/// </summary>
/// <param name="ids">
/// A comma-separated list of the Spotify IDs for the shows. Maximum: 50 IDs.
/// </param>
public ShowsRequest(IList<string> ids) public ShowsRequest(IList<string> ids)
{ {
Ensure.ArgumentNotNullOrEmptyList(ids, nameof(ids)); Ensure.ArgumentNotNullOrEmptyList(ids, nameof(ids));
@ -11,9 +17,23 @@ namespace SpotifyAPI.Web
Ids = ids; Ids = ids;
} }
/// <summary>
/// A comma-separated list of the Spotify IDs for the shows. Maximum: 50 IDs.
/// </summary>
/// <value></value>
[QueryParam("ids")] [QueryParam("ids")]
public IList<string> Ids { get; } public IList<string> Ids { get; }
/// <summary>
/// An ISO 3166-1 alpha-2 country code. If a country code is specified, only shows and episodes
/// that are available in that market will be returned.
/// If a valid user access token is specified in the request header,
/// the country associated with the user account will take priority over this parameter.
/// Note: If neither market or user country are provided,
/// the content is considered unavailable for the client.
/// Users can view the country that is associated with their account in the account settings.
/// </summary>
/// <value></value>
[QueryParam("market")] [QueryParam("market")]
public string? Market { get; set; } public string? Market { get; set; }
} }

View File

@ -2,7 +2,7 @@ namespace SpotifyAPI.Web
{ {
public class CategoriesResponse public class CategoriesResponse
{ {
public Paging<Category> Categories { get; set; } = default!; public Paging<Category, CategoriesResponse> Categories { get; set; } = default!;
} }
} }

View File

@ -2,7 +2,7 @@ namespace SpotifyAPI.Web
{ {
public class CategoryPlaylistsResponse public class CategoryPlaylistsResponse
{ {
public Paging<SimplePlaylist> Playlists { get; set; } = default!; public Paging<SimplePlaylist, CategoryPlaylistsResponse> Playlists { get; set; } = default!;
} }
} }

View File

@ -3,7 +3,7 @@ namespace SpotifyAPI.Web
public class FeaturedPlaylistsResponse public class FeaturedPlaylistsResponse
{ {
public string Message { get; set; } = default!; public string Message { get; set; } = default!;
public Paging<SimplePlaylist> Playlists { get; set; } = default!; public Paging<SimplePlaylist, FeaturedPlaylistsResponse> Playlists { get; set; } = default!;
} }
} }

View File

@ -3,7 +3,7 @@ namespace SpotifyAPI.Web
public class NewReleasesResponse public class NewReleasesResponse
{ {
public string Message { get; set; } = default!; public string Message { get; set; } = default!;
public Paging<SimpleAlbum> Albums { get; set; } = default!; public Paging<SimpleAlbum, NewReleasesResponse> Albums { get; set; } = default!;
} }
} }