From e112882fe0a2602a7ffe7ab7760b6071bf84114e Mon Sep 17 00:00:00 2001 From: Lucas Teles Date: Wed, 28 Dec 2022 15:27:06 -0300 Subject: [PATCH] add simple email model --- src/BrazilModels.csproj | 2 +- src/Email.cs | 95 ++++++++++++++++++++++++++ tests/BrazilModels.Tests/EmailTests.cs | 81 ++++++++++++++++++++++ 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 src/Email.cs create mode 100644 tests/BrazilModels.Tests/EmailTests.cs diff --git a/src/BrazilModels.csproj b/src/BrazilModels.csproj index 47b37ca..66d35ba 100644 --- a/src/BrazilModels.csproj +++ b/src/BrazilModels.csproj @@ -24,7 +24,7 @@ README.md - + diff --git a/src/Email.cs b/src/Email.cs new file mode 100644 index 0000000..dd508ee --- /dev/null +++ b/src/Email.cs @@ -0,0 +1,95 @@ +namespace BrazilModels; + +using System; +using System.ComponentModel; +using System.Diagnostics; + +/// +/// Basic strongly typed E-mail representation +/// +[System.Text.Json.Serialization.JsonConverter(typeof(StringSystemTextJsonConverter))] +[TypeConverter(typeof(StringTypeConverter))] +[DebuggerDisplay("{DebuggerDisplay(),nq}")] +[Swashbuckle.AspNetCore.Annotations.SwaggerSchemaFilter(typeof(StringSchemaFilter))] +public readonly record struct Email : IComparable +{ + /// + /// String representation of the Email + /// + public string Value { get; } + + /// + /// Create a new email instance + /// + /// + /// + public Email(string email) + { + ArgumentNullException.ThrowIfNull(email); + this.Value = email.ToLowerInvariant(); + } + + /// + public override string ToString() => Value; + + /// + /// Get Email instance of an Value string + /// + /// + /// + public static explicit operator Email(string value) + => Parse(value); + + /// + /// Return string representation of Email + /// + public static implicit operator string(Email email) + => email.Value; + + /// + /// Try parse an Value string to an Email instance + /// + public static bool TryParse(string? value, out Email email) + { + email = default; + if (value is null || !IsValid(value)) + return false; + + email = new(value); + return true; + } + + /// + /// Parse an Value string to an Email instance + /// + /// + /// + public static Email Parse(string value) => + TryParse(value, out var valid) + ? valid + : throw new InvalidOperationException($"Invalid E-mail {value}"); + + /// + /// Validate Email string + /// + /// + /// + public static bool IsValid(string? value) + { + const string at = "@"; + if (value is null) + return false; + + var index = value.IndexOf(at, StringComparison.OrdinalIgnoreCase); + + return index > 0 && + index != value.Length - 1 && + index == value.LastIndexOf(at, StringComparison.OrdinalIgnoreCase); + } + + /// + public int CompareTo(Email other) => + string.Compare(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + string DebuggerDisplay() => $"EMAIL{{{Value}}}"; +} diff --git a/tests/BrazilModels.Tests/EmailTests.cs b/tests/BrazilModels.Tests/EmailTests.cs new file mode 100644 index 0000000..ef465f2 --- /dev/null +++ b/tests/BrazilModels.Tests/EmailTests.cs @@ -0,0 +1,81 @@ +namespace BrazilModels.Tests; + +using System.Text.Json; + +public class EmailTests : BaseTest +{ + [Test] + public void ShouldReturnValidForValidEmail() + { + var emailStr = faker.Person.Email; + Email.IsValid(emailStr).Should().BeTrue(); + } + + [Test] + public void ShouldReturnFalseForInvalid() + { + var emailStr = faker.Person.FirstName; + Email.IsValid(emailStr).Should().BeFalse(); + } + + [Test] + public void ShouldParseEmail() + { + var emailStr = "teste@email.com"; + var sut = Email.Parse(emailStr); + sut.Value.Should().Be(emailStr); + } + + [Test] + public void ShouldThrowInvalidEmail() + { + var emailStr = faker.Person.FirstName; + var action = () => Email.Parse(emailStr); + action.Should().Throw(); + } + + [Test] + public void ShouldTryParseSuccessfully() + { + var emailStr = faker.Person.Email; + Email.TryParse(emailStr, out var email).Should().BeTrue(); + email.Value.Should().Be(emailStr.ToLower()); + } + + [Test] + public void ShouldTryParseReturnFalse() + { + var emailStr = faker.Person.FirstName; + Email.TryParse(emailStr, out _).Should().BeFalse(); + } + + [Test] + public void ShouldCompareEmail() + { + var emailStr = faker.Person.Email; + Email emailLower = Email.Parse(emailStr.ToLowerInvariant()); + Email emailUpper = Email.Parse(emailStr.ToUpperInvariant()); + emailLower.Should().Be(emailUpper); + } + + + record Sut(Email Value); + + [Test] + public void ShouldSerializeEmail() + { + var data = faker.Person.Email; + var json = JsonSerializer.Serialize(new Sut(Email.Parse(data))); + json.Should().Be(@$"{{""Value"":""{data.ToLowerInvariant()}""}}"); + } + + [Test] + public void ShouldDeserializeEmail() + { + var value = Email.Parse(faker.Person.Email); + var body = @$"{{""Value"":""{value.Value}""}}"; + var parsed = JsonSerializer.Deserialize(body)!; + var expected = Email.Parse(value); + parsed.Value.Should().Be(expected); + } +}