Either i problem z niezgodnością typów błędów

0

Mam problem z niezgodnością typów błędów przy używaniu Either.

Metoda tworząca encję Account:

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)));
    }

Value objecty UserName i Password:

@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;
    }
}

Dostaje błędy: Required Either<AccountError,Account>, provided Either<WrongUserNameFormat, Object>, incompatible constraint: AccountError and WrongUserNameFormatError mimo, że wszystkie błędy dziedziczą po AccountError.

1

Zamień
static Either<WrongUserNameFormatError,UserName> create(

na
static Either< AccountError,UserName> create(

0

@jarekr000000: Miałem tak wcześniej, ale zmieniłem, bo ta metoda create w UserName może zwrócić tylko jeden błąd - WrongUserNameFormat, który oczywiście dziedziczy po AccountError. W ogóle z czego wynika ten błąd, bo serio go nie rozumiem?

3

Wynika z tego, że zmieniłeś.
To, że WrongUserNameFormat, dziedziczy po AccountError nie znaczy, że List<WrongUserNameFormat> dziedziczy po List<AccountError>.
I analogicznie jest z Either<WrongUserName,B> vs Either<AccountError,B>
Niestety to jest problem, na który w Javie nie ma dobrego rozwiązania.

1

Nie chcę się wygłupić bo to teoretyczny/akademicki termin którego normalnie w programowaniu się raczej nie używa ale chodzi o to że Either w Javie nie jest kowariantny, czyli właśnie nie możesz przypisać wartości Either<WrongUserNameFormat, UserName> do zmiennej typu Either<AccountError, Account>. Jeśli dobrze pamiętam w Kotlinie i Scali można zrobić takie rzeczy, np:

import scala.util.Either
val e1: Either[String, Int] = Left("Hello World")
val e2: Either[Any, Int] = e1

print(e2)

P.S. Ale IMHO i tak zwykle lepiej używać wszędzie typu Either<BładBazowy, JakaśWartość> bo jak dodasz w implementacji błąd innego typu to nie musisz zmieniać sygnatury

2

W ogóle z czego wynika ten błąd, bo serio go nie rozumiem?

Wynika mniej więcej z tego:

List<String> x = new ArrayList<String>();
List<Object> y = x; // hmmm
y.add(new Integer(1));
String z = x.get(0); // panic

Gdyby uznać że możemy przypisać List<String> do List<Object> to nagle moglibyśmy dodać do tej listy coś co nie jest Stringiem

1 użytkowników online, w tym zalogowanych: 0, gości: 1