/
dOJer113
/
Insect
Обзор
Документация
Войти
/
dOJer113
/
Insect
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
server
Server/Program.cs
382 строки
10 KB
alex
валидация создания
23 дек 2025, 17:10
23 дек 2025, 17:10
71517e3
Код
Авторство
О чём код?
using System.Text; using System.Text.Json; using insects; using OpenApiValidator; using InsectServer; var builder = WebApplication.CreateBuilder(args); TryConfigureConsoleEncoding(); builder.Services.AddCors(options => { options.AddPolicy("AllowAll", policy => { policy.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); }); builder.WebHost.UseUrls("http://127.0.0.1:5000", "https://127.0.0.1:5001"); var app = builder.Build(); app.UseCors("AllowAll"); app.Use(async (ctx, next) => { Console.WriteLine($"Request: {ctx.Request.Method} {ctx.Request.Path}"); await next(); Console.WriteLine($"Response: {ctx.Response.StatusCode}"); }); var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true, WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All) }; jsonOptions.Converters.Add(new InsectJsonConverter()); var schemaPath = Path.Combine(AppContext.BaseDirectory, "InsectOpenApi.json"); var validator = new OpenApiSchemaValidator(schemaPath); var repositoryPath = Path.Combine(AppContext.BaseDirectory, "insects.json"); var repository = new InsectRepository(repositoryPath); var insectTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase) { ["Ladybug"] = typeof(Ladybug), ["Necrophages"] = typeof(Necrophages) }; var api = app.MapGroup("/v1"); api.MapGet("/insects/list", () => { try { var reload = repository.Reload(); if (!reload.isValid) { return StorageUnavailable(reload.errors); } var insects = repository.GetAll(); var response = new { status = new { code = 200, description = "OK" }, insects }; ValidateResponse("GetInsectList", "get", "/insects/list", response, "200"); return Results.Json(response, jsonOptions); } catch (Exception ex) { var errorResponse = new { status = new { code = 500, description = $"Ошибка при загрузке данных: {ex.Message}" } }; ValidateResponse("GetInsectList", "get", "/insects/list", errorResponse, "500"); return Results.Json(errorResponse, jsonOptions, statusCode: 500); } }); api.MapGet("/insects/{id}", (int id) => GetInsectById(id)); api.MapPost("/insects", async (HttpContext context) => await CreateInsectAsync(context)); api.MapPut("/insects/{id}", async (HttpContext context, int id) => await UpdateInsectAsync(context, id)); api.MapDelete("/insects/{id}", (int id) => DeleteInsect(id)); Console.WriteLine("Сервер доступен по https://127.0.0.1:5001/v1"); Console.WriteLine("Сервер доступен по http://127.0.0.1:5000/v1"); app.Run(); IResult GetInsectById(int id) { try { var reload = repository.Reload(); if (!reload.isValid) { return StorageUnavailable(reload.errors); } var insect = repository.GetById(id); if (insect is null) { return NotFound(); } var response = new { status = new { code = 200, description = "OK" }, insect }; ValidateResponse("GetInsect", "get", "/insects/{id}", response, "200"); return Results.Json(response, jsonOptions); } catch (Exception ex) { return HandleException(ex); } } async Task<IResult> CreateInsectAsync(HttpContext context) { try { var reload = repository.Reload(); if (!reload.isValid) { return StorageUnavailable(reload.errors); } var jsonString = await ReadBodyAsync(context); var validationResult = validator.ValidateRequest("CreateInsect", "post", "/insects", jsonString); if (!validationResult.IsValid) { Console.WriteLine($"CreateInsect: validation failed: {validationResult.ErrorMessage}"); return ValidationFailed(context,new[] { validationResult.ErrorMessage ?? "Ошибка валидации запроса" }); } var insect = DeserializeInsect(jsonString); if (insect is null) { Console.WriteLine("CreateInsect: deserialize returned null"); return BadRequest("Не удалось определить тип насекомого по typeName"); } insect.Id = 0; var repoResult = repository.Add(insect); if (!repoResult.IsValid) { Console.WriteLine($"CreateInsect: repository validation failed: {string.Join(", ", repoResult.ErrorMessages)}"); return ValidationFailed(context, repoResult.ErrorMessages); } var response = new { status = new { code = 201, description = "Created" }, id = insect.Id }; ValidateResponse("CreateInsect", "post", "/insects", response, "201"); return Results.Json(response, jsonOptions, statusCode: 201); } catch (Exception ex) { return HandleException(ex); } } async Task<IResult> UpdateInsectAsync(HttpContext context, int id) { try { var reload = repository.Reload(); if (!reload.isValid) { return StorageUnavailable(reload.errors); } var jsonString = await ReadBodyAsync(context); var validationResult = validator.ValidateRequest("UpdateInsect", "put", "/insects/{id}", jsonString); if (!validationResult.IsValid) { Console.WriteLine($"UpdateInsect: validation failed: {validationResult.ErrorMessage}"); return ValidationFailed(context,new[] { validationResult.ErrorMessage ?? "Ошибка валидации запроса" }); } var insect = DeserializeInsect(jsonString); if (insect is null) { Console.WriteLine("UpdateInsect: deserialize returned null"); return BadRequest("Не удалось определить тип насекомого по typeName"); } if (insect.Id != 0 && insect.Id != id) { return ValidationFailed(context,new[] { "ID в теле запроса не совпадает с ID в пути." }); } insect.Id = id; var repoResult = repository.Update(insect); if (!repoResult.IsValid) { return ValidationFailed(context,repoResult.ErrorMessages); } var response = new { status = new { code = 200, description = "Updated" } }; ValidateResponse("UpdateInsect", "put", "/insects/{id}", response, "200"); return Results.Json(response, jsonOptions); } catch (Exception ex) { return HandleException(ex); } } IResult DeleteInsect(int id) { try { var reload = repository.Reload(); if (!reload.isValid) { return StorageUnavailable(reload.errors); } var existing = repository.GetById(id); if (existing is null) { return NotFound(); } var deleted = repository.Delete(id); if (!deleted) { return NotFound(); } var response = new { status = new { code = 200, description = "Deleted" } }; ValidateResponse("DeleteInsect", "delete", "/insects/{id}", response, "200"); return Results.Json(response, jsonOptions); } catch (Exception ex) { return HandleException(ex); } } Insect? DeserializeInsect(string jsonString) { using var document = JsonDocument.Parse(jsonString); if (!document.RootElement.TryGetProperty("typeName", out var typeElement)) { Console.WriteLine("DeserializeInsect: missing typeName"); return null; } var typeName = typeElement.GetString(); if (string.IsNullOrWhiteSpace(typeName) || !insectTypes.TryGetValue(typeName, out var clrType)) { Console.WriteLine($"DeserializeInsect: unknown or empty typeName '{typeName}'"); return null; } var insect = (Insect?)JsonSerializer.Deserialize(jsonString, clrType, jsonOptions); if (insect is null) { Console.WriteLine("DeserializeInsect: deserialization returned null"); return null; } if (!string.Equals(insect.TypeName, typeName, StringComparison.OrdinalIgnoreCase)) { Console.WriteLine($"DeserializeInsect: typeName mismatch payload '{typeName}' vs object '{insect.TypeName}'"); return null; } return insect; } static async Task<string> ReadBodyAsync(HttpContext context) { context.Request.EnableBuffering(); using var reader = new StreamReader(context.Request.Body, leaveOpen: true); var jsonString = await reader.ReadToEndAsync(); context.Request.Body.Position = 0; return jsonString; } IResult ValidationFailed(HttpContext context, IEnumerable<string> errors) { var response = new { status = new { code = 400, description = "Validation Failed" }, errors = errors.ToArray() }; context.Response.StatusCode = 400; return Results.Json(response, jsonOptions, statusCode: 400); } IResult BadRequest(string message) { var response = new { status = new { code = 400, description = message } }; return Results.Json(response, jsonOptions, statusCode: 400); } IResult NotFound() { var response = new { status = new { code = 404, description = "Not Found" } }; return Results.Json(response, jsonOptions, statusCode: 404); } IResult HandleException(Exception ex) { Console.WriteLine($"Ошибка: {ex.Message}"); var response = new { status = new { code = 500, description = "Internal Server Error" } }; return Results.Json(response, jsonOptions, statusCode: 500); } void ValidateResponse(string operationId, string method, string path, object payload, string statusCode) { try { var serialized = JsonSerializer.Serialize(payload, jsonOptions); var validationResult = validator.ValidateResponse(operationId, method, path, serialized, statusCode); if (!validationResult.IsValid) { Console.WriteLine($"Предупреждение: ответ не соответствует схеме: {validationResult.ErrorMessage}"); } } catch (Exception ex) { Console.WriteLine($"Ошибка валидации ответа: {ex.Message}"); } } IResult StorageUnavailable(IEnumerable<string> errors) { var response = new { status = new { code = 500, description = "Storage Error" }, errors = errors.ToArray() }; return Results.Json(response, jsonOptions, statusCode: 500); } void TryConfigureConsoleEncoding() { if (Console.IsOutputRedirected) { return; } try { Console.OutputEncoding = Encoding.UTF8; } catch (IOException) { // Если смена кодировки недоступна, просто продолжаем без падения. } }