Skip to content

Commit 237925d

Browse files
committed
Added paging methods, closes JohnnyCrazy#538
1 parent 8dd3142 commit 237925d

5 files changed

Lines changed: 245 additions & 10 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System.Reflection;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Moq;
5+
using NUnit.Framework;
6+
using SpotifyAPI.Web.Http;
7+
8+
namespace SpotifyAPI.Web.Tests
9+
{
10+
[TestFixture]
11+
public class SpotifyClientTest
12+
{
13+
[Test]
14+
public async Task NextPageForIPaginatable()
15+
{
16+
var api = new Mock<IAPIConnector>();
17+
var config = SpotifyClientConfig.CreateDefault("FakeToken").WithAPIConnector(api.Object);
18+
var spotify = new SpotifyClient(config);
19+
20+
var response = new SearchResponse
21+
{
22+
Albums = new Paging<SimpleAlbum, SearchResponse>
23+
{
24+
Next = "https://next-url",
25+
}
26+
};
27+
28+
await spotify.NextPage(response.Albums);
29+
api.Verify(a => a.Get<SearchResponse>(new System.Uri("https://next-url")), Times.Once);
30+
}
31+
32+
[Test]
33+
public async Task NextPageForCursorPaging()
34+
{
35+
var api = new Mock<IAPIConnector>();
36+
var config = SpotifyClientConfig.CreateDefault("FakeToken").WithAPIConnector(api.Object);
37+
var spotify = new SpotifyClient(config);
38+
39+
var response = new CursorPaging<PlayHistoryItem>
40+
{
41+
Next = "https://next-url"
42+
};
43+
44+
await spotify.NextPage(response);
45+
api.Verify(a => a.Get<CursorPaging<PlayHistoryItem>>(new System.Uri("https://next-url")), Times.Once);
46+
}
47+
48+
[Test]
49+
public async Task NextPageForPaging()
50+
{
51+
var api = new Mock<IAPIConnector>();
52+
var config = SpotifyClientConfig.CreateDefault("FakeToken").WithAPIConnector(api.Object);
53+
var spotify = new SpotifyClient(config);
54+
55+
var response = new Paging<PlayHistoryItem>
56+
{
57+
Next = "https://next-url"
58+
};
59+
60+
await spotify.NextPage(response);
61+
api.Verify(a => a.Get<Paging<PlayHistoryItem>>(new System.Uri("https://next-url")), Times.Once);
62+
}
63+
64+
[Test]
65+
public async Task PreviousPageForPaging()
66+
{
67+
var api = new Mock<IAPIConnector>();
68+
var config = SpotifyClientConfig.CreateDefault("FakeToken").WithAPIConnector(api.Object);
69+
var spotify = new SpotifyClient(config);
70+
71+
var response = new Paging<PlayHistoryItem>
72+
{
73+
Previous = "https://previous-url"
74+
};
75+
76+
await spotify.PreviousPage(response);
77+
api.Verify(a => a.Get<Paging<PlayHistoryItem>>(new System.Uri("https://previous-url")), Times.Once);
78+
}
79+
80+
[Test]
81+
public async Task PreviousPageForCustomPaging()
82+
{
83+
var api = new Mock<IAPIConnector>();
84+
var config = SpotifyClientConfig.CreateDefault("FakeToken").WithAPIConnector(api.Object);
85+
var spotify = new SpotifyClient(config);
86+
87+
var response = new Paging<PlayHistoryItem, SearchResponse>
88+
{
89+
Previous = "https://previous-url"
90+
};
91+
92+
await spotify.PreviousPage(response);
93+
api.Verify(a => a.Get<SearchResponse>(new System.Uri("https://previous-url")), Times.Once);
94+
}
95+
}
96+
}

‎SpotifyAPI.Web/Clients/Interfaces/ISpotifyClient.cs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,5 +167,15 @@ IAsyncEnumerable<T> Paginate<T, TNext>(
167167
);
168168

169169
#endif
170+
171+
public Task<Paging<T>> NextPage<T>(Paging<T> paging);
172+
173+
public Task<CursorPaging<T>> NextPage<T>(CursorPaging<T> cursorPaging);
174+
175+
public Task<TNext> NextPage<T, TNext>(IPaginatable<T, TNext> paginatable);
176+
177+
public Task<Paging<T>> PreviousPage<T>(Paging<T> paging);
178+
179+
public Task<TNext> PreviousPage<T, TNext>(Paging<T, TNext> paging);
170180
}
171181
}

‎SpotifyAPI.Web/Clients/SpotifyClient.cs‎

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Threading;
43
using System.Threading.Tasks;
54
using SpotifyAPI.Web.Http;
65
using System.Runtime.CompilerServices;
6+
using System.Threading;
77

88
namespace SpotifyAPI.Web
99
{
@@ -29,14 +29,7 @@ public SpotifyClient(SpotifyClientConfig config)
2929
#pragma warning restore CA2208
3030
}
3131

32-
_apiConnector = new APIConnector(
33-
config.BaseAddress,
34-
config.Authenticator,
35-
config.JSONSerializer,
36-
config.HTTPClient,
37-
config.RetryHandler,
38-
config.HTTPLogger
39-
);
32+
_apiConnector = config.BuildAPIConnector();
4033
_apiConnector.ResponseReceived += (sender, response) =>
4134
{
4235
LastResponse = response;
@@ -124,6 +117,79 @@ public Task<IList<T>> PaginateAll<T, TNext>(
124117
return (paginator ?? DefaultPaginator).PaginateAll(firstPage, mapper, _apiConnector);
125118
}
126119

120+
private Task<T> FetchPage<T>(string? nextUrl)
121+
{
122+
if (nextUrl == null)
123+
{
124+
throw new APIPagingException("The paging object has no next page");
125+
}
126+
127+
return _apiConnector.Get<T>(new Uri(nextUrl, UriKind.Absolute));
128+
}
129+
130+
/// <summary>
131+
/// Fetches the next page of the paging object
132+
/// </summary>
133+
/// <param name="paging">A paging object which has a next page</param>
134+
/// <typeparam name="T"></typeparam>
135+
/// <returns></returns>
136+
public Task<Paging<T>> NextPage<T>(Paging<T> paging)
137+
{
138+
Ensure.ArgumentNotNull(paging, nameof(paging));
139+
return FetchPage<Paging<T>>(paging.Next);
140+
}
141+
142+
/// <summary>
143+
/// Fetches the next page of the cursor paging object
144+
/// </summary>
145+
/// <param name="cursorPaging">A cursor paging object which has a next page</param>
146+
/// <typeparam name="T"></typeparam>
147+
/// <returns></returns>
148+
public Task<CursorPaging<T>> NextPage<T>(CursorPaging<T> cursorPaging)
149+
{
150+
Ensure.ArgumentNotNull(cursorPaging, nameof(cursorPaging));
151+
return FetchPage<CursorPaging<T>>(cursorPaging.Next);
152+
}
153+
154+
/// <summary>
155+
/// Fetches the next page of the complex IPaginatable object.
156+
/// </summary>
157+
/// <param name="paginatable">A complex IPaginatable object with a next page</param>
158+
/// <typeparam name="T"></typeparam>
159+
/// <typeparam name="TNext">The type of the next page</typeparam>
160+
/// <returns></returns>
161+
public Task<TNext> NextPage<T, TNext>(IPaginatable<T, TNext> paginatable)
162+
{
163+
Ensure.ArgumentNotNull(paginatable, nameof(paginatable));
164+
return FetchPage<TNext>(paginatable.Next);
165+
}
166+
167+
/// <summary>
168+
/// Fetches the previous page of the paging object.
169+
/// </summary>
170+
/// <param name="paging">A paging object with a previous page</param>
171+
/// <typeparam name="T"></typeparam>
172+
/// <returns></returns>
173+
public Task<Paging<T>> PreviousPage<T>(Paging<T> paging)
174+
{
175+
Ensure.ArgumentNotNull(paging, nameof(paging));
176+
return FetchPage<Paging<T>>(paging.Previous);
177+
}
178+
179+
180+
/// <summary>
181+
/// Fetches the previous page of the complex paging object.
182+
/// </summary>
183+
/// <param name="paging">A complex paging object with a previous page</param>
184+
/// <typeparam name="T"></typeparam>
185+
/// <typeparam name="TNext">The type of the next page</typeparam>
186+
/// <returns></returns>
187+
public Task<TNext> PreviousPage<T, TNext>(Paging<T, TNext> paging)
188+
{
189+
Ensure.ArgumentNotNull(paging, nameof(paging));
190+
return FetchPage<TNext>(paging.Previous);
191+
}
192+
127193
#if NETSTANDARD2_1
128194

129195
/// <summary>

‎SpotifyAPI.Web/Clients/SpotifyClientConfig.cs‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class SpotifyClientConfig
1313
public IHTTPLogger? HTTPLogger { get; private set; }
1414
public IRetryHandler? RetryHandler { get; private set; }
1515
public IPaginator DefaultPaginator { get; private set; }
16+
public IAPIConnector? APIConnector { get; private set; }
1617

1718
/// <summary>
1819
/// This config spefies the internal parts of the SpotifyClient.
@@ -24,14 +25,16 @@ public class SpotifyClientConfig
2425
/// <param name="retryHandler"></param>
2526
/// <param name="httpLogger"></param>
2627
/// <param name="defaultPaginator"></param>
28+
/// <param name="apiConnector"></param>
2729
public SpotifyClientConfig(
2830
Uri baseAddress,
2931
IAuthenticator? authenticator,
3032
IJSONSerializer jsonSerializer,
3133
IHTTPClient httpClient,
3234
IRetryHandler? retryHandler,
3335
IHTTPLogger? httpLogger,
34-
IPaginator defaultPaginator
36+
IPaginator defaultPaginator,
37+
IAPIConnector? apiConnector = null
3538
)
3639
{
3740
BaseAddress = baseAddress;
@@ -41,6 +44,7 @@ IPaginator defaultPaginator
4144
RetryHandler = retryHandler;
4245
HTTPLogger = httpLogger;
4346
DefaultPaginator = defaultPaginator;
47+
APIConnector = apiConnector;
4448
}
4549

4650
public SpotifyClientConfig WithToken(string token, string tokenType = "Bearer")
@@ -145,6 +149,34 @@ public SpotifyClientConfig WithDefaultPaginator(IPaginator defaultPaginator)
145149
);
146150
}
147151

152+
public SpotifyClientConfig WithAPIConnector(IAPIConnector apiConnector)
153+
{
154+
Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector));
155+
156+
return new SpotifyClientConfig(
157+
BaseAddress,
158+
Authenticator,
159+
JSONSerializer,
160+
HTTPClient,
161+
RetryHandler,
162+
HTTPLogger,
163+
DefaultPaginator,
164+
apiConnector
165+
);
166+
}
167+
168+
public IAPIConnector BuildAPIConnector()
169+
{
170+
return APIConnector ?? new APIConnector(
171+
BaseAddress,
172+
Authenticator,
173+
JSONSerializer,
174+
HTTPClient,
175+
RetryHandler,
176+
HTTPLogger
177+
);
178+
}
179+
148180
public static SpotifyClientConfig CreateDefault(string token, string tokenType = "Bearer")
149181
{
150182
return CreateDefault().WithAuthenticator(new TokenAuthenticator(token, tokenType));
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.Globalization;
2+
using System.Runtime.Serialization;
3+
using System;
4+
using SpotifyAPI.Web.Http;
5+
6+
namespace SpotifyAPI.Web
7+
{
8+
[Serializable]
9+
public class APIPagingException : APIException
10+
{
11+
public TimeSpan RetryAfter { get; }
12+
13+
public APIPagingException(IResponse response) : base(response)
14+
{
15+
Ensure.ArgumentNotNull(response, nameof(response));
16+
17+
if (response.Headers.TryGetValue("Retry-After", out string? retryAfter))
18+
{
19+
RetryAfter = TimeSpan.FromSeconds(int.Parse(retryAfter, CultureInfo.InvariantCulture));
20+
}
21+
}
22+
23+
public APIPagingException() { }
24+
25+
public APIPagingException(string message) : base(message) { }
26+
27+
public APIPagingException(string message, Exception innerException) : base(message, innerException) { }
28+
29+
protected APIPagingException(SerializationInfo info, StreamingContext context) : base(info, context) { }
30+
}
31+
}

0 commit comments

Comments
 (0)