Convert classes using @JsonUnwrapped to records

This commit is contained in:
Chris Eager
2025-07-19 13:01:59 -05:00
committed by Chris Eager
parent 94361b2d5d
commit 4618b47141
9 changed files with 212 additions and 318 deletions

View File

@@ -64,7 +64,6 @@ public class RegistrationController {
private static final String COUNTRY_CODE_TAG_NAME = "countryCode";
private static final String REGION_CODE_TAG_NAME = "regionCode";
private static final String VERIFICATION_TYPE_TAG_NAME = "verification";
private static final String INVALID_ACCOUNT_ATTRS_COUNTER_NAME = name(RegistrationController.class, "invalidAccountAttrs");
private final AccountsManager accounts;
private final PhoneVerificationTokenManager phoneVerificationTokenManager;

View File

@@ -178,11 +178,11 @@ public class VerificationController {
throws RateLimitExceededException, ObsoletePhoneNumberFormatException {
final Pair<String, PushNotification.TokenType> pushTokenAndType = validateAndExtractPushToken(
request.getUpdateVerificationSessionRequest());
request.updateVerificationSessionRequest());
final Phonenumber.PhoneNumber phoneNumber;
try {
phoneNumber = Util.canonicalizePhoneNumber(PhoneNumberUtil.getInstance().parse(request.getNumber(), null));
phoneNumber = Util.canonicalizePhoneNumber(PhoneNumberUtil.getInstance().parse(request.number(), null));
} catch (final NumberParseException e) {
throw new ServerErrorException("could not parse already validated number", Response.Status.INTERNAL_SERVER_ERROR);
}
@@ -192,7 +192,7 @@ public class VerificationController {
final String sourceHost = (String) requestContext.getProperty(RemoteAddressFilter.REMOTE_ADDRESS_ATTRIBUTE_NAME);
registrationServiceSession = registrationServiceClient.createRegistrationSession(phoneNumber, sourceHost,
accountsManager.getByE164(request.getNumber()).isPresent(),
accountsManager.getByE164(request.number()).isPresent(),
REGISTRATION_RPC_TIMEOUT).join();
} catch (final CancellationException e) {

View File

@@ -8,25 +8,11 @@ package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import io.swagger.v3.oas.annotations.media.Schema;
// Note, this class cannot be converted into a record because @JsonUnwrapped does not work with records until 2.19. We are on 2.18 until Dropwizards BOM updates.
// https://github.com/FasterXML/jackson-databind/issues/1467
public class AccountCreationResponse {
public record AccountCreationResponse(
@JsonUnwrapped
private AccountIdentityResponse identityResponse;
@JsonUnwrapped
AccountIdentityResponse identityResponse,
@Schema(description = "If true, there was an existing account registered for this number")
private boolean reregistration;
public AccountCreationResponse() {
}
public AccountCreationResponse(AccountIdentityResponse identityResponse, boolean reregistration) {
this.identityResponse = identityResponse;
this.reregistration = reregistration;
}
public boolean isReregistration() {
return reregistration;
}
@Schema(description = "If true, there was an existing account registered for this number")
boolean reregistration) {
}

View File

@@ -7,41 +7,23 @@ package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.google.common.annotations.VisibleForTesting;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.whispersystems.textsecuregcm.util.E164;
// Not a record, because Jackson does not support @JsonUnwrapped with records
// https://github.com/FasterXML/jackson-databind/issues/1497
public final class CreateVerificationSessionRequest {
public record CreateVerificationSessionRequest(
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "The e164-formatted phone number to be verified")
@E164
@NotBlank
@JsonProperty
private String number;
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "The e164-formatted phone number to be verified")
@E164
@NotBlank
@JsonProperty
String number,
@Valid
@JsonUnwrapped
private UpdateVerificationSessionRequest updateVerificationSessionRequest;
public CreateVerificationSessionRequest() {
}
@Valid
@JsonUnwrapped
UpdateVerificationSessionRequest updateVerificationSessionRequest) {
@VisibleForTesting
public CreateVerificationSessionRequest(final String number, final UpdateVerificationSessionRequest updateVerificationSessionRequest) {
this.number = number;
this.updateVerificationSessionRequest = updateVerificationSessionRequest;
}
public String getNumber() {
return number;
}
public UpdateVerificationSessionRequest getUpdateVerificationSessionRequest() {
return updateVerificationSessionRequest;
}
}

View File

@@ -5,7 +5,6 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@@ -16,7 +15,6 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.signal.libsignal.protocol.IdentityKey;
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
@@ -72,31 +70,9 @@ public record RegistrationRequest(@Schema(requiredMode = Schema.RequiredMode.NOT
@NotNull
@Valid
@JsonUnwrapped
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@JsonProperty
DeviceActivationRequest deviceActivationRequest) implements PhoneVerificationRequest {
@JsonCreator
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public RegistrationRequest(@JsonProperty("sessionId") String sessionId,
@JsonProperty("recoveryPassword") byte[] recoveryPassword,
@JsonProperty("accountAttributes") AccountAttributes accountAttributes,
@JsonProperty("skipDeviceTransfer") boolean skipDeviceTransfer,
@JsonProperty("aciIdentityKey") @NotNull @Valid IdentityKey aciIdentityKey,
@JsonProperty("pniIdentityKey") @NotNull @Valid IdentityKey pniIdentityKey,
@JsonProperty("aciSignedPreKey") @NotNull @Valid ECSignedPreKey aciSignedPreKey,
@JsonProperty("pniSignedPreKey") @NotNull @Valid ECSignedPreKey pniSignedPreKey,
@JsonProperty("aciPqLastResortPreKey") @NotNull @Valid KEMSignedPreKey aciPqLastResortPreKey,
@JsonProperty("pniPqLastResortPreKey") @NotNull @Valid KEMSignedPreKey pniPqLastResortPreKey,
@JsonProperty("apnToken") Optional<@Valid ApnRegistrationId> apnToken,
@JsonProperty("gcmToken") Optional<@Valid GcmRegistrationId> gcmToken) {
// This may seem a little verbose, but at the time of writing, Jackson struggles with `@JsonUnwrapped` members in
// records, and this is a workaround. Please see
// https://github.com/FasterXML/jackson-databind/issues/3726#issuecomment-1525396869 for additional context.
this(sessionId, recoveryPassword, accountAttributes, skipDeviceTransfer, aciIdentityKey, pniIdentityKey,
new DeviceActivationRequest(aciSignedPreKey, pniSignedPreKey, aciPqLastResortPreKey, pniPqLastResortPreKey, apnToken, gcmToken));
}
public boolean isEverySignedKeyValid(@Nullable final String userAgent) {
if (deviceActivationRequest().aciSignedPreKey() == null ||
deviceActivationRequest().pniSignedPreKey() == null ||

View File

@@ -11,86 +11,37 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.textsecuregcm.util.ByteArrayBase64WithPaddingAdapter;
// Note, this class cannot be converted into a record because @JsonUnwrapped does not work with records.
// https://github.com/FasterXML/jackson-databind/issues/1467
public class VersionedProfileResponse {
public record VersionedProfileResponse(
@JsonUnwrapped
private BaseProfileResponse baseProfileResponse;
@JsonUnwrapped
BaseProfileResponse baseProfileResponse,
@JsonProperty
@JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class)
private byte[] name;
@JsonProperty
@JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class)
byte[] name,
@JsonProperty
@JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class)
private byte[] about;
@JsonProperty
@JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class)
byte[] about,
@JsonProperty
@JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class)
private byte[] aboutEmoji;
@JsonProperty
@JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class)
byte[] aboutEmoji,
@JsonProperty
private String avatar;
@JsonProperty
String avatar,
@JsonProperty
@JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class)
private byte[] paymentAddress;
@JsonProperty
@JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class)
byte[] paymentAddress,
@JsonProperty
@JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class)
private byte[] phoneNumberSharing;
@JsonProperty
@JsonSerialize(using = ByteArrayBase64WithPaddingAdapter.Serializing.class)
@JsonDeserialize(using = ByteArrayBase64WithPaddingAdapter.Deserializing.class)
byte[] phoneNumberSharing) {
public VersionedProfileResponse() {
}
public VersionedProfileResponse(final BaseProfileResponse baseProfileResponse,
final byte[] name,
final byte[] about,
final byte[] aboutEmoji,
final String avatar,
final byte[] paymentAddress,
final byte[] phoneNumberSharing) {
this.baseProfileResponse = baseProfileResponse;
this.name = name;
this.about = about;
this.aboutEmoji = aboutEmoji;
this.avatar = avatar;
this.paymentAddress = paymentAddress;
this.phoneNumberSharing = phoneNumberSharing;
}
public BaseProfileResponse getBaseProfileResponse() {
return baseProfileResponse;
}
public byte[] getName() {
return name;
}
public byte[] getAbout() {
return about;
}
public byte[] getAboutEmoji() {
return aboutEmoji;
}
public String getAvatar() {
return avatar;
}
public byte[] getPaymentAddress() {
return paymentAddress;
}
public byte[] getPhoneNumberSharing() {
return phoneNumberSharing;
}
}