improvement: 메시지 저장/검증 계약 통일 (#222)
- MessageValidateRequestDto에 JsonPropertyName 추가 (snake_case 통일) - MessageValidateRequestDto.Data 타입 string? → object? 변경 - MessageValidationService.ValidateData 파라미터 타입 변경 - Swagger Description 업데이트 (save/validate 엔드포인트) Closes #222
This commit is contained in:
parent
fecd322763
commit
b373d59710
|
|
@ -22,7 +22,7 @@ public class MessageController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("save")]
|
[HttpPost("save")]
|
||||||
[SwaggerOperation(Summary = "메시지 저장", Description = "메시지 템플릿을 저장합니다. 메시지 코드가 자동 생성됩니다.")]
|
[SwaggerOperation(Summary = "메시지 저장", Description = "메시지 템플릿을 저장합니다. 메시지 코드가 자동 생성됩니다. 필드명은 snake_case(title, body, image_url, link_url, link_type, data)를 사용합니다. 저장 전 validate API로 사전 검증을 권장합니다.")]
|
||||||
public async Task<IActionResult> SaveAsync([FromBody] MessageSaveRequestDto request)
|
public async Task<IActionResult> SaveAsync([FromBody] MessageSaveRequestDto request)
|
||||||
{
|
{
|
||||||
var serviceId = GetServiceId();
|
var serviceId = GetServiceId();
|
||||||
|
|
@ -59,7 +59,7 @@ public class MessageController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("validate")]
|
[HttpPost("validate")]
|
||||||
[SwaggerOperation(Summary = "메시지 유효성 검사", Description = "메시지 내용의 유효성을 검사합니다.")]
|
[SwaggerOperation(Summary = "메시지 유효성 검사", Description = "메시지 내용의 유효성을 검사합니다. save API와 동일한 snake_case 필드명(title, body, image_url, link_url, link_type, data)을 사용합니다. 검증 실패 시 data.errors[]에 field + message 단위로 오류가 반환됩니다.")]
|
||||||
public IActionResult ValidateAsync([FromBody] MessageValidateRequestDto request)
|
public IActionResult ValidateAsync([FromBody] MessageValidateRequestDto request)
|
||||||
{
|
{
|
||||||
var result = _validationService.Validate(request);
|
var result = _validationService.Validate(request);
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,27 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace SPMS.Application.DTOs.Message;
|
namespace SPMS.Application.DTOs.Message;
|
||||||
|
|
||||||
public class MessageValidateRequestDto
|
public class MessageValidateRequestDto
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
|
[JsonPropertyName("title")]
|
||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
|
[JsonPropertyName("body")]
|
||||||
public string Body { get; set; } = string.Empty;
|
public string Body { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("image_url")]
|
||||||
public string? ImageUrl { get; set; }
|
public string? ImageUrl { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("link_url")]
|
||||||
public string? LinkUrl { get; set; }
|
public string? LinkUrl { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("link_type")]
|
||||||
public string? LinkType { get; set; }
|
public string? LinkType { get; set; }
|
||||||
|
|
||||||
public string? Data { get; set; }
|
[JsonPropertyName("data")]
|
||||||
|
public object? Data { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,27 +82,55 @@ public class MessageValidationService : IMessageValidationService
|
||||||
errors.Add(new FieldError { Field = "link_type", Message = "link_type은 deeplink, web, none 중 하나여야 합니다." });
|
errors.Add(new FieldError { Field = "link_type", Message = "link_type은 deeplink, web, none 중 하나여야 합니다." });
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateData(string? data, List<FieldError> errors)
|
private static void ValidateData(object? data, List<FieldError> errors)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(data))
|
if (data is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try
|
// object? → JSON 문자열로 변환
|
||||||
|
string jsonString;
|
||||||
|
if (data is JsonElement jsonElement)
|
||||||
{
|
{
|
||||||
using var doc = JsonDocument.Parse(data);
|
if (jsonElement.ValueKind == JsonValueKind.Null || jsonElement.ValueKind == JsonValueKind.Undefined)
|
||||||
if (doc.RootElement.ValueKind != JsonValueKind.Object)
|
return;
|
||||||
|
|
||||||
|
if (jsonElement.ValueKind != JsonValueKind.Object)
|
||||||
{
|
{
|
||||||
errors.Add(new FieldError { Field = "data", Message = "data는 JSON 객체여야 합니다." });
|
errors.Add(new FieldError { Field = "data", Message = "data는 JSON 객체여야 합니다." });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jsonString = jsonElement.GetRawText();
|
||||||
}
|
}
|
||||||
catch (JsonException)
|
else if (data is string strData)
|
||||||
{
|
{
|
||||||
errors.Add(new FieldError { Field = "data", Message = "유효한 JSON 형식이 아닙니다." });
|
if (string.IsNullOrWhiteSpace(strData))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var doc = JsonDocument.Parse(strData);
|
||||||
|
if (doc.RootElement.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
errors.Add(new FieldError { Field = "data", Message = "data는 JSON 객체여야 합니다." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
errors.Add(new FieldError { Field = "data", Message = "유효한 JSON 형식이 아닙니다." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonString = strData;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 기타 타입은 직렬화하여 검증
|
||||||
|
jsonString = JsonSerializer.Serialize(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System.Text.Encoding.UTF8.GetByteCount(data) > MaxDataSizeBytes)
|
if (System.Text.Encoding.UTF8.GetByteCount(jsonString) > MaxDataSizeBytes)
|
||||||
errors.Add(new FieldError { Field = "data", Message = $"data는 {MaxDataSizeBytes / 1024}KB를 초과할 수 없습니다." });
|
errors.Add(new FieldError { Field = "data", Message = $"data는 {MaxDataSizeBytes / 1024}KB를 초과할 수 없습니다." });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user