Skip to content
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
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
<ItemGroup Label="Tests">
<PackageVersion Include="coverlet.collector" Version="10.0.1" />
<PackageVersion Include="coverlet.msbuild" Version="10.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.11" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
Expand Down
93 changes: 93 additions & 0 deletions SysML2.NET.Serializer.Json.Tests/PsmSerializerTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,17 @@ namespace SysML2.NET.Serializer.Json.Tests
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;

using NUnit.Framework;

Expand Down Expand Up @@ -86,6 +95,7 @@ public void VerifySerializeResponse()
{
Assert.That(json, Does.Contain("\"created\":\"1976-08-20T00:00:00Z\""));
Assert.That(localJson, Does.Match("\"created\":\"[^\"]+[+-][0-9]{2}:[0-9]{2}\""));

Assert.That(JsonDocument.Parse(localJson).RootElement.GetProperty("created").GetDateTime().Kind,
Is.EqualTo(DateTimeKind.Local));
}
Expand Down Expand Up @@ -288,6 +298,73 @@ public void VerifySerializeUnsupportedMode()
Throws.TypeOf<NotSupportedException>());
}

[Test]
public async Task VerifySerializeResponseAsync()
{
var project = new Project { Id = ProjectIdentifier, Name = "the name" };

await using var stream = new AsyncOnlyStream();

Assert.That(() => this.serializer.SerializeResponse(project, SerializationModeKind.JSON, false, stream, this.jsonWriterOptions),
Throws.TypeOf<InvalidOperationException>());

await this.serializer.SerializeResponseAsync(project, SerializationModeKind.JSON, false, stream, this.jsonWriterOptions, CancellationToken.None);
await this.serializer.SerializeResponseAsync([project], SerializationModeKind.JSON, false, stream, this.jsonWriterOptions, CancellationToken.None);

Assert.That(stream.Length, Is.GreaterThan(0));
}

[Test]
public async Task SerializeResponseAsync_ToTestServerResponseBody_RoundTripsWithoutDataLoss()
{
IResponse[] responses =
[
new Project
{
Id = ProjectIdentifier,
Alias = ["first", "second"],
Created = new DateTime(1976, 8, 20, 0, 0, 0, DateTimeKind.Utc),
DefaultBranch = BranchIdentifier,
Description = "the description",
Name = "the name"
},
new Branch
{
Id = BranchIdentifier,
Created = new DateTime(1976, 8, 21, 0, 0, 0, DateTimeKind.Utc),
Head = DataVersionIdentifier,
Name = "the branch",
OwningProject = ProjectIdentifier,
ReferencedCommit = DataVersionIdentifier
}
];

using var host = await new HostBuilder()
.ConfigureWebHost(webHost => webHost.UseTestServer().Configure(app => app.Run(context => context.Request.Path == "/sync"
? Task.Run(() => this.serializer.SerializeResponse(responses, SerializationModeKind.JSON, false, context.Response.Body, this.jsonWriterOptions))
: this.serializer.SerializeResponseAsync(responses, SerializationModeKind.JSON, false, context.Response.Body, this.jsonWriterOptions, context.RequestAborted))))
.StartAsync();

using var client = host.GetTestClient();

await Assert.ThatAsync(() => client.GetAsync("/sync"), Throws.TypeOf<InvalidOperationException>());

using var response = await client.GetAsync("/");
await using var body = await response.Content.ReadAsStreamAsync();

var roundTripped = (await new DeSerializer().DeSerializeResponsesAsync<IResponse>(body, SerializationModeKind.JSON, false, CancellationToken.None)).ToList();

var expected = Serialize(stream => this.serializer.SerializeResponse(responses, SerializationModeKind.JSON, false, stream, this.jsonWriterOptions));
var actual = Serialize(stream => this.serializer.SerializeResponse(roundTripped, SerializationModeKind.JSON, false, stream, this.jsonWriterOptions));

using (Assert.EnterMultipleScope())
{
Assert.That(response.IsSuccessStatusCode, Is.True);
Assert.That(roundTripped, Has.Count.EqualTo(2));
Assert.That(actual, Is.EqualTo(expected));
}
}

private static string Serialize(Action<Stream> serialize)
{
using var stream = new MemoryStream();
Expand All @@ -296,5 +373,21 @@ private static string Serialize(Action<Stream> serialize)

return Encoding.UTF8.GetString(stream.ToArray());
}

/// <summary>
/// A <see cref="MemoryStream"/> that rejects synchronous writes, as an ASP.NET Core response body does.
/// </summary>
private sealed class AsyncOnlyStream : MemoryStream
{
public override void Write(byte[] buffer, int offset, int count) => throw new InvalidOperationException();

public override void Write(ReadOnlySpan<byte> buffer) => throw new InvalidOperationException();

public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
base.Write(buffer.ToArray(), 0, buffer.Length);
return ValueTask.CompletedTask;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NUnit" />
Expand Down
45 changes: 30 additions & 15 deletions SysML2.NET.Serializer.Json/Serializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public class Serializer : ISerializer
/// <param name="jsonWriterOptions">
/// The <see cref="JsonWriterOptions"/> to use
/// </param>
public void Serialize(IEnumerable<IIdentified> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream, JsonWriterOptions jsonWriterOptions)
public void Serialize(IEnumerable<IIdentified> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream,
JsonWriterOptions jsonWriterOptions)
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why a new line?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be compliant with wrapping rules that are set on the editorconfig

using var writer = new Utf8JsonWriter(stream, jsonWriterOptions);

Expand Down Expand Up @@ -132,7 +133,8 @@ public void Serialize(IIdentified dataItem, SerializationModeKind serializationM
/// <param name="cancellationToken">
/// The <see cref="CancellationToken"/> used to cancel the operation
/// </param>
public async Task SerializeAsync(IEnumerable<IIdentified> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream, JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
public async Task SerializeAsync(IEnumerable<IIdentified> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream,
JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
{
await using var writer = new Utf8JsonWriter(stream, jsonWriterOptions);

Expand Down Expand Up @@ -180,7 +182,8 @@ public async Task SerializeAsync(IEnumerable<IIdentified> dataItems, Serializati
/// <param name="cancellationToken">
/// The <see cref="CancellationToken"/> used to cancel the operation
/// </param>
public async Task SerializeAsync(IIdentified dataItem, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream, JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
public async Task SerializeAsync(IIdentified dataItem, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream,
JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
{
await using var writer = new Utf8JsonWriter(stream, jsonWriterOptions);

Expand All @@ -206,11 +209,14 @@ public async Task SerializeAsync(IIdentified dataItem, SerializationModeKind ser
/// <param name="includeDerivedProperties">Asserts that derived properties should also be part of the serialization.</param>
/// <param name="stream">The target <see cref="Stream"/>.</param>
/// <param name="jsonWriterOptions">The <see cref="JsonWriterOptions"/> to use.</param>
public void SerializeRequest(IRequest dataItem, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream, JsonWriterOptions jsonWriterOptions)
public void SerializeRequest(IRequest dataItem, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream,
JsonWriterOptions jsonWriterOptions)
{
using var writer = new Utf8JsonWriter(stream, jsonWriterOptions);

WriteSingle(dataItem, writer, serializationModeKind, includeDerivedProperties);

writer.Flush();
}

/// <summary>
Expand All @@ -221,11 +227,14 @@ public void SerializeRequest(IRequest dataItem, SerializationModeKind serializat
/// <param name="includeDerivedProperties">Asserts that derived properties should also be part of the serialization.</param>
/// <param name="stream">The target <see cref="Stream"/>.</param>
/// <param name="jsonWriterOptions">The <see cref="JsonWriterOptions"/> to use.</param>
public void SerializeRequest(IEnumerable<IRequest> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream, JsonWriterOptions jsonWriterOptions)
public void SerializeRequest(IEnumerable<IRequest> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream,
JsonWriterOptions jsonWriterOptions)
{
using var writer = new Utf8JsonWriter(stream, jsonWriterOptions);

WriteMany(dataItems, writer, serializationModeKind, includeDerivedProperties);

writer.Flush();
}

/// <summary>
Expand All @@ -236,11 +245,14 @@ public void SerializeRequest(IEnumerable<IRequest> dataItems, SerializationModeK
/// <param name="includeDerivedProperties">Asserts that derived properties should also be part of the serialization.</param>
/// <param name="stream">The target <see cref="Stream"/>.</param>
/// <param name="jsonWriterOptions">The <see cref="JsonWriterOptions"/> to use.</param>
public void SerializeResponse(IResponse dataItem, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream, JsonWriterOptions jsonWriterOptions)
public void SerializeResponse(IResponse dataItem, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream,
JsonWriterOptions jsonWriterOptions)
{
using var writer = new Utf8JsonWriter(stream, jsonWriterOptions);

WriteSingle(dataItem, writer, serializationModeKind, includeDerivedProperties);

writer.Flush();
}

/// <summary>
Expand All @@ -251,11 +263,14 @@ public void SerializeResponse(IResponse dataItem, SerializationModeKind serializ
/// <param name="includeDerivedProperties">Asserts that derived properties should also be part of the serialization.</param>
/// <param name="stream">The target <see cref="Stream"/>.</param>
/// <param name="jsonWriterOptions">The <see cref="JsonWriterOptions"/> to use.</param>
public void SerializeResponse(IEnumerable<IResponse> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream, JsonWriterOptions jsonWriterOptions)
public void SerializeResponse(IEnumerable<IResponse> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream,
JsonWriterOptions jsonWriterOptions)
{
using var writer = new Utf8JsonWriter(stream, jsonWriterOptions);

WriteMany(dataItems, writer, serializationModeKind, includeDerivedProperties);

writer.Flush();
}

/// <summary>
Expand All @@ -268,7 +283,8 @@ public void SerializeResponse(IEnumerable<IResponse> dataItems, SerializationMod
/// <param name="jsonWriterOptions">The <see cref="JsonWriterOptions"/> to use.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to cancel the operation.</param>
/// <returns>an awaitable <see cref="Task"/>.</returns>
public async Task SerializeRequestAsync(IRequest dataItem, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream, JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
public async Task SerializeRequestAsync(IRequest dataItem, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream,
JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
{
await using var writer = new Utf8JsonWriter(stream, jsonWriterOptions);

Expand All @@ -287,7 +303,8 @@ public async Task SerializeRequestAsync(IRequest dataItem, SerializationModeKind
/// <param name="jsonWriterOptions">The <see cref="JsonWriterOptions"/> to use.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to cancel the operation.</param>
/// <returns>an awaitable <see cref="Task"/>.</returns>
public async Task SerializeRequestAsync(IEnumerable<IRequest> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream, JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
public async Task SerializeRequestAsync(IEnumerable<IRequest> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream,
JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
{
await using var writer = new Utf8JsonWriter(stream, jsonWriterOptions);

Expand All @@ -306,7 +323,8 @@ public async Task SerializeRequestAsync(IEnumerable<IRequest> dataItems, Seriali
/// <param name="jsonWriterOptions">The <see cref="JsonWriterOptions"/> to use.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to cancel the operation.</param>
/// <returns>an awaitable <see cref="Task"/>.</returns>
public async Task SerializeResponseAsync(IResponse dataItem, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream, JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
public async Task SerializeResponseAsync(IResponse dataItem, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream,
JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
{
await using var writer = new Utf8JsonWriter(stream, jsonWriterOptions);

Expand All @@ -325,7 +343,8 @@ public async Task SerializeResponseAsync(IResponse dataItem, SerializationModeKi
/// <param name="jsonWriterOptions">The <see cref="JsonWriterOptions"/> to use.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to cancel the operation.</param>
/// <returns>an awaitable <see cref="Task"/>.</returns>
public async Task SerializeResponseAsync(IEnumerable<IResponse> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream, JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
public async Task SerializeResponseAsync(IEnumerable<IResponse> dataItems, SerializationModeKind serializationModeKind, bool includeDerivedProperties, Stream stream,
JsonWriterOptions jsonWriterOptions, CancellationToken cancellationToken)
{
await using var writer = new Utf8JsonWriter(stream, jsonWriterOptions);

Expand All @@ -346,8 +365,6 @@ private static void WriteSingle(object dataItem, Utf8JsonWriter writer, Serializ
var serializationAction = PsmSerializationProvider.Provide(dataItem.GetType());

serializationAction(dataItem, writer, serializationModeKind, includeDerivedProperties);

writer.Flush();
}

/// <summary>
Expand All @@ -369,8 +386,6 @@ private static void WriteMany(IEnumerable<object> dataItems, Utf8JsonWriter writ
}

writer.WriteEndArray();

writer.Flush();
}
}
}
Loading