diff --git a/.editorconfig b/.editorconfig index bb0c3a50..b7f043d6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,6 +14,10 @@ dotnet_diagnostic.CA1034.severity = none dotnet_diagnostic.CA1054.severity = none dotnet_diagnostic.CA2227.severity = none dotnet_diagnostic.CA1308.severity = none +dotnet_diagnostic.CA1002.severity = none +# TODO: Enable for next major version, EventArgs breaking change: +dotnet_diagnostic.CA1003.severity = none +# # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true dotnet_style_require_accessibility_modifiers = always:warning diff --git a/SpotifyAPI.Web.Auth/AssemblyInfo.cs b/SpotifyAPI.Web.Auth/AssemblyInfo.cs new file mode 100644 index 00000000..661cd812 --- /dev/null +++ b/SpotifyAPI.Web.Auth/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("SpotifyAPI.Web.Tests")] +[assembly: CLSCompliant(true)] diff --git a/SpotifyAPI.Web.Tests/Http/NetHTTPClientTest.cs b/SpotifyAPI.Web.Tests/Http/NetHTTPClientTest.cs deleted file mode 100644 index f93948cf..00000000 --- a/SpotifyAPI.Web.Tests/Http/NetHTTPClientTest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace SpotifyAPI.Web.Tests -{ - [TestFixture] - public class NetHTTPClientTest - { - [TestFixture] - public class BuildRequestsMethod - { - public void AddsHeaders() - { - - } - } - } -} diff --git a/SpotifyAPI.Web.Tests/UtilTests/URIExtensionTest.cs b/SpotifyAPI.Web.Tests/UtilTests/URIExtensionTest.cs index 96daa508..6b3d7a2d 100644 --- a/SpotifyAPI.Web.Tests/UtilTests/URIExtensionTest.cs +++ b/SpotifyAPI.Web.Tests/UtilTests/URIExtensionTest.cs @@ -11,7 +11,7 @@ namespace SpotifyAPI.Web.Tests public void ApplyParameters_WithoutExistingParameters() { var expected = "http://google.com/?hello=world&nice=day"; - Uri uri = new Uri("http://google.com/"); + var uri = new Uri("http://google.com/"); var parameters = new Dictionary { @@ -25,7 +25,7 @@ namespace SpotifyAPI.Web.Tests public void ApplyParameters_WithExistingParameters() { var expected = "http://google.com/?existing=paramter&hello=world&nice=day"; - Uri uri = new Uri("http://google.com/?existing=paramter"); + var uri = new Uri("http://google.com/?existing=paramter"); var parameters = new Dictionary { @@ -39,7 +39,7 @@ namespace SpotifyAPI.Web.Tests public void ApplyParameters_HandlesEscape() { var expected = "http://google.com/?existing=paramter&hello=%26world++"; - Uri uri = new Uri("http://google.com/?existing=paramter"); + var uri = new Uri("http://google.com/?existing=paramter"); var parameters = new Dictionary { diff --git a/SpotifyAPI.Web/Assembly.cs b/SpotifyAPI.Web/Assembly.cs index 6e71e55c..661cd812 100644 --- a/SpotifyAPI.Web/Assembly.cs +++ b/SpotifyAPI.Web/Assembly.cs @@ -1,3 +1,5 @@ +using System; using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("SpotifyAPI.Web.Tests")] +[assembly: CLSCompliant(true)] diff --git a/SpotifyAPI.Web/Clients/APIClient.cs b/SpotifyAPI.Web/Clients/APIClient.cs index a778e1ae..c12c983f 100644 --- a/SpotifyAPI.Web/Clients/APIClient.cs +++ b/SpotifyAPI.Web/Clients/APIClient.cs @@ -4,7 +4,7 @@ namespace SpotifyAPI.Web { public abstract class APIClient { - public APIClient(IAPIConnector apiConnector) + protected APIClient(IAPIConnector apiConnector) { Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); diff --git a/SpotifyAPI.Web/Clients/PlaylistsClient.cs b/SpotifyAPI.Web/Clients/PlaylistsClient.cs index 3b3ab3e5..6a598d00 100644 --- a/SpotifyAPI.Web/Clients/PlaylistsClient.cs +++ b/SpotifyAPI.Web/Clients/PlaylistsClient.cs @@ -49,12 +49,12 @@ namespace SpotifyAPI.Web return API.Post(URLs.UserPlaylists(userId), null, request.BuildBodyParams()); } - public async Task UploadCover(string playlistId, string base64Jpg) + public async Task UploadCover(string playlistId, string base64JpgData) { Ensure.ArgumentNotNullOrEmptyString(playlistId, nameof(playlistId)); - Ensure.ArgumentNotNullOrEmptyString(base64Jpg, nameof(base64Jpg)); + Ensure.ArgumentNotNullOrEmptyString(base64JpgData, nameof(base64JpgData)); - var statusCode = await API.PutRaw(URLs.PlaylistImages(playlistId), null, base64Jpg).ConfigureAwait(false); + var statusCode = await API.PutRaw(URLs.PlaylistImages(playlistId), null, base64JpgData).ConfigureAwait(false); return statusCode == HttpStatusCode.Accepted; } diff --git a/SpotifyAPI.Web/Clients/SpotifyClient.cs b/SpotifyAPI.Web/Clients/SpotifyClient.cs index 31bcad71..f4266540 100644 --- a/SpotifyAPI.Web/Clients/SpotifyClient.cs +++ b/SpotifyAPI.Web/Clients/SpotifyClient.cs @@ -24,7 +24,9 @@ namespace SpotifyAPI.Web Ensure.ArgumentNotNull(config, nameof(config)); if (config.Authenticator == null) { - throw new NullReferenceException("Authenticator in config is null. Please supply it via `WithAuthenticator` or `WithToken`"); +#pragma warning disable CA2208 + throw new ArgumentNullException("Authenticator in config is null. Please supply it via `WithAuthenticator` or `WithToken`"); +#pragma warning restore CA2208 } _apiConnector = new APIConnector( diff --git a/SpotifyAPI.Web/Http/APIConnector.cs b/SpotifyAPI.Web/Http/APIConnector.cs index ecc24862..21961811 100644 --- a/SpotifyAPI.Web/Http/APIConnector.cs +++ b/SpotifyAPI.Web/Http/APIConnector.cs @@ -279,15 +279,13 @@ namespace SpotifyAPI.Web.Http return; } - switch (response.StatusCode) + throw response.StatusCode switch { - case HttpStatusCode.Unauthorized: - throw new APIUnauthorizedException(response); - case (HttpStatusCode)429: // TODO: Remove hack once .netstandard 2.0 is not supported - throw new APITooManyRequestsException(response); - default: - throw new APIException(response); - } + HttpStatusCode.Unauthorized => new APIUnauthorizedException(response), + // TODO: Remove hack once .netstandard 2.0 is not supported + (HttpStatusCode)429 => new APITooManyRequestsException(response), + _ => new APIException(response), + }; } } } diff --git a/SpotifyAPI.Web/Http/NetHttpClient.cs b/SpotifyAPI.Web/Http/NetHttpClient.cs index 26b4435b..5a8a72c0 100644 --- a/SpotifyAPI.Web/Http/NetHttpClient.cs +++ b/SpotifyAPI.Web/Http/NetHttpClient.cs @@ -35,14 +35,12 @@ namespace SpotifyAPI.Web.Http { Ensure.ArgumentNotNull(request, nameof(request)); - using (HttpRequestMessage requestMsg = BuildRequestMessage(request)) - { - var responseMsg = await _httpClient - .SendAsync(requestMsg, HttpCompletionOption.ResponseContentRead) - .ConfigureAwait(false); + using HttpRequestMessage requestMsg = BuildRequestMessage(request); + var responseMsg = await _httpClient + .SendAsync(requestMsg, HttpCompletionOption.ResponseContentRead) + .ConfigureAwait(false); - return await BuildResponse(responseMsg).ConfigureAwait(false); - } + return await BuildResponse(responseMsg).ConfigureAwait(false); } private static async Task BuildResponse(HttpResponseMessage responseMsg) @@ -50,19 +48,17 @@ namespace SpotifyAPI.Web.Http Ensure.ArgumentNotNull(responseMsg, nameof(responseMsg)); // We only support text stuff for now - using (var content = responseMsg.Content) - { - var headers = responseMsg.Headers.ToDictionary(header => header.Key, header => header.Value.First()); - var body = await responseMsg.Content.ReadAsStringAsync().ConfigureAwait(false); - var contentType = content.Headers?.ContentType?.MediaType; + using var content = responseMsg.Content; + var headers = responseMsg.Headers.ToDictionary(header => header.Key, header => header.Value.First()); + var body = await responseMsg.Content.ReadAsStringAsync().ConfigureAwait(false); + var contentType = content.Headers?.ContentType?.MediaType; - return new Response(headers) - { - ContentType = contentType, - StatusCode = responseMsg.StatusCode, - Body = body - }; - } + return new Response(headers) + { + ContentType = contentType, + StatusCode = responseMsg.StatusCode, + Body = body + }; } private static HttpRequestMessage BuildRequestMessage(IRequest request) diff --git a/SpotifyAPI.Web/Http/SimpleConsoleHTTPLogger.cs b/SpotifyAPI.Web/Http/SimpleConsoleHTTPLogger.cs index 9fb3e0ae..6049e17e 100644 --- a/SpotifyAPI.Web/Http/SimpleConsoleHTTPLogger.cs +++ b/SpotifyAPI.Web/Http/SimpleConsoleHTTPLogger.cs @@ -16,7 +16,7 @@ namespace SpotifyAPI.Web.Http if (request.Parameters != null) { parameters = string.Join(",", - request.Parameters?.Select(kv => kv.Key + "=" + kv.Value)?.ToArray() ?? Array.Empty() + request.Parameters.Select(kv => kv.Key + "=" + kv.Value)?.ToArray() ?? Array.Empty() ); } @@ -33,7 +33,7 @@ namespace SpotifyAPI.Web.Http string? body = response.Body?.ToString()?.Replace("\n", "", StringComparison.InvariantCulture); #endif - body = body?.Substring(0, Math.Min(50, body?.Length ?? 0)); + body = body?.Substring(0, Math.Min(50, body.Length)); Console.WriteLine("--> {0} {1} {2}\n", response.StatusCode, response.ContentType, body); } } diff --git a/SpotifyAPI.Web/Models/Converters/PlayableItemConverter.cs b/SpotifyAPI.Web/Models/Converters/PlayableItemConverter.cs index 74a7aa48..bb5d5ef1 100644 --- a/SpotifyAPI.Web/Models/Converters/PlayableItemConverter.cs +++ b/SpotifyAPI.Web/Models/Converters/PlayableItemConverter.cs @@ -36,7 +36,7 @@ namespace SpotifyAPI.Web } else { - throw new Exception($@"Received unkown playlist element type: {type}. + throw new APIException($@"Received unkown playlist element type: {type}. If you're requesting a subset of available fields via the fields query paramter, make sure to include at least the type field. Often it's `items(track(type))` or `item(type)`"); } diff --git a/SpotifyAPI.Web/Models/Request/LoginRequest.cs b/SpotifyAPI.Web/Models/Request/LoginRequest.cs index 32944d61..40a9fa90 100644 --- a/SpotifyAPI.Web/Models/Request/LoginRequest.cs +++ b/SpotifyAPI.Web/Models/Request/LoginRequest.cs @@ -29,7 +29,7 @@ namespace SpotifyAPI.Web public Uri ToUri() { - StringBuilder builder = new StringBuilder(SpotifyUrls.Authorize.ToString()); + var builder = new StringBuilder(SpotifyUrls.Authorize.ToString()); builder.Append($"?client_id={ClientId}"); builder.Append($"&response_type={ResponseTypeParam.ToString().ToLower(CultureInfo.InvariantCulture)}"); builder.Append($"&redirect_uri={HttpUtility.UrlEncode(RedirectUri.ToString())}"); diff --git a/SpotifyAPI.Web/Models/Request/RequestParams.cs b/SpotifyAPI.Web/Models/Request/RequestParams.cs index 97a05bd7..40066325 100644 --- a/SpotifyAPI.Web/Models/Request/RequestParams.cs +++ b/SpotifyAPI.Web/Models/Request/RequestParams.cs @@ -11,7 +11,7 @@ namespace SpotifyAPI.Web public abstract class RequestParams { private static readonly ConcurrentDictionary> _bodyParamsCache = - new ConcurrentDictionary>(); + new(); public JObject BuildBodyParams() { @@ -59,7 +59,7 @@ namespace SpotifyAPI.Web } private static readonly ConcurrentDictionary> _queryParamsCache = - new ConcurrentDictionary>(); + new(); public Dictionary BuildQueryParams() { @@ -146,7 +146,7 @@ namespace SpotifyAPI.Web } else { - queryParams.Add(attribute.Key ?? prop.Name, value.ToString() ?? throw new Exception("ToString was null on a value")); + queryParams.Add(attribute.Key ?? prop.Name, value.ToString() ?? throw new APIException("ToString returned null for query parameter")); } } } @@ -156,7 +156,7 @@ namespace SpotifyAPI.Web } [AttributeUsage(AttributeTargets.Property)] - public class QueryParamAttribute : Attribute + public sealed class QueryParamAttribute : Attribute { public string Key { get; } @@ -167,7 +167,7 @@ namespace SpotifyAPI.Web } [AttributeUsage(AttributeTargets.Property)] - public class BodyParamAttribute : Attribute + public sealed class BodyParamAttribute : Attribute { public string Key { get; } diff --git a/SpotifyAPI.Web/SpotifyAPI.Web.csproj b/SpotifyAPI.Web/SpotifyAPI.Web.csproj index 6d78b384..f70019e4 100644 --- a/SpotifyAPI.Web/SpotifyAPI.Web.csproj +++ b/SpotifyAPI.Web/SpotifyAPI.Web.csproj @@ -35,9 +35,9 @@ None - - all - runtime; build; native; contentfiles; analyzers - + + + AllEnabledByDefault + diff --git a/SpotifyAPI.Web/SpotifyUrls.cs b/SpotifyAPI.Web/SpotifyUrls.cs index f0950000..b41364c4 100644 --- a/SpotifyAPI.Web/SpotifyUrls.cs +++ b/SpotifyAPI.Web/SpotifyUrls.cs @@ -3,13 +3,13 @@ namespace SpotifyAPI.Web { public static class SpotifyUrls { - static private readonly URIParameterFormatProvider _provider = new URIParameterFormatProvider(); + static private readonly URIParameterFormatProvider _provider = new(); - public static readonly Uri APIV1 = new Uri("https://api.spotify.com/v1/"); + public static readonly Uri APIV1 = new("https://api.spotify.com/v1/"); - public static readonly Uri Authorize = new Uri("https://accounts.spotify.com/authorize"); + public static readonly Uri Authorize = new("https://accounts.spotify.com/authorize"); - public static readonly Uri OAuthToken = new Uri("https://accounts.spotify.com/api/token"); + public static readonly Uri OAuthToken = new("https://accounts.spotify.com/api/token"); public static Uri Me() => EUri($"me"); @@ -125,6 +125,6 @@ namespace SpotifyAPI.Web public static Uri LibraryShows() => EUri($"me/shows"); - private static Uri EUri(FormattableString path) => new Uri(path.ToString(_provider), UriKind.Relative); + private static Uri EUri(FormattableString path) => new(path.ToString(_provider), UriKind.Relative); } } diff --git a/SpotifyAPI.Web/Util/Base64Util.cs b/SpotifyAPI.Web/Util/Base64Util.cs index c2eb5a71..e1e91e1c 100644 --- a/SpotifyAPI.Web/Util/Base64Util.cs +++ b/SpotifyAPI.Web/Util/Base64Util.cs @@ -114,21 +114,17 @@ namespace SpotifyAPI.Web private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength) { - switch (inputLength % 4) + return (inputLength % 4) switch { - case 0: - return 0; - case 2: - return 2; - case 3: - return 1; - default: - throw new FormatException( - string.Format( - CultureInfo.CurrentCulture, - WebEncoders_MalformedInput, - inputLength)); - } + 0 => 0, + 2 => 2, + 3 => 1, + _ => throw new FormatException( + string.Format( + CultureInfo.CurrentCulture, + WebEncoders_MalformedInput, + inputLength)), + }; } } diff --git a/SpotifyAPI.Web/Util/PKCEUtil.cs b/SpotifyAPI.Web/Util/PKCEUtil.cs index 73ec6b6f..55157a8d 100644 --- a/SpotifyAPI.Web/Util/PKCEUtil.cs +++ b/SpotifyAPI.Web/Util/PKCEUtil.cs @@ -52,22 +52,18 @@ namespace SpotifyAPI.Web private static string GenerateRandomURLSafeString(int length) { - using (var rng = new RNGCryptoServiceProvider()) - { - var bit_count = length * 6; - var byte_count = (bit_count + 7) / 8; // rounded up - var bytes = new byte[byte_count]; - rng.GetBytes(bytes); - return Base64Util.UrlEncode(bytes); - } + using var rng = new RNGCryptoServiceProvider(); + var bit_count = length * 6; + var byte_count = (bit_count + 7) / 8; // rounded up + var bytes = new byte[byte_count]; + rng.GetBytes(bytes); + return Base64Util.UrlEncode(bytes); } private static byte[] ComputeSHA256(string value) { - using (var hash = SHA256.Create()) - { - return hash.ComputeHash(Encoding.UTF8.GetBytes(value)); - } + using var hash = SHA256.Create(); + return hash.ComputeHash(Encoding.UTF8.GetBytes(value)); } } } diff --git a/SpotifyAPI.Web/Util/StringAttribute.cs b/SpotifyAPI.Web/Util/StringAttribute.cs index d54e39df..bc13301a 100644 --- a/SpotifyAPI.Web/Util/StringAttribute.cs +++ b/SpotifyAPI.Web/Util/StringAttribute.cs @@ -6,14 +6,14 @@ using System.Diagnostics.CodeAnalysis; namespace SpotifyAPI.Web { [AttributeUsage(AttributeTargets.Field)] - public class StringAttribute : Attribute + public sealed class StringAttribute : Attribute { public StringAttribute(string value) { Value = value; } - public string Value { get; set; } + public string Value { get; } #if NETSTANDARD2_0 public static bool GetValue(Type enumType, Enum enumValue, out string? result)