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 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>(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? 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 left, IReadOnlyList 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); } }