Skip to content

Commit 07038d8

Browse files
authored
SIANXSVC-1193: dotnet-e5e does not handle binary requests as expected (#5)
* SIANXSVC-1193: dotnet-e5e does not handle binary requests as expected * SIANXSVC-1193: Add example code to the InlineHandler Closes SIANXSVC-1193
1 parent c1c785e commit 07038d8

16 files changed

+422
-39
lines changed

examples/InlineHandler/Program.cs

+12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ await Host.CreateDefaultBuilder(args)
99
var res = E5EResponse.From("test");
1010
return Task.FromResult(res);
1111
});
12+
builder.RegisterEntrypoint("Binary", request =>
13+
{
14+
// This entrypoint receives one file (type: binary) and returns an anonymous object with the length.
15+
var fileData = request.Event.AsBytes();
16+
return Task.FromResult(E5EResponse.From(new { FileLength = fileData?.LongLength }));
17+
});
18+
builder.RegisterEntrypoint("ReturnFirstFile", request =>
19+
{
20+
// This entrypoint receives multiple files as a mixed request and returns the first.
21+
var files = request.Event.AsFiles();
22+
return Task.FromResult(E5EResponse.From(files.First()));
23+
});
1224
})
1325
.UseConsoleLifetime()
1426
.Build()

src/Anexia.E5E.Tests/Anexia.E5E.Tests.csproj

+10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
55
<Nullable>enable</Nullable>
66
<IsPackable>false</IsPackable>
7+
<LangVersion>12</LangVersion>
78
</PropertyGroup>
89

910
<ItemGroup>
@@ -27,4 +28,13 @@
2728
<ProjectReference Include="..\Anexia.E5E\Anexia.E5E.csproj"/>
2829
</ItemGroup>
2930

31+
<ItemGroup>
32+
<None Update="TestData\binary_request_with_multiple_files.json">
33+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
34+
</None>
35+
<None Update="TestData\binary_request_unknown_content_type.json">
36+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
37+
</None>
38+
</ItemGroup>
39+
3040
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System.Threading.Tasks;
2+
3+
using Anexia.E5E.Exceptions;
4+
using Anexia.E5E.Functions;
5+
using Anexia.E5E.Tests.TestHelpers;
6+
7+
using static Anexia.E5E.Tests.TestData.TestData;
8+
9+
using Xunit;
10+
using Xunit.Abstractions;
11+
12+
namespace Anexia.E5E.Tests.Integration;
13+
14+
public sealed class BinaryRequestIntegrationTests(ITestOutputHelper outputHelper) : IntegrationTestBase(outputHelper)
15+
{
16+
[Fact]
17+
public async Task DecodeToBytesThrowsForMixedRequest()
18+
{
19+
await Host.StartWithTestEntrypointAsync(request =>
20+
{
21+
Assert.Throws<E5EInvalidConversionException>(() => request.Event.AsBytes());
22+
return null!;
23+
});
24+
await Host.WriteOnceAsync(BinaryRequestWithMultipleFiles);
25+
}
26+
27+
[Fact]
28+
public async Task MultipleFilesAreProperlyDecoded()
29+
{
30+
await Host.StartWithTestEntrypointAsync(request =>
31+
{
32+
var content = "Hello world!"u8.ToArray();
33+
var files = request.Event.AsFiles();
34+
Assert.Collection(files, first =>
35+
{
36+
Assert.NotNull(first);
37+
Assert.Equivalent(first, new E5EFileData(content)
38+
{
39+
FileSizeInBytes = 12,
40+
Filename = "my-file-1.name",
41+
ContentType = "application/my-content-type-1",
42+
Charset = "utf-8",
43+
});
44+
}, second =>
45+
{
46+
Assert.NotNull(second);
47+
Assert.Equivalent(second, new E5EFileData(content)
48+
{
49+
FileSizeInBytes = 12,
50+
Filename = "my-file-2.name",
51+
ContentType = "application/my-content-type-2",
52+
Charset = "utf-8",
53+
});
54+
});
55+
return null!;
56+
});
57+
await Host.WriteOnceAsync(BinaryRequestWithMultipleFiles);
58+
}
59+
60+
[Fact]
61+
public async Task UnknownContentType()
62+
{
63+
await Host.StartWithTestEntrypointAsync(request =>
64+
{
65+
Assert.Equal("Hello world!"u8.ToArray(), request.Event.AsBytes());
66+
return null!;
67+
});
68+
await Host.WriteOnceAsync(BinaryRequestWithUnknownContentType);
69+
}
70+
71+
[Fact]
72+
public async Task FallbackForByteArrayReturnsValidResponse()
73+
{
74+
// act
75+
await Host.StartWithTestEntrypointAsync(_ => E5EResponse.From("Hello world!"u8.ToArray()));
76+
var response = await Host.WriteOnceAsync(x => x.WithData("test"));
77+
78+
// assert
79+
const string expected =
80+
"""
81+
{"data":{"binary":"SGVsbG8gd29ybGQh","type":"binary","size":0,"name":"dotnet-e5e-binary-response.blob","content_type":"application/octet-stream","charset":"utf-8"},"type":"binary"}
82+
""";
83+
Assert.Contains(expected, response.Stdout);
84+
}
85+
86+
[Fact]
87+
public async Task FileDataReturnsValidResponse()
88+
{
89+
// act
90+
await Host.StartWithTestEntrypointAsync(_ => E5EResponse.From(new E5EFileData("Hello world!"u8.ToArray())
91+
{
92+
Type = "binary",
93+
FileSizeInBytes = 16,
94+
Filename = "hello-world.txt",
95+
ContentType = "text/plain",
96+
Charset = "utf-8",
97+
}));
98+
var response = await Host.WriteOnceAsync(x => x.WithData("test"));
99+
100+
// assert
101+
const string expected =
102+
"""
103+
{"data":{"binary":"SGVsbG8gd29ybGQh","type":"binary","size":16,"name":"hello-world.txt","content_type":"text/plain","charset":"utf-8"},"type":"binary"}
104+
""";
105+
Assert.Contains(expected, response.Stdout);
106+
}
107+
}

src/Anexia.E5E.Tests/Integration/DocumentationTests.cs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Text;
3+
using System.Text.Json;
34
using System.Threading.Tasks;
45

56
using Anexia.E5E.Functions;
@@ -42,7 +43,11 @@ await Host.StartWithTestEntrypointAsync(request =>
4243
var data = request.Event.As<SumData>()!;
4344
return E5EResponse.From(data.A + data.B);
4445
});
45-
var response = await Host.WriteRequestOnceAsync(x => x.WithData(new SumData { A = 3, B = 2 }));
46+
var response = await Host.WriteRequestOnceAsync(x => x.WithData(new SumData
47+
{
48+
A = 3,
49+
B = 2,
50+
}));
4651
Assert.Equal(5, response.As<int>());
4752
}
4853

@@ -55,8 +60,8 @@ await Host.StartWithTestEntrypointAsync(request =>
5560
var resp = Encoding.UTF8.GetBytes($"Hello {name}");
5661
return E5EResponse.From(resp);
5762
});
58-
var response = await Host.WriteRequestOnceAsync(x => x.WithData(Encoding.UTF8.GetBytes("Luna")));
59-
Assert.Equal("\"SGVsbG8gTHVuYQ==\"", response.Data.GetRawText());
63+
var response = await Host.WriteRequestOnceAsync(x => x.WithData(new E5EFileData("Luna"u8.ToArray())));
64+
Assert.Equal("Hello Luna"u8.ToArray(), response.Data.Deserialize<E5EFileData>()!.Bytes);
6065
Assert.Equal(E5EResponseType.Binary, response.Type);
6166
}
6267

src/Anexia.E5E.Tests/Serialization/SerializationTests.cs

+13-12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Collections.Generic;
44
using System.Linq;
55
using System.Net;
6-
using System.Text;
76
using System.Text.Json;
87
using System.Threading.Tasks;
98

@@ -98,8 +97,9 @@ public void ResponseSerializationWorksBidirectional(string _, E5EResponse input)
9897
public void ResponseSerializationRecognisesCorrectType()
9998
{
10099
Assert.Equal(E5EResponseType.Text, E5EResponse.From("test").Type);
101-
Assert.Equal(E5EResponseType.Binary, E5EResponse.From(Encoding.UTF8.GetBytes("test")).Type);
102-
Assert.Equal(E5EResponseType.Binary, E5EResponse.From(Encoding.UTF8.GetBytes("test").AsEnumerable()).Type);
100+
Assert.Equal(E5EResponseType.Binary, E5EResponse.From("test"u8.ToArray()).Type);
101+
Assert.Equal(E5EResponseType.Binary, E5EResponse.From("test"u8.ToArray().AsEnumerable()).Type);
102+
Assert.Equal(E5EResponseType.Binary, E5EResponse.From(new E5EFileData("something"u8.ToArray())).Type);
103103
Assert.Equal(E5EResponseType.StructuredObject, E5EResponse.From(new E5ERuntimeMetadata()).Type);
104104
}
105105

@@ -159,6 +159,7 @@ private class SerializationTestsData : IEnumerable<object[]>
159159
new E5EContext("generic", DateTimeOffset.FromUnixTimeSeconds(0), true),
160160
new E5ERequestParameters(),
161161
new E5ERuntimeMetadata(),
162+
new E5EFileData("data"u8.ToArray()),
162163
};
163164

164165
private IEnumerable<object[]> Data => _objects.Select(obj => new[] { obj });
@@ -179,11 +180,8 @@ private class RequestSerializationTestsData : IEnumerable<object[]>
179180
private readonly Dictionary<string, E5EEvent> _tests = new()
180181
{
181182
{ "simple text request", new TestRequestBuilder().WithData("test").BuildEvent() },
182-
{ "simple binary request", new TestRequestBuilder().WithData(Encoding.UTF8.GetBytes("test")).BuildEvent() },
183-
{
184-
"simple object request",
185-
new TestRequestBuilder().WithData(new Dictionary<string, string> { { "test", "value" } }).BuildEvent()
186-
},
183+
{ "simple binary request", new TestRequestBuilder().WithData(new E5EFileData("hello"u8.ToArray())).BuildEvent() },
184+
{ "simple object request", new TestRequestBuilder().WithData(new Dictionary<string, string> { { "test", "value" } }).BuildEvent() },
187185
{
188186
"request with headers and parameters", new TestRequestBuilder().WithData("test")
189187
.AddParam("param", "value")
@@ -210,12 +208,15 @@ private class ResponseSerializationTestsData : IEnumerable<object[]>
210208
private readonly Dictionary<string, E5EResponse> _tests = new()
211209
{
212210
{ "simple text response", E5EResponse.From("test") },
213-
{ "simple binary response", E5EResponse.From(Encoding.UTF8.GetBytes("test")) },
214-
{ "simple object response", E5EResponse.From(new Dictionary<string, int> { { "a", 1 }, { "b", 2 } }) },
211+
{ "simple binary response", E5EResponse.From("hello"u8.ToArray()) },
215212
{
216-
"text response with headers and status code", E5EResponse.From("test", HttpStatusCode.Moved,
217-
new E5EHttpHeaders { { "Location", "https://example.com" } })
213+
"simple object response", E5EResponse.From(new Dictionary<string, int>
214+
{
215+
{ "a", 1 },
216+
{ "b", 2 },
217+
})
218218
},
219+
{ "text response with headers and status code", E5EResponse.From("test", HttpStatusCode.Moved, new E5EHttpHeaders { { "Location", "https://example.com" } }) },
219220
};
220221

221222
private IEnumerable<object[]> Data => _tests.Select(obj => new object[] { obj.Key, obj.Value });
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"type":"binary","data":"dGVzdA=="}
1+
{"type":"binary","data":{"binary":"aGVsbG8=","type":"binary","size":0,"name":null,"content_type":null,"charset":"utf-8"}}
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"data":"dGVzdA==","type":"binary"}
1+
{"data":{"binary":"aGVsbG8=","type":"binary","size":0,"name":"dotnet-e5e-binary-response.blob","content_type":"application/octet-stream","charset":"utf-8"},"type":"binary"}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.IO;
2+
3+
namespace Anexia.E5E.Tests.TestData;
4+
5+
internal static class TestData
6+
{
7+
private static string ReadTestDataFile(string path) => File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "TestData", path));
8+
9+
internal static string BinaryRequestWithMultipleFiles => ReadTestDataFile("binary_request_with_multiple_files.json");
10+
internal static string BinaryRequestWithUnknownContentType => ReadTestDataFile("binary_request_unknown_content_type.json");
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"context": {
3+
"type": "integration-test",
4+
"async": true,
5+
"date": "2024-01-01T00:00:00Z"
6+
},
7+
"event": {
8+
"type": "binary",
9+
"data": {
10+
"binary": "SGVsbG8gd29ybGQh",
11+
"type": "binary",
12+
"name": "my-file-1.name",
13+
"size": 12,
14+
"content_type": "application/my-content-type-1",
15+
"charset": "utf-8"
16+
}
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"context": {
3+
"type": "integration-test",
4+
"async": true,
5+
"date": "2024-01-01T00:00:00Z"
6+
},
7+
"event": {
8+
"type": "mixed",
9+
"data": [
10+
{
11+
"binary": "SGVsbG8gd29ybGQh",
12+
"type": "binary",
13+
"name": "my-file-1.name",
14+
"size": 12,
15+
"content_type": "application/my-content-type-1",
16+
"charset": "utf-8"
17+
},
18+
{
19+
"binary": "SGVsbG8gd29ybGQh",
20+
"type": "binary",
21+
"name": "my-file-2.name",
22+
"size": 12,
23+
"content_type": "application/my-content-type-2",
24+
"charset": "utf-8"
25+
}
26+
]
27+
}
28+
}

src/Anexia.E5E.Tests/TestHelpers/TestRequestBuilder.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Text.Json;
34

@@ -17,7 +18,9 @@ public TestRequestBuilder WithData<T>(T data)
1718
_requestType = data switch
1819
{
1920
string => E5ERequestDataType.Text,
20-
IEnumerable<byte> => E5ERequestDataType.Binary,
21+
IEnumerable<byte> => throw new InvalidOperationException(
22+
$"E5E does not compose binary requests just from the bytes. Please convert this call to use {nameof(E5EFileData)} instead."),
23+
E5EFileData => E5ERequestDataType.Binary,
2124
_ => E5ERequestDataType.StructuredObject,
2225
};
2326
_data = JsonSerializer.SerializeToElement(data);

src/Anexia.E5E/Exceptions/E5EInvalidConversionException.cs

+17-7
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,36 @@ namespace Anexia.E5E.Exceptions;
1010
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
1111
public class E5EInvalidConversionException : E5EException
1212
{
13-
private E5EInvalidConversionException(E5ERequestDataType expected, E5ERequestDataType actual)
14-
: base($"Cannot convert data of type {actual} into the type {expected}")
13+
private E5EInvalidConversionException(E5ERequestDataType actual, E5ERequestDataType[] allowedTypes)
14+
: base($"Cannot convert data of type {actual} into one of {actual}")
1515
{
16-
Expected = expected;
16+
AllowedTypes = allowedTypes;
1717
Actual = actual;
18+
#pragma warning disable CS0618 // Type or member is obsolete
19+
Expected = allowedTypes[0];
20+
#pragma warning restore CS0618 // Type or member is obsolete
1821
}
1922

2023
/// <summary>
21-
/// The required data type for this conversion call.
24+
/// The required data type for this conversion call. Obsolete, got replaced with <see cref="AllowedTypes"/>.
2225
/// </summary>
26+
[Obsolete(
27+
"The library got support for multiple allowedTypes data types per conversion. Please migrate this call to AllowedTypes.")]
2328
public E5ERequestDataType Expected { get; }
2429

2530
/// <summary>
2631
/// The actual data type.
2732
/// </summary>
2833
public E5ERequestDataType Actual { get; }
2934

30-
internal static void ThrowIfNotMatch(E5ERequestDataType expected, E5ERequestDataType actual)
35+
/// <summary>
36+
/// The allowed data types for this conversion call.
37+
/// </summary>
38+
public E5ERequestDataType[] AllowedTypes { get; }
39+
40+
internal static void ThrowIfNotMatch(E5ERequestDataType value, params E5ERequestDataType[] allowed)
3141
{
32-
if (expected != actual)
33-
throw new E5EInvalidConversionException(expected, actual);
42+
if (!allowed.Contains(value))
43+
throw new E5EInvalidConversionException(value, allowed);
3444
}
3545
}

0 commit comments

Comments
 (0)