Skip to content

Commit

Permalink
add simple email model
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasteles committed Dec 28, 2022
1 parent 11aabf1 commit e112882
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/BrazilModels.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\"/>
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
Expand Down
95 changes: 95 additions & 0 deletions src/Email.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
namespace BrazilModels;

using System;
using System.ComponentModel;
using System.Diagnostics;

/// <summary>
/// Basic strongly typed E-mail representation
/// </summary>
[System.Text.Json.Serialization.JsonConverter(typeof(StringSystemTextJsonConverter<Email>))]
[TypeConverter(typeof(StringTypeConverter<Email>))]
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
[Swashbuckle.AspNetCore.Annotations.SwaggerSchemaFilter(typeof(StringSchemaFilter))]
public readonly record struct Email : IComparable<Email>
{
/// <summary>
/// String representation of the Email
/// </summary>
public string Value { get; }

/// <summary>
/// Create a new email instance
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ArgumentNullException"></exception>
public Email(string email)
{
ArgumentNullException.ThrowIfNull(email);
this.Value = email.ToLowerInvariant();
}

/// <inheritdoc />
public override string ToString() => Value;

/// <summary>
/// Get Email instance of an Value string
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ArgumentNullException"></exception>
public static explicit operator Email(string value)
=> Parse(value);

/// <summary>
/// Return string representation of Email
/// </summary>
public static implicit operator string(Email email)
=> email.Value;

/// <summary>
/// Try parse an Value string to an Email instance
/// </summary>
public static bool TryParse(string? value, out Email email)
{
email = default;
if (value is null || !IsValid(value))
return false;

email = new(value);
return true;
}

/// <summary>
/// Parse an Value string to an Email instance
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ArgumentNullException"></exception>
public static Email Parse(string value) =>
TryParse(value, out var valid)
? valid
: throw new InvalidOperationException($"Invalid E-mail {value}");

/// <summary>
/// Validate Email string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
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);
}

/// <inheritdoc />
public int CompareTo(Email other) =>
string.Compare(Value, other.Value, StringComparison.OrdinalIgnoreCase);

string DebuggerDisplay() => $"EMAIL{{{Value}}}";
}
81 changes: 81 additions & 0 deletions tests/BrazilModels.Tests/EmailTests.cs
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]";
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<InvalidOperationException>();
}

[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<Sut>(body)!;
var expected = Email.Parse(value);
parsed.Value.Should().Be(expected);
}
}

0 comments on commit e112882

Please sign in to comment.