improvement: 메시지 저장/검증 계약 통일 (#222) #223

Merged
seonkyu.kim merged 1 commits from improvement/#222-message-validate-contract into develop 2026-02-25 05:10:24 +00:00
3 changed files with 47 additions and 12 deletions
Showing only changes of commit b373d59710 - Show all commits

View File

@ -22,7 +22,7 @@ public class MessageController : ControllerBase
}
[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)
{
var serviceId = GetServiceId();
@ -59,7 +59,7 @@ public class MessageController : ControllerBase
}
[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)
{
var result = _validationService.Validate(request);

View File

@ -1,20 +1,27 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace SPMS.Application.DTOs.Message;
public class MessageValidateRequestDto
{
[Required]
[JsonPropertyName("title")]
public string Title { get; set; } = string.Empty;
[Required]
[JsonPropertyName("body")]
public string Body { get; set; } = string.Empty;
[JsonPropertyName("image_url")]
public string? ImageUrl { get; set; }
[JsonPropertyName("link_url")]
public string? LinkUrl { get; set; }
[JsonPropertyName("link_type")]
public string? LinkType { get; set; }
public string? Data { get; set; }
[JsonPropertyName("data")]
public object? Data { get; set; }
}

View File

@ -82,27 +82,55 @@ public class MessageValidationService : IMessageValidationService
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;
try
// object? → JSON 문자열로 변환
string jsonString;
if (data is JsonElement jsonElement)
{
using var doc = JsonDocument.Parse(data);
if (doc.RootElement.ValueKind != JsonValueKind.Object)
if (jsonElement.ValueKind == JsonValueKind.Null || jsonElement.ValueKind == JsonValueKind.Undefined)
return;
if (jsonElement.ValueKind != JsonValueKind.Object)
{
errors.Add(new FieldError { Field = "data", Message = "data는 JSON 객체여야 합니다." });
return;
}
jsonString = jsonElement.GetRawText();
}
catch (JsonException)
else if (data is string strData)
{
errors.Add(new FieldError { Field = "data", Message = "유효한 JSON 형식이 아닙니다." });
return;
if (string.IsNullOrWhiteSpace(strData))
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를 초과할 수 없습니다." });
}
}