Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xor options #1510

Open
xenoterracide opened this issue Mar 28, 2024 · 2 comments
Open

xor options #1510

xenoterracide opened this issue Mar 28, 2024 · 2 comments

Comments

@xenoterracide
Copy link

So, I'm using Factory builders to build JPA aggregates.

problem is coming up when I have an aggregate with children that have a composite key that points back to the aggregate id, and they also have a parent reference for the aggregate.

In order to do test data builders I've got a little bit of a construction problem since collections handed to the static factory is also immutable, so by default this structure is getting tricky. Included the current state of code to provide hopefully some illumination.

Anyways, so in order to construct IdentityProviderUser.Identifier I have to have a User.Identifier, at some point it also needs a full User instance.

Obviously this addIf might or might not be a good plan, it certainly isn't the immutable way.

Alright, the ask, I can easily early create IdentifyProviderUser.Identifier when User.Identifier exists. However, depending on circumstances you'll see this means I need a User.Identifer and User as parameters. However, these are mutually exclusive, and you should provide only one or the other. This is similar in concept to Switch but these aren't enums or booleans. Maybe something like @Xor("userId", "user") could be added.

// © Copyright 2024 Caleb Cushing
// SPDX-License-Identifier: AGPL-3.0-or-later

package com.xenoterracide.model.security;

import com.github.f4b6a3.uuid.UuidCreator;
import com.xenoterracide.model.Identifiable;
import jakarta.annotation.Nonnull;
import java.util.Optional;
import java.util.Set;
import org.immutables.builder.Builder;
import org.immutables.value.Value;

@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Value.Style(
  typeBuilder = "*TestDataBuilder",
  newBuilder = "create",
  jakarta = true,
  jdkOnly = true,
  jdk9Collections = true
)
final class UserTestDataBuilders {

  private UserTestDataBuilders() {}

  @Builder.Factory
  static User user(@Nonnull Optional<String> name, @Nonnull Optional<Set<IdentityProviderUser>> identityProviderUsers) {
    var uid = new User.Identifier(UuidCreator.getTimeOrderedEpoch());
    var u = new User(
      uid,
      name.orElse("xeno"),
      identityProviderUsers.orElseGet(() -> {
        return Set.of(IdentityProviderUserTestDataBuilder.create().userId(uid).build());
      })
    );
    u.getIdentityProviderUsers().forEach(ipu -> ipu.setUser(u));
    return u;
  }

  @Builder.Factory
  static IdentityProviderUser identityProviderUser(
    @Nonnull Optional<IdentityProviderUser.IdP> idP,
    @Nonnull Optional<String> idPUserId,
    @Nonnull Optional<User.Identifier> userId,
    @Nonnull Optional<User> user
  ) {
    var optIdP = idP.orElse(IdentityProviderUser.IdP.AUTH0);
    var optIdPUserId = idPUserId.orElse("1234");

    var optUserId = user
      .map(Identifiable::getId)
      .orElseGet(() -> userId.orElseGet(() -> new User.Identifier(UuidCreator.getTimeOrderedEpoch())));

    return new IdentityProviderUser(new IdentityProviderUser.Identifier(optIdP, optIdPUserId, optUserId));
  }
}
@elucash
Copy link
Member

elucash commented May 21, 2024

@Xor or not, that would (as far as I understand) would ideally require that to be enforced via API (statically and or runtime). Statically it would be quite hard to add, it's something like staged-builder like approach, if not than it won't be any different as 2 optionals in your example. Runtime check would be just if (!(a.isPresent() ^ b.isPresent())) throw .... I would go with creating a specialization of Either for this case. UserEither.asId(..) UserEither.asEntity(..), then

   .user(asId(...)) // or  .user(asEntity(user))

@xenoterracide
Copy link
Author

xenoterracide commented May 21, 2024

I apologize for the messy ticket, I realize what I'm trying to get to is hard to understand.

Although I don't have an example handy, I don't know why there couldn't be N options that are mutually exclusive.

I'm fine with a runtime check, I don't think I was expecting a staged builder.

  @Builder.Xor({"user", "userId", "userSupplier"})
  @Builder.Factory
  static IdentityProviderUser identityProviderUser(
    @Nonnull Optional<User.Identifier> userId,
    @Nonnull Optional<User> user,
    @Nonnull Supplier<@NonNull User> userSupplier // only thing I can think of for a 3rd option... there's probably better examples
  ) {
IdentityProviderUserBuilder.create().userId(userId).build();            // OK
IdentityProviderUserBuilder.create().user(user).build();                // OK
IdentityProviderUserBuilder.create().userId(userId).user(user).build(); // throws
IdentityProviderUserBuilder.create().build();                           // throws

note: this supplier concept is interesting. I might make another issue for it because I've been finding writing these internally to be rather obnoxious and I'm wondering if there's something that could be done to improve writing factories.

update: I'm trying to figure out if using the body of a TestDataBuilder was a mistake, or if I'm trying to demonstrate an additional problem. that might take me a few days...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants