Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/enable nullable reference types core #52

Merged
merged 20 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Sinch.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=PSTN/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
3 changes: 3 additions & 0 deletions examples/Console/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[*]
resharper_unused_variable_highlighting=none
resharper_unused_variable_compiler_highlighting=none
1 change: 0 additions & 1 deletion examples/Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using DotNetEnv;
using Sinch;
using Sinch.Auth;

// Assume .env file is present in your output directory
Env.Load();
Expand Down
4 changes: 2 additions & 2 deletions examples/Console/UseServicePlanIdForSms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public void Example()
var sinchClient = new SinchClient(default, default, default,
options =>
{
options.UseServicePlanIdWithSms(Environment.GetEnvironmentVariable("SINCH_SERVICE_PLAN_ID"),
Environment.GetEnvironmentVariable("SINCH_API_TOKEN"), SmsServicePlanIdHostingRegion.Ca);
options.UseServicePlanIdWithSms(Environment.GetEnvironmentVariable("SINCH_SERVICE_PLAN_ID")!,
Environment.GetEnvironmentVariable("SINCH_API_TOKEN")!, SmsServicePlanIdHostingRegion.Ca);
});
sinchClient.Sms.Batches.Send(new SendTextBatchRequest()
{
Expand Down
12 changes: 6 additions & 6 deletions src/Sinch/ApiError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ namespace Sinch
{
internal sealed class ApiErrorResponse
{
public ApiError Error { get; set; }
public ApiError? Error { get; set; }

public string Code { get; set; }
public string? Code { get; set; }

public string Text { get; set; }
public string? Text { get; set; }
}

internal sealed class ApiError
{
public int Code { get; set; }

public string Message { get; set; }
public string? Message { get; set; }

public string Status { get; set; }
public string? Status { get; set; }

public List<JsonNode> Details { get; set; }
public List<JsonNode>? Details { get; set; }
}
}
14 changes: 7 additions & 7 deletions src/Sinch/Auth/ApplicationSignedAuth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ internal class ApplicationSignedAuth : ISinchAuth
{
private readonly string _appSecret;
private readonly string _appKey;
private byte[] _jsonBodyInBytes;
private string _httpVerb;
private string _requestContentType;
private string _requestPath;
private string _timestamp;
private byte[]? _jsonBodyInBytes;
private string? _httpVerb;
private string? _requestContentType;
private string? _requestPath;
private string? _timestamp;
asein-sinch marked this conversation as resolved.
Show resolved Hide resolved

public string Scheme { get; } = AuthSchemes.Application;

Expand All @@ -24,7 +24,7 @@ public ApplicationSignedAuth(string appKey, string appSecret)
}

public string GetSignedAuth(byte[] jsonBodyBytes, string httpVerb,
string requestPath, string timestamp, string contentType)
string requestPath, string timestamp, string? contentType)
{
_jsonBodyInBytes = jsonBodyBytes;
_httpVerb = httpVerb;
Expand All @@ -44,7 +44,7 @@ public Task<string> GetAuthToken(bool force = false)
encodedBody = Convert.ToBase64String(md5Bytes);
}

var toSign = new StringBuilder().AppendJoin('\n', _httpVerb.ToUpperInvariant(), encodedBody,
var toSign = new StringBuilder().AppendJoin('\n', _httpVerb?.ToUpperInvariant(), encodedBody,
_requestContentType,
_timestamp, _requestPath).ToString();

Expand Down
8 changes: 4 additions & 4 deletions src/Sinch/Auth/AuthApiError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ namespace Sinch.Auth
{
internal class AuthApiError
{
public string Error { get; set; }
public string? Error { get; set; }

[JsonPropertyName("error_verbose")]
public string ErrorVerbose { get; set; }
public string? ErrorVerbose { get; set; }

[JsonPropertyName("error_description")]
public string ErrorDescription { get; set; }
public string? ErrorDescription { get; set; }

[JsonPropertyName("error_hint")]
public string ErrorHint { get; set; }
public string? ErrorHint { get; set; }
}
}
14 changes: 10 additions & 4 deletions src/Sinch/Auth/OAuth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ internal class OAuth : ISinchAuth
private readonly HttpClient _httpClient;
private readonly string _keyId;
private readonly string _keySecret;
private readonly ILoggerAdapter<OAuth> _logger;
private volatile string _token;
private readonly ILoggerAdapter<OAuth>? _logger;
private volatile string? _token;
private readonly Uri _baseAddress;

public string Scheme { get; } = AuthSchemes.Bearer;

public OAuth(string keyId, string keySecret, HttpClient httpClient, ILoggerAdapter<OAuth> logger, Uri baseAddress)
public OAuth(string keyId, string keySecret, HttpClient httpClient, ILoggerAdapter<OAuth>? logger,
Uri baseAddress)
{
_keyId = keyId;
_keySecret = keySecret;
Expand Down Expand Up @@ -80,7 +81,12 @@ public async Task<string> GetAuthToken(bool force = false)
private class AuthResponse
{
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
#if NET7_0_OR_GREATER
public required string AccessToken { get; set; }
#else
public string AccessToken { get; set; } = null!;
#endif


/// <summary>
/// In seconds
Expand Down
12 changes: 6 additions & 6 deletions src/Sinch/Auth/SinchAuthException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ namespace Sinch.Auth
{
public sealed class SinchAuthException : HttpRequestException
{
private SinchAuthException(HttpStatusCode statusCode, string message, Exception inner) : base(message, inner,
private SinchAuthException(HttpStatusCode statusCode, string? message, Exception? inner) : base(message, inner,
statusCode)
{
}

internal SinchAuthException(HttpStatusCode statusCode, string message, Exception inner, AuthApiError authApiError)
internal SinchAuthException(HttpStatusCode statusCode, string? message, Exception? inner, AuthApiError? authApiError)
: this(statusCode, message, inner)
{
Error = authApiError?.Error;
Expand All @@ -20,12 +20,12 @@ internal SinchAuthException(HttpStatusCode statusCode, string message, Exception
ErrorVerbose = authApiError?.ErrorVerbose;
}

public string Error { get; }
public string? Error { get; }

public string ErrorVerbose { get; }
public string? ErrorVerbose { get; }

public string ErrorDescription { get; }
public string? ErrorDescription { get; }

public string ErrorHint { get; }
public string? ErrorHint { get; }
}
}
6 changes: 4 additions & 2 deletions src/Sinch/Core/EnumRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public class EnumRecordJsonConverter<T> : JsonConverter<T> where T : EnumRecord
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return Activator.CreateInstance(typeToConvert, reader.GetString()) as T;
return Activator.CreateInstance(typeToConvert, reader.GetString()) as T ??
throw new InvalidOperationException("Created instance is null");
}

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
Expand All @@ -28,7 +29,8 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions
public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert,
JsonSerializerOptions options)
{
return Activator.CreateInstance(typeToConvert, reader.GetString()) as T;
return Activator.CreateInstance(typeToConvert, reader.GetString()) as T ??
throw new InvalidOperationException("Created instance is null");
}

public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
Expand Down
2 changes: 1 addition & 1 deletion src/Sinch/Core/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static async Task EnsureSuccessApiStatusCode(this HttpResponseMessage htt
throw new SinchApiException(httpResponseMessage.StatusCode, httpResponseMessage.ReasonPhrase, null, apiError);
}

public static async Task<T> TryGetJson<T>(this HttpResponseMessage httpResponseMessage)
public static async Task<T?> TryGetJson<T>(this HttpResponseMessage httpResponseMessage)
{
var authResponse = default(T);
if (httpResponseMessage.IsJson()) authResponse = await httpResponseMessage.Content.ReadFromJsonAsync<T>();
Expand Down
32 changes: 20 additions & 12 deletions src/Sinch/Core/Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal interface IHttp
/// <param name="cancellationToken"></param>
/// <typeparam name="TResponse">The type of the response object.</typeparam>
/// <returns></returns>
Task<TResponse> Send<TResponse>(Uri uri, HttpMethod httpMethod,
Task<TResponse?> Send<TResponse>(Uri uri, HttpMethod httpMethod,
CancellationToken cancellationToken = default);

/// <summary>
Expand All @@ -43,7 +43,7 @@ Task<TResponse> Send<TResponse>(Uri uri, HttpMethod httpMethod,
/// <typeparam name="TRequest">The type of the request object.</typeparam>
/// <typeparam name="TResponse">The type of the response object.</typeparam>
/// <returns></returns>
Task<TResponse> Send<TRequest, TResponse>(Uri uri, HttpMethod httpMethod, TRequest httpContent,
Task<TResponse?> Send<TRequest, TResponse>(Uri uri, HttpMethod httpMethod, TRequest httpContent,
CancellationToken cancellationToken = default);
}

Expand All @@ -52,12 +52,12 @@ internal class Http : IHttp
{
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonSerializerOptions;
private readonly ILoggerAdapter<Http> _logger;
private readonly ILoggerAdapter<IHttp>? _logger;
private readonly ISinchAuth _auth;
private readonly string _userAgentHeaderValue;


public Http(ISinchAuth auth, HttpClient httpClient, ILoggerAdapter<Http> logger,
public Http(ISinchAuth auth, HttpClient httpClient, ILoggerAdapter<IHttp>? logger,
JsonNamingPolicy jsonNamingPolicy)
{
_logger = logger;
Expand All @@ -68,25 +68,25 @@ public Http(ISinchAuth auth, HttpClient httpClient, ILoggerAdapter<Http> logger,
PropertyNamingPolicy = jsonNamingPolicy,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
var sdkVersion = new AssemblyName(typeof(Http).GetTypeInfo()!.Assembly!.FullName!).Version!.ToString();
var sdkVersion = new AssemblyName(typeof(Http).GetTypeInfo().Assembly.FullName!).Version!.ToString();
_userAgentHeaderValue =
$"sinch-sdk/{sdkVersion} (csharp/{RuntimeInformation.FrameworkDescription};;)";
}

public Task<TResponse> Send<TResponse>(Uri uri, HttpMethod httpMethod,
public Task<TResponse?> Send<TResponse>(Uri uri, HttpMethod httpMethod,
CancellationToken cancellationToken = default)
{
return Send<object, TResponse>(uri, httpMethod, null, cancellationToken);
}

public async Task<TResponse> Send<TRequest, TResponse>(Uri uri, HttpMethod httpMethod, TRequest request,
public async Task<TResponse?> Send<TRequest, TResponse>(Uri uri, HttpMethod httpMethod, TRequest? request,
CancellationToken cancellationToken = default)
{
var retry = true;
while (true)
{
_logger?.LogDebug("Sending request to {uri}", uri);
HttpContent httpContent =
HttpContent? httpContent =
request == null ? null : JsonContent.Create(request, options: _jsonSerializerOptions);

#if DEBUG
Expand All @@ -108,10 +108,17 @@ public async Task<TResponse> Send<TRequest, TResponse>(Uri uri, HttpMethod httpM
var now = DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture);
const string headerName = "x-timestamp";
msg.Headers.Add(headerName, now);

var bytes = Array.Empty<byte>();
if (msg.Content is not null)
{
bytes = await msg.Content.ReadAsByteArrayAsync(cancellationToken);
}

token = appSignAuth.GetSignedAuth(
msg.Content?.ReadAsByteArrayAsync(cancellationToken).GetAwaiter().GetResult(),
bytes,
msg.Method.ToString().ToUpperInvariant(), msg.RequestUri.PathAndQuery,
$"{headerName}:{now}", msg.Content?.Headers?.ContentType?.ToString());
$"{headerName}:{now}", msg.Content?.Headers.ContentType?.ToString());
retry = false;
}
else
Expand All @@ -130,8 +137,8 @@ public async Task<TResponse> Send<TRequest, TResponse>(Uri uri, HttpMethod httpM
{
// will not retry when no "expired" header for a token.
const string wwwAuthenticateHeader = "www-authenticate";
if (_auth.Scheme == AuthSchemes.Bearer && true == result.Headers?.Contains(wwwAuthenticateHeader) &&
false == result.Headers?.GetValues(wwwAuthenticateHeader)?.Contains("expired"))
if (_auth.Scheme == AuthSchemes.Bearer && result.Headers.Contains(wwwAuthenticateHeader) &&
!result.Headers.GetValues(wwwAuthenticateHeader).Contains("expired"))
{
_logger?.LogDebug("OAuth Unauthorized");
}
Expand All @@ -152,6 +159,7 @@ public async Task<TResponse> Send<TRequest, TResponse>(Uri uri, HttpMethod httpM

_logger?.LogWarning("Response is not json, but {content}",
await result.Content.ReadAsStringAsync(cancellationToken));

return default;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Sinch/Core/JsonExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Sinch.Core
{
public static class JsonExtensions
{
public static object ToObject(this JsonElement element, Type type, JsonSerializerOptions options = null)
public static object? ToObject(this JsonElement element, Type type, JsonSerializerOptions? options = null)
{
var bufferWriter = new ArrayBufferWriter<byte>();
using (var writer = new Utf8JsonWriter(bufferWriter))
Expand Down
27 changes: 17 additions & 10 deletions src/Sinch/Core/JsonInterfaceConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,38 @@ public JsonInterfaceConverterAttribute(Type convertedType) : base(convertedType)
}
}

public class InterfaceConverter<T> : JsonConverter<T> where T : class
public class InterfaceConverter<T> : JsonConverter<T?> where T : class
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var type = typeof(T);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => type.IsAssignableFrom(p));

var elem = JsonElement.ParseValue(ref reader);
dynamic obj = null;
dynamic? obj = null;
foreach (var @class in types)
{
if (elem.IsTypeOf(@class, options))
{
obj = elem.ToObject(@class, options);
break;
}
if (!elem.IsTypeOf(@class, options)) continue;

obj = elem.ToObject(@class, options);
break;
}

return (T)obj;
return (T?)obj;
}

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
// just serialize as usual null object
if (value is null)
{
JsonSerializer.Serialize(writer, value, typeof(object), options);
return;
}

// need getType to get an actual instance and not typeof<T> which is always the interface itself
var type = value.GetType();
asein-sinch marked this conversation as resolved.
Show resolved Hide resolved
JsonSerializer.Serialize(writer, value, type, options);
}
Expand Down
Loading
Loading