feat(templates): add service language template management
Add normalized service and template catalogs, scoped administration, migration, validation, and regression coverage. Ref: IT-1601
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user