SteveDunn / Vogen

A semi-opinionated library which is a source generator and a code analyser. It Source generates Value Objects
Apache License 2.0
877 stars 45 forks source link

make defaultable values validatable #51

Open dzmitry-lahoda opened 2 years ago

dzmitry-lahoda commented 2 years ago

https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.ivalidatableobject.validate .

SteveDunn commented 2 years ago

Hi @dzmitry-lahoda - visiting some old comments now that I've got a bit of time free. Do you mean to make the generated code IValidatableObject?

dzmitry-lahoda commented 2 years ago

yes, so it will depend on componentmodel. and may be using validation attributes support on ctor parameters us enough. so that when error is raised it looks noce in xaml or api.

SteveDunn commented 1 week ago

Hi @dzmitry-lahoda , I'm taking a quick look at this one again.

may be using validation attributes support on ctor parameters

Vogen deliberately doesn't have constructors.

Do you expect it to look something like this:

[ValueObject<int>]
public partial class CustomerId : IValidatableObject
{
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if(Value is < 1 or > 100)
            yield return new ValidationResult("Value must be between 1 and 100.", new[] { "Value" });
    }
}

With this, Vogen still won't know whether it's valid. So do you propose that when creating an instance, e.g:

var id = CustomerId.From(123);

That Vogen generates the code to call your Validate method and throws a ValueObjectValidationException containing the errors return from it?

... or, are you proposing that Validate(ValidationContext validationContext) is generated based on the results of your user provided Validate method, e.g.

[ValueObject<int>]
public partial class Whatever : IValidatableObject
{
    private static Validation Validate(int v)
    {
        if (v is < 1 or > 100) return Validation.Invalid("Value must be between 1 and 100");
        return Validation.Ok;
    }

   // THIS METHOD IS AUTO GENERATED BY VOGEN
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        Validation result = Validate(Value);

        if(result == Validation.Ok) yield break;

        yield return new ValidationResult(result.ErrorMessage);
    }
}
dzmitry-lahoda commented 1 week ago

Yes. As developer I expect that if I have strongly typed id, ans I have instance of type, it is valid up to its type(so instances do not violate type). For example, each user may have up to 32 accounts. So account id can be u8 backed storage storing 5 bits of data. Neither deserialization nor instantiation via some well know method(constructor/new/from) should violate that. In understant problems in csharp(not clr afaik) to allow structs without default constructor and activation via reflection so. In Rust these do exist and they market unsafe so.

Parse do not validate.

dzmitry-lahoda commented 1 week ago

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/parameterless-struct-constructors