mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 20:08:01 +01:00
DRY gRPC tests, refactor error mapping
This commit is contained in:
@@ -24,11 +24,6 @@ public class CallingGrpcService extends ReactorCallingGrpc.CallingImplBase {
|
||||
this.rateLimiters = rateLimiters;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Throwable onErrorMap(final Throwable throwable) {
|
||||
return RateLimitUtil.mapRateLimitExceededException(throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GetTurnCredentialsResponse> getTurnCredentials(final GetTurnCredentialsRequest request) {
|
||||
final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice();
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Status;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Interface to be imlemented by our custom exceptions that are consistently mapped to a gRPC status.
|
||||
*/
|
||||
public interface ConvertibleToGrpcStatus {
|
||||
|
||||
Status grpcStatus();
|
||||
|
||||
Optional<Metadata> grpcMetadata();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import io.grpc.ForwardingServerCall;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.ServerCallHandler;
|
||||
import io.grpc.ServerInterceptor;
|
||||
import io.grpc.Status;
|
||||
|
||||
/**
|
||||
* This interceptor observes responses from the service and if the response status is {@link Status#UNKNOWN}
|
||||
* and there is a non-null cause which is an instance of {@link ConvertibleToGrpcStatus},
|
||||
* then status and metadata to be returned to the client is resolved from that object.
|
||||
* </p>
|
||||
* This eliminates the need of having each service to override {@code `onErrorMap()`} method for commonly used exceptions.
|
||||
*/
|
||||
public class ErrorMappingInterceptor implements ServerInterceptor {
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
|
||||
final ServerCall<ReqT, RespT> call,
|
||||
final Metadata headers,
|
||||
final ServerCallHandler<ReqT, RespT> next) {
|
||||
return next.startCall(new ForwardingServerCall.SimpleForwardingServerCall<>(call) {
|
||||
@Override
|
||||
public void close(final Status status, final Metadata trailers) {
|
||||
// The idea is to only apply the automatic conversion logic in the cases
|
||||
// when there was no explicit decision by the service to provide a status.
|
||||
// I.e. if at this point we see anything but the `UNKNOWN`,
|
||||
// that means that some logic in the service made this decision already
|
||||
// and automatic conversion may conflict with it.
|
||||
if (status.getCode().equals(Status.Code.UNKNOWN)
|
||||
&& status.getCause() instanceof ConvertibleToGrpcStatus convertibleToGrpcStatus) {
|
||||
super.close(
|
||||
convertibleToGrpcStatus.grpcStatus(),
|
||||
convertibleToGrpcStatus.grpcMetadata().orElseGet(Metadata::new)
|
||||
);
|
||||
} else {
|
||||
super.close(status, trailers);
|
||||
}
|
||||
}
|
||||
}, headers);
|
||||
}
|
||||
}
|
||||
@@ -74,11 +74,6 @@ public class KeysGrpcService extends ReactorKeysGrpc.KeysImplBase {
|
||||
this.rateLimiters = rateLimiters;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Throwable onErrorMap(final Throwable throwable) {
|
||||
return RateLimitUtil.mapRateLimitExceededException(throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<GetPreKeyCountResponse> getPreKeyCount(final GetPreKeyCountRequest request) {
|
||||
return Mono.fromSupplier(AuthenticationUtil::requireAuthenticatedDevice)
|
||||
|
||||
@@ -100,11 +100,6 @@ public class ProfileGrpcService extends ReactorProfileGrpc.ProfileImplBase {
|
||||
this.bucket = bucket;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Throwable onErrorMap(final Throwable throwable) {
|
||||
return RateLimitUtil.mapRateLimitExceededException(throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<SetProfileResponse> setProfile(final SetProfileRequest request) {
|
||||
validateRequest(request);
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.grpc;
|
||||
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusException;
|
||||
import java.time.Duration;
|
||||
import javax.annotation.Nullable;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
|
||||
public class RateLimitUtil {
|
||||
|
||||
public static final Metadata.Key<Duration> RETRY_AFTER_DURATION_KEY =
|
||||
Metadata.Key.of("retry-after", new Metadata.AsciiMarshaller<>() {
|
||||
@Override
|
||||
public String toAsciiString(final Duration value) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration parseAsciiString(final String serialized) {
|
||||
return Duration.parse(serialized);
|
||||
}
|
||||
});
|
||||
|
||||
public static Throwable mapRateLimitExceededException(final Throwable throwable) {
|
||||
if (throwable instanceof RateLimitExceededException rateLimitExceededException) {
|
||||
@Nullable final Metadata trailers = rateLimitExceededException.getRetryDuration()
|
||||
.map(duration -> {
|
||||
final Metadata metadata = new Metadata();
|
||||
metadata.put(RETRY_AFTER_DURATION_KEY, duration);
|
||||
|
||||
return metadata;
|
||||
}).orElse(null);
|
||||
|
||||
return new StatusException(Status.RESOURCE_EXHAUSTED, trailers);
|
||||
}
|
||||
|
||||
return throwable;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user