Skip to content

Commit

Permalink
String-based smart enums are ISpanParsable
Browse files Browse the repository at this point in the history
  • Loading branch information
PawelGerr committed Nov 19, 2024
1 parent d3133b5 commit b2658b8
Show file tree
Hide file tree
Showing 14 changed files with 1,979 additions and 431 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public bool Equals(ICodeGeneratorFactory<T> other)

public static class InterfaceCodeGeneratorFactory
{
private static readonly ICodeGeneratorFactory<ParsableGeneratorState> _parsable = new InterfaceCodeGeneratorFactory<ParsableGeneratorState>(ParsableCodeGenerator.Default);
private static readonly ICodeGeneratorFactory<ParsableGeneratorState> _parsableForValueObject = new InterfaceCodeGeneratorFactory<ParsableGeneratorState>(ParsableCodeGenerator.ForValueObject);
private static readonly ICodeGeneratorFactory<ParsableGeneratorState> _parsableForEnum = new InterfaceCodeGeneratorFactory<ParsableGeneratorState>(ParsableCodeGenerator.ForEnum);
private static readonly ICodeGeneratorFactory<ParsableGeneratorState> _parsableForValidatableEnum = new InterfaceCodeGeneratorFactory<ParsableGeneratorState>(ParsableCodeGenerator.ForValidatableEnum);
private static readonly ICodeGeneratorFactory<InterfaceCodeGeneratorState> _comparable = new InterfaceCodeGeneratorFactory<InterfaceCodeGeneratorState>(ComparableCodeGenerator.Default);

Expand All @@ -40,9 +41,13 @@ public static ICodeGeneratorFactory<InterfaceCodeGeneratorState> Comparable(stri
return String.IsNullOrWhiteSpace(comparerAccessor) ? _comparable : new InterfaceCodeGeneratorFactory<InterfaceCodeGeneratorState>(new ComparableCodeGenerator(comparerAccessor));
}

public static ICodeGeneratorFactory<ParsableGeneratorState> Parsable(bool forValidatableEnum)
public static ICodeGeneratorFactory<ParsableGeneratorState> Parsable(
bool forEnum,
bool forValidatableEnum)
{
return forValidatableEnum ? _parsableForValidatableEnum : _parsable;
return forEnum
? forValidatableEnum ? _parsableForValidatableEnum : _parsableForEnum
: _parsableForValueObject;
}

public static bool EqualityComparison(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,79 @@ namespace Thinktecture.CodeAnalysis;

public sealed class ParsableCodeGenerator : IInterfaceCodeGenerator<ParsableGeneratorState>
{
public static readonly IInterfaceCodeGenerator<ParsableGeneratorState> Default = new ParsableCodeGenerator(false);
public static readonly IInterfaceCodeGenerator<ParsableGeneratorState> ForValidatableEnum = new ParsableCodeGenerator(true);
public static readonly IInterfaceCodeGenerator<ParsableGeneratorState> ForValueObject = new ParsableCodeGenerator(false, false);
public static readonly IInterfaceCodeGenerator<ParsableGeneratorState> ForEnum = new ParsableCodeGenerator(true, false);
public static readonly IInterfaceCodeGenerator<ParsableGeneratorState> ForValidatableEnum = new ParsableCodeGenerator(true, true);

private readonly bool _isForEnum;
private readonly bool _isForValidatableEnum;

public string CodeGeneratorName => "Parsable-CodeGenerator";
public string FileNameSuffix => ".Parsable";

private ParsableCodeGenerator(bool isForValidatableEnum)
private ParsableCodeGenerator(
bool isForEnum,
bool isForValidatableEnum)
{
_isForEnum = isForEnum;
_isForValidatableEnum = isForValidatableEnum;
}

public void GenerateBaseTypes(StringBuilder sb, ParsableGeneratorState state)
{
sb.Append(@"
global::System.IParsable<").AppendTypeFullyQualified(state.Type).Append(">");

if (_isForEnum && state.KeyMember?.IsString() == true)
{
sb.Append(@"
#if NET9_0_OR_GREATER
, global::System.ISpanParsable<").AppendTypeFullyQualified(state.Type).Append(@">
#endif");
}
}

public void GenerateImplementation(StringBuilder sb, ParsableGeneratorState state)
{
var isKeyTypeString = state.KeyMember?.IsString() == true;

GenerateValidate(sb, state);

GenerateParse(sb, state);

if (_isForEnum && isKeyTypeString)
GenerateParseForReadOnlySpanOfChar(sb, state);

GenerateTryParse(sb, state);

if (_isForEnum && isKeyTypeString)
GenerateTryParseForReadOnlySpanOfChar(sb, state);
}

private static void GenerateValidate(StringBuilder sb, ParsableGeneratorState state)
private void GenerateValidate(StringBuilder sb, ParsableGeneratorState state)
{
var keyType = state.KeyMember?.IsString() == true || state.HasStringBasedValidateMethod ? "string" : state.KeyMember?.TypeFullyQualified;
var isKeyTypeString = state.KeyMember?.IsString() == true;
var keyType = isKeyTypeString || state.HasStringBasedValidateMethod ? "string" : state.KeyMember?.TypeFullyQualified;

sb.Append(@"
private static ").AppendTypeFullyQualified(state.ValidationError).Append("? Validate<T>(").Append(keyType).Append(" key, global::System.IFormatProvider? provider, out ").AppendTypeFullyQualifiedNullAnnotated(state.Type).Append(@" result)
where T : global::Thinktecture.IValueObjectFactory<").AppendTypeFullyQualified(state.Type).Append(", ").Append(keyType).Append(", ").AppendTypeFullyQualified(state.ValidationError).Append(@">
{
return T.Validate(key, provider, out result);
}");

if (_isForEnum && isKeyTypeString)
{
sb.Append(@"
#if NET9_0_OR_GREATER
private static ").AppendTypeFullyQualified(state.ValidationError).Append("? Validate<T>(global::System.ReadOnlySpan<char> key, global::System.IFormatProvider? provider, out ").AppendTypeFullyQualifiedNullAnnotated(state.Type).Append(@" result)
where T : global::Thinktecture.IValueObjectFactory<").AppendTypeFullyQualified(state.Type).Append(", global::System.ReadOnlySpan<char>, ").AppendTypeFullyQualified(state.ValidationError).Append(@">
{
return T.Validate(key, provider, out result);
}
#endif");
}
}

private void GenerateParse(StringBuilder sb, ParsableGeneratorState state)
Expand Down Expand Up @@ -79,6 +117,39 @@ private void GenerateParse(StringBuilder sb, ParsableGeneratorState state)
}
}

private void GenerateParseForReadOnlySpanOfChar(StringBuilder sb, ParsableGeneratorState state)
{
sb.Append(@"
#if NET9_0_OR_GREATER
/// <inheritdoc />
public static ").AppendTypeFullyQualified(state.Type).Append(@" Parse(global::System.ReadOnlySpan<char> s, global::System.IFormatProvider? provider)
{");

sb.Append(@"
var validationError = Validate<").AppendTypeFullyQualified(state.Type).Append(">(s, provider, out var result);");

if (_isForValidatableEnum)
{
sb.Append(@"
return result!;
}");
}
else
{
sb.Append(@"
if(validationError is null)
return result!;
throw new global::System.FormatException(validationError.ToString() ?? ""Unable to parse \""").Append(state.Type.Name).Append(@"\""."");
}");
}

sb.Append(@"
#endif");
}

private void GenerateTryParse(StringBuilder sb, ParsableGeneratorState state)
{
sb.Append(@"
Expand Down Expand Up @@ -127,4 +198,34 @@ public static bool TryParse(
}");
}
}

private void GenerateTryParseForReadOnlySpanOfChar(StringBuilder sb, ParsableGeneratorState state)
{
sb.Append(@"
#if NET9_0_OR_GREATER
/// <inheritdoc />
public static bool TryParse(
global::System.ReadOnlySpan<char> s,
global::System.IFormatProvider? provider,
[global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out ").AppendTypeFullyQualified(state.Type).Append(@" result)
{
var validationError = Validate<").AppendTypeFullyQualified(state.Type).Append(">(s, provider, out result!);");

if (_isForValidatableEnum)
{
sb.Append(@"
return true;
}");
}
else
{
sb.Append(@"
return validationError is null;
}");
}

sb.Append(@"
#endif");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Thinktecture.CodeAnalysis;
public ValidationErrorState ValidationError { get; }
public bool SkipIParsable { get; }
public bool IsKeyMemberParsable { get; }
public bool IsEnum { get; }
public bool IsValidatableEnum { get; }
public bool HasStringBasedValidateMethod { get; }

Expand All @@ -16,6 +17,7 @@ public ParsableGeneratorState(
ValidationErrorState validationError,
bool skipIParsable,
bool isKeyMemberParsable,
bool isEnum,
bool isValidatableEnum,
bool hasStringBasedValidateMethod)
{
Expand All @@ -24,6 +26,7 @@ public ParsableGeneratorState(
ValidationError = validationError;
SkipIParsable = skipIParsable;
IsKeyMemberParsable = isKeyMemberParsable;
IsEnum = isEnum;
IsValidatableEnum = isValidatableEnum;
HasStringBasedValidateMethod = hasStringBasedValidateMethod;
}
Expand All @@ -35,6 +38,7 @@ public bool Equals(ParsableGeneratorState other)
&& ValidationError.Equals(other.ValidationError)
&& SkipIParsable == other.SkipIParsable
&& IsKeyMemberParsable == other.IsKeyMemberParsable
&& IsEnum == other.IsEnum
&& IsValidatableEnum == other.IsValidatableEnum
&& HasStringBasedValidateMethod == other.HasStringBasedValidateMethod;
}
Expand All @@ -53,6 +57,7 @@ public override int GetHashCode()
hashCode = (hashCode * 397) ^ ValidationError.GetHashCode();
hashCode = (hashCode * 397) ^ SkipIParsable.GetHashCode();
hashCode = (hashCode * 397) ^ IsKeyMemberParsable.GetHashCode();
hashCode = (hashCode * 397) ^ IsEnum.GetHashCode();
hashCode = (hashCode * 397) ^ IsValidatableEnum.GetHashCode();
hashCode = (hashCode * 397) ^ HasStringBasedValidateMethod.GetHashCode();

Expand Down
Loading

0 comments on commit b2658b8

Please sign in to comment.