0

I have a problem with Either errors types incompatibility.

A method to create Account entity:

static Either<AccountError,Account> create(
String userNameCandidate,
UserNameUniquenessValidator userNameUniquenessValidator,
String passwordCandidate,
PasswordEncoder passwordEncoder) {

   return UserName
            .create(userNameCandidate,userNameUniquenessValidator)
            .flatMap(correctUserName -> Password
                    .create(passwordCandidate,passwordEncoder)
                    .map(correctPassword -> new Account(correctUserName,correctPassword)));
}

UserName and Password value objects:

@Embeddable
final class UserName implements Serializable {

public static final Integer MIN_USER_NAME_LENGTH = 3;

public static final Integer MAX_USER_NAME_LENGTH = 15;

@Column
private final String userName;

UserName(String userNam) {
    this.userName = userNam;
}

//For JPA only.Don't use!
public UserName() {

    this.userName = "default";
}

static Either<WrongUserNameFormatError,UserName> create(
        String userNameCandidate,
        UserNameUniquenessValidator userNameUniquenessValidator) {

    if (userNameCandidate.isEmpty()) {

        return Either.left(new WrongUserNameFormatError("Empty user name."));
    }

    var isUserNameCandidateLengthWrong =
            userNameCandidate.length() < MIN_USER_NAME_LENGTH &&
            userNameCandidate.length() > MAX_USER_NAME_LENGTH;

    if (isUserNameCandidateLengthWrong) {

        return Either.left(new WrongUserNameFormatError(
                    "Wrong user name length: " + userNameCandidate.length() +
                            ".Min: " + MIN_USER_NAME_LENGTH +
                            ".Max: " + MAX_USER_NAME_LENGTH));
    }

    if (!userNameUniquenessValidator.isUnique(userNameCandidate)) {

        return Either.left(new WrongUserNameFormatError("Not unique user name: " + 
userNameCandidate));
    }

    return Either.right(new UserName(userNameCandidate));
}

public String getUserName() {
    return userName;
  }
}

@Embeddable
final class Password implements Serializable {

public static final Integer MIN_PASSWORD_LENGTH = 5;

@Column
private final String password;

private Password(String password) {
    this.password = password;
}

//For JPA only.Don't use!
public Password() {

    this.password = "default";
}

static Either<WrongPasswordFormatError,Password> create(String passwordCandidate, PasswordEncoder passwordEncoder) {

    if (passwordCandidate.length() >= MIN_PASSWORD_LENGTH) {

        var encodedPassword= passwordEncoder.encode(passwordCandidate);

        return Either.right(new Password(encodedPassword));
    }
    else {

        return Either.left(new WrongPasswordFormatError(
                    "Wrong password length: " + passwordCandidate.length() +
                            ". Min: " + MIN_PASSWORD_LENGTH));
    }
}

public String getPassword() {
    return password;
}

}

I get error: Required Either<AccountError,Account>, provided Either<WrongUserNameFormat, Object>, incompatible constraint: AccountError and WrongUserNameFormatError even though all errors extends AccountError.

Sampeteq
  • 123
  • 8
  • Unrelated comment: you can make the constructord of the VOs private and JPA will use them. Unrelated commend 2: drop vavr and use Kotlin or another JVM language that supports functional constructs your quality of life will improve dramatically :D. – Augusto Jan 20 '22 at 20:43

2 Answers2

1

I'd use Validation here since there you can collect (validation) errors. :

Since I don't have the object types of your example I came up with this:

Validation<Seq<? extends Number>, String> c = Validation.invalid(io.vavr.collection.List.of(1L));
Validation<Seq<? extends Number>, String> d = Validation.valid("x");
    
final Validation<Seq<? extends Number>, String> result = c.flatMap(s -> d);

Validation is basically a special case of Either (there is also a .toEither on the Validation object).

c is the result of UserName.create and d Password.create

The 'trick' is to use a Seq here instead of the Object type directly. I can't explain why (so I realize this might sound like bad advice).

In our project we use this construction extensively since we often can have multiple validation errors in our flows and then the left side is always the same and easy to carry over to other flows.

jvwilge
  • 2,474
  • 2
  • 16
  • 21
0

The issue is that Either<WrongUserNameFormat, ...> does not extend Either<AccountError, ...> even though WrongUserNameFormat may extend AccountError. This is because Java does not have good support for type parameter variance. VAVr recognizes this issue, so it provides the narrow method for many of it's types including Either. So you can wrap the return value of Account.create with Either.<AccountError, Account>narrow(...).

DBear
  • 312
  • 2
  • 9