81654c0d33
Add normalized service and template catalogs, scoped administration, migration, validation, and regression coverage. Ref: IT-1601
77 lines
4.1 KiB
C#
77 lines
4.1 KiB
C#
using System.Text.Json;
|
|
|
|
namespace HrynCo.NotificationService.DAL.Abstract.Templates;
|
|
|
|
public static class EmailTemplateValidation
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
|
|
{
|
|
PropertyNameCaseInsensitive = false,
|
|
UnmappedMemberHandling = System.Text.Json.Serialization.JsonUnmappedMemberHandling.Disallow
|
|
};
|
|
|
|
public static IReadOnlyList<EmailTemplateVariable> ParseVariables(string json)
|
|
{
|
|
using var document = JsonDocument.Parse(json);
|
|
if (document.RootElement.ValueKind == JsonValueKind.Null)
|
|
throw new EmailTemplateWriteException("Variables must be a JSON array.", false);
|
|
if (document.RootElement.ValueKind != JsonValueKind.Array)
|
|
throw new JsonException("Variables must be an array.");
|
|
foreach (var item in document.RootElement.EnumerateArray())
|
|
ValidateVariableShape(item);
|
|
var variables = JsonSerializer.Deserialize<List<EmailTemplateVariable>>(json, JsonOptions);
|
|
ValidateVariables(variables);
|
|
return variables!;
|
|
}
|
|
|
|
private static void ValidateVariableShape(JsonElement item)
|
|
{
|
|
if (item.ValueKind != JsonValueKind.Object)
|
|
throw new JsonException("Each variable must be an object.");
|
|
if (!item.TryGetProperty("name", out var name) || name.ValueKind != JsonValueKind.String)
|
|
throw new JsonException("Every variable needs a name string.");
|
|
if (!item.TryGetProperty("required", out var required) || required.ValueKind is not (JsonValueKind.True or JsonValueKind.False))
|
|
throw new JsonException("Every variable needs a required boolean.");
|
|
}
|
|
|
|
public static void ValidateVariables(IReadOnlyList<EmailTemplateVariable>? variables)
|
|
{
|
|
if (variables is null)
|
|
throw new EmailTemplateWriteException("Variables must be a JSON array.", false);
|
|
if (variables.Any(v => v is null || string.IsNullOrWhiteSpace(v.Name)))
|
|
throw new EmailTemplateWriteException("Every variable needs a non-empty name.", false);
|
|
if (variables.Select(v => v.Name).Distinct(StringComparer.Ordinal).Count() != variables.Count)
|
|
throw new EmailTemplateWriteException("Variable names must be unique.", false);
|
|
}
|
|
|
|
public static bool ContractsEqual(IReadOnlyList<EmailTemplateVariable> left, IReadOnlyList<EmailTemplateVariable> right) =>
|
|
left.OrderBy(v => v.Name, StringComparer.Ordinal).SequenceEqual(right.OrderBy(v => v.Name, StringComparer.Ordinal));
|
|
|
|
public static void ValidateIdentity(string serviceName, string key)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(serviceName) || serviceName.Length > 100)
|
|
throw new EmailTemplateWriteException("Service name is required and must be at most 100 characters.", false);
|
|
if (string.IsNullOrWhiteSpace(key) || key.Length > 100)
|
|
throw new EmailTemplateWriteException("Key is required and must be at most 100 characters.", false);
|
|
}
|
|
|
|
public static void ValidateLanguage(EmailTemplate template)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(template.LanguageCode) || template.LanguageCode.Length > 10)
|
|
throw new EmailTemplateWriteException("Language code is required and must be at most 10 characters.", false);
|
|
if (string.IsNullOrWhiteSpace(template.Subject) || string.IsNullOrWhiteSpace(template.HtmlBody) || string.IsNullOrWhiteSpace(template.TextBody))
|
|
throw new EmailTemplateWriteException("Every language needs a subject, HTML body, and text body.", false);
|
|
}
|
|
|
|
public static void ValidateDefinition(EmailTemplateDefinition definition)
|
|
{
|
|
ValidateIdentity(definition.ServiceName, definition.Key);
|
|
ValidateVariables(definition.Variables);
|
|
foreach (var language in definition.Languages)
|
|
ValidateLanguage(language);
|
|
var codes = definition.Languages.Select(t => t.LanguageCode);
|
|
if (codes.Distinct(StringComparer.Ordinal).Count() != definition.Languages.Count)
|
|
throw new EmailTemplateWriteException("Language codes must be unique within a template.", false);
|
|
}
|
|
}
|