Skip to content

Commit

Permalink
Expanded naming rules for identifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
Jezz Santos committed May 14, 2023
1 parent 734eea4 commit 10abb0c
Show file tree
Hide file tree
Showing 14 changed files with 269 additions and 92 deletions.
1 change: 1 addition & 0 deletions src/Automate.sln.DotSettings
Expand Up @@ -1636,6 +1636,7 @@ namespace $NAMESPACE$
<s:Boolean x:Key="/Default/UserDictionary/Words/=aproductname/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=arelativepath/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=arequestid/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=areservedname/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=arootpath/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=aschildof/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=asessionid/@EntryIndexedValue">True</s:Boolean>
Expand Down
32 changes: 16 additions & 16 deletions src/Core.UnitTests/Authoring/Domain/PatternElementSpec.cs
Expand Up @@ -55,9 +55,9 @@ public void WhenAddAttributeWithReservedName_ThenThrows()
{
this.element
.Invoking(x => x.AddAttribute(Attribute.ReservedAttributeNames[0], null))
.Should().Throw<AutomateException>()
.WithMessage(
ExceptionMessages.PatternElement_AttributeNameReserved.Substitute(
.Should().Throw<ArgumentOutOfRangeException>()
.WithMessageLike(
ValidationMessages.Attribute_ReservedName.Substitute(
Attribute.ReservedAttributeNames[0]));
}

Expand Down Expand Up @@ -116,9 +116,9 @@ public void WhenUpdateAttributeAndNewNameIsReserved_ThenThrows()

this.element
.Invoking(x => x.UpdateAttribute("anattributename", Attribute.ReservedAttributeNames.First()))
.Should().Throw<AutomateException>()
.WithMessage(
ExceptionMessages.PatternElement_AttributeNameReserved.Substitute(
.Should().Throw<ArgumentOutOfRangeException>()
.WithMessageLike(
ValidationMessages.Attribute_ReservedName.Substitute(
Attribute.ReservedAttributeNames.First()));
}

Expand Down Expand Up @@ -202,11 +202,11 @@ public void WhenAddElementWithExistingNameOfAttribute_ThenThrows()
public void WhenAddElementWithReservedName_ThenThrows()
{
this.element
.Invoking(x => x.AddElement(Element.ReservedElementNames[0]))
.Should().Throw<AutomateException>()
.WithMessage(
ExceptionMessages.PatternElement_ElementNameReserved.Substitute(
Attribute.ReservedAttributeNames[0]));
.Invoking(x => x.AddElement(PatternElement.ReservedElementNames[0]))
.Should().Throw<ArgumentOutOfRangeException>()
.WithMessageLike(
ValidationMessages.Element_ReservedName.Substitute(
PatternElement.ReservedElementNames[0]));
}

[Fact]
Expand Down Expand Up @@ -238,11 +238,11 @@ public void WhenUpdateElementAndNewNameIsReserved_ThenThrows()
this.element.AddElement("anelementname");

this.element
.Invoking(x => x.UpdateElement("anelementname", Attribute.ReservedAttributeNames.First()))
.Should().Throw<AutomateException>()
.WithMessage(
ExceptionMessages.PatternElement_ElementNameReserved.Substitute(
Attribute.ReservedAttributeNames.First()));
.Invoking(x => x.UpdateElement("anelementname", PatternElement.ReservedElementNames.First()))
.Should().Throw<ArgumentOutOfRangeException>()
.WithMessageLike(
ValidationMessages.Element_ReservedName.Substitute(
PatternElement.ReservedElementNames.First()));
}

[Fact]
Expand Down
40 changes: 40 additions & 0 deletions src/Core.UnitTests/Authoring/Domain/ValidationsSpec.cs
Expand Up @@ -9,6 +9,46 @@ namespace Core.UnitTests.Authoring.Domain
[Trait("Category", "Unit")]
public class ValidationsSpec
{
[Fact]
public void WhenIsNameIdentifierWithInvalidCharacters_ThenReturnsFalse()
{
var result = Validations.IsNameIdentifier("aninvalidname^");

result.Should().BeFalse();
}

[Fact]
public void WhenIsNameIdentifierAndReservedName_ThenReturnsFalse()
{
var result = Validations.IsNameIdentifier("Parent");

result.Should().BeFalse();
}

[Fact]
public void WhenIsNameIdentifier_ThenReturnsTrue()
{
var result = Validations.IsNameIdentifier("aname");

result.Should().BeTrue();
}

[Fact]
public void WhenIsNotReservedNameAndReservedName_ThenReturnsFalse()
{
var result = Validations.IsNotReservedName("areservedname", new[] { "areservedname" });

result.Should().BeFalse();
}

[Fact]
public void WhenIsNotReservedNameAndNotReservedName_ThenReturnsTrue()
{
var result = Validations.IsNotReservedName("aname", new[] { "areservedname" });

result.Should().BeTrue();
}

[Fact]
public void WhenIsRuntimeFilePathAndIsNull_ThenReturnsFalse()
{
Expand Down
114 changes: 114 additions & 0 deletions src/Core.UnitTests/FluentAssertionExtensions.cs
@@ -0,0 +1,114 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Execution;
using FluentAssertions.Primitives;
using FluentAssertions.Specialized;

namespace Core.UnitTests
{
// ReSharper disable once CheckNamespace
[ExcludeFromCodeCoverage]
public static class ExceptionAssertionExtensions
{
public static ExceptionAssertions<TException> WithMessageLike<TException>(
this ExceptionAssertions<TException> @throw, string messageWithFormatters, string because = "",
params object[] becauseArgs) where TException : Exception
{
if (!string.IsNullOrEmpty(messageWithFormatters))
{
var exception = @throw.Subject.Single();
var expectedFormat = messageWithFormatters.Replace("{", "{{").Replace("}", "}}");
Execute.Assertion
.BecauseOf(because, becauseArgs)
.ForCondition(IsFormattedFrom(exception.Message, messageWithFormatters))
.UsingLineBreaks
.FailWith(
$"Expected exception message to match the equivalent of\n\"{expectedFormat}\", but\n\"{exception.Message}\" does not.")
;
}

return new ExceptionAssertions<TException>(@throw.Subject);
}

public static async Task<ExceptionAssertions<TException>> WithMessageLike<TException>(
this Task<ExceptionAssertions<TException>> @throw, string messageWithFormatters, string because = "",
params object[] becauseArgs) where TException : Exception
{
if (!string.IsNullOrEmpty(messageWithFormatters))
{
var exception = (await @throw).Subject.Single();
var expectedFormat = messageWithFormatters.Replace("{", "{{").Replace("}", "}}");
Execute.Assertion
.BecauseOf(because, becauseArgs)
.ForCondition(IsFormattedFrom(exception.Message, messageWithFormatters))
.UsingLineBreaks
.FailWith(
$"Expected exception message to match the equivalent of\n\"{expectedFormat}\", but\n\"{exception.Message}\" does not.")
;
}

return new ExceptionAssertions<TException>((await @throw).Subject);
}

internal static bool IsFormattedFrom(string actualExceptionMessage, string expectedMessageWithFormatters)
{
var escapedPattern = expectedMessageWithFormatters
.Replace("[", "\\[")
.Replace("]", "\\]")
.Replace("(", "\\(")
.Replace(")", "\\)")
.Replace(".", "\\.")
.Replace("<", "\\<")
.Replace(">", "\\>");

var pattern = Regex.Replace(escapedPattern, @"\{\d+\}", ".*")
.Replace(" ", @"\s");

return new Regex(pattern).IsMatch(actualExceptionMessage);
}
}

[ExcludeFromCodeCoverage]
public static class DateTimeAssertionExtensions
{
public static AndConstraint<DateTimeAssertions> BeNear(this DateTimeAssertions assertions,
DateTime nearbyTime,
int precision = 850,
string because = "",
params object[] becauseArgs)
{
return assertions.BeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(precision), because, becauseArgs);
}

public static AndConstraint<DateTimeAssertions> BeNear(this DateTimeAssertions assertions,
DateTime nearbyTime,
TimeSpan precision,
string because = "",
params object[] becauseArgs)
{
return assertions.BeCloseTo(nearbyTime, precision, because, becauseArgs);
}

public static AndConstraint<NullableDateTimeAssertions> BeNear(this NullableDateTimeAssertions assertions,
DateTime nearbyTime,
int precision = 850,
string because = "",
params object[] becauseArgs)
{
return assertions.BeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(precision), because, becauseArgs);
}

public static AndConstraint<NullableDateTimeAssertions> BeNear(this NullableDateTimeAssertions assertions,
DateTime nearbyTime,
TimeSpan precision,
string because = "",
params object[] becauseArgs)
{
return assertions.BeCloseTo(nearbyTime, precision, because, becauseArgs);
}
}
}
4 changes: 4 additions & 0 deletions src/Core/Authoring/Domain/Attribute.cs
Expand Up @@ -27,6 +27,8 @@ public class Attribute : INamedEntity, IValidateable, IPersistable, IPatternVisi
name.GuardAgainstNullOrEmpty(nameof(name));
name.GuardAgainstInvalid(Validations.IsNameIdentifier, nameof(name),
ValidationMessages.InvalidNameIdentifier);
name.GuardAgainstInvalid(x => Validations.IsNotReservedName(x, ReservedAttributeNames), nameof(name),
ValidationMessages.Attribute_ReservedName);
if (dataType.HasValue())
{
dataType.GuardAgainstInvalid(Validations.IsSupportedAttributeDataType, nameof(dataType),
Expand Down Expand Up @@ -173,6 +175,8 @@ public void Rename(string name)
name.GuardAgainstNullOrEmpty(nameof(name));
name.GuardAgainstInvalid(Validations.IsNameIdentifier, nameof(name),
ValidationMessages.InvalidNameIdentifier);
name.GuardAgainstInvalid(x => Validations.IsNotReservedName(x, ReservedAttributeNames), nameof(name),
ValidationMessages.Attribute_ReservedName);

if (name.NotEqualsOrdinal(Name))
{
Expand Down
6 changes: 6 additions & 0 deletions src/Core/Authoring/Domain/Automation.cs
Expand Up @@ -9,6 +9,10 @@ namespace Automate.Authoring.Domain
{
public class Automation : IAutomation, IPersistable, IPatternVisitable
{
private static readonly string[] ReservedAutomationNames =
{
nameof(INamedEntity.Id)
};
#if TESTINGONLY
private static int testTurn;
#endif
Expand All @@ -20,6 +24,8 @@ public class Automation : IAutomation, IPersistable, IPatternVisitable
name.GuardAgainstNullOrEmpty(nameof(name));
name.GuardAgainstInvalid(Validations.IsNameIdentifier, nameof(name),
ValidationMessages.InvalidNameIdentifier);
name.GuardAgainstInvalid(x => Validations.IsNotReservedName(x, ReservedAutomationNames), nameof(name),
ValidationMessages.Automation_ReservedName);
metadata.GuardAgainstNull(nameof(metadata));

Id = IdGenerator.Create();
Expand Down
6 changes: 6 additions & 0 deletions src/Core/Authoring/Domain/CodeTemplate.cs
Expand Up @@ -7,11 +7,17 @@ namespace Automate.Authoring.Domain
{
public class CodeTemplate : INamedEntity, IPersistable, IPatternVisitable
{
private static readonly string[] ReservedCodeTemplateNames =
{
nameof(INamedEntity.Id)
};
public CodeTemplate(string name, string fullPath, string fileExtension)
{
name.GuardAgainstNullOrEmpty(nameof(name));
name.GuardAgainstInvalid(Validations.IsNameIdentifier, nameof(name),
ValidationMessages.InvalidNameIdentifier);
name.GuardAgainstInvalid(x => Validations.IsNotReservedName(x, ReservedCodeTemplateNames), nameof(name),
ValidationMessages.CodeTemplate_ReservedName);
fullPath.GuardAgainstNullOrEmpty(nameof(fullPath));
fileExtension.GuardAgainstNullOrEmpty(nameof(fileExtension));
Id = IdGenerator.Create();
Expand Down
2 changes: 0 additions & 2 deletions src/Core/Authoring/Domain/Element.cs
Expand Up @@ -5,8 +5,6 @@ namespace Automate.Authoring.Domain
{
public class Element : PatternElement, IValidateable, IPersistable
{
public static readonly string[] ReservedElementNames = Attribute.ReservedAttributeNames;

public Element(string name,
ElementCardinality cardinality = ElementCardinality.One, bool autoCreate = true,
string displayName = null, string description = null) : base(name, displayName, description)
Expand Down
38 changes: 6 additions & 32 deletions src/Core/Authoring/Domain/PatternElement.cs
Expand Up @@ -9,6 +9,7 @@ namespace Automate.Authoring.Domain
{
public abstract class PatternElement : IPatternElement, IPatternVisitable
{
public static readonly string[] ReservedElementNames = Attribute.ReservedAttributeNames;
internal const string LaunchPointSelectionWildcard = "*";
private readonly List<Attribute> attributes;
private readonly List<Automation> automations;
Expand All @@ -21,6 +22,9 @@ protected PatternElement(string name, string displayName, string description)
name.GuardAgainstNullOrEmpty(nameof(name));
name.GuardAgainstInvalid(Validations.IsNameIdentifier, nameof(name),
ValidationMessages.InvalidNameIdentifier);
name.GuardAgainstInvalid(x => Validations.IsNotReservedName(x, ReservedElementNames), nameof(name),
ValidationMessages.Element_ReservedName);


Id = IdGenerator.Create();
Name = name;
Expand Down Expand Up @@ -188,6 +192,8 @@ void SetName(string value)
{
value.GuardAgainstInvalid(Validations.IsNameIdentifier, nameof(name),
ValidationMessages.InvalidNameIdentifier);
name.GuardAgainstInvalid(x => Validations.IsNotReservedName(x, ReservedElementNames), nameof(name),
ValidationMessages.Element_ReservedName);
if (value.NotEqualsOrdinal(Name))
{
Name = value;
Expand Down Expand Up @@ -223,11 +229,6 @@ void SetDescription(string value)
{
name.GuardAgainstNullOrEmpty(nameof(name));

if (AttributeNameIsReserved(name))
{
throw new AutomateException(ExceptionMessages.PatternElement_AttributeNameReserved.Substitute(name));
}

if (AttributeExistsByName(this, name))
{
throw new AutomateException(ExceptionMessages.PatternElement_AttributeByNameExists.Substitute(name));
Expand Down Expand Up @@ -261,12 +262,6 @@ void SetDescription(string value)
{
if (name.NotEqualsIgnoreCase(attribute.Name))
{
if (AttributeNameIsReserved(name))
{
throw new AutomateException(ExceptionMessages.PatternElement_AttributeNameReserved
.Substitute(name));
}

if (AttributeExistsByName(this, name))
{
throw new AutomateException(ExceptionMessages.PatternElement_AttributeByNameExists
Expand Down Expand Up @@ -326,11 +321,6 @@ public Attribute DeleteAttribute(string name)
{
name.GuardAgainstNullOrEmpty(nameof(name));

if (ElementNameIsReserved(name))
{
throw new AutomateException(ExceptionMessages.PatternElement_ElementNameReserved.Substitute(name));
}

if (ElementExistsByName(this, name))
{
throw new AutomateException(ExceptionMessages.PatternElement_ElementByNameExists.Substitute(name));
Expand Down Expand Up @@ -364,12 +354,6 @@ public Attribute DeleteAttribute(string name)
{
if (name.NotEqualsIgnoreCase(element.Name))
{
if (ElementNameIsReserved(name))
{
throw new AutomateException(
ExceptionMessages.PatternElement_ElementNameReserved.Substitute(name));
}

if (ElementExistsByName(this, name))
{
throw new AutomateException(
Expand Down Expand Up @@ -931,16 +915,6 @@ private static Attribute GetAttributeByName(IAttributeContainer element, string
return element.Attributes.Safe().FirstOrDefault(attr => attr.Name.EqualsIgnoreCase(attributeName));
}

private static bool AttributeNameIsReserved(string attributeName)
{
return Attribute.ReservedAttributeNames.Any(reserved => reserved.EqualsIgnoreCase(attributeName));
}

private static bool ElementNameIsReserved(string attributeName)
{
return Element.ReservedElementNames.Any(reserved => reserved.EqualsIgnoreCase(attributeName));
}

private static bool ElementExistsByName(IElementContainer element, string elementName)
{
return element.Elements.Safe().Any(ele => ele.Name.EqualsIgnoreCase(elementName));
Expand Down

0 comments on commit 10abb0c

Please sign in to comment.