SPMS_API/SPMS.Application/Services/MessageValidationService.cs
SEAN 4bc08715fa improvement: 공통 응답/에러 포맷 고정 (#164)
- FieldError DTO 공통화 (SPMS.Domain/Common)
- ValidationErrorData + ApiResponse.ValidationFail() 추가
- InvalidModelStateResponseFactory로 ModelState 에러 ApiResponse 변환
- Controller Unauthorized 응답 throw SpmsException으로 통일 (에러코드 102)
- MessageValidationService ValidationErrorDto → FieldError 교체

Closes #164
2026-02-24 16:24:56 +09:00

109 lines
3.8 KiB
C#

using System.Text.Json;
using SPMS.Application.DTOs.Message;
using SPMS.Application.Interfaces;
using SPMS.Domain.Common;
namespace SPMS.Application.Services;
public class MessageValidationService : IMessageValidationService
{
private const int MaxTitleLength = 100;
private const int MaxBodyLength = 2000;
private const int MaxDataSizeBytes = 4096;
private static readonly string[] AllowedLinkTypes = ["deeplink", "web", "none"];
public MessageValidationResultDto Validate(MessageValidateRequestDto request)
{
var errors = new List<FieldError>();
ValidateTitle(request.Title, errors);
ValidateBody(request.Body, errors);
ValidateImageUrl(request.ImageUrl, errors);
ValidateLinkUrl(request.LinkUrl, errors);
ValidateLinkType(request.LinkType, errors);
ValidateData(request.Data, errors);
return new MessageValidationResultDto
{
IsValid = errors.Count == 0,
Errors = errors
};
}
private static void ValidateTitle(string title, List<FieldError> errors)
{
if (string.IsNullOrWhiteSpace(title))
{
errors.Add(new FieldError { Field = "title", Message = "제목은 필수입니다." });
return;
}
if (title.Length > MaxTitleLength)
errors.Add(new FieldError { Field = "title", Message = $"제목은 {MaxTitleLength}자를 초과할 수 없습니다." });
}
private static void ValidateBody(string body, List<FieldError> errors)
{
if (string.IsNullOrWhiteSpace(body))
{
errors.Add(new FieldError { Field = "body", Message = "본문은 필수입니다." });
return;
}
if (body.Length > MaxBodyLength)
errors.Add(new FieldError { Field = "body", Message = $"본문은 {MaxBodyLength}자를 초과할 수 없습니다." });
}
private static void ValidateImageUrl(string? imageUrl, List<FieldError> errors)
{
if (string.IsNullOrWhiteSpace(imageUrl))
return;
if (!Uri.TryCreate(imageUrl, UriKind.Absolute, out var uri) ||
(uri.Scheme != "http" && uri.Scheme != "https"))
errors.Add(new FieldError { Field = "image_url", Message = "유효한 URL 형식이 아닙니다." });
}
private static void ValidateLinkUrl(string? linkUrl, List<FieldError> errors)
{
if (string.IsNullOrWhiteSpace(linkUrl))
return;
if (!Uri.TryCreate(linkUrl, UriKind.Absolute, out _))
errors.Add(new FieldError { Field = "link_url", Message = "유효한 URL 형식이 아닙니다." });
}
private static void ValidateLinkType(string? linkType, List<FieldError> errors)
{
if (string.IsNullOrWhiteSpace(linkType))
return;
if (!AllowedLinkTypes.Contains(linkType.ToLowerInvariant()))
errors.Add(new FieldError { Field = "link_type", Message = "link_type은 deeplink, web, none 중 하나여야 합니다." });
}
private static void ValidateData(string? data, List<FieldError> errors)
{
if (string.IsNullOrWhiteSpace(data))
return;
try
{
using var doc = JsonDocument.Parse(data);
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;
}
if (System.Text.Encoding.UTF8.GetByteCount(data) > MaxDataSizeBytes)
errors.Add(new FieldError { Field = "data", Message = $"data는 {MaxDataSizeBytes / 1024}KB를 초과할 수 없습니다." });
}
}