mirror of
https://github.com/Sarsoo/Spotify.NET.git
synced 2024-12-24 06:56:27 +00:00
Added Retry Handler and removed old enums
This commit is contained in:
parent
221d7534fd
commit
be7bdb6a93
@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using SpotifyAPI.Web.Enums;
|
||||
|
||||
namespace SpotifyAPI.Web.Examples.ASP
|
||||
{
|
||||
@ -26,8 +25,8 @@ namespace SpotifyAPI.Web.Examples.ASP
|
||||
.AddCookie()
|
||||
.AddSpotify(options =>
|
||||
{
|
||||
var scopes = Scope.UserLibraryRead | Scope.UserModifyPlaybackState;
|
||||
options.Scope.Add(scopes.GetStringAttribute(","));
|
||||
// var scopes = Scope.UserLibraryRead | Scope.UserModifyPlaybackState;
|
||||
// options.Scope.Add(scopes.GetStringAttribute(","));
|
||||
|
||||
options.SaveTokens = true;
|
||||
options.ClientId = Configuration["client_id"];
|
||||
|
95
SpotifyAPI.Web.Tests/Http/APIConnectorTest.cs
Normal file
95
SpotifyAPI.Web.Tests/Http/APIConnectorTest.cs
Normal file
@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using SpotifyAPI.Web.Http;
|
||||
|
||||
namespace SpotifyAPI.Web.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class APIConnectorTest
|
||||
{
|
||||
[Test]
|
||||
public async Task RetryHandler_IsUsed()
|
||||
{
|
||||
var apiResponse = new Mock<IAPIResponse<string>>();
|
||||
apiResponse.SetupGet(a => a.Body).Returns("Hello World");
|
||||
|
||||
var response = new Mock<IResponse>();
|
||||
response.SetupGet(r => r.ContentType).Returns("application/json");
|
||||
response.SetupGet(r => r.StatusCode).Returns(HttpStatusCode.OK);
|
||||
response.SetupGet(r => r.Body).Returns("\"Hello World\"");
|
||||
|
||||
var authenticator = new Mock<IAuthenticator>();
|
||||
var serializer = new Mock<IJSONSerializer>();
|
||||
serializer.Setup(s => s.DeserializeResponse<string>(It.IsAny<IResponse>())).Returns(apiResponse.Object);
|
||||
|
||||
var httpClient = new Mock<IHTTPClient>();
|
||||
var retryHandler = new Mock<IRetryHandler>();
|
||||
retryHandler.Setup(r =>
|
||||
r.HandleRetry(
|
||||
It.IsAny<IRequest>(),
|
||||
It.IsAny<IResponse>(),
|
||||
It.IsAny<Func<IRequest, Task<IResponse>>>()
|
||||
)
|
||||
).Returns(Task.FromResult(response.Object));
|
||||
|
||||
var apiConnector = new APIConnector(
|
||||
new Uri("https://spotify.com"),
|
||||
authenticator.Object,
|
||||
serializer.Object,
|
||||
httpClient.Object,
|
||||
retryHandler.Object
|
||||
);
|
||||
await apiConnector.SendAPIRequest<string>(new Uri("/me", UriKind.Relative), HttpMethod.Get);
|
||||
|
||||
authenticator.Verify(a => a.Apply(It.IsAny<IRequest>()), Times.Once);
|
||||
httpClient.Verify(h => h.DoRequest(It.IsAny<IRequest>()), Times.Once);
|
||||
serializer.Verify(s => s.DeserializeResponse<string>(response.Object), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RetryHandler_CanRetry()
|
||||
{
|
||||
var apiResponse = new Mock<IAPIResponse<string>>();
|
||||
apiResponse.SetupGet(a => a.Body).Returns("Hello World");
|
||||
|
||||
var response = new Mock<IResponse>();
|
||||
response.SetupGet(r => r.ContentType).Returns("application/json");
|
||||
response.SetupGet(r => r.StatusCode).Returns(HttpStatusCode.OK);
|
||||
response.SetupGet(r => r.Body).Returns("\"Hello World\"");
|
||||
|
||||
var authenticator = new Mock<IAuthenticator>();
|
||||
var serializer = new Mock<IJSONSerializer>();
|
||||
serializer.Setup(s => s.DeserializeResponse<string>(It.IsAny<IResponse>())).Returns(apiResponse.Object);
|
||||
|
||||
var httpClient = new Mock<IHTTPClient>();
|
||||
httpClient.Setup(h => h.DoRequest(It.IsAny<IRequest>())).Returns(Task.FromResult(response.Object));
|
||||
|
||||
var retryHandler = new Mock<IRetryHandler>();
|
||||
retryHandler.Setup(r =>
|
||||
r.HandleRetry(
|
||||
It.IsAny<IRequest>(),
|
||||
It.IsAny<IResponse>(),
|
||||
It.IsAny<Func<IRequest, Task<IResponse>>>()
|
||||
)
|
||||
).Returns((IRequest request, IResponse response, Func<IRequest, Task<IResponse>> retry) => retry(request));
|
||||
|
||||
var apiConnector = new APIConnector(
|
||||
new Uri("https://spotify.com"),
|
||||
authenticator.Object,
|
||||
serializer.Object,
|
||||
httpClient.Object,
|
||||
retryHandler.Object
|
||||
);
|
||||
await apiConnector.SendAPIRequest<string>(new Uri("/me", UriKind.Relative), HttpMethod.Get);
|
||||
|
||||
serializer.Verify(s => s.SerializeRequest(It.IsAny<IRequest>()), Times.Once);
|
||||
authenticator.Verify(a => a.Apply(It.IsAny<IRequest>()), Times.Exactly(2));
|
||||
httpClient.Verify(h => h.DoRequest(It.IsAny<IRequest>()), Times.Exactly(2));
|
||||
serializer.Verify(s => s.DeserializeResponse<string>(response.Object), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace SpotifyAPI.Web.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class UtilTest
|
||||
{
|
||||
[Test]
|
||||
public void TimestampShouldBeNoFloatingPoint()
|
||||
{
|
||||
string timestamp = DateTime.Now.ToUnixTimeMillisecondsPoly().ToString();
|
||||
|
||||
StringAssert.DoesNotContain(".", timestamp);
|
||||
StringAssert.DoesNotContain(",", timestamp);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ namespace SpotifyAPI.Web
|
||||
public IAuthenticator Authenticator { get; }
|
||||
public IJSONSerializer JSONSerializer { get; }
|
||||
public IHTTPClient HTTPClient { get; }
|
||||
public IRetryHandler RetryHandler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This config spefies the internal parts of the SpotifyClient.
|
||||
@ -19,17 +20,20 @@ namespace SpotifyAPI.Web
|
||||
/// <param name="authenticator"></param>
|
||||
/// <param name="jsonSerializer"></param>
|
||||
/// <param name="httpClient"></param>
|
||||
/// <param name="retryHandler"></param>
|
||||
public SpotifyClientConfig(
|
||||
Uri baseAddress,
|
||||
IAuthenticator authenticator,
|
||||
IJSONSerializer jsonSerializer,
|
||||
IHTTPClient httpClient
|
||||
IHTTPClient httpClient,
|
||||
IRetryHandler retryHandler
|
||||
)
|
||||
{
|
||||
BaseAddress = baseAddress;
|
||||
Authenticator = authenticator;
|
||||
JSONSerializer = jsonSerializer;
|
||||
HTTPClient = httpClient;
|
||||
RetryHandler = retryHandler;
|
||||
}
|
||||
|
||||
internal IAPIConnector CreateAPIConnector()
|
||||
@ -40,7 +44,7 @@ namespace SpotifyAPI.Web
|
||||
Ensure.PropertyNotNull(JSONSerializer, nameof(JSONSerializer));
|
||||
Ensure.PropertyNotNull(HTTPClient, nameof(HTTPClient));
|
||||
|
||||
return new APIConnector(BaseAddress, Authenticator, JSONSerializer, HTTPClient);
|
||||
return new APIConnector(BaseAddress, Authenticator, JSONSerializer, HTTPClient, RetryHandler);
|
||||
}
|
||||
|
||||
public SpotifyClientConfig WithToken(string token, string tokenType = "Bearer")
|
||||
@ -50,11 +54,14 @@ namespace SpotifyAPI.Web
|
||||
return WithAuthenticator(new TokenHeaderAuthenticator(token, tokenType));
|
||||
}
|
||||
|
||||
public SpotifyClientConfig WithRetryHandler(IRetryHandler retryHandler)
|
||||
{
|
||||
return new SpotifyClientConfig(BaseAddress, Authenticator, JSONSerializer, HTTPClient, retryHandler);
|
||||
}
|
||||
|
||||
public SpotifyClientConfig WithAuthenticator(IAuthenticator authenticator)
|
||||
{
|
||||
Ensure.ArgumentNotNull(authenticator, nameof(authenticator));
|
||||
|
||||
return new SpotifyClientConfig(BaseAddress, Authenticator, JSONSerializer, HTTPClient);
|
||||
return new SpotifyClientConfig(BaseAddress, authenticator, JSONSerializer, HTTPClient, RetryHandler);
|
||||
}
|
||||
|
||||
public static SpotifyClientConfig CreateDefault(string token, string tokenType = "Bearer")
|
||||
@ -79,7 +86,8 @@ namespace SpotifyAPI.Web
|
||||
SpotifyUrls.API_V1,
|
||||
authenticator,
|
||||
new NewtonsoftJSONSerializer(),
|
||||
new NetHttpClient()
|
||||
new NetHttpClient(),
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SpotifyAPI.Web.Enums
|
||||
{
|
||||
[Flags]
|
||||
public enum AlbumType
|
||||
{
|
||||
[String("album")]
|
||||
Album = 1,
|
||||
|
||||
[String("single")]
|
||||
Single = 2,
|
||||
|
||||
[String("compilation")]
|
||||
Compilation = 4,
|
||||
|
||||
[String("appears_on")]
|
||||
AppearsOn = 8,
|
||||
|
||||
[String("album,single,compilation,appears_on")]
|
||||
All = 16
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SpotifyAPI.Web.Enums
|
||||
{
|
||||
[Flags]
|
||||
public enum FollowType
|
||||
{
|
||||
[String("artist")]
|
||||
Artist = 1,
|
||||
|
||||
[String("user")]
|
||||
User = 2
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SpotifyAPI.Web.Enums
|
||||
{
|
||||
[Flags]
|
||||
public enum RepeatState
|
||||
{
|
||||
[String("track")]
|
||||
Track = 1,
|
||||
|
||||
[String("context")]
|
||||
Context = 2,
|
||||
|
||||
[String("off")]
|
||||
Off = 4
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SpotifyAPI.Web.Enums
|
||||
{
|
||||
[Flags]
|
||||
public enum Scope
|
||||
{
|
||||
[String("")]
|
||||
None = 1,
|
||||
|
||||
[String("playlist-modify-public")]
|
||||
PlaylistModifyPublic = 2,
|
||||
|
||||
[String("playlist-modify-private")]
|
||||
PlaylistModifyPrivate = 4,
|
||||
|
||||
[String("playlist-read-private")]
|
||||
PlaylistReadPrivate = 8,
|
||||
|
||||
[String("streaming")]
|
||||
Streaming = 16,
|
||||
|
||||
[String("user-read-private")]
|
||||
UserReadPrivate = 32,
|
||||
|
||||
[String("user-read-email")]
|
||||
UserReadEmail = 64,
|
||||
|
||||
[String("user-library-read")]
|
||||
UserLibraryRead = 128,
|
||||
|
||||
[String("user-library-modify")]
|
||||
UserLibraryModify = 256,
|
||||
|
||||
[String("user-follow-modify")]
|
||||
UserFollowModify = 512,
|
||||
|
||||
[String("user-follow-read")]
|
||||
UserFollowRead = 1024,
|
||||
|
||||
[String("user-read-birthdate")]
|
||||
UserReadBirthdate = 2048,
|
||||
|
||||
[String("user-top-read")]
|
||||
UserTopRead = 4096,
|
||||
|
||||
[String("playlist-read-collaborative")]
|
||||
PlaylistReadCollaborative = 8192,
|
||||
|
||||
[String("user-read-recently-played")]
|
||||
UserReadRecentlyPlayed = 16384,
|
||||
|
||||
[String("user-read-playback-state")]
|
||||
UserReadPlaybackState = 32768,
|
||||
|
||||
[String("user-modify-playback-state")]
|
||||
UserModifyPlaybackState = 65536,
|
||||
|
||||
[String("user-read-currently-playing")]
|
||||
UserReadCurrentlyPlaying = 131072,
|
||||
|
||||
[String("app-remote-control")]
|
||||
AppRemoteControl = 262144,
|
||||
|
||||
[String("ugc-image-upload")]
|
||||
UgcImageUpload = 524288
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SpotifyAPI.Web.Enums
|
||||
{
|
||||
[Flags]
|
||||
public enum SearchType
|
||||
{
|
||||
[String("artist")]
|
||||
Artist = 1,
|
||||
|
||||
[String("album")]
|
||||
Album = 2,
|
||||
|
||||
[String("track")]
|
||||
Track = 4,
|
||||
|
||||
[String("playlist")]
|
||||
Playlist = 8,
|
||||
|
||||
[String("track,album,artist,playlist")]
|
||||
All = 16
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SpotifyAPI.Web.Enums
|
||||
{
|
||||
/// <summary>
|
||||
/// Only one value allowed
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TimeRangeType
|
||||
{
|
||||
[String("long_term")]
|
||||
LongTerm = 1,
|
||||
|
||||
[String("medium_term")]
|
||||
MediumTerm = 2,
|
||||
|
||||
[String("short_term")]
|
||||
ShortTerm = 4
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SpotifyAPI.Web.Enums
|
||||
{
|
||||
[Flags]
|
||||
public enum TrackType
|
||||
{
|
||||
[String("track")]
|
||||
Track = 1,
|
||||
|
||||
[String("episode")]
|
||||
Episode = 2,
|
||||
|
||||
[String("ad")]
|
||||
Ad = 4,
|
||||
|
||||
[String("unknown")]
|
||||
Unknown = 8
|
||||
}
|
||||
}
|
@ -12,16 +12,23 @@ namespace SpotifyAPI.Web.Http
|
||||
private readonly IAuthenticator _authenticator;
|
||||
private readonly IJSONSerializer _jsonSerializer;
|
||||
private readonly IHTTPClient _httpClient;
|
||||
private readonly IRetryHandler _retryHandler;
|
||||
|
||||
public APIConnector(Uri baseAddress, IAuthenticator authenticator) :
|
||||
this(baseAddress, authenticator, new NewtonsoftJSONSerializer(), new NetHttpClient())
|
||||
this(baseAddress, authenticator, new NewtonsoftJSONSerializer(), new NetHttpClient(), null)
|
||||
{ }
|
||||
public APIConnector(Uri baseAddress, IAuthenticator authenticator, IJSONSerializer jsonSerializer, IHTTPClient httpClient)
|
||||
public APIConnector(
|
||||
Uri baseAddress,
|
||||
IAuthenticator authenticator,
|
||||
IJSONSerializer jsonSerializer,
|
||||
IHTTPClient httpClient,
|
||||
IRetryHandler retryHandler)
|
||||
{
|
||||
_baseAddress = baseAddress;
|
||||
_authenticator = authenticator;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_httpClient = httpClient;
|
||||
_retryHandler = retryHandler;
|
||||
}
|
||||
|
||||
public Task<T> Delete<T>(Uri uri)
|
||||
@ -106,7 +113,7 @@ namespace SpotifyAPI.Web.Http
|
||||
_httpClient.SetRequestTimeout(timeout);
|
||||
}
|
||||
|
||||
private async Task<T> SendAPIRequest<T>(
|
||||
public async Task<T> SendAPIRequest<T>(
|
||||
Uri uri,
|
||||
HttpMethod method,
|
||||
IDictionary<string, string> parameters = null,
|
||||
@ -128,6 +135,11 @@ namespace SpotifyAPI.Web.Http
|
||||
_jsonSerializer.SerializeRequest(request);
|
||||
await _authenticator.Apply(request).ConfigureAwait(false);
|
||||
IResponse response = await _httpClient.DoRequest(request).ConfigureAwait(false);
|
||||
response = await _retryHandler?.HandleRetry(request, response, async (newRequest) =>
|
||||
{
|
||||
await _authenticator.Apply(newRequest).ConfigureAwait(false);
|
||||
return await _httpClient.DoRequest(request).ConfigureAwait(false);
|
||||
});
|
||||
ProcessErrors(response);
|
||||
|
||||
IAPIResponse<T> apiResponse = _jsonSerializer.DeserializeResponse<T>(response);
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SpotifyAPI.Web.Http
|
||||
@ -27,6 +28,8 @@ namespace SpotifyAPI.Web.Http
|
||||
Task<T> Delete<T>(Uri uri, IDictionary<string, string> parameters);
|
||||
Task<T> Delete<T>(Uri uri, IDictionary<string, string> parameters, object body);
|
||||
|
||||
Task<T> SendAPIRequest<T>(Uri uri, HttpMethod method, IDictionary<string, string> parameters = null, object body = null);
|
||||
|
||||
void SetRequestTimeout(TimeSpan timeout);
|
||||
}
|
||||
}
|
||||
|
13
SpotifyAPI.Web/Http/Interfaces/IRetryHandler.cs
Normal file
13
SpotifyAPI.Web/Http/Interfaces/IRetryHandler.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SpotifyAPI.Web.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// The Retry Handler will be directly called after the response is retrived and before errors and body are processed.
|
||||
/// </summary>
|
||||
public interface IRetryHandler
|
||||
{
|
||||
Task<IResponse> HandleRetry(IRequest request, IResponse response, Func<IRequest, Task<IResponse>> retry);
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace SpotifyAPI.Web
|
||||
{
|
||||
public static class Util
|
||||
{
|
||||
public static string GetStringAttribute<T>(this T en, string separator = "") where T : struct, IConvertible
|
||||
{
|
||||
Enum e = (Enum) (object) en;
|
||||
IEnumerable<StringAttribute> attributes =
|
||||
Enum.GetValues(typeof(T))
|
||||
.Cast<T>()
|
||||
.Where(v => e.HasFlag((Enum) (object) v))
|
||||
.Select(v => typeof(T).GetField(v.ToString(CultureInfo.InvariantCulture)))
|
||||
.Select(f => f.GetCustomAttributes(typeof(StringAttribute), false) [0])
|
||||
.Cast<StringAttribute>();
|
||||
|
||||
List<string> list = new List<string>();
|
||||
attributes.ToList().ForEach(element => list.Add(element.Text));
|
||||
return string.Join(separator, list);
|
||||
}
|
||||
|
||||
public static long ToUnixTimeMillisecondsPoly(this DateTime time)
|
||||
{
|
||||
return (long) time.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StringAttribute : Attribute
|
||||
{
|
||||
public string Text { get; set; }
|
||||
|
||||
public StringAttribute(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user