Skip to content

Commit

Permalink
Use source generation on json api serialization/deserialization.
Browse files Browse the repository at this point in the history
  • Loading branch information
arenekosreal committed Nov 22, 2024
1 parent 31b207a commit 519cfa9
Show file tree
Hide file tree
Showing 16 changed files with 198 additions and 118 deletions.
12 changes: 6 additions & 6 deletions E5Renewer.Tests/Controllers/JsonAPIV1ControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public JsonAPIV1ControllerTests()
generator.GetUnixTimestamp().Returns((long)42);

IDummyResultGenerator dummyResultGenerator = Substitute.For<IDummyResultGenerator>();
InvokeResult dummyResult = new();
JsonAPIV1Response dummyResult = new();
HttpContext context = new DefaultHttpContext();
dummyResultGenerator.GenerateDummyResultAsync(context).ReturnsForAnyArgs(dummyResult);
dummyResultGenerator.GenerateDummyResult(context).ReturnsForAnyArgs(dummyResult);
Expand All @@ -50,7 +50,7 @@ public JsonAPIV1ControllerTests()
[TestMethod]
public async Task TestGetListApis()
{
InvokeResult result = await this.controller.GetListApis();
JsonAPIV1Response result = await this.controller.GetListApis();
Assert.AreEqual(0, result.args.Count);
Assert.AreEqual("list_apis", result.method);
string? id = ((IEnumerable<string>?)result.result)?.First();
Expand All @@ -62,7 +62,7 @@ public async Task TestGetListApis()
[TestMethod]
public async Task TestGetRunningUsers()
{
InvokeResult result = await this.controller.GetRunningUsers();
JsonAPIV1Response result = await this.controller.GetRunningUsers();
Assert.AreEqual(0, result.args.Count);
Assert.AreEqual("running_users", result.method);
string? user = ((IEnumerable<string>?)result.result)?.First();
Expand All @@ -74,7 +74,7 @@ public async Task TestGetRunningUsers()
[TestMethod]
public async Task TestGetWaitingUsers()
{
InvokeResult result = await this.controller.GetWaitingUsers();
JsonAPIV1Response result = await this.controller.GetWaitingUsers();
Assert.AreEqual(0, result.args.Count);
Assert.AreEqual("waiting_users", result.method);
string? user = ((IEnumerable<string>?)result.result)?.First();
Expand All @@ -86,7 +86,7 @@ public async Task TestGetWaitingUsers()
[TestMethod]
public async Task TestGetUserResults()
{
InvokeResult result = await this.controller.GetUserResults("testName", "testId");
JsonAPIV1Response result = await this.controller.GetUserResults("testName", "testId");
Assert.AreEqual(2, result.args.Count);
Assert.AreEqual("testName", result.args["user"]);
Assert.AreEqual("testId", result.args["api_name"]);
Expand All @@ -99,7 +99,7 @@ public async Task TestGetUserResults()
[TestMethod]
public async Task TestHandle()
{
InvokeResult result = await this.controller.Handle();
JsonAPIV1Response result = await this.controller.Handle();
Assert.AreEqual(new(), result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public SimpleDummyResultGeneratorTests()
public async Task TestGenerateDummyResultAsync()
{
HttpContext context = new DefaultHttpContext();
InvokeResult result = await this.dummyResultGenerator.GenerateDummyResultAsync(context);
JsonAPIV1Response result = await this.dummyResultGenerator.GenerateDummyResultAsync(context);
Assert.AreEqual((long)42, result.timestamp);
}
/// <summary>Test
Expand All @@ -42,7 +42,7 @@ public async Task TestGenerateDummyResultAsync()
public void TestGenerateDummyResult()
{
HttpContext context = new DefaultHttpContext();
InvokeResult result = this.dummyResultGenerator.GenerateDummyResult(context);
JsonAPIV1Response result = this.dummyResultGenerator.GenerateDummyResult(context);
Assert.AreEqual((long)42, result.timestamp);
}
}
4 changes: 2 additions & 2 deletions E5Renewer.Tests/Controllers/UnspecifiedControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public UnspecifiedControllerTests()
{
ILogger<UnspecifiedController> logger = Substitute.For<ILogger<UnspecifiedController>>();
IDummyResultGenerator dummyResultGenerator = Substitute.For<IDummyResultGenerator>();
InvokeResult result = new();
JsonAPIV1Response result = new();
HttpContext context = new DefaultHttpContext();
dummyResultGenerator.GenerateDummyResultAsync(context).Returns(Task.FromResult(result));
dummyResultGenerator.GenerateDummyResult(context).Returns(result);
Expand All @@ -34,7 +34,7 @@ public UnspecifiedControllerTests()
[TestMethod]
public async Task TestHandle()
{
InvokeResult result = await this.controller.Handle();
JsonAPIV1Response result = await this.controller.Handle();
Assert.AreEqual(new(), result);
}
}
Expand Down
4 changes: 2 additions & 2 deletions E5Renewer/Controllers/IDummyResultGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ namespace E5Renewer.Controllers
public interface IDummyResultGenerator
{
/// <summary>Generate a dummy result when something not right.</summary>
public Task<InvokeResult> GenerateDummyResultAsync(HttpContext httpContext);
public Task<JsonAPIV1Response> GenerateDummyResultAsync(HttpContext httpContext);

/// <summary>Generate a dummy result when something not right.</summary>
public InvokeResult GenerateDummyResult(HttpContext httpContext);
public JsonAPIV1Response GenerateDummyResult(HttpContext httpContext);
}
}
31 changes: 31 additions & 0 deletions E5Renewer/Controllers/IQueryCollectionExtends.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Microsoft.Extensions.Primitives;

namespace E5Renewer.Controllers
{
internal static class IQueryCollectionExtends
{
public static JsonAPIV1Request ToJsonAPIV1Request(this IQueryCollection queryCollection)
{
StringValues value;
long timestamp = queryCollection.TryGetValue(nameof(timestamp), out value) &&
long.TryParse(value, out long parsedValue)
? parsedValue : default;
string method;
if (queryCollection.TryGetValue(nameof(method), out value))
{
method = value.FirstOrDefault() ?? string.Empty;
}
else
{
method = string.Empty;
}

Dictionary<string, string?> args =
queryCollection.TakeWhile((kv) => kv.Key != nameof(timestamp) && kv.Key != nameof(method))
.Select((kv) => new KeyValuePair<string, string?>(kv.Key, kv.Value.FirstOrDefault()))
.ToDictionary();

return new(timestamp, method, new(args));
}
}
}
28 changes: 0 additions & 28 deletions E5Renewer/Controllers/InvokeResult.cs

This file was deleted.

37 changes: 20 additions & 17 deletions E5Renewer/Controllers/JsonAPIV1Controller.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.ObjectModel;

using E5Renewer.Models.GraphAPIs;
using E5Renewer.Models.Statistics;

Expand Down Expand Up @@ -35,50 +37,50 @@ IDummyResultGenerator dummyResponseGenerator

/// <summary>Handler for <c>/v1/list_apis</c>.</summary>
[HttpGet("list_apis")]
public ValueTask<InvokeResult> GetListApis()
public ValueTask<JsonAPIV1Response> GetListApis()
{
IEnumerable<string> result = this.apiFunctions.
Select((c) => c.id);
logger.LogDebug("Getting result [{0}]", string.Join(", ", result));
return ValueTask.FromResult(new InvokeResult(
return ValueTask.FromResult(new JsonAPIV1Response(
this.unixTimestampGenerator.GetUnixTimestamp(),
"list_apis",
new(),
result,
this.unixTimestampGenerator.GetUnixTimestamp()
ReadOnlyDictionary<string, string?>.Empty
));
}

/// <summary>Handler for <c>/v1/running_users</c>.</summary>
[HttpGet("running_users")]
public async ValueTask<InvokeResult> GetRunningUsers()
public async ValueTask<JsonAPIV1Response> GetRunningUsers()
{
IEnumerable<string> result = await this.statusManager.GetRunningUsersAsync();
logger.LogDebug("Getting result [{0}]", string.Join(", ", result));
return new(
this.unixTimestampGenerator.GetUnixTimestamp(),
"running_users",
new(),
result,
this.unixTimestampGenerator.GetUnixTimestamp()
ReadOnlyDictionary<string, string?>.Empty
);
}

/// <summary>Handler for <c>/v1/waiting_users</c>.</summary>
[HttpGet("waiting_users")]
public async ValueTask<InvokeResult> GetWaitingUsers()
public async ValueTask<JsonAPIV1Response> GetWaitingUsers()
{
IEnumerable<string> result = await this.statusManager.GetWaitingUsersAsync();
logger.LogDebug("Getting result [{0}]", string.Join(", ", result));
return new(
this.unixTimestampGenerator.GetUnixTimestamp(),
"waiting_users",
new(),
result,
this.unixTimestampGenerator.GetUnixTimestamp()
ReadOnlyDictionary<string, string?>.Empty
);
}

/// <summary>Handler for <c>/v1/user_results</c>.</summary>
[HttpGet("user_results")]
public async ValueTask<InvokeResult> GetUserResults(
public async ValueTask<JsonAPIV1Response> GetUserResults(
[FromQuery(Name = "user")]
string userName,

Expand All @@ -88,20 +90,21 @@ string apiName
{
IEnumerable<string> result = await this.statusManager.GetResultsAsync(userName, apiName);
logger.LogDebug("Getting result [{0}]", string.Join(", ", result));
Dictionary<string, string?> queries = new()
{
{"user", userName},{"api_name", apiName}
};
return new(
this.unixTimestampGenerator.GetUnixTimestamp(),
"user_results",
new Dictionary<string, object?>()
{
{ "user", userName }, { "api_name", apiName }
},
result,
this.unixTimestampGenerator.GetUnixTimestamp()
new(queries)
);
}

/// <summary>Handler for <c>/v1/*</c>.</summary>
[Route("{*method}")]
public async ValueTask<InvokeResult> Handle() =>
public async ValueTask<JsonAPIV1Response> Handle() =>
await this.dummyResponseGenerator.GenerateDummyResultAsync(this.HttpContext);
}
}
36 changes: 36 additions & 0 deletions E5Renewer/Controllers/JsonAPIV1Request.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.ObjectModel;
using System.Text.Json.Serialization;

namespace E5Renewer.Controllers
{
/// <summary>Readonly struct to store json api request.</summary>
public readonly struct JsonAPIV1Request
{
/// <value>If this protocol is valid.</value>
[JsonIgnore]
public bool valid { get => timestamp > 0 && !string.IsNullOrEmpty(method) && string.IsNullOrWhiteSpace(method); }

/// <value>The timestamp.</value>
public long timestamp { get; }

/// <value>Request method.</value>
/// <remarks>Is **NOT** HttpContext.Request.Method</remarks>
public string? method { get; }

/// <value>The params to be passed to request method.</value>
public ReadOnlyDictionary<string, string?> args { get; }

/// <summary>Initialize <see cref="JsonAPIV1Request"/> with arguments given.</summary>
[JsonConstructor]
public JsonAPIV1Request(long timestamp, string? method, ReadOnlyDictionary<string, string?> args) =>
(this.timestamp, this.method, this.args) = (timestamp, method, args);
}

[JsonSourceGenerationOptions(
WriteIndented = true,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
IncludeFields = true
)]
[JsonSerializable(typeof(JsonAPIV1Request))]
internal partial class JsonAPIV1RequestJsonSerializerContext : JsonSerializerContext { }
}
45 changes: 45 additions & 0 deletions E5Renewer/Controllers/JsonAPIV1Response.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Collections.ObjectModel;
using System.Text.Json.Serialization;

namespace E5Renewer.Controllers
{
/// <summary>Readonly struct to store json api response.</summary>
public readonly struct JsonAPIV1Response
{
/// <value>If this protocol is valid.</value>
[JsonIgnore]
public bool valid { get => timestamp > 0 && !string.IsNullOrEmpty(method) && string.IsNullOrWhiteSpace(method); }

/// <value>The timestamp.</value>
public long timestamp { get; }

/// <value>Request method.</value>
/// <remarks>Is **NOT** HttpContext.Request.Method</remarks>
public string method { get; }

/// <value>The request result.</value>
public object? result { get; }

/// <value>The params to be passed to request method.</value>
public ReadOnlyDictionary<string, string?> args { get; }

/// <summary>Initialize <see cref="JsonAPIV1Response"/> with arguments given.</summary>
[JsonConstructor]
public JsonAPIV1Response(long timestamp, string method, object? result, ReadOnlyDictionary<string, string?> args) =>
(this.timestamp, this.method, this.result, this.args) = (timestamp, method, result, args);
}

[JsonSourceGenerationOptions(
WriteIndented = true,
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
IncludeFields = true
)]
[JsonSerializable(typeof(JsonAPIV1Response))]
[JsonSerializable(typeof(IEnumerable<string>))]
[JsonSerializable(typeof(IEnumerable<object>))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(object))]
internal partial class JsonAPIV1ResponseJsonSerializerContext : JsonSerializerContext { }
}
Loading

0 comments on commit 519cfa9

Please sign in to comment.