Skip to content

Commit

Permalink
Merge branch 'release/0.99.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Jericho committed Jun 1, 2023
2 parents 11121d9 + 419cce5 commit b2b991e
Show file tree
Hide file tree
Showing 15 changed files with 124 additions and 63 deletions.
10 changes: 5 additions & 5 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ insert_final_newline = false
trim_trailing_whitespace = true

# .NET Code files
[*.{cs,csx,cake,vb}]
[*.{cs,csx,cake,vb,vbx,h,cpp,idl}]
indent_style = tab
tab_width = 4
insert_final_newline = true
Expand All @@ -21,12 +21,12 @@ insert_final_newline = true
indent_style = tab
tab_width = 4

# Visual Studio XML Project Files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
# MSBuild project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,msbuildproj}]
indent_size = 2

# Various XML Configuration Files
[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}]
# Xml config files
[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct,runsettings}]
indent_size = 2

# JSON Files
Expand Down
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,6 @@
*.DOT diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

# The macOS codesign tool is extremely picky, and requires LF line endings.
*.plist eol=lf
2 changes: 1 addition & 1 deletion Source/StrongGrid.Benchmark/StrongGrid.Benchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.3" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
<PackageReference Include="RichardSzalay.MockHttp" Version="6.0.0" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Logzio.DotNet.NLog" Version="1.0.13" />
<PackageReference Include="Logzio.DotNet.NLog" Version="1.0.16" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.1" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.2" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions Source/StrongGrid.IntegrationTests/TestsRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,10 @@ public async Task<int> RunAsync()
await summary.WriteLineAsync("******************** SUMMARY *********************").ConfigureAwait(false);
await summary.WriteLineAsync("**************************************************").ConfigureAwait(false);

var nameMaxLength = Math.Min(results.Max(r => r.TestName.Length), TEST_NAME_MAX_LENGTH);
foreach (var (TestName, ResultCode, Message) in results.OrderBy(r => r.TestName).ToArray())
{
var name = TestName.Length <= TEST_NAME_MAX_LENGTH ? TestName : TestName.Substring(0, TEST_NAME_MAX_LENGTH - 3) + "...";
await summary.WriteLineAsync($"{name.PadRight(TEST_NAME_MAX_LENGTH, ' ')} : {Message}").ConfigureAwait(false);
await summary.WriteLineAsync($"{TestName.ToExactLength(nameMaxLength)} : {Message}").ConfigureAwait(false);
}

await summary.WriteLineAsync("**************************************************").ConfigureAwait(false);
Expand Down
2 changes: 1 addition & 1 deletion Source/StrongGrid.UnitTests/StrongGrid.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="RichardSzalay.MockHttp" Version="6.0.0" />
<PackageReference Include="Shouldly" Version="4.1.0" />
Expand Down
36 changes: 36 additions & 0 deletions Source/StrongGrid.UnitTests/WebhookParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,42 @@ public void ValidateWebhookSignature()
result.Length.ShouldBe(11); // The sample payload contains 11 events
}

[Fact]
// This unit test validates that we fixed the issue described in GH-492
// It proves that we can serialize and deserilize events without loosing
// properties of derived types such as 'Reason' for a bounced event,
// 'UserAgent' for a click event and 'IpAddress' for an open event.
public async Task Serialize_and_deserialize_derived_types()
{
// Arrange
var events = new Event[]
{
new BouncedEvent() { Email = "test1@whatever.com", Reason = "This is a test"},
new ClickedEvent() { Email = "test2@whatever.com", UserAgent = "This is a test" },
new OpenedEvent() { Email = "test3@whatever.com", IpAddress = "This is a test" }
};

// Act
var serializedEvents = new MemoryStream(JsonSerializer.SerializeToUtf8Bytes(events));
var result = await JsonSerializer.DeserializeAsync<Event[]>(serializedEvents).ConfigureAwait(false);

// Assert
result.ShouldNotBeNull();
result.Length.ShouldBe(3);

result[0].Email.ShouldBe("test1@whatever.com");
result[0].EventType.ShouldBe(EventType.Bounce);
((BouncedEvent)result[0]).Reason.ShouldBe("This is a test");

result[1].Email.ShouldBe("test2@whatever.com");
result[1].EventType.ShouldBe(EventType.Click);
((ClickedEvent)result[1]).UserAgent.ShouldBe("This is a test");

result[2].Email.ShouldBe("test3@whatever.com");
result[2].EventType.ShouldBe(EventType.Open);
((OpenedEvent)result[2]).IpAddress.ShouldBe("This is a test");
}

private Stream GetStream(string responseContent)
{
var byteArray = Encoding.UTF8.GetBytes(responseContent);
Expand Down
10 changes: 9 additions & 1 deletion Source/StrongGrid/Extensions/Internal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ internal static async Task<string> ReadAsStringAsync(this HttpContent httpConten

if (httpContent != null)
{
#if NET7_0_OR_GREATER
#if NET5_0_OR_GREATER
var contentStream = await httpContent.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
#else
var contentStream = await httpContent.ReadAsStreamAsync().ConfigureAwait(false);
Expand Down Expand Up @@ -801,6 +801,14 @@ internal static string ToHexString(this byte[] bytes)
return result.ToString();
}

internal static string ToExactLength(this string source, int totalWidth, string postfix = "...", char paddingChar = ' ')
{
if (string.IsNullOrEmpty(source)) return new string(paddingChar, totalWidth);
if (source.Length <= totalWidth) return source.PadRight(totalWidth, paddingChar);
var result = $"{source.Substring(0, totalWidth - (postfix?.Length ?? 0))}{postfix ?? string.Empty}";
return result;
}

/// <summary>Asynchronously converts the JSON encoded content and convert it to an object of the desired type.</summary>
/// <typeparam name="T">The response model to deserialize into.</typeparam>
/// <param name="httpContent">The content.</param>
Expand Down
8 changes: 8 additions & 0 deletions Source/StrongGrid/Extensions/Public.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1277,7 +1277,11 @@ public static async Task DownloadExportFilesAsync(this IContacts contacts, strin
var destinationPath = Path.Combine(destinationFolder, exportFile.FileName);
using (Stream output = File.OpenWrite(destinationPath))
{
#if NET5_0_OR_GREATER
await exportFile.Stream.CopyToAsync(output, cancellationToken).ConfigureAwait(false);
#else
await exportFile.Stream.CopyToAsync(output).ConfigureAwait(false);
#endif
}
}
}
Expand Down Expand Up @@ -1314,7 +1318,11 @@ public static async Task DownloadCsvAsync(this IEmailActivities emailActivities,
{
using (Stream output = File.OpenWrite(destinationPath))
{
#if NET5_0_OR_GREATER
await responseStream.CopyToAsync(output, cancellationToken).ConfigureAwait(false);
#else
await responseStream.CopyToAsync(output).ConfigureAwait(false);
#endif
}
}
}
Expand Down
21 changes: 12 additions & 9 deletions Source/StrongGrid/Json/BaseJsonConverter{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
var typeOfValue = typeof(T);
if (value == null) return;

var typeOfValue = value.GetType(); // Do not use typeof(T). See https://github.com/Jericho/StrongGrid/issues/492
if (typeOfValue.IsArray)
{
var typeOfItems = typeOfValue.GetElementType();
Expand All @@ -46,26 +47,28 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions
}
else
{
Serialize(writer, value, options, null);
Serialize(writer, value, typeOfValue, options, null);
}
}

internal void Serialize(Utf8JsonWriter writer, T value, JsonSerializerOptions options, Action<string, object, Type, JsonSerializerOptions, JsonConverterAttribute> propertySerializer = null)
internal static void Serialize(Utf8JsonWriter writer, T value, JsonSerializerOptions options, Action<string, object, Type, JsonSerializerOptions, JsonConverterAttribute> propertySerializer = null)
{
Serialize(writer, value, value.GetType(), options, propertySerializer);
}

internal static void Serialize(Utf8JsonWriter writer, T value, Type typeOfValue, JsonSerializerOptions options, Action<string, object, Type, JsonSerializerOptions, JsonConverterAttribute> propertySerializer = null)
{
if (value == null)
{
writer.WriteNullValue();
return;
}

if (propertySerializer == null)
{
propertySerializer = (propertyName, propertyValue, propertyType, options, propertyConverterAttribute) => WriteJsonProperty(writer, propertyName, propertyValue, propertyType, options, propertyConverterAttribute);
}
propertySerializer ??= (propertyName, propertyValue, propertyType, options, propertyConverterAttribute) => WriteJsonProperty(writer, propertyName, propertyValue, propertyType, options, propertyConverterAttribute);

writer.WriteStartObject();

var allProperties = typeof(T).GetProperties();
var allProperties = typeOfValue.GetProperties();

foreach (var propertyInfo in allProperties)
{
Expand All @@ -89,7 +92,7 @@ internal void Serialize(Utf8JsonWriter writer, T value, JsonSerializerOptions op
writer.WriteEndObject();
}

internal void WriteJsonProperty(Utf8JsonWriter writer, string propertyName, object propertyValue, Type propertyType, JsonSerializerOptions options, JsonConverterAttribute propertyConverterAttribute)
internal static void WriteJsonProperty(Utf8JsonWriter writer, string propertyName, object propertyValue, Type propertyType, JsonSerializerOptions options, JsonConverterAttribute propertyConverterAttribute)
{
writer.WritePropertyName(propertyName);

Expand Down
8 changes: 4 additions & 4 deletions Source/StrongGrid/StrongGrid.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="HttpMultipartParser" Version="8.0.0" />
<PackageReference Include="HttpMultipartParser" Version="8.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.1" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="MimeTypesMap" Version="1.0.8" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.2.0" />
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.3.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.Text.Json" Version="7.0.1" />
<PackageReference Include="System.Text.Json" Version="7.0.2" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace StrongGrid.Utilities
Expand Down Expand Up @@ -34,9 +35,9 @@ public static SendGridMultipartFormDataParser Parse(Stream stream)
return ConvertToSendGridParser(parser);
}

public static async Task<SendGridMultipartFormDataParser> ParseAsync(Stream stream)
public static async Task<SendGridMultipartFormDataParser> ParseAsync(Stream stream, CancellationToken cancellationToken = default)
{
var parser = await MultipartFormBinaryDataParser.ParseAsync(stream, Encoding.UTF8).ConfigureAwait(false);
var parser = await MultipartFormBinaryDataParser.ParseAsync(stream, Encoding.UTF8, cancellationToken: cancellationToken).ConfigureAwait(false);
return ConvertToSendGridParser(parser);
}

Expand Down
36 changes: 13 additions & 23 deletions Source/StrongGrid/WebhookParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace StrongGrid
Expand Down Expand Up @@ -53,13 +54,18 @@ static WebhookParser()
/// <param name="publicKey">Your public key. To obtain this value, see <see cref="StrongGrid.Resources.WebhookSettings.GetSignedEventsPublicKeyAsync"/>.</param>
/// <param name="signature">The signature.</param>
/// <param name="timestamp">The timestamp.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>An array of <see cref="Event">events</see>.</returns>
public async Task<Event[]> ParseSignedEventsWebhookAsync(Stream stream, string publicKey, string signature, string timestamp)
public async Task<Event[]> ParseSignedEventsWebhookAsync(Stream stream, string publicKey, string signature, string timestamp, CancellationToken cancellationToken = default)
{
string requestBody;
using (var streamReader = new StreamReader(stream))
{
#if NET7_0_OR_GREATER
requestBody = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
#else
requestBody = await streamReader.ReadToEndAsync().ConfigureAwait(false);
#endif
}

var webHookEvents = ParseSignedEventsWebhook(requestBody, publicKey, signature, timestamp);
Expand All @@ -70,10 +76,11 @@ public async Task<Event[]> ParseSignedEventsWebhookAsync(Stream stream, string p
/// Parses the events webhook asynchronously.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>An array of <see cref="Event">events</see>.</returns>
public async Task<Event[]> ParseEventsWebhookAsync(Stream stream)
public async Task<Event[]> ParseEventsWebhookAsync(Stream stream, CancellationToken cancellationToken = default)
{
var webHookEvents = await JsonSerializer.DeserializeAsync<Event[]>(stream, JsonFormatter.DeserializerOptions).ConfigureAwait(false);
var webHookEvents = await JsonSerializer.DeserializeAsync<Event[]>(stream, JsonFormatter.DeserializerOptions, cancellationToken).ConfigureAwait(false);
return webHookEvents;
}

Expand Down Expand Up @@ -156,23 +163,15 @@ public Event[] ParseEventsWebhook(string requestBody)
/// Parses the inbound email webhook asynchronously.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The <see cref="InboundEmail"/>.</returns>
public async Task<InboundEmail> ParseInboundEmailWebhookAsync(Stream stream)
public async Task<InboundEmail> ParseInboundEmailWebhookAsync(Stream stream, CancellationToken cancellationToken = default)
{
// We need to be able to rewind the stream.
// Therefore, we must make a copy of the stream if it doesn't allow changing the position
if (!stream.CanSeek)
{
using var ms = Utils.MemoryStreamManager.GetStream();
await stream.CopyToAsync(ms).ConfigureAwait(false);
return await ParseInboundEmailWebhookAsync(ms).ConfigureAwait(false);
}

// It's important to rewind the stream
stream.Position = 0;

// Asynchronously parse the multipart content received from SendGrid
var parser = await SendGridMultipartFormDataParser.ParseAsync(stream).ConfigureAwait(false);
var parser = await SendGridMultipartFormDataParser.ParseAsync(stream, cancellationToken).ConfigureAwait(false);

return ParseInboundEmail(parser);
}
Expand All @@ -185,15 +184,6 @@ public async Task<InboundEmail> ParseInboundEmailWebhookAsync(Stream stream)
[Obsolete("Use the async version of this method, it can read the content of the stream much more efficiently.")]
public InboundEmail ParseInboundEmailWebhook(Stream stream)
{
// We need to be able to rewind the stream.
// Therefore, we must make a copy of the stream if it doesn't allow changing the position
if (!stream.CanSeek)
{
using var ms = Utils.MemoryStreamManager.GetStream();
stream.CopyTo(ms);
return ParseInboundEmailWebhook(ms);
}

// It's important to rewind the stream
stream.Position = 0;

Expand Down
Loading

0 comments on commit b2b991e

Please sign in to comment.