Updated to .NET analyzers and fixed warnings

This commit is contained in:
Jonas Dellinger 2020-12-26 18:28:44 +01:00
parent 7659f83122
commit 8dd31420ea
19 changed files with 80 additions and 101 deletions

View File

@ -14,6 +14,10 @@ dotnet_diagnostic.CA1034.severity = none
dotnet_diagnostic.CA1054.severity = none dotnet_diagnostic.CA1054.severity = none
dotnet_diagnostic.CA2227.severity = none dotnet_diagnostic.CA2227.severity = none
dotnet_diagnostic.CA1308.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 # Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true dotnet_sort_system_directives_first = true
dotnet_style_require_accessibility_modifiers = always:warning dotnet_style_require_accessibility_modifiers = always:warning

View File

@ -0,0 +1,5 @@
using System;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("SpotifyAPI.Web.Tests")]
[assembly: CLSCompliant(true)]

View File

@ -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()
{
}
}
}
}

View File

@ -11,7 +11,7 @@ namespace SpotifyAPI.Web.Tests
public void ApplyParameters_WithoutExistingParameters() public void ApplyParameters_WithoutExistingParameters()
{ {
var expected = "http://google.com/?hello=world&nice=day"; 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<string, string> var parameters = new Dictionary<string, string>
{ {
@ -25,7 +25,7 @@ namespace SpotifyAPI.Web.Tests
public void ApplyParameters_WithExistingParameters() public void ApplyParameters_WithExistingParameters()
{ {
var expected = "http://google.com/?existing=paramter&hello=world&nice=day"; 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<string, string> var parameters = new Dictionary<string, string>
{ {
@ -39,7 +39,7 @@ namespace SpotifyAPI.Web.Tests
public void ApplyParameters_HandlesEscape() public void ApplyParameters_HandlesEscape()
{ {
var expected = "http://google.com/?existing=paramter&hello=%26world++"; 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<string, string> var parameters = new Dictionary<string, string>
{ {

View File

@ -1,3 +1,5 @@
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("SpotifyAPI.Web.Tests")] [assembly: InternalsVisibleTo("SpotifyAPI.Web.Tests")]
[assembly: CLSCompliant(true)]

View File

@ -4,7 +4,7 @@ namespace SpotifyAPI.Web
{ {
public abstract class APIClient public abstract class APIClient
{ {
public APIClient(IAPIConnector apiConnector) protected APIClient(IAPIConnector apiConnector)
{ {
Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector));

View File

@ -49,12 +49,12 @@ namespace SpotifyAPI.Web
return API.Post<FullPlaylist>(URLs.UserPlaylists(userId), null, request.BuildBodyParams()); return API.Post<FullPlaylist>(URLs.UserPlaylists(userId), null, request.BuildBodyParams());
} }
public async Task<bool> UploadCover(string playlistId, string base64Jpg) public async Task<bool> UploadCover(string playlistId, string base64JpgData)
{ {
Ensure.ArgumentNotNullOrEmptyString(playlistId, nameof(playlistId)); 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; return statusCode == HttpStatusCode.Accepted;
} }

View File

@ -24,7 +24,9 @@ namespace SpotifyAPI.Web
Ensure.ArgumentNotNull(config, nameof(config)); Ensure.ArgumentNotNull(config, nameof(config));
if (config.Authenticator == null) 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( _apiConnector = new APIConnector(

View File

@ -279,15 +279,13 @@ namespace SpotifyAPI.Web.Http
return; return;
} }
switch (response.StatusCode) throw response.StatusCode switch
{ {
case HttpStatusCode.Unauthorized: HttpStatusCode.Unauthorized => new APIUnauthorizedException(response),
throw new APIUnauthorizedException(response); // TODO: Remove hack once .netstandard 2.0 is not supported
case (HttpStatusCode)429: // TODO: Remove hack once .netstandard 2.0 is not supported (HttpStatusCode)429 => new APITooManyRequestsException(response),
throw new APITooManyRequestsException(response); _ => new APIException(response),
default: };
throw new APIException(response);
}
} }
} }
} }

View File

@ -35,14 +35,12 @@ namespace SpotifyAPI.Web.Http
{ {
Ensure.ArgumentNotNull(request, nameof(request)); Ensure.ArgumentNotNull(request, nameof(request));
using (HttpRequestMessage requestMsg = BuildRequestMessage(request)) using HttpRequestMessage requestMsg = BuildRequestMessage(request);
{ var responseMsg = await _httpClient
var responseMsg = await _httpClient .SendAsync(requestMsg, HttpCompletionOption.ResponseContentRead)
.SendAsync(requestMsg, HttpCompletionOption.ResponseContentRead) .ConfigureAwait(false);
.ConfigureAwait(false);
return await BuildResponse(responseMsg).ConfigureAwait(false); return await BuildResponse(responseMsg).ConfigureAwait(false);
}
} }
private static async Task<IResponse> BuildResponse(HttpResponseMessage responseMsg) private static async Task<IResponse> BuildResponse(HttpResponseMessage responseMsg)
@ -50,19 +48,17 @@ namespace SpotifyAPI.Web.Http
Ensure.ArgumentNotNull(responseMsg, nameof(responseMsg)); Ensure.ArgumentNotNull(responseMsg, nameof(responseMsg));
// We only support text stuff for now // We only support text stuff for now
using (var content = responseMsg.Content) using var content = responseMsg.Content;
{ var headers = responseMsg.Headers.ToDictionary(header => header.Key, header => header.Value.First());
var headers = responseMsg.Headers.ToDictionary(header => header.Key, header => header.Value.First()); var body = await responseMsg.Content.ReadAsStringAsync().ConfigureAwait(false);
var body = await responseMsg.Content.ReadAsStringAsync().ConfigureAwait(false); var contentType = content.Headers?.ContentType?.MediaType;
var contentType = content.Headers?.ContentType?.MediaType;
return new Response(headers) return new Response(headers)
{ {
ContentType = contentType, ContentType = contentType,
StatusCode = responseMsg.StatusCode, StatusCode = responseMsg.StatusCode,
Body = body Body = body
}; };
}
} }
private static HttpRequestMessage BuildRequestMessage(IRequest request) private static HttpRequestMessage BuildRequestMessage(IRequest request)

View File

@ -16,7 +16,7 @@ namespace SpotifyAPI.Web.Http
if (request.Parameters != null) if (request.Parameters != null)
{ {
parameters = string.Join(",", parameters = string.Join(",",
request.Parameters?.Select(kv => kv.Key + "=" + kv.Value)?.ToArray() ?? Array.Empty<string>() request.Parameters.Select(kv => kv.Key + "=" + kv.Value)?.ToArray() ?? Array.Empty<string>()
); );
} }
@ -33,7 +33,7 @@ namespace SpotifyAPI.Web.Http
string? body = response.Body?.ToString()?.Replace("\n", "", StringComparison.InvariantCulture); string? body = response.Body?.ToString()?.Replace("\n", "", StringComparison.InvariantCulture);
#endif #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); Console.WriteLine("--> {0} {1} {2}\n", response.StatusCode, response.ContentType, body);
} }
} }

View File

@ -36,7 +36,7 @@ namespace SpotifyAPI.Web
} }
else 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, 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)`"); make sure to include at least the type field. Often it's `items(track(type))` or `item(type)`");
} }

View File

@ -29,7 +29,7 @@ namespace SpotifyAPI.Web
public Uri ToUri() public Uri ToUri()
{ {
StringBuilder builder = new StringBuilder(SpotifyUrls.Authorize.ToString()); var builder = new StringBuilder(SpotifyUrls.Authorize.ToString());
builder.Append($"?client_id={ClientId}"); builder.Append($"?client_id={ClientId}");
builder.Append($"&response_type={ResponseTypeParam.ToString().ToLower(CultureInfo.InvariantCulture)}"); builder.Append($"&response_type={ResponseTypeParam.ToString().ToLower(CultureInfo.InvariantCulture)}");
builder.Append($"&redirect_uri={HttpUtility.UrlEncode(RedirectUri.ToString())}"); builder.Append($"&redirect_uri={HttpUtility.UrlEncode(RedirectUri.ToString())}");

View File

@ -11,7 +11,7 @@ namespace SpotifyAPI.Web
public abstract class RequestParams public abstract class RequestParams
{ {
private static readonly ConcurrentDictionary<Type, List<(PropertyInfo, BodyParamAttribute)>> _bodyParamsCache = private static readonly ConcurrentDictionary<Type, List<(PropertyInfo, BodyParamAttribute)>> _bodyParamsCache =
new ConcurrentDictionary<Type, List<(PropertyInfo, BodyParamAttribute)>>(); new();
public JObject BuildBodyParams() public JObject BuildBodyParams()
{ {
@ -59,7 +59,7 @@ namespace SpotifyAPI.Web
} }
private static readonly ConcurrentDictionary<Type, List<(PropertyInfo, QueryParamAttribute)>> _queryParamsCache = private static readonly ConcurrentDictionary<Type, List<(PropertyInfo, QueryParamAttribute)>> _queryParamsCache =
new ConcurrentDictionary<Type, List<(PropertyInfo, QueryParamAttribute)>>(); new();
public Dictionary<string, string> BuildQueryParams() public Dictionary<string, string> BuildQueryParams()
{ {
@ -146,7 +146,7 @@ namespace SpotifyAPI.Web
} }
else 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)] [AttributeUsage(AttributeTargets.Property)]
public class QueryParamAttribute : Attribute public sealed class QueryParamAttribute : Attribute
{ {
public string Key { get; } public string Key { get; }
@ -167,7 +167,7 @@ namespace SpotifyAPI.Web
} }
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public class BodyParamAttribute : Attribute public sealed class BodyParamAttribute : Attribute
{ {
public string Key { get; } public string Key { get; }

View File

@ -35,9 +35,9 @@
<PackageReference Include="Newtonsoft.Json" Version="12.0.3"> <PackageReference Include="Newtonsoft.Json" Version="12.0.3">
<PrivateAssets>None</PrivateAssets> <PrivateAssets>None</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<PropertyGroup>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
</PropertyGroup>
</Project> </Project>

View File

@ -3,13 +3,13 @@ namespace SpotifyAPI.Web
{ {
public static class SpotifyUrls 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"); public static Uri Me() => EUri($"me");
@ -125,6 +125,6 @@ namespace SpotifyAPI.Web
public static Uri LibraryShows() => EUri($"me/shows"); 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);
} }
} }

View File

@ -114,21 +114,17 @@ namespace SpotifyAPI.Web
private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength) private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength)
{ {
switch (inputLength % 4) return (inputLength % 4) switch
{ {
case 0: 0 => 0,
return 0; 2 => 2,
case 2: 3 => 1,
return 2; _ => throw new FormatException(
case 3: string.Format(
return 1; CultureInfo.CurrentCulture,
default: WebEncoders_MalformedInput,
throw new FormatException( inputLength)),
string.Format( };
CultureInfo.CurrentCulture,
WebEncoders_MalformedInput,
inputLength));
}
} }
} }

View File

@ -52,22 +52,18 @@ namespace SpotifyAPI.Web
private static string GenerateRandomURLSafeString(int length) private static string GenerateRandomURLSafeString(int length)
{ {
using (var rng = new RNGCryptoServiceProvider()) using var rng = new RNGCryptoServiceProvider();
{ var bit_count = length * 6;
var bit_count = length * 6; var byte_count = (bit_count + 7) / 8; // rounded up
var byte_count = (bit_count + 7) / 8; // rounded up var bytes = new byte[byte_count];
var bytes = new byte[byte_count]; rng.GetBytes(bytes);
rng.GetBytes(bytes); return Base64Util.UrlEncode(bytes);
return Base64Util.UrlEncode(bytes);
}
} }
private static byte[] ComputeSHA256(string value) private static byte[] ComputeSHA256(string value)
{ {
using (var hash = SHA256.Create()) using var hash = SHA256.Create();
{ return hash.ComputeHash(Encoding.UTF8.GetBytes(value));
return hash.ComputeHash(Encoding.UTF8.GetBytes(value));
}
} }
} }
} }

View File

@ -6,14 +6,14 @@ using System.Diagnostics.CodeAnalysis;
namespace SpotifyAPI.Web namespace SpotifyAPI.Web
{ {
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
public class StringAttribute : Attribute public sealed class StringAttribute : Attribute
{ {
public StringAttribute(string value) public StringAttribute(string value)
{ {
Value = value; Value = value;
} }
public string Value { get; set; } public string Value { get; }
#if NETSTANDARD2_0 #if NETSTANDARD2_0
public static bool GetValue(Type enumType, Enum enumValue, out string? result) public static bool GetValue(Type enumType, Enum enumValue, out string? result)