yavi icon indicating copy to clipboard operation
yavi copied to clipboard

Dynamic error messages

Open sergtitov opened this issue 3 years ago • 7 comments

Please add support for custom dynamic error messages, for example:

In my validator I call The API that returns a list of supported values and I want to include that list in the error message:

var validator = ValidatorBuilder.<MyObject>of()
                .constraint(MyObject::type, "type", x -> x.notBlank().predicate(
                        type -> myApi.getTypes().contains(type),
                        ViolationMessage.of(<How do I include the list of supported types from myApi.getTypes() here?>)))
                .build();

Alternatively, I tried to implement CustomConstraint as described in https://yavi.ik.am/#creating-a-custom-constraint, but could not make it return dynamic value from arguments():

@Override
 public Object[] arguments() {
     return new Object[] { <How do I return a result of the call myApi.getTypes() here> };
 }

 @Override
 public String defaultMessageFormat() {
     return "Type value \"{0}\" must be one of: \"{1}\".";
 }
@Override
public String messageKey() {
    return "type";
}

@Override
public boolean test(String type) {
    return myApi.getTypes().contains(type);
}

sergtitov avatar Sep 21 '21 05:09 sergtitov

I think it is related to https://github.com/making/yavi/issues/165

Can you try 0.9.0-SNAPSHOT? It's available in the following repository

<repository>
    <id>sonatype-snapshots</id>
    <name>Sonatype Snapshots</name>
    <url>https://oss.sonatype.org/content/repositories/snapshots</url>
    <snapshots>
        <enabled>true</enabled>
    </snapshots>
</repository>

making avatar Sep 21 '21 06:09 making

@making I could not find in 0.9.0 how to create dynamic messages, can you point me to an example please?

Object[] arguments() allows only defining arguments at validator "build time" - when validator is created but not when validate is called. So I can't make the message contain dynamic values - API call results (at the time of validation) or values from the object being validated.

For example, I have a collection of elements and I want to check they are all unique and if found 2 equivalent ones, I want to add them to the violation message. How can I achieve that?

sergtitov avatar Sep 22 '21 00:09 sergtitov

@sergtitov I also need this functionality in our project and I created a similar CustomConstraint implementation. This works in our case. Please beware that there is a small bug #173 which will be fixed in the next release.

@making Maybe this can be a candidate as a built-in validation?

import java.util.Collection;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import am.ik.yavi.core.CustomConstraint;

public final class EnumeratedConstraint<T> implements CustomConstraint<String> {
    private final Set<String> values;

    public static <E extends Enum<E>> EnumeratedConstraint<E> enumValues(Class<E> type) {
        return new EnumeratedConstraint<>(EnumSet.allOf(type), Enum::name);
    }

    public static EnumeratedConstraint<String> codes(Collection<String> codes) {
        return new EnumeratedConstraint<>(codes, Function.identity());
    }

    public EnumeratedConstraint(Collection<T> values, Function<T, String> codeMapper) {
        this.values = Objects.requireNonNull(values).stream().map(codeMapper).collect(Collectors.toSet());
    }

    @Override
    public String defaultMessageFormat() {
        return "\"{0}\" value must be one of: \"{1}\".";
    }

    @Override
    public String messageKey() {
        return "string.enumerated";
    }

    @Override
    public boolean test(String s) {
        return values.contains(s);
    }

    @Override
    public Object[] arguments() {
        return new Object[] { values };
    }
}

duponter avatar Oct 11 '21 14:10 duponter

@duponter thanks for sharing your code! In my case it is more complicated since the list of allowed values is determined at runtime (via API call or some other way).

@making I see you added "enhancement" label. Does this mean custom/dynamic error messages are not supported now but will be in a future version?

sergtitov avatar Oct 11 '21 19:10 sergtitov

I'm looking for a way to provide error messages dynamically but have little time to focus on this at this moment.

making avatar Oct 12 '21 00:10 making

@sergtitov Can you provide a complete and minimal working example? Where does myApi come from? Isn't it enough to pass myApi in the constructor of the CustomConstraint implementation class?

It is possible to add a breaking change that passes the target object to the arguments of arguments method. But not sure this is what you want.

making avatar Oct 12 '21 03:10 making

@sergtitov Here is the enhancement I can think of. https://github.com/making/yavi/pull/178 Do you think this will work for you as well?

making avatar Oct 12 '21 03:10 making