- FieldError DTO 공통화 (SPMS.Domain/Common) - ValidationErrorData + ApiResponse.ValidationFail() 추가 - InvalidModelStateResponseFactory로 ModelState 에러 ApiResponse 변환 - Controller Unauthorized 응답 throw SpmsException으로 통일 (에러코드 102) - MessageValidationService ValidationErrorDto → FieldError 교체 Closes #164
109 lines
3.8 KiB
C#
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를 초과할 수 없습니다." });
|
|
}
|
|
}
|