From 750c83ae755bc61be4c872ca71702c052154eb2e Mon Sep 17 00:00:00 2001 From: martincostello Date: Sat, 25 Nov 2023 16:08:12 +0000 Subject: [PATCH] Remove polymorphism Remove polymorphism as it is not supported when the type discriminator is not the first property in the JSON document. See dotnet/runtime#72604. --- .../AppJsonSerializerContext.cs | 2 - .../Models/IntentRequest.cs | 18 --------- .../Models/LaunchRequest.cs | 12 ------ src/LondonTravel.Skill/Models/Request.cs | 37 +++++++++++++++---- .../Models/SessionEndedRequest.cs | 20 ---------- .../Models/SystemExceptionRequest.cs | 18 --------- .../SkillRequestExtensions.cs | 16 ++++---- .../AlexaFunctionTests.cs | 4 +- .../LondonTravel.Skill.Tests/EndToEndTests.cs | 17 +++------ .../LondonTravel.Skill.Tests/FunctionTests.cs | 10 ++--- test/LondonTravel.Skill.Tests/LaunchTests.cs | 2 +- .../SerializationTests.cs | 12 +++--- .../SessionEndedTests.cs | 2 +- .../UnknownIntentTests.cs | 5 --- .../UnknownRequestTests.cs | 7 +--- 15 files changed, 58 insertions(+), 124 deletions(-) delete mode 100644 src/LondonTravel.Skill/Models/IntentRequest.cs delete mode 100644 src/LondonTravel.Skill/Models/LaunchRequest.cs delete mode 100644 src/LondonTravel.Skill/Models/SessionEndedRequest.cs delete mode 100644 src/LondonTravel.Skill/Models/SystemExceptionRequest.cs diff --git a/src/LondonTravel.Skill/AppJsonSerializerContext.cs b/src/LondonTravel.Skill/AppJsonSerializerContext.cs index 154abb465..f871c2029 100644 --- a/src/LondonTravel.Skill/AppJsonSerializerContext.cs +++ b/src/LondonTravel.Skill/AppJsonSerializerContext.cs @@ -14,9 +14,7 @@ namespace MartinCostello.LondonTravel.Skill; [ExcludeFromCodeCoverage] [JsonSerializable(typeof(IList))] [JsonSerializable(typeof(IList))] -[JsonSerializable(typeof(IntentRequest))] [JsonSerializable(typeof(LinkAccountCard))] -[JsonSerializable(typeof(LaunchRequest))] [JsonSerializable(typeof(SkillRequest))] [JsonSerializable(typeof(SkillResponse))] [JsonSerializable(typeof(SkillUserPreferences))] diff --git a/src/LondonTravel.Skill/Models/IntentRequest.cs b/src/LondonTravel.Skill/Models/IntentRequest.cs deleted file mode 100644 index a878941b0..000000000 --- a/src/LondonTravel.Skill/Models/IntentRequest.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Martin Costello, 2017. All rights reserved. -// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. - -using System.Text.Json.Serialization; - -namespace MartinCostello.LondonTravel.Skill.Models; - -public sealed class IntentRequest : Request -{ - [JsonIgnore] - public override string Type => "IntentRequest"; - - [JsonPropertyName("dialogState")] - public string DialogState { get; set; } - - [JsonPropertyName("intent")] - public Intent Intent { get; set; } -} diff --git a/src/LondonTravel.Skill/Models/LaunchRequest.cs b/src/LondonTravel.Skill/Models/LaunchRequest.cs deleted file mode 100644 index 4982ec2e1..000000000 --- a/src/LondonTravel.Skill/Models/LaunchRequest.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Martin Costello, 2017. All rights reserved. -// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. - -using System.Text.Json.Serialization; - -namespace MartinCostello.LondonTravel.Skill.Models; - -public sealed class LaunchRequest : Request -{ - [JsonIgnore] - public override string Type => "LaunchRequest"; -} diff --git a/src/LondonTravel.Skill/Models/Request.cs b/src/LondonTravel.Skill/Models/Request.cs index c6aeb3ff5..0d7b05e9f 100644 --- a/src/LondonTravel.Skill/Models/Request.cs +++ b/src/LondonTravel.Skill/Models/Request.cs @@ -7,15 +7,11 @@ namespace MartinCostello.LondonTravel.Skill.Models; #pragma warning disable CA1724 -[JsonDerivedType(typeof(IntentRequest), "IntentRequest")] -[JsonDerivedType(typeof(LaunchRequest), "LaunchRequest")] -[JsonDerivedType(typeof(SessionEndedRequest), "SessionEndedRequest")] -[JsonDerivedType(typeof(SystemExceptionRequest), "System.ExceptionEncountered")] -[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] -public class Request +public sealed class Request { - [JsonIgnore] - public virtual string Type { get; } + [JsonPropertyName("type")] + [JsonRequired] + public string Type { get; set; } [JsonPropertyName("requestId")] public string RequestId { get; set; } @@ -26,4 +22,29 @@ public class Request [JsonConverter(typeof(MixedDateTimeConverter))] [JsonPropertyName("timestamp")] public DateTime Timestamp { get; set; } + + //// Properties for "IntentRequest" + + [JsonPropertyName("dialogState")] + public string DialogState { get; set; } + + [JsonPropertyName("intent")] + public Intent Intent { get; set; } + + //// Properties for "SessionEndedRequest" + + [JsonConverter(typeof(CustomStringEnumConverter))] + [JsonPropertyName("reason")] + public Reason Reason { get; set; } + + //// Properties for "System.ExceptionEncountered" + + [JsonPropertyName("cause")] + public AlexaErrorCause ErrorCause { get; set; } + + //// Properties for "SessionEndedRequest" and "System.ExceptionEncountered" + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] + public AlexaError Error { get; set; } } diff --git a/src/LondonTravel.Skill/Models/SessionEndedRequest.cs b/src/LondonTravel.Skill/Models/SessionEndedRequest.cs deleted file mode 100644 index 753a3e81b..000000000 --- a/src/LondonTravel.Skill/Models/SessionEndedRequest.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Martin Costello, 2017. All rights reserved. -// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. - -using System.Text.Json.Serialization; - -namespace MartinCostello.LondonTravel.Skill.Models; - -public sealed class SessionEndedRequest : Request -{ - [JsonIgnore] - public override string Type => "SessionEndedRequest"; - - [JsonConverter(typeof(CustomStringEnumConverter))] - [JsonPropertyName("reason")] - public Reason Reason { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public AlexaError Error { get; set; } -} diff --git a/src/LondonTravel.Skill/Models/SystemExceptionRequest.cs b/src/LondonTravel.Skill/Models/SystemExceptionRequest.cs deleted file mode 100644 index 3ec89d2bc..000000000 --- a/src/LondonTravel.Skill/Models/SystemExceptionRequest.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Martin Costello, 2017. All rights reserved. -// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. - -using System.Text.Json.Serialization; - -namespace MartinCostello.LondonTravel.Skill.Models; - -public sealed class SystemExceptionRequest : Request -{ - [JsonIgnore] - public override string Type => "System.ExceptionEncountered"; - - [JsonPropertyName("error")] - public AlexaError Error { get; set; } - - [JsonPropertyName("cause")] - public AlexaErrorCause ErrorCause { get; set; } -} diff --git a/src/LondonTravel.Skill/SkillRequestExtensions.cs b/src/LondonTravel.Skill/SkillRequestExtensions.cs index 7af7bfac6..387c18f9b 100644 --- a/src/LondonTravel.Skill/SkillRequestExtensions.cs +++ b/src/LondonTravel.Skill/SkillRequestExtensions.cs @@ -12,12 +12,12 @@ public static SkillRequest FromModel(this Models.SkillRequest request) { var result = MapRequest(request); - result.Request = request.Request switch + result.Request = request.Request.Type switch { - Models.IntentRequest intent => MapIntent(intent), - Models.LaunchRequest => new LaunchRequest(), - Models.SessionEndedRequest session => MapSessionEnd(session), - Models.SystemExceptionRequest exception => MapException(exception), + "IntentRequest" => MapIntent(request.Request), + "LaunchRequest" => new LaunchRequest(), + "SessionEndedRequest" => MapSessionEnd(request.Request), + "System.ExceptionEncountered" => MapException(request.Request), _ => new UnknownRequest(), }; @@ -106,7 +106,7 @@ private static SkillRequest MapRequest(Models.SkillRequest request) return result; } - private static SystemExceptionRequest MapException(Models.SystemExceptionRequest exception) + private static SystemExceptionRequest MapException(Models.Request exception) { var result = new SystemExceptionRequest(); @@ -127,7 +127,7 @@ private static SystemExceptionRequest MapException(Models.SystemExceptionRequest return result; } - private static IntentRequest MapIntent(Models.IntentRequest request) + private static IntentRequest MapIntent(Models.Request request) { var result = new IntentRequest() { @@ -151,7 +151,7 @@ private static IntentRequest MapIntent(Models.IntentRequest request) return result; } - private static SessionEndedRequest MapSessionEnd(Models.SessionEndedRequest session) + private static SessionEndedRequest MapSessionEnd(Models.Request session) { var result = new SessionEndedRequest() { diff --git a/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs b/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs index d03a67a3f..0633a459a 100644 --- a/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs +++ b/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs @@ -55,7 +55,7 @@ public async Task Cannot_Invoke_Function_With_System_Failure() // Arrange AlexaFunction function = await CreateFunctionAsync(); - var error = new SystemExceptionRequest() + var error = new Request() { Error = new() { @@ -68,7 +68,7 @@ public async Task Cannot_Invoke_Function_With_System_Failure() }, }; - var request = CreateRequest(error); + var request = CreateRequest("System.ExceptionEncountered", error); // Act SkillResponse actual = await function.HandlerAsync(request); diff --git a/test/LondonTravel.Skill.Tests/EndToEndTests.cs b/test/LondonTravel.Skill.Tests/EndToEndTests.cs index 6228d623c..e1ae43fa0 100644 --- a/test/LondonTravel.Skill.Tests/EndToEndTests.cs +++ b/test/LondonTravel.Skill.Tests/EndToEndTests.cs @@ -15,12 +15,7 @@ public class EndToEndTests(ITestOutputHelper outputHelper) : FunctionTests(outpu public async Task Alexa_Function_Can_Process_Intent_Request() { // Arrange - var intent = new IntentRequest() - { - Intent = new() { Name = "AMAZON.CancelIntent" }, - }; - - SkillRequest request = CreateRequest(intent); + SkillRequest request = CreateIntentRequest("AMAZON.CancelIntent"); // Act var actual = await ProcessRequestAsync(request); @@ -37,7 +32,7 @@ public async Task Alexa_Function_Can_Process_Intent_Request() public async Task Alexa_Function_Can_Process_Launch_Request() { // Arrange - SkillRequest request = CreateRequest(); + SkillRequest request = CreateRequest("LaunchRequest"); // Act var actual = await ProcessRequestAsync(request); @@ -59,7 +54,7 @@ public async Task Alexa_Function_Can_Process_Launch_Request() public async Task Alexa_Function_Can_Process_Session_Ended_Request() { // Arrange - var session = new SessionEndedRequest() + var session = new Request() { Reason = Reason.ExceededMaxReprompts, Error = new() @@ -69,7 +64,7 @@ public async Task Alexa_Function_Can_Process_Session_Ended_Request() }, }; - SkillRequest request = CreateRequest(session); + SkillRequest request = CreateRequest("SessionEndedRequest", session); // Act var actual = await ProcessRequestAsync(request); @@ -91,7 +86,7 @@ public async Task Alexa_Function_Can_Process_Session_Ended_Request() public async Task Alexa_Function_Can_Process_System_Exception_Request() { // Arrange - var exception = new SystemExceptionRequest() + var exception = new Request() { Error = new() { @@ -104,7 +99,7 @@ public async Task Alexa_Function_Can_Process_System_Exception_Request() }, }; - SkillRequest request = CreateRequest(exception); + SkillRequest request = CreateRequest("System.ExceptionEncountered", exception); // Act var actual = await ProcessRequestAsync(request); diff --git a/test/LondonTravel.Skill.Tests/FunctionTests.cs b/test/LondonTravel.Skill.Tests/FunctionTests.cs index 44d161ab3..69808a865 100644 --- a/test/LondonTravel.Skill.Tests/FunctionTests.cs +++ b/test/LondonTravel.Skill.Tests/FunctionTests.cs @@ -60,7 +60,7 @@ protected virtual async Task CreateFunctionAsync() protected virtual SkillRequest CreateIntentRequest(string name, params Slot[] slots) { - var request = new IntentRequest() + var request = new Request() { Intent = new Intent() { @@ -78,11 +78,10 @@ protected virtual SkillRequest CreateIntentRequest(string name, params Slot[] sl } } - return CreateRequest(request); + return CreateRequest("IntentRequest", request); } - protected virtual SkillRequest CreateRequest(T request = null) - where T : Request, new() + protected virtual SkillRequest CreateRequest(string type, Request request = null) { var application = new Application() { @@ -111,7 +110,7 @@ protected virtual SkillRequest CreateRequest(T request = null) User = user, }, }, - Request = request ?? new T(), + Request = request ?? new(), Session = new() { Application = application, @@ -122,6 +121,7 @@ protected virtual SkillRequest CreateRequest(T request = null) Version = "1.0", }; + result.Request.Type = type; result.Request.Locale = "en-GB"; return result; diff --git a/test/LondonTravel.Skill.Tests/LaunchTests.cs b/test/LondonTravel.Skill.Tests/LaunchTests.cs index c684abe04..6bfc240e1 100644 --- a/test/LondonTravel.Skill.Tests/LaunchTests.cs +++ b/test/LondonTravel.Skill.Tests/LaunchTests.cs @@ -14,7 +14,7 @@ public async Task Can_Invoke_Function() // Arrange AlexaFunction function = await CreateFunctionAsync(); - SkillRequest request = CreateRequest(); + SkillRequest request = CreateRequest("LaunchRequest"); // Act SkillResponse actual = await function.HandlerAsync(request); diff --git a/test/LondonTravel.Skill.Tests/SerializationTests.cs b/test/LondonTravel.Skill.Tests/SerializationTests.cs index 461082469..9731bb36a 100644 --- a/test/LondonTravel.Skill.Tests/SerializationTests.cs +++ b/test/LondonTravel.Skill.Tests/SerializationTests.cs @@ -2,18 +2,17 @@ // Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. using System.Text.Json; -using MartinCostello.LondonTravel.Skill.Models; namespace MartinCostello.LondonTravel.Skill; public static class SerializationTests { [Theory] - [InlineData("IntentRequest", typeof(IntentRequest))] - [InlineData("LaunchRequest", typeof(LaunchRequest))] - [InlineData("LaunchRequestWithEpochTimestamp", typeof(LaunchRequest))] - [InlineData("SessionEndedRequest", typeof(SessionEndedRequest))] - public static async Task Can_Deserialize_Request(string name, Type expectedType) + [InlineData("IntentRequest")] + [InlineData("LaunchRequest")] + [InlineData("LaunchRequestWithEpochTimestamp")] + [InlineData("SessionEndedRequest")] + public static async Task Can_Deserialize_Request(string name) { // Arrange string json = await File.ReadAllTextAsync(Path.Combine("Samples", $"{name}.json")); @@ -24,7 +23,6 @@ public static async Task Can_Deserialize_Request(string name, Type expectedType) // Assert actual.ShouldNotBeNull(); actual.Request.ShouldNotBeNull(); - actual.Request.ShouldBeOfType(expectedType); } [Theory] diff --git a/test/LondonTravel.Skill.Tests/SessionEndedTests.cs b/test/LondonTravel.Skill.Tests/SessionEndedTests.cs index 45c811cb5..a192f847b 100644 --- a/test/LondonTravel.Skill.Tests/SessionEndedTests.cs +++ b/test/LondonTravel.Skill.Tests/SessionEndedTests.cs @@ -14,7 +14,7 @@ public async Task Can_Invoke_Function() // Arrange AlexaFunction function = await CreateFunctionAsync(); - SkillRequest request = CreateRequest(); + SkillRequest request = CreateRequest("SessionEndedRequest"); // Act SkillResponse actual = await function.HandlerAsync(request); diff --git a/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs b/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs index c7dbe16de..7ce203ee3 100644 --- a/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs +++ b/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs @@ -31,9 +31,4 @@ public async Task Can_Invoke_Function() var ssml = response.OutputSpeech.ShouldBeOfType(); ssml.Ssml.ShouldBe("Sorry, I don't understand how to do that."); } - - private sealed class UnknownRequest : Request - { - public override string Type => "Unknown"; - } } diff --git a/test/LondonTravel.Skill.Tests/UnknownRequestTests.cs b/test/LondonTravel.Skill.Tests/UnknownRequestTests.cs index aae81dac2..4bab1edd0 100644 --- a/test/LondonTravel.Skill.Tests/UnknownRequestTests.cs +++ b/test/LondonTravel.Skill.Tests/UnknownRequestTests.cs @@ -13,7 +13,7 @@ public async Task Can_Invoke_Function() // Arrange AlexaFunction function = await CreateFunctionAsync(); - SkillRequest request = CreateRequest(); + SkillRequest request = CreateRequest("Unknown"); // Act SkillResponse actual = await function.HandlerAsync(request); @@ -21,9 +21,4 @@ public async Task Can_Invoke_Function() // Assert AssertResponse(actual); } - - private sealed class UnknownRequest : Request - { - public override string Type => "Unknown"; - } }