diff --git a/src/Contracts/ApiJsonOptions.cs b/src/Contracts/ApiJsonOptions.cs
new file mode 100644
index 0000000..2e345e1
--- /dev/null
+++ b/src/Contracts/ApiJsonOptions.cs
@@ -0,0 +1,20 @@
+using System.Text.Json;
+using Contracts.Json;
+
+namespace Contracts;
+
+public static class ApiJsonOptions
+{
+ public static JsonSerializerOptions Instance { get; } = Create();
+
+ private static JsonSerializerOptions Create()
+ {
+ var options = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = null,
+ };
+
+ options.Converters.Add(new DecimalFromStringJsonConverter());
+ return options;
+ }
+}
diff --git a/src/Contracts/Class1.cs b/src/Contracts/Class1.cs
deleted file mode 100644
index 7f7c7e3..0000000
--- a/src/Contracts/Class1.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Contracts;
-
-public class Class1
-{
-
-}
diff --git a/src/Contracts/Commands.cs b/src/Contracts/Commands.cs
new file mode 100644
index 0000000..f640467
--- /dev/null
+++ b/src/Contracts/Commands.cs
@@ -0,0 +1,7 @@
+namespace Contracts;
+
+public static class Commands
+{
+ public const string GetMenu = "GetMenu";
+ public const string SendOrder = "SendOrder";
+}
diff --git a/src/Contracts/Contracts.csproj b/src/Contracts/Contracts.csproj
index b760144..fa68a1d 100644
--- a/src/Contracts/Contracts.csproj
+++ b/src/Contracts/Contracts.csproj
@@ -6,4 +6,8 @@
enable
+
+
+
+
diff --git a/src/Contracts/Json/DecimalFromStringJsonConverter.cs b/src/Contracts/Json/DecimalFromStringJsonConverter.cs
new file mode 100644
index 0000000..3dc565c
--- /dev/null
+++ b/src/Contracts/Json/DecimalFromStringJsonConverter.cs
@@ -0,0 +1,24 @@
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Contracts.Json;
+
+public sealed class DecimalFromStringJsonConverter : JsonConverter
+{
+ public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return reader.TokenType switch
+ {
+ JsonTokenType.String => decimal.Parse(
+ reader.GetString()!,
+ NumberStyles.Number,
+ CultureInfo.InvariantCulture),
+ JsonTokenType.Number => reader.GetDecimal(),
+ _ => throw new JsonException($"Unexpected token {reader.TokenType} when parsing decimal."),
+ };
+ }
+
+ public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) =>
+ writer.WriteNumberValue(value);
+}
diff --git a/src/Contracts/Menu/GetMenuData.cs b/src/Contracts/Menu/GetMenuData.cs
new file mode 100644
index 0000000..e50d25c
--- /dev/null
+++ b/src/Contracts/Menu/GetMenuData.cs
@@ -0,0 +1,8 @@
+using Domain.Entities;
+
+namespace Contracts.Menu;
+
+public sealed class GetMenuData
+{
+ public IReadOnlyList MenuItems { get; init; } = [];
+}
diff --git a/src/Contracts/Menu/GetMenuParameters.cs b/src/Contracts/Menu/GetMenuParameters.cs
new file mode 100644
index 0000000..bc0106b
--- /dev/null
+++ b/src/Contracts/Menu/GetMenuParameters.cs
@@ -0,0 +1,6 @@
+namespace Contracts.Menu;
+
+public sealed class GetMenuParameters
+{
+ public bool WithPrice { get; set; } = true;
+}
diff --git a/src/Contracts/Orders/SendOrderParameters.cs b/src/Contracts/Orders/SendOrderParameters.cs
new file mode 100644
index 0000000..9f3baa3
--- /dev/null
+++ b/src/Contracts/Orders/SendOrderParameters.cs
@@ -0,0 +1,10 @@
+using Domain.Entities;
+
+namespace Contracts.Orders;
+
+public sealed class SendOrderParameters
+{
+ public required string OrderId { get; init; }
+
+ public IReadOnlyList MenuItems { get; init; } = [];
+}
diff --git a/src/Contracts/Requests/ApiRequest.cs b/src/Contracts/Requests/ApiRequest.cs
new file mode 100644
index 0000000..e58b920
--- /dev/null
+++ b/src/Contracts/Requests/ApiRequest.cs
@@ -0,0 +1,11 @@
+namespace Contracts.Requests;
+
+public class ApiRequest
+{
+ public string Command { get; set; } = "";
+}
+
+public class ApiRequest : ApiRequest
+{
+ public TParameters? CommandParameters { get; set; }
+}
diff --git a/src/Contracts/Requests/ApiRequestDeserializer.cs b/src/Contracts/Requests/ApiRequestDeserializer.cs
new file mode 100644
index 0000000..30bb1d0
--- /dev/null
+++ b/src/Contracts/Requests/ApiRequestDeserializer.cs
@@ -0,0 +1,33 @@
+using System.Text.Json;
+
+namespace Contracts.Requests;
+
+public static class ApiRequestDeserializer
+{
+ public static ApiRequest Deserialize(string json) =>
+ Deserialize(JsonDocument.Parse(json).RootElement);
+
+ public static ApiRequest Deserialize(ReadOnlySpan utf8Json) =>
+ Deserialize(JsonSerializer.Deserialize(utf8Json, ApiJsonOptions.Instance)!);
+
+ public static async Task DeserializeAsync(Stream stream, CancellationToken cancellationToken = default)
+ {
+ using var document = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken);
+ return Deserialize(document.RootElement);
+ }
+
+ public static ApiRequest Deserialize(JsonElement json)
+ {
+ if (!json.TryGetProperty("Command", out var commandElement))
+ {
+ return json.Deserialize(ApiJsonOptions.Instance)!;
+ }
+
+ return commandElement.GetString() switch
+ {
+ Commands.GetMenu => json.Deserialize(ApiJsonOptions.Instance)!,
+ Commands.SendOrder => json.Deserialize(ApiJsonOptions.Instance)!,
+ _ => json.Deserialize(ApiJsonOptions.Instance)!,
+ };
+ }
+}
diff --git a/src/Contracts/Requests/GetMenuApiRequest.cs b/src/Contracts/Requests/GetMenuApiRequest.cs
new file mode 100644
index 0000000..a5340f6
--- /dev/null
+++ b/src/Contracts/Requests/GetMenuApiRequest.cs
@@ -0,0 +1,11 @@
+using Contracts.Menu;
+
+namespace Contracts.Requests;
+
+public sealed class GetMenuApiRequest : ApiRequest
+{
+ public GetMenuApiRequest()
+ {
+ Command = Commands.GetMenu;
+ }
+}
diff --git a/src/Contracts/Requests/SendOrderApiRequest.cs b/src/Contracts/Requests/SendOrderApiRequest.cs
new file mode 100644
index 0000000..b09d4d4
--- /dev/null
+++ b/src/Contracts/Requests/SendOrderApiRequest.cs
@@ -0,0 +1,11 @@
+using Contracts.Orders;
+
+namespace Contracts.Requests;
+
+public sealed class SendOrderApiRequest : ApiRequest
+{
+ public SendOrderApiRequest()
+ {
+ Command = Commands.SendOrder;
+ }
+}
diff --git a/src/Contracts/Responses/ApiResponse.cs b/src/Contracts/Responses/ApiResponse.cs
new file mode 100644
index 0000000..4bbe37a
--- /dev/null
+++ b/src/Contracts/Responses/ApiResponse.cs
@@ -0,0 +1,10 @@
+namespace Contracts.Responses;
+
+public class ApiResponse
+{
+ public required string Command { get; init; }
+
+ public bool Success { get; init; }
+
+ public string ErrorMessage { get; init; } = "";
+}
diff --git a/src/Contracts/Responses/ApiResponseDeserializer.cs b/src/Contracts/Responses/ApiResponseDeserializer.cs
new file mode 100644
index 0000000..eadde04
--- /dev/null
+++ b/src/Contracts/Responses/ApiResponseDeserializer.cs
@@ -0,0 +1,33 @@
+using System.Text.Json;
+
+namespace Contracts.Responses;
+
+public static class ApiResponseDeserializer
+{
+ public static ApiResponse Deserialize(string json) =>
+ Deserialize(JsonDocument.Parse(json).RootElement);
+
+ public static ApiResponse Deserialize(ReadOnlySpan utf8Json) =>
+ Deserialize(JsonSerializer.Deserialize(utf8Json, ApiJsonOptions.Instance)!);
+
+ public static async Task DeserializeAsync(Stream stream, CancellationToken cancellationToken = default)
+ {
+ using var document = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken);
+ return Deserialize(document.RootElement);
+ }
+
+ public static ApiResponse Deserialize(JsonElement json)
+ {
+ if (!json.TryGetProperty("Command", out var commandElement))
+ {
+ return json.Deserialize(ApiJsonOptions.Instance)!;
+ }
+
+ return commandElement.GetString() switch
+ {
+ Commands.GetMenu => json.Deserialize(ApiJsonOptions.Instance)!,
+ Commands.SendOrder => json.Deserialize(ApiJsonOptions.Instance)!,
+ _ => json.Deserialize(ApiJsonOptions.Instance)!,
+ };
+ }
+}
diff --git a/src/Contracts/Responses/GetMenuApiResponse.cs b/src/Contracts/Responses/GetMenuApiResponse.cs
new file mode 100644
index 0000000..5a94c79
--- /dev/null
+++ b/src/Contracts/Responses/GetMenuApiResponse.cs
@@ -0,0 +1,8 @@
+using Contracts.Menu;
+
+namespace Contracts.Responses;
+
+public sealed class GetMenuApiResponse : ApiResponse
+{
+ public GetMenuData? Data { get; init; }
+}
diff --git a/src/Contracts/Responses/SendOrderApiResponse.cs b/src/Contracts/Responses/SendOrderApiResponse.cs
new file mode 100644
index 0000000..240874e
--- /dev/null
+++ b/src/Contracts/Responses/SendOrderApiResponse.cs
@@ -0,0 +1,4 @@
+namespace Contracts.Responses;
+
+public sealed class SendOrderApiResponse : ApiResponse;
+
diff --git a/src/Domain/Entities/Order.cs b/src/Domain/Entities/Order.cs
index f5777fc..dd58510 100644
--- a/src/Domain/Entities/Order.cs
+++ b/src/Domain/Entities/Order.cs
@@ -16,9 +16,9 @@ public sealed class Order
Id = id ?? Guid.NewGuid();
}
- public void AddItem(string menuItemId, decimal quantity)
+ public void AddItem(string id, decimal quantity)
{
- ArgumentException.ThrowIfNullOrWhiteSpace(menuItemId);
+ ArgumentException.ThrowIfNullOrWhiteSpace(id);
if (quantity <= 0)
{
@@ -30,7 +30,7 @@ public sealed class Order
_items.Add(new OrderItem
{
- MenuItemId = menuItemId,
+ Id = id,
Quantity = quantity,
});
}
diff --git a/src/Domain/Entities/OrderItem.cs b/src/Domain/Entities/OrderItem.cs
index ac14388..9f14fd1 100644
--- a/src/Domain/Entities/OrderItem.cs
+++ b/src/Domain/Entities/OrderItem.cs
@@ -8,7 +8,7 @@ public sealed class OrderItem
///
/// Идентификатор блюда на сервере ().
///
- public required string MenuItemId { get; init; }
+ public required string Id { get; init; }
///
/// Количество. Для весовых блюд допускаются дробные значения.