From 01b935e11247e6033fccffaf2687e20b512e87c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=8B=D1=82=D0=BA=D0=BE=D0=B2=20=D0=A0=D0=BE=D0=BC?= =?UTF-8?q?=D0=B0=D0=BD?= Date: Sun, 31 May 2026 23:09:30 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=BE=D0=BD=D1=82=D1=80=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=20http?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Contracts/ApiJsonOptions.cs | 20 +++++++++++ src/Contracts/Class1.cs | 6 ---- src/Contracts/Commands.cs | 7 ++++ src/Contracts/Contracts.csproj | 4 +++ .../Json/DecimalFromStringJsonConverter.cs | 24 ++++++++++++++ src/Contracts/Menu/GetMenuData.cs | 8 +++++ src/Contracts/Menu/GetMenuParameters.cs | 6 ++++ src/Contracts/Orders/SendOrderParameters.cs | 10 ++++++ src/Contracts/Requests/ApiRequest.cs | 11 +++++++ .../Requests/ApiRequestDeserializer.cs | 33 +++++++++++++++++++ src/Contracts/Requests/GetMenuApiRequest.cs | 11 +++++++ src/Contracts/Requests/SendOrderApiRequest.cs | 11 +++++++ src/Contracts/Responses/ApiResponse.cs | 10 ++++++ .../Responses/ApiResponseDeserializer.cs | 33 +++++++++++++++++++ src/Contracts/Responses/GetMenuApiResponse.cs | 8 +++++ .../Responses/SendOrderApiResponse.cs | 4 +++ src/Domain/Entities/Order.cs | 6 ++-- src/Domain/Entities/OrderItem.cs | 2 +- 18 files changed, 204 insertions(+), 10 deletions(-) create mode 100644 src/Contracts/ApiJsonOptions.cs delete mode 100644 src/Contracts/Class1.cs create mode 100644 src/Contracts/Commands.cs create mode 100644 src/Contracts/Json/DecimalFromStringJsonConverter.cs create mode 100644 src/Contracts/Menu/GetMenuData.cs create mode 100644 src/Contracts/Menu/GetMenuParameters.cs create mode 100644 src/Contracts/Orders/SendOrderParameters.cs create mode 100644 src/Contracts/Requests/ApiRequest.cs create mode 100644 src/Contracts/Requests/ApiRequestDeserializer.cs create mode 100644 src/Contracts/Requests/GetMenuApiRequest.cs create mode 100644 src/Contracts/Requests/SendOrderApiRequest.cs create mode 100644 src/Contracts/Responses/ApiResponse.cs create mode 100644 src/Contracts/Responses/ApiResponseDeserializer.cs create mode 100644 src/Contracts/Responses/GetMenuApiResponse.cs create mode 100644 src/Contracts/Responses/SendOrderApiResponse.cs 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; } /// /// Количество. Для весовых блюд допускаются дробные значения.