-
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add test extensions to easily deserialize responses (#505)
- Loading branch information
1 parent
9dd3e31
commit 938e559
Showing
5 changed files
with
249 additions
and
111 deletions.
There are no files selected for viewing
152 changes: 81 additions & 71 deletions
152
Modules/Conversion/Serializers/SerializationRegistry.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,81 @@ | ||
using System.Collections.Generic; | ||
|
||
using GenHTTP.Api.Protocol; | ||
|
||
namespace GenHTTP.Modules.Conversion.Providers | ||
{ | ||
|
||
/// <summary> | ||
/// Registers formats that can be used to serialize and | ||
/// deserialize objects sent to or received from a | ||
/// service oriented handler. | ||
/// </summary> | ||
public sealed class SerializationRegistry | ||
{ | ||
|
||
#region Get-/Setters | ||
|
||
private FlexibleContentType Default { get; } | ||
|
||
private Dictionary<FlexibleContentType, ISerializationFormat> Formats { get; } | ||
|
||
#endregion | ||
|
||
#region Initialization | ||
|
||
public SerializationRegistry(FlexibleContentType defaultType, | ||
Dictionary<FlexibleContentType, ISerializationFormat> formats) | ||
{ | ||
Default = defaultType; | ||
Formats = formats; | ||
} | ||
|
||
#endregion | ||
|
||
#region Functionality | ||
|
||
public ISerializationFormat? GetDeserialization(IRequest request) | ||
{ | ||
if (request.Headers.TryGetValue("Content-Type", out string? requested)) | ||
{ | ||
return GetFormat(FlexibleContentType.Parse(requested)); | ||
} | ||
|
||
return GetFormat(Default); | ||
} | ||
|
||
public ISerializationFormat? GetSerialization(IRequest request) | ||
{ | ||
if (request.Headers.TryGetValue("Accept", out string? accepted)) | ||
{ | ||
return GetFormat(FlexibleContentType.Parse(accepted)) ?? GetFormat(Default); | ||
} | ||
|
||
return GetFormat(Default); | ||
} | ||
|
||
private ISerializationFormat? GetFormat(FlexibleContentType contentType) | ||
{ | ||
if (Formats.TryGetValue(contentType, out var format)) | ||
{ | ||
return format; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
#endregion | ||
|
||
} | ||
|
||
} | ||
using System.Collections.Generic; | ||
|
||
using GenHTTP.Api.Protocol; | ||
|
||
namespace GenHTTP.Modules.Conversion.Providers | ||
{ | ||
|
||
/// <summary> | ||
/// Registers formats that can be used to serialize and | ||
/// deserialize objects sent to or received from a | ||
/// service oriented handler. | ||
/// </summary> | ||
public sealed class SerializationRegistry | ||
{ | ||
|
||
#region Get-/Setters | ||
|
||
private FlexibleContentType Default { get; } | ||
|
||
private Dictionary<FlexibleContentType, ISerializationFormat> Formats { get; } | ||
|
||
#endregion | ||
|
||
#region Initialization | ||
|
||
public SerializationRegistry(FlexibleContentType defaultType, | ||
Dictionary<FlexibleContentType, ISerializationFormat> formats) | ||
{ | ||
Default = defaultType; | ||
Formats = formats; | ||
} | ||
|
||
#endregion | ||
|
||
#region Functionality | ||
|
||
public ISerializationFormat? GetDeserialization(IRequest request) | ||
{ | ||
if (request.Headers.TryGetValue("Content-Type", out string? requested)) | ||
{ | ||
return GetFormat(FlexibleContentType.Parse(requested)); | ||
} | ||
|
||
return GetFormat(Default); | ||
} | ||
|
||
public ISerializationFormat? GetSerialization(IRequest request) | ||
{ | ||
if (request.Headers.TryGetValue("Accept", out string? accepted)) | ||
{ | ||
return GetFormat(FlexibleContentType.Parse(accepted)) ?? GetFormat(Default); | ||
} | ||
|
||
return GetFormat(Default); | ||
} | ||
|
||
public ISerializationFormat? GetFormat(string? contentType) | ||
{ | ||
if (contentType != null) | ||
{ | ||
return GetFormat(FlexibleContentType.Parse(contentType)); | ||
} | ||
|
||
return GetFormat(Default); | ||
} | ||
|
||
private ISerializationFormat? GetFormat(FlexibleContentType contentType) | ||
{ | ||
if (Formats.TryGetValue(contentType, out var format)) | ||
{ | ||
return format; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
#endregion | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
using System; | ||
using System.Threading.Tasks; | ||
using GenHTTP.Api.Protocol; | ||
using GenHTTP.Modules.Functional; | ||
using GenHTTP.Modules.Reflection; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
||
namespace GenHTTP.Testing.Acceptance.Testing | ||
{ | ||
|
||
[TestClass] | ||
public sealed class ContentTests | ||
{ | ||
|
||
public record class MyType(int ID); | ||
|
||
[TestMethod] | ||
public async Task TestDeserialization() | ||
{ | ||
var expectation = new MyType(42); | ||
|
||
var handler = Inline.Create() | ||
.Get(() => expectation); | ||
|
||
using var host = TestHost.Run(handler); | ||
|
||
using var response = await host.GetResponseAsync(); | ||
|
||
Assert.AreEqual(expectation, await response.GetContentAsync<MyType>()); | ||
} | ||
|
||
[TestMethod] | ||
public async Task TestNull() | ||
{ | ||
var handler = Inline.Create() | ||
.Get(() => (MyType?)null); | ||
|
||
using var host = TestHost.Run(handler); | ||
|
||
using var response = await host.GetResponseAsync(); | ||
|
||
Assert.IsNull(await response.GetOptionalContentAsync<MyType>()); | ||
} | ||
|
||
[TestMethod] | ||
public async Task TestUnsupported() | ||
{ | ||
var handler = Inline.Create() | ||
.Get(() => new Result<string>("Nah").Type(FlexibleContentType.Get("text/html"))); | ||
|
||
using var host = TestHost.Run(handler); | ||
|
||
using var response = await host.GetResponseAsync(); | ||
|
||
await Assert.ThrowsExceptionAsync<InvalidOperationException>(async () => | ||
{ | ||
await response.GetOptionalContentAsync<MyType>(); | ||
}); | ||
} | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
using System; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
|
||
using GenHTTP.Modules.Conversion; | ||
using GenHTTP.Modules.Protobuf; | ||
|
||
namespace GenHTTP.Testing | ||
{ | ||
|
||
public static class ContentExtensions | ||
{ | ||
|
||
/// <summary> | ||
/// Reads the response body as a string. | ||
/// </summary> | ||
/// <param name="response">The response to read</param> | ||
/// <returns>The content of the HTTP response</returns> | ||
public static async ValueTask<string> GetContentAsync(this HttpResponseMessage response) => await response.Content.ReadAsStringAsync(); | ||
|
||
/// <summary> | ||
/// Deserializes the payload of the HTTP response into the given type. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the payload to be read</typeparam> | ||
/// <param name="response">The response to read the payload from</param> | ||
/// <returns>The deserialized payload of the response</returns> | ||
/// <exception cref="InvalidOperationException">Thrown if the format returned by the server is not supported</exception> | ||
/// <remarks> | ||
/// This method supports all formats that ship with the GenHTTP framework (JSON, XML, form encoded, Protobuf) | ||
/// and falls back to JSON if the server does not indicate a content type. | ||
/// </remarks> | ||
public static async ValueTask<T> GetContentAsync<T>(this HttpResponseMessage response) | ||
{ | ||
return await response.GetOptionalContentAsync<T>() ?? throw new InvalidOperationException("Server did not return a result"); | ||
} | ||
|
||
/// <summary> | ||
/// Attempts to deserialize the payload of the HTTP response into the given type. | ||
/// </summary> | ||
/// <typeparam name="T">The type of the payload to be read</typeparam> | ||
/// <param name="response">The response to read the payload from</param> | ||
/// <returns>The deserialized payload of the response or null, if the server did not return data</returns> | ||
/// <exception cref="InvalidOperationException">Thrown if the format returned by the server is not supported</exception> | ||
/// <remarks> | ||
/// This method supports all formats that ship with the GenHTTP framework (JSON, XML, form encoded, Protobuf) | ||
/// and falls back to JSON if the server does not indicate a content type. | ||
/// </remarks> | ||
public static async ValueTask<T?> GetOptionalContentAsync<T>(this HttpResponseMessage response) | ||
{ | ||
if (response.StatusCode == HttpStatusCode.NoContent) | ||
{ | ||
return default; | ||
} | ||
|
||
var type = response.GetContentHeader("Content-Type"); | ||
|
||
var registry = Serialization.Default() | ||
.AddProtobuf() | ||
.Build(); | ||
|
||
var format = registry.GetFormat(type) ?? throw new InvalidOperationException($"Unable to find deserializer for content type '{type}'"); | ||
|
||
using var body = await response.Content.ReadAsStreamAsync(); | ||
|
||
return (T?)await format.DeserializeAsync(body, typeof(T)); | ||
} | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 32 additions & 40 deletions
72
Testing/Testing/TestExtensions.cs → Testing/Testing/HeaderExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,32 @@ | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
|
||
namespace GenHTTP.Testing | ||
{ | ||
|
||
public static class TestExtensions | ||
{ | ||
|
||
/// <summary> | ||
/// Reads the response body as a string. | ||
/// </summary> | ||
/// <param name="response">The response to read</param> | ||
/// <returns>The content of the HTTP response</returns> | ||
public static async ValueTask<string> GetContentAsync(this HttpResponseMessage response) => await response.Content.ReadAsStringAsync(); | ||
|
||
public static string? GetHeader(this HttpResponseMessage response, string key) | ||
{ | ||
if (response.Headers.TryGetValues(key, out var values)) | ||
{ | ||
return values.FirstOrDefault(); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public static string? GetContentHeader(this HttpResponseMessage response, string key) | ||
{ | ||
if (response.Content.Headers.TryGetValues(key, out var values)) | ||
{ | ||
return values.FirstOrDefault(); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
} | ||
|
||
} | ||
using System.Linq; | ||
using System.Net.Http; | ||
|
||
namespace GenHTTP.Testing | ||
{ | ||
|
||
public static class HeaderExtensions | ||
{ | ||
|
||
public static string? GetHeader(this HttpResponseMessage response, string key) | ||
{ | ||
if (response.Headers.TryGetValues(key, out var values)) | ||
{ | ||
return values.FirstOrDefault(); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public static string? GetContentHeader(this HttpResponseMessage response, string key) | ||
{ | ||
if (response.Content.Headers.TryGetValues(key, out var values)) | ||
{ | ||
return values.FirstOrDefault(); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
} | ||
|
||
} |