Skip to content

Commit

Permalink
optimize parse logic
Browse files Browse the repository at this point in the history
  • Loading branch information
tinohager committed Dec 11, 2024
1 parent cc21fdc commit 6ab51bd
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 39 deletions.
33 changes: 16 additions & 17 deletions src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,79 +6,78 @@ public sealed class DmarcRecordParserTest
[TestMethod]
public void TryParse_InvalidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord()
{
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC", out var dmarcDataFragment, out var unrecognizedParts, out var parseErrors);
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC", out var dmarcDataFragment, out var parseErrors);
Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dmarcDataFragment);
Assert.IsNull(unrecognizedParts);
}

[TestMethod]
public void TryParse_InvalidDmarcString2_ReturnsTrueAndPopulatesDmarcRecord()
{
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1", out var dmarcDataFragment, out var unrecognizedParts, out var parseErrors);
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1", out var dmarcDataFragment, out var parseErrors);
Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dmarcDataFragment);
Assert.IsNull(unrecognizedParts);
Assert.IsNull(parseErrors);
}

[TestMethod]
public void TryParse_InvalidDmarcString3_ReturnsTrueAndPopulatesDmarcRecord()
{
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=Test", out var dmarcDataFragment, out var unrecognizedParts, out var parseErrors);
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=Test", out var dmarcDataFragment, out var parseErrors);
Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dmarcDataFragment);
Assert.AreEqual("Test", dmarcDataFragment.DomainPolicy);
Assert.IsNull(unrecognizedParts);
Assert.IsNull(parseErrors);
}

[TestMethod]
public void TryParse_InvalidDmarcString4_ReturnsTrueAndPopulatesDmarcRecord()
{
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=Test;", out var dmarcDataFragment, out var unrecognizedParts, out var parseErrors);
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=Test;", out var dmarcDataFragment, out var parseErrors);
Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dmarcDataFragment);
Assert.AreEqual("Test", dmarcDataFragment.DomainPolicy);
Assert.IsNull(unrecognizedParts);
Assert.IsNull(parseErrors);
}

[TestMethod]
public void TryParse_ValidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord()
{
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject;", out var dmarcDataFragment, out var unrecognizedParts, out var parseErrors);
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject;", out var dmarcDataFragment, out var parseErrors);
Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dmarcDataFragment);
Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy);
Assert.IsNull(unrecognizedParts);
Assert.IsNull(parseErrors);
}

[TestMethod]
public void TryParse_ValidDmarcString2_ReturnsTrueAndPopulatesDmarcRecord()
{
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; sp=none;", out var dmarcDataFragment, out var unrecognizedParts, out var parseErrors);
var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; sp=none;", out var dmarcDataFragment, out var parseErrors);
Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dmarcDataFragment);
Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy);
Assert.AreEqual("none", dmarcDataFragment.SubdomainPolicy);
Assert.IsNull(unrecognizedParts);
Assert.IsNull(parseErrors);
}

[TestMethod]
public void TryParse_CorruptDmarcString1_ReturnsTrueAndPopulatesDmarcRecord()
{
var isSuccessful = DmarcRecordParser.TryParse("verification=123456789", out var dmarcDataFragment, out var unrecognizedParts, out var parseErrors);
var isSuccessful = DmarcRecordParser.TryParse("verification=123456789", out var dmarcDataFragment, out var parseErrors);
Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dmarcDataFragment);
Assert.IsNotNull(unrecognizedParts);
Assert.IsTrue(unrecognizedParts.Length == 1);
Assert.IsNotNull(parseErrors);
Assert.IsTrue(parseErrors.Length == 2);
}

[TestMethod]
public void TryParse_CorruptDmarcString2_ReturnsTrueAndPopulatesDmarcRecord()
{
var isSuccessful = DmarcRecordParser.TryParse(" ", out var dmarcDataFragment, out var unrecognizedParts, out var parseErrors);
var isSuccessful = DmarcRecordParser.TryParse(" ", out var dmarcDataFragment, out var parseErrors);
Assert.IsFalse(isSuccessful);
Assert.IsNull(dmarcDataFragment);
Assert.IsNull(unrecognizedParts);
Assert.IsNull(parseErrors);
}
}
}
32 changes: 13 additions & 19 deletions src/Nager.EmailAuthentication/DmarcRecordParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,21 @@ public static bool TryParse(
string dmarcRaw,
out DmarcDataFragment? dmarcDataFragment)
{
return TryParse(dmarcRaw, out dmarcDataFragment, out _, out _);
return TryParse(dmarcRaw, out dmarcDataFragment, out _);
}

/// <summary>
/// Attempts to parse a raw DMARC string into a <see cref="DmarcDataFragment"/> object.
/// </summary>
/// <param name="dmarcRaw">The raw DMARC string to parse.</param>
/// <param name="dmarcDataFragment">The parsed DMARC record, if successful.</param>
/// <param name="unrecognizedParts">A list of unrecognized parts in the DMARC string, if any.</param>
/// <param name="parseErrors">A list of errors in the DMARC string, if any.</param>
/// <returns><see langword="true"/> if parsing is successful; otherwise <see langword="false"/>.</returns>
public static bool TryParse(
string dmarcRaw,
out DmarcDataFragment? dmarcDataFragment,
out string[]? unrecognizedParts,
out ParseError[]? parseErrors)
{
unrecognizedParts = null;
parseErrors = null;

var errors = new List<ParseError>();
Expand All @@ -45,16 +42,17 @@ public static bool TryParse(
return false;
}

if (dmarcRaw.StartsWith("v=DMARC1", StringComparison.OrdinalIgnoreCase))
if (!dmarcRaw.StartsWith("v=DMARC1", StringComparison.OrdinalIgnoreCase))
{
errors.Add(new ParseError
{
ErrorMessage = "DMARC record is invalid: it must start with 'v=DMARC1'.",
Severity = ErrorSeverity.Critical
Severity = ErrorSeverity.Critical,
ErrorMessage = "DMARC record is invalid: it must start with 'v=DMARC1'."
});
}

var keyValueParser = new KeyValueParser.MemoryEfficientKeyValueParser(';', '=');
var keyValueSeperator = '=';
var keyValueParser = new KeyValueParser.MemoryEfficientKeyValueParser(';', keyValueSeperator);
if (!keyValueParser.TryParse(dmarcRaw, out var parseResult))
{
dmarcDataFragment = null;
Expand All @@ -75,14 +73,12 @@ public static bool TryParse(
{
errors.Add(new ParseError
{
ErrorMessage = $"Duplicate configuration detected for key: '{duplicate.Key}'.",
Severity = ErrorSeverity.Warning
Severity = ErrorSeverity.Error,
ErrorMessage = $"Duplicate configuration detected for key: '{duplicate.Key}'."
});
}

var dataFragment = new DmarcDataFragment();

var unrecognizedHandlers = new List<string>();

var handlers = new Dictionary<string, Action<string>>
{
Expand Down Expand Up @@ -112,15 +108,13 @@ public static bool TryParse(
continue;
}

unrecognizedHandlers.Add($"{keyValue.Key} {keyValue.Value}");
}

if (parseResult.UnrecognizedParts != null && parseResult.UnrecognizedParts.Length > 0)
{
unrecognizedHandlers.AddRange(parseResult.UnrecognizedParts);
errors.Add(new ParseError
{
ErrorMessage = $"Unrecognized Part {keyValue.Key}{keyValueSeperator}{keyValue.Value}",
Severity = ErrorSeverity.Warning
});
}

unrecognizedParts = unrecognizedHandlers.Count == 0 ? null : [.. unrecognizedHandlers];
parseErrors = errors.Count == 0 ? null : [.. errors];
dmarcDataFragment = dataFragment;

Expand Down
11 changes: 8 additions & 3 deletions src/Nager.EmailAuthentication/Models/ErrorSeverity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@
public enum ErrorSeverity
{
/// <summary>
/// Minor issues or informational messages
/// Info
/// </summary>
Info,

/// <summary>
/// Potential issues that don't invalidate the DMARC string
/// Warning
/// </summary>
Warning,

/// <summary>
/// Severe issues that invalidate the DMARC string
/// Error
/// </summary>
Error,

/// <summary>
/// Critical
/// </summary>
Critical
}
Expand Down

0 comments on commit 6ab51bd

Please sign in to comment.