diff --git a/Directory.Packages.props b/Directory.Packages.props
index 326b381e..324714b2 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -15,15 +15,15 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
+
@@ -38,7 +38,7 @@
-
+
diff --git a/NuGet.config b/NuGet.config
index 38ac8e75..25f97f34 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -2,9 +2,13 @@
+
+
+
+
diff --git a/global.json b/global.json
index 52dae233..7c2687c0 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "9.0.100-rc.1.24452.12",
+ "version": "9.0.100-rc.2.24463.1",
"allowPrerelease": false,
"rollForward": "latestMajor"
},
diff --git a/src/LondonTravel.Skill/AlexaSkill.cs b/src/LondonTravel.Skill/AlexaSkill.cs
index 43046726..40cbde3b 100644
--- a/src/LondonTravel.Skill/AlexaSkill.cs
+++ b/src/LondonTravel.Skill/AlexaSkill.cs
@@ -26,7 +26,7 @@ internal sealed class AlexaSkill(
///
/// The to return from the skill.
///
- public SkillResponse OnError(ISystemExceptionRequest error, Session session)
+ public SkillResponse OnError(SystemExceptionRequest error, Session session)
{
Log.SystemError(
logger,
@@ -67,7 +67,7 @@ public SkillResponse OnError(Exception? exception, Session session, string reque
/// A representing the asynchronous operation
/// which returns the to return from the skill.
///
- public async Task OnIntentAsync(IIntentRequest intent, Session session)
+ public async Task OnIntentAsync(IntentRequest intent, Session session)
{
var handler = intentFactory.Create(intent.Intent);
return await handler.RespondAsync(intent.Intent, session);
diff --git a/src/LondonTravel.Skill/AppJsonSerializerContext.cs b/src/LondonTravel.Skill/AppJsonSerializerContext.cs
index 04a1f94f..731becbc 100644
--- a/src/LondonTravel.Skill/AppJsonSerializerContext.cs
+++ b/src/LondonTravel.Skill/AppJsonSerializerContext.cs
@@ -19,5 +19,5 @@ namespace MartinCostello.LondonTravel.Skill;
[JsonSerializable(typeof(SkillResponse))]
[JsonSerializable(typeof(SkillUserPreferences))]
[JsonSerializable(typeof(StandardCard))]
-[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
+[JsonSourceGenerationOptions(AllowOutOfOrderMetadataProperties = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
public sealed partial class AppJsonSerializerContext : JsonSerializerContext;
diff --git a/src/LondonTravel.Skill/FunctionHandler.cs b/src/LondonTravel.Skill/FunctionHandler.cs
index 8eb73f42..98f0ee9f 100644
--- a/src/LondonTravel.Skill/FunctionHandler.cs
+++ b/src/LondonTravel.Skill/FunctionHandler.cs
@@ -49,12 +49,12 @@ private async Task HandleRequestAsync(SkillRequest request)
{
try
{
- return request.Request.Type switch
+ return request.Request switch
{
- RequestTypes.Intent => await skill.OnIntentAsync(request.Request, request.Session),
- RequestTypes.Launch => skill.OnLaunch(request.Session),
- RequestTypes.SessionEnded => skill.OnSessionEnded(request.Session),
- RequestTypes.SystemException => skill.OnError(request.Request, request.Session),
+ IntentRequest intent => await skill.OnIntentAsync(intent, request.Session),
+ LaunchRequest => skill.OnLaunch(request.Session),
+ SessionEndedRequest => skill.OnSessionEnded(request.Session),
+ SystemExceptionRequest exception => skill.OnError(exception, request.Session),
_ => skill.OnError(null, request.Session, request.Request.Type),
};
}
diff --git a/src/LondonTravel.Skill/Models/IRequest.cs b/src/LondonTravel.Skill/Models/IRequest.cs
deleted file mode 100644
index c92a4bc5..00000000
--- a/src/LondonTravel.Skill/Models/IRequest.cs
+++ /dev/null
@@ -1,15 +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.
-
-namespace MartinCostello.LondonTravel.Skill.Models;
-
-internal interface IRequest
-{
- string Type { get; }
-
- string RequestId { get; }
-
- string Locale { get; }
-
- DateTime Timestamp { get; }
-}
diff --git a/src/LondonTravel.Skill/Models/ISessionEndedRequest.cs b/src/LondonTravel.Skill/Models/ISessionEndedRequest.cs
deleted file mode 100644
index 12b9809d..00000000
--- a/src/LondonTravel.Skill/Models/ISessionEndedRequest.cs
+++ /dev/null
@@ -1,11 +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.
-
-namespace MartinCostello.LondonTravel.Skill.Models;
-
-internal interface ISessionEndedRequest : IRequest
-{
- Reason Reason { get; }
-
- AlexaError Error { get; }
-}
diff --git a/src/LondonTravel.Skill/Models/ISystemExceptionRequest.cs b/src/LondonTravel.Skill/Models/ISystemExceptionRequest.cs
deleted file mode 100644
index dfef918b..00000000
--- a/src/LondonTravel.Skill/Models/ISystemExceptionRequest.cs
+++ /dev/null
@@ -1,11 +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.
-
-namespace MartinCostello.LondonTravel.Skill.Models;
-
-internal interface ISystemExceptionRequest : IRequest
-{
- AlexaErrorCause ErrorCause { get; }
-
- AlexaError Error { get; }
-}
diff --git a/src/LondonTravel.Skill/Models/IntentRequest.cs b/src/LondonTravel.Skill/Models/IntentRequest.cs
new file mode 100644
index 00000000..fa7fb912
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/IntentRequest.cs
@@ -0,0 +1,18 @@
+// 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; } = default!;
+
+ [JsonPropertyName("intent")]
+ public Intent Intent { get; set; } = default!;
+}
diff --git a/src/LondonTravel.Skill/Models/IIntentRequest.cs b/src/LondonTravel.Skill/Models/LaunchRequest.cs
similarity index 60%
rename from src/LondonTravel.Skill/Models/IIntentRequest.cs
rename to src/LondonTravel.Skill/Models/LaunchRequest.cs
index 7f646b69..4982ec2e 100644
--- a/src/LondonTravel.Skill/Models/IIntentRequest.cs
+++ b/src/LondonTravel.Skill/Models/LaunchRequest.cs
@@ -1,11 +1,12 @@
// 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;
-internal interface IIntentRequest : IRequest
+public sealed class LaunchRequest : Request
{
- string DialogState { get; }
-
- Intent Intent { get; }
+ [JsonIgnore]
+ public override string Type => "LaunchRequest";
}
diff --git a/src/LondonTravel.Skill/Models/Request.cs b/src/LondonTravel.Skill/Models/Request.cs
index d2d408d5..2e93ff30 100644
--- a/src/LondonTravel.Skill/Models/Request.cs
+++ b/src/LondonTravel.Skill/Models/Request.cs
@@ -5,11 +5,15 @@
namespace MartinCostello.LondonTravel.Skill.Models;
-public sealed class Request : IRequest, IIntentRequest, ISessionEndedRequest, ISystemExceptionRequest
+[JsonDerivedType(typeof(IntentRequest), RequestTypes.Intent)]
+[JsonDerivedType(typeof(LaunchRequest), RequestTypes.Launch)]
+[JsonDerivedType(typeof(SessionEndedRequest), RequestTypes.SessionEnded)]
+[JsonDerivedType(typeof(SystemExceptionRequest), RequestTypes.SystemException)]
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
+public abstract class Request
{
- [JsonPropertyName("type")]
- [JsonRequired]
- public string Type { get; set; } = default!;
+ [JsonIgnore]
+ public abstract string Type { get; }
[JsonPropertyName("requestId")]
public string RequestId { get; set; } = default!;
@@ -20,29 +24,4 @@ public sealed class Request : IRequest, IIntentRequest, ISessionEndedRequest, IS
[JsonConverter(typeof(MixedDateTimeConverter))]
[JsonPropertyName("timestamp")]
public DateTime Timestamp { get; set; }
-
- //// Properties for "IntentRequest"
-
- [JsonPropertyName("dialogState")]
- public string DialogState { get; set; } = default!;
-
- [JsonPropertyName("intent")]
- public Intent Intent { get; set; } = default!;
-
- //// Properties for "SessionEndedRequest"
-
- [JsonConverter(typeof(JsonStringEnumConverter))]
- [JsonPropertyName("reason")]
- public Reason Reason { get; set; }
-
- //// Properties for "System.ExceptionEncountered"
-
- [JsonPropertyName("cause")]
- public AlexaErrorCause ErrorCause { get; set; } = default!;
-
- //// Properties for "SessionEndedRequest" and "System.ExceptionEncountered"
-
- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- [JsonPropertyName("error")]
- public AlexaError Error { get; set; } = default!;
}
diff --git a/src/LondonTravel.Skill/Models/SessionEndedRequest.cs b/src/LondonTravel.Skill/Models/SessionEndedRequest.cs
new file mode 100644
index 00000000..7737a994
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/SessionEndedRequest.cs
@@ -0,0 +1,20 @@
+// 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(JsonStringEnumConverter))]
+ [JsonPropertyName("reason")]
+ public Reason Reason { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("error")]
+ public AlexaError Error { get; set; } = default!;
+}
diff --git a/src/LondonTravel.Skill/Models/SystemExceptionRequest.cs b/src/LondonTravel.Skill/Models/SystemExceptionRequest.cs
new file mode 100644
index 00000000..32edf6a0
--- /dev/null
+++ b/src/LondonTravel.Skill/Models/SystemExceptionRequest.cs
@@ -0,0 +1,19 @@
+// 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("cause")]
+ public AlexaErrorCause ErrorCause { get; set; } = default!;
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("error")]
+ public AlexaError Error { get; set; } = default!;
+}
diff --git a/test/LondonTravel.Skill.NativeAotTests/EndToEndTests.cs b/test/LondonTravel.Skill.NativeAotTests/EndToEndTests.cs
index 00c00d31..cec1ade7 100644
--- a/test/LondonTravel.Skill.NativeAotTests/EndToEndTests.cs
+++ b/test/LondonTravel.Skill.NativeAotTests/EndToEndTests.cs
@@ -132,7 +132,7 @@ public async Task Alexa_Function_Can_Process_Intent_Request_For_Line_Status()
public async Task Alexa_Function_Can_Process_Launch_Request()
{
// Arrange
- var request = CreateRequest("LaunchRequest");
+ var request = CreateRequest();
// Act
var actual = await ProcessRequestAsync(request);
@@ -153,7 +153,7 @@ public async Task Alexa_Function_Can_Process_Launch_Request()
public async Task Alexa_Function_Can_Process_Session_Ended_Request()
{
// Arrange
- var session = new Request()
+ var session = new SessionEndedRequest()
{
Reason = Reason.ExceededMaxReprompts,
Error = new()
@@ -163,7 +163,7 @@ public async Task Alexa_Function_Can_Process_Session_Ended_Request()
},
};
- var request = CreateRequest("SessionEndedRequest", session);
+ var request = CreateRequest(session);
// Act
var actual = await ProcessRequestAsync(request);
@@ -184,7 +184,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 Request()
+ var exception = new SystemExceptionRequest()
{
Error = new()
{
@@ -197,7 +197,7 @@ public async Task Alexa_Function_Can_Process_System_Exception_Request()
},
};
- var request = CreateRequest("System.ExceptionEncountered", exception);
+ var request = CreateRequest(exception);
// Act
var actual = await ProcessRequestAsync(request);
@@ -215,7 +215,7 @@ public async Task Alexa_Function_Can_Process_System_Exception_Request()
private static SkillRequest CreateIntentRequest(string name, params Slot[] slots)
{
- var request = new Request()
+ var request = new IntentRequest()
{
Intent = new Intent()
{
@@ -233,10 +233,11 @@ private static SkillRequest CreateIntentRequest(string name, params Slot[] slots
}
}
- return CreateRequest("IntentRequest", request);
+ return CreateRequest(request);
}
- private static SkillRequest CreateRequest(string type, Request? request = null)
+ private static SkillRequest CreateRequest(T? request = null)
+ where T : Request, new()
{
var application = new Application()
{
@@ -265,7 +266,7 @@ private static SkillRequest CreateRequest(string type, Request? request = null)
User = user,
},
},
- Request = request ?? new(),
+ Request = request ?? new T(),
Session = new()
{
Application = application,
@@ -276,7 +277,6 @@ private static SkillRequest CreateRequest(string type, Request? request = null)
Version = "1.0",
};
- result.Request.Type = type;
result.Request.Locale = "en-GB";
return result;
diff --git a/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs b/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs
index ce872b9e..b9654f35 100644
--- a/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs
+++ b/test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs
@@ -58,7 +58,7 @@ public async Task Cannot_Invoke_Function_With_System_Failure()
var function = await CreateFunctionAsync();
var context = new TestLambdaContext();
- var error = new Request()
+ var error = new SystemExceptionRequest()
{
Error = new()
{
@@ -71,7 +71,7 @@ public async Task Cannot_Invoke_Function_With_System_Failure()
},
};
- var request = CreateRequest("System.ExceptionEncountered", error);
+ var request = CreateRequest(error);
// Act
var actual = await function.HandlerAsync(request, context);
diff --git a/test/LondonTravel.Skill.Tests/EndToEndTests.cs b/test/LondonTravel.Skill.Tests/EndToEndTests.cs
index d05bd3ee..f218185b 100644
--- a/test/LondonTravel.Skill.Tests/EndToEndTests.cs
+++ b/test/LondonTravel.Skill.Tests/EndToEndTests.cs
@@ -126,7 +126,7 @@ public async Task Alexa_Function_Can_Process_Intent_Request_For_Line_Status(
public async Task Alexa_Function_Can_Process_Launch_Request()
{
// Arrange
- var request = CreateRequest("LaunchRequest");
+ var request = CreateRequest();
// Act
var actual = await ProcessRequestAsync(request);
@@ -146,7 +146,7 @@ public async Task Alexa_Function_Can_Process_Launch_Request()
public async Task Alexa_Function_Can_Process_Session_Ended_Request()
{
// Arrange
- var session = new Request()
+ var session = new SessionEndedRequest()
{
Reason = Reason.ExceededMaxReprompts,
Error = new()
@@ -156,7 +156,7 @@ public async Task Alexa_Function_Can_Process_Session_Ended_Request()
},
};
- var request = CreateRequest("SessionEndedRequest", session);
+ var request = CreateRequest(session);
// Act
var actual = await ProcessRequestAsync(request);
@@ -176,7 +176,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 Request()
+ var exception = new SystemExceptionRequest()
{
Error = new()
{
@@ -189,7 +189,7 @@ public async Task Alexa_Function_Can_Process_System_Exception_Request()
},
};
- var request = CreateRequest("System.ExceptionEncountered", exception);
+ var request = CreateRequest(exception);
// Act
var actual = await ProcessRequestAsync(request);
diff --git a/test/LondonTravel.Skill.Tests/FunctionTests.cs b/test/LondonTravel.Skill.Tests/FunctionTests.cs
index 5ba890f7..207f55cf 100644
--- a/test/LondonTravel.Skill.Tests/FunctionTests.cs
+++ b/test/LondonTravel.Skill.Tests/FunctionTests.cs
@@ -38,7 +38,7 @@ protected virtual async Task CreateFunctionAsync()
protected virtual SkillRequest CreateIntentRequest(string name, params Slot[] slots)
{
- var request = new Request()
+ var request = new IntentRequest()
{
Intent = new Intent()
{
@@ -56,10 +56,11 @@ protected virtual SkillRequest CreateIntentRequest(string name, params Slot[] sl
}
}
- return CreateRequest("IntentRequest", request);
+ return CreateRequest(request);
}
- protected virtual SkillRequest CreateRequest(string type, Request? request = null)
+ protected virtual SkillRequest CreateRequest(T? request = null)
+ where T : Request, new()
{
var application = new Application()
{
@@ -88,7 +89,7 @@ protected virtual SkillRequest CreateRequest(string type, Request? request = nul
User = user,
},
},
- Request = request ?? new(),
+ Request = request ?? new T(),
Session = new()
{
Application = application,
@@ -99,7 +100,6 @@ protected virtual SkillRequest CreateRequest(string type, Request? request = nul
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 7ae7d87a..a8ed22bf 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
var function = await CreateFunctionAsync();
- var request = CreateRequest("LaunchRequest");
+ var request = CreateRequest();
var context = new TestLambdaContext();
// Act
diff --git a/test/LondonTravel.Skill.Tests/SerializationTests.cs b/test/LondonTravel.Skill.Tests/SerializationTests.cs
index 2c638e5e..f1eaf8bf 100644
--- a/test/LondonTravel.Skill.Tests/SerializationTests.cs
+++ b/test/LondonTravel.Skill.Tests/SerializationTests.cs
@@ -9,11 +9,11 @@ namespace MartinCostello.LondonTravel.Skill;
public static class SerializationTests
{
[Theory]
- [InlineData("IntentRequest")]
- [InlineData("LaunchRequest")]
- [InlineData("LaunchRequestWithEpochTimestamp")]
- [InlineData("SessionEndedRequest")]
- public static void Can_Deserialize_Request(string name)
+ [InlineData("IntentRequest", typeof(IntentRequest))]
+ [InlineData("LaunchRequest", typeof(LaunchRequest))]
+ [InlineData("LaunchRequestWithEpochTimestamp", typeof(LaunchRequest))]
+ [InlineData("SessionEndedRequest", typeof(SessionEndedRequest))]
+ public static void Can_Deserialize_Request(string name, Type expectedType)
{
// Arrange
JsonSerializer.IsReflectionEnabledByDefault.ShouldBeFalse();
@@ -27,6 +27,7 @@ public static void Can_Deserialize_Request(string name)
// Assert
actual.ShouldNotBeNull();
actual.Request.ShouldNotBeNull();
+ actual.Request.ShouldBeOfType(expectedType);
}
[Fact]
diff --git a/test/LondonTravel.Skill.Tests/SessionEndedTests.cs b/test/LondonTravel.Skill.Tests/SessionEndedTests.cs
index f20e9a28..830cc4a1 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
var function = await CreateFunctionAsync();
- var request = CreateRequest("SessionEndedRequest");
+ var request = CreateRequest();
var context = new TestLambdaContext();
// Act
diff --git a/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs b/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs
index f26ff149..6b478650 100644
--- a/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs
+++ b/test/LondonTravel.Skill.Tests/UnknownIntentTests.cs
@@ -30,4 +30,9 @@ public async Task Can_Invoke_Function()
response.OutputSpeech.Type.ShouldBe("SSML");
response.OutputSpeech.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 8de4420a..93501056 100644
--- a/test/LondonTravel.Skill.Tests/UnknownRequestTests.cs
+++ b/test/LondonTravel.Skill.Tests/UnknownRequestTests.cs
@@ -15,7 +15,7 @@ public async Task Can_Invoke_Function()
var function = await CreateFunctionAsync();
var context = new TestLambdaContext();
- var request = CreateRequest("Unknown");
+ var request = CreateRequest();
// Act
var actual = await function.HandlerAsync(request, context);
@@ -23,4 +23,9 @@ public async Task Can_Invoke_Function()
// Assert
AssertResponse(actual);
}
+
+ private sealed class UnknownRequest : Request
+ {
+ public override string Type => "Unknown";
+ }
}