Vogen icon indicating copy to clipboard operation
Vogen copied to clipboard

Validation reuse

Open glen-84 opened this issue 2 years ago • 10 comments

Describe the feature

It's currently possible to do something like this:

[ValueObject]
public readonly partial struct ArticleId
{
    private static Validation Validate(int value) => IdValidator.IsValid(value);
}

[ValueObject<short>]
public readonly partial struct LocaleId
{
    private static Validation Validate(short value) => IdValidator.IsValid(value);
}

// Define once.
public static class IdValidator
{
    public static Validation IsValid(long value)
    {
        return value > 0
            ? Validation.Ok
            : Validation.Invalid("ID must be a positive integer.");
    }
}

But that line of code seems unnecessary. What about a parameter to specify the type of a static validation class?

[ValueObject(validator: typeof(IdValidator))]
public readonly partial struct ArticleId { }

A generic overload could also work, but if I'm not mistaken it would require you to always specify the underlying type.

[ValueObject<int, IdValidator>]
public readonly partial struct ArticleId { }

I think that would be worth it though.

glen-84 avatar Jan 10 '23 10:01 glen-84

That's a great idea - thanks @glen-84 ! I'll take a look at implementing that soon.

SteveDunn avatar Jan 14 '23 11:01 SteveDunn

Is this still planned? It could be useful.

One thing to note is that the Validate method (if one exists) should still be called, even when you specify a validator on the attribute itself, to allow for additional validation of a specific value object.

It might also make sense for the call to the Validator class to pass the value object type, like:

IdValidator.IsValid(typeof(ArticleId), value);

// and/or

IdValidator.IsValid<ArticleId>(value);

So that the validator can access information about the value object type, or access its (static abstract) properties.

Ultimately, I'm looking for a way to "bake" some validation into a value object. For example, given the following value objects:

public sealed class StringValueAttribute : ValueObjectAttribute<string>
{
    public StringValueAttribute(Conversions conversions = Conversions.None) : base(conversions) { }
}

[StringValue]
public sealed partial class EmailAddress
{
    public static int MinimumLength { get; } = 3;
    public static int MaximumLength { get; } = 60;
}

[StringValue]
public sealed partial class Username
{
    public static int MinimumLength { get; } = 2;
    public static int MaximumLength { get; } = 20;
}

... a way to validate these objects while making use of their properties, without having to manually call a validation method within Validate.

glen-84 avatar Dec 14 '23 15:12 glen-84

Thanks for the feedback @glen-84 - sorry that this feature has taken so long to implement. I haven't had much time lately.

Re. your suggestion above: are you saying that the validator type somehow 'inspects' the type for known properties such as the length properties, and generates the associated code? I'm thinking, as I write this, that maybe an AutoValidated attribute would be useful, and would work something like this:

  • has the type got a Vogen attribute?
  • if so, has it got an AutoValidated attribute?
  • if so, has it got any of the known properties that control validation

... and this makes me think of FluentValidation.

I think there's definitely an opportunity to improve validation, but I'm also thinking that it might be better as a completely separate nuget package and just have Vogen provide 'hooks' for this new package...

SteveDunn avatar Dec 17 '23 07:12 SteveDunn

Re. your suggestion above: are you saying that the validator type somehow 'inspects' the type for known properties such as the length properties, and generates the associated code?

Nope. The validator class would be manually written. Something like this:

public interface IStringValue
{
    public static abstract int MinimumLength { get; }
    public static abstract int MaximumLength { get; }
}

public static class MyStringValidator
{
    public static Validation IsValid<TValueObject>(string value) where TValueObject : IStringValue
    {
        var minimumLength = TValueObject.MinimumLength;
        var maximumLength = TValueObject.MaximumLength;

        // validation here ...
    }
}

public sealed class StringValueAttribute : ValueObjectAttribute<string>
{
    public StringValueAttribute(
        Conversions conversions = Conversions.None)
        : base(conversions, validator: typeof(MyStringValidator)) { }
}

[StringValue]
public sealed partial class EmailAddress : IStringValue
{
    public static int MinimumLength { get; } = 3;
    public static int MaximumLength { get; } = 60;
}

[StringValue]
public sealed partial class Username : IStringValue
{
    public static int MinimumLength { get; } = 2;
    public static int MaximumLength { get; } = 20;
}

I don't know if static abstract properties are the best solution, but it's just an idea.

glen-84 avatar Dec 17 '23 10:12 glen-84