Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Vogen validations #47

Merged
merged 2 commits into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

<ItemGroup>
<PackageVersion Include="Basic.Reference.Assemblies.Net80" Version="1.6.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.2" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.3.3" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
<PackageVersion Include="Immediate.Handlers" Version="1.4.0" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
Expand All @@ -28,6 +28,7 @@
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageVersion Include="Verify.SourceGenerators" Version="2.2.0" />
<PackageVersion Include="Verify.Xunit" Version="24.2.0" />
<PackageVersion Include="Vogen" Version="4.0.8" />
<PackageVersion Include="xunit" Version="2.8.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.1" />
</ItemGroup>
Expand Down
20 changes: 20 additions & 0 deletions src/Immediate.Validations.Generators/ITypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,26 @@ typeSymbol is
},
};

public static bool IsVogenAttribute(this INamedTypeSymbol? typeSymbol) =>
typeSymbol is
{
Name: "ValueObjectAttribute",
ContainingNamespace:
{
Name: "Vogen",
ContainingNamespace.IsGlobalNamespace: true,
},
}
or
{
MetadataName: "ValueObjectAttribute`1",
ContainingNamespace:
{
Name: "Vogen",
ContainingNamespace.IsGlobalNamespace: true,
},
};

public static bool IsValidatorAttribute(this INamedTypeSymbol? typeSymbol) =>
typeSymbol is
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,22 @@ CancellationToken token

token.ThrowIfCancellationRequested();

var isValidationProperty = propertyType.GetAttributes()
.Any(v => v.AttributeClass.IsValidateAttribute());
var isValidationProperty =
propertyType
.GetAttributes()
.Any(v => v.AttributeClass.IsValidateAttribute());

var isVogenProperty =
propertyType
.GetAttributes()
.Any(v => v.AttributeClass.IsVogenAttribute())
&& propertyType
.GetMembers()
.Any(m => m is
{
Name: "Validate",
IsStatic: true,
});

token.ThrowIfCancellationRequested();

Expand Down Expand Up @@ -323,6 +337,7 @@ CancellationToken token
if (
(isNullable || !isReferenceType)
&& !isValidationProperty
&& !isVogenProperty
&& collectionPropertyDetails is null
&& validations is []
)
Expand All @@ -339,9 +354,11 @@ CancellationToken token
IsNullable = isNullable,

IsValidationProperty = isValidationProperty,
ValidationTypeFullName = isValidationProperty
? baseType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
: null,
IsVogenProperty = isVogenProperty,
ValidationTypeFullName =
(isValidationProperty || isVogenProperty)
? baseType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
: null,

CollectionPropertyDetails = collectionPropertyDetails,

Expand Down
1 change: 1 addition & 0 deletions src/Immediate.Validations.Generators/Models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public sealed record ValidationTargetProperty
public required bool IsReferenceType { get; init; }
public required bool IsNullable { get; init; }
public required bool IsValidationProperty { get; init; }
public required bool IsVogenProperty { get; init; }
public required string? ValidationTypeFullName { get; init; }
public required ValidationTargetProperty? CollectionPropertyDetails { get; init; }
public required EquatableReadOnlyList<PropertyValidation> Validations { get; init; }
Expand Down
12 changes: 12 additions & 0 deletions src/Immediate.Validations.Generators/Templates/Validations.sbntxt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ partial {{ class.type }} {{ class.name }}
PropertyName = $"{{ get_prop_name(p.name, depth) }}.{error.PropertyName}",
});
}
{{~ else if p.is_vogen_property ~}}
{
var validation = {{ p.validation_type_full_name }}.Validate(t.Value);
if (!string.IsNullOrWhiteSpace(validation.ErrorMessage))
{
errors.Add(new()
{
PropertyName = $"{{ get_prop_name(p.name, depth) }}",
ErrorMessage = validation.ErrorMessage,
});
}
}
{{~ end ~}}

{{~ if p.collection_property_details ~}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<PackageReference Include="Immediate.Handlers" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Vogen" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" PrivateAssets="all" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Immediate.Validations.Shared;
using Vogen;
using Xunit;

namespace Immediate.Validations.FunctionalTests.IntegrationTests;

[ValueObject(deserializationStrictness: DeserializationStrictness.AllowAnything)]
[SuppressMessage(
"Design",
"CA1036:Override methods on comparable types",
Justification = "Intentionally not implemented in Vogen"
)]
public readonly partial struct UserId
{
public static Validation Validate(int value) =>
value > 0 ? Validation.Ok : Validation.Invalid("Must be greater than zero.");
}

public sealed partial class VogenTests
{
[Validate]
public sealed partial record VogenRecord : IValidationTarget<VogenRecord>
{
public required UserId UserId { get; init; }
}

[Fact]
public void ValidUserIdWithNoErrors()
{
var record = JsonSerializer.Deserialize<VogenRecord>(
/*lang=json,strict*/
"""
{
"UserId": 1
}
"""
);

var errors = VogenRecord.Validate(record);

Assert.Empty(errors);
}

[Fact]
public void InvalidUserIdWithErrors()
{
var record = JsonSerializer.Deserialize<VogenRecord>(
/*lang=json,strict*/
"""
{
"UserId": -1
}
"""
);

var errors = VogenRecord.Validate(record);

Assert.Equal(
[
new()
{
PropertyName = "UserId",
ErrorMessage = "Must be greater than zero.",
}
],
errors
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//HintName: IV...ValidateClass.g.cs
using System.Collections.Generic;
using Immediate.Validations.Shared;

#nullable enable
#pragma warning disable CS1591


partial class ValidateClass
{
static List<ValidationError> IValidationTarget<ValidateClass>.Validate(ValidateClass? target) =>
Validate(target);

public static List<ValidationError> Validate(ValidateClass? target)
{
if (target is not { } t)
{
return
[
new()
{
PropertyName = ".self",
ErrorMessage = "`target` must not be `null`.",
},
];
}

var errors = new List<ValidationError>();


__ValidateUserId(errors, t, t.UserId);


return errors;
}



private static void __ValidateUserId(
List<ValidationError> errors, ValidateClass instance, global::UserId target
)
{

var t = target;

{
var validation = global::UserId.Validate(t.Value);
if (!string.IsNullOrWhiteSpace(validation.ErrorMessage))
{
errors.Add(new()
{
PropertyName = $"UserId",
ErrorMessage = validation.ErrorMessage,
});
}
}


}

}

Original file line number Diff line number Diff line change
Expand Up @@ -412,4 +412,37 @@ public partial class SubClass : BaseClass, IValidationTarget<SubClass>;

_ = await Verify(result);
}

[Fact]
public async Task VogenValidation()
{
var driver = GeneratorTestHelper.GetDriver(
"""
#nullable enable

using System.Collections.Generic;
using Immediate.Validations.Shared;
using Vogen;

[ValueObject]
public readonly partial struct UserId
{
public static Validation Validate(int value) =>
value > 0 ? Validation.Ok : Validation.Invalid("Must be greater than zero.");
}

[Validate]
public partial class ValidateClass : IValidationTarget<ValidateClass>
{
public required UserId UserId { get; init; }
}
""");

var result = driver.GetRunResult();

Assert.Empty(result.Diagnostics);
_ = Assert.Single(result.GeneratedTrees);

_ = await Verify(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Verify.SourceGenerators" />
<PackageReference Include="Verify.Xunit" />
<PackageReference Include="Vogen" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" PrivateAssets="all" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions tests/Immediate.Validations.Tests/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public static MetadataReference[] GetMetadataReferences() =>
[
MetadataReference.CreateFromFile("./Immediate.Handlers.Shared.dll"),
MetadataReference.CreateFromFile("./Immediate.Validations.Shared.dll"),
MetadataReference.CreateFromFile("./Vogen.SharedTypes.dll"),
];
}
Loading