@Test public void itProactivelyRateLimits() { SlackWebClient slackClient = (SlackWebClient) SlackClientFactory.defaultFactory().create(SlackClientRuntimeConfig.builder() .setTokenSupplier(() -> "xoxp-test-token") .setRequestDebugger((requestId, method, request) -> {}) .setSlackRateLimiter((slackToken, slackMethod) -> SlackWebClient.RATE_LIMIT_SENTINEL_VALUE) .build()); Result<SlackResponse, SlackError> result = slackClient.executeLoggedAs(SlackMethods.api_test, HttpRequest.newBuilder() .setUrl("api.slack.com/" + SlackMethods.api_test.getMethod()) .build(), null) .join(); assertThat(result.isErr()).isTrue(); assertThat(result.unwrapErrOrElseThrow().getType()).isEqualTo(SlackErrorType.RATE_LIMITED); assertThat(result.unwrapErrOrElseThrow().getError()).isEqualTo(SlackErrorType.RATE_LIMITED.key()); }
@VisibleForTesting <T extends SlackResponse> CompletableFuture<Result<T, SlackError>> executeLoggedAs( SlackMethod method, HttpRequest request, Class<T> responseType ) { long requestId = REQUEST_COUNTER.getAndIncrement(); requestDebugger.debug(requestId, method, request); Stopwatch timer = Stopwatch.createStarted(); double acquireSeconds = acquirePermit(method); if (acquireSeconds == RATE_LIMIT_SENTINEL_VALUE) { responseDebugger.debugProactiveRateLimit(requestId, method, request); return CompletableFuture.completedFuture( Result.err(SlackError.of(SlackErrorType.RATE_LIMITED.key())) ); } return executeLogged(requestId, method, request, timer) .thenApply( response -> { try { return parseSlackResponse(response, responseType, requestId, method, request); } catch (JsonProcessingException e) { responseDebugger.debugProcessingFailure( requestId, method, request, response, e ); return Result.err( SlackError .builder() .setError(SlackErrorType.JSON_PARSING_FAILED.getCode()) .setType(SlackErrorType.JSON_PARSING_FAILED) .build() ); } catch (RuntimeException ex) { responseDebugger.debugProcessingFailure( requestId, method, request, response, ex ); throw ex; } } ); }
SlackWebClient implements SlackClient { @VisibleForTesting <T extends SlackResponse> CompletableFuture<Result<T, SlackError>> executeLoggedAs( SlackMethod method, HttpRequest request, Class<T> responseType ) { long requestId = REQUEST_COUNTER.getAndIncrement(); requestDebugger.debug(requestId, method, request); Stopwatch timer = Stopwatch.createStarted(); double acquireSeconds = acquirePermit(method); if (acquireSeconds == RATE_LIMIT_SENTINEL_VALUE) { responseDebugger.debugProactiveRateLimit(requestId, method, request); return CompletableFuture.completedFuture( Result.err(SlackError.of(SlackErrorType.RATE_LIMITED.key())) ); } return executeLogged(requestId, method, request, timer) .thenApply( response -> { try { return parseSlackResponse(response, responseType, requestId, method, request); } catch (JsonProcessingException e) { responseDebugger.debugProcessingFailure( requestId, method, request, response, e ); return Result.err( SlackError .builder() .setError(SlackErrorType.JSON_PARSING_FAILED.getCode()) .setType(SlackErrorType.JSON_PARSING_FAILED) .build() ); } catch (RuntimeException ex) { responseDebugger.debugProcessingFailure( requestId, method, request, response, ex ); throw ex; } } ); } }
SlackWebClient implements SlackClient { @VisibleForTesting <T extends SlackResponse> CompletableFuture<Result<T, SlackError>> executeLoggedAs( SlackMethod method, HttpRequest request, Class<T> responseType ) { long requestId = REQUEST_COUNTER.getAndIncrement(); requestDebugger.debug(requestId, method, request); Stopwatch timer = Stopwatch.createStarted(); double acquireSeconds = acquirePermit(method); if (acquireSeconds == RATE_LIMIT_SENTINEL_VALUE) { responseDebugger.debugProactiveRateLimit(requestId, method, request); return CompletableFuture.completedFuture( Result.err(SlackError.of(SlackErrorType.RATE_LIMITED.key())) ); } return executeLogged(requestId, method, request, timer) .thenApply( response -> { try { return parseSlackResponse(response, responseType, requestId, method, request); } catch (JsonProcessingException e) { responseDebugger.debugProcessingFailure( requestId, method, request, response, e ); return Result.err( SlackError .builder() .setError(SlackErrorType.JSON_PARSING_FAILED.getCode()) .setType(SlackErrorType.JSON_PARSING_FAILED) .build() ); } catch (RuntimeException ex) { responseDebugger.debugProcessingFailure( requestId, method, request, response, ex ); throw ex; } } ); } @AssistedInject  SlackWebClient(
    DefaultHttpRequestDebugger defaultHttpRequestDebugger,
    DefaultHttpResponseDebugger defaultHttpResponseDebugger,
    NioHttpClient.Factory nioHttpClientFactory,
    ByMethodRateLimiter defaultRateLimiter,
    @Assisted SlackClientRuntimeConfig config
  );   SlackWebClient(
    NioHttpClientFactory nioHttpClientFactory,
    SlackClientRuntimeConfig config
  ); }
SlackWebClient implements SlackClient { @VisibleForTesting <T extends SlackResponse> CompletableFuture<Result<T, SlackError>> executeLoggedAs( SlackMethod method, HttpRequest request, Class<T> responseType ) { long requestId = REQUEST_COUNTER.getAndIncrement(); requestDebugger.debug(requestId, method, request); Stopwatch timer = Stopwatch.createStarted(); double acquireSeconds = acquirePermit(method); if (acquireSeconds == RATE_LIMIT_SENTINEL_VALUE) { responseDebugger.debugProactiveRateLimit(requestId, method, request); return CompletableFuture.completedFuture( Result.err(SlackError.of(SlackErrorType.RATE_LIMITED.key())) ); } return executeLogged(requestId, method, request, timer) .thenApply( response -> { try { return parseSlackResponse(response, responseType, requestId, method, request); } catch (JsonProcessingException e) { responseDebugger.debugProcessingFailure( requestId, method, request, response, e ); return Result.err( SlackError .builder() .setError(SlackErrorType.JSON_PARSING_FAILED.getCode()) .setType(SlackErrorType.JSON_PARSING_FAILED) .build() ); } catch (RuntimeException ex) { responseDebugger.debugProcessingFailure( requestId, method, request, response, ex ); throw ex; } } ); } @AssistedInject  SlackWebClient(
    DefaultHttpRequestDebugger defaultHttpRequestDebugger,
    DefaultHttpResponseDebugger defaultHttpResponseDebugger,
    NioHttpClient.Factory nioHttpClientFactory,
    ByMethodRateLimiter defaultRateLimiter,
    @Assisted SlackClientRuntimeConfig config
  );   SlackWebClient(
    NioHttpClientFactory nioHttpClientFactory,
    SlackClientRuntimeConfig config
  ); @Override CompletableFuture<Result<AuthTestResponse, SlackError>> testAuth(); @Override CompletableFuture<Result<AuthRevokeResponse, SlackError>> revokeAuth(
    AuthRevokeParams params
  ); @Override CompletableFuture<Result<SearchMessageResponse, SlackError>> searchMessages(
    SearchMessagesParams params
  ); @Override CompletableFuture<Result<FindRepliesResponse, SlackError>> findReplies(
    FindRepliesParams params
  ); @Override CompletableFuture<Result<ConversationsRepliesResponse, SlackError>> getConversationReplies(
    ConversationsRepliesParams params
  ); @Override CompletableFuture<Result<ConversationsInfoResponse, SlackError>> joinConversation(
    ConversationsJoinParams params
  ); @Override CompletableFuture<Result<ConversationSetPurposeResponse, SlackError>> setConversationPurpose(
    ConversationSetPurposeParams params
  ); @Override CompletableFuture<Result<ConversationSetTopicResponse, SlackError>> setConversationTopic(
    ConversationSetTopicParams params
  ); @Override Iterable<CompletableFuture<Result<List<SlackUser>, SlackError>>> listUsers(); @Override CompletableFuture<Result<UsersListResponse, SlackError>> listUsersPaginated(
    UsersListParams params
  ); @Override CompletableFuture<Result<UsersInfoResponse, SlackError>> lookupUserByEmail(
    UserEmailParams params
  ); @Override CompletableFuture<Result<UsersInfoResponse, SlackError>> findUser(
    UsersInfoParams params
  ); @Override CompletableFuture<Result<UsersProfileResponse, SlackError>> getUserProfile(
    UsersInfoParams params
  ); @Override Iterable<CompletableFuture<Result<List<SlackChannel>, SlackError>>> listChannels(
    ChannelsListParams filter
  ); @Override Iterable<CompletableFuture<Result<List<LiteMessage>, SlackError>>> channelHistory(
    ChannelsHistoryParams params
  ); @Override CompletableFuture<Result<SlackChannel, SlackError>> getChannelByName(
    String channelName,
    ChannelsFilter channelsFilter
  ); @Override CompletableFuture<Result<ChannelsInfoResponse, SlackError>> getChannelInfo(
    ChannelsInfoParams params
  ); @Override CompletableFuture<Result<ChannelsKickResponse, SlackError>> kickUserFromChannel(
    ChannelsKickParams params
  ); @Override CompletableFuture<Result<ImOpenResponse, SlackError>> openIm(
    ImOpenParams params
  ); @Override CompletableFuture<Result<ChatPostMessageResponse, SlackError>> postMessage(
    ChatPostMessageParams params
  ); @Override CompletableFuture<Result<ChatPostEphemeralMessageResponse, SlackError>> postEphemeralMessage(
    ChatPostEphemeralMessageParams params
  ); @Override CompletableFuture<Result<ChatUpdateMessageResponse, SlackError>> updateMessage(
    ChatUpdateMessageParams params
  ); @Override CompletableFuture<Result<ChatGetPermalinkResponse, SlackError>> getPermalink(
    ChatGetPermalinkParams params
  ); @Override CompletableFuture<Result<ChatDeleteResponse, SlackError>> deleteMessage(
    ChatDeleteParams params
  ); @Override CompletableFuture<Result<ChatUnfurlResponse, SlackError>> unfurlLinks(
    ChatUnfurlParams params
  ); @Override Iterable<CompletableFuture<Result<List<Conversation>, SlackError>>> listConversations(
    ConversationsListParams params
  ); @Override CompletableFuture<Result<ConversationListResponse, SlackError>> listConversationsPaginated(
    ConversationsListParams params
  ); @Override Iterable<CompletableFuture<Result<List<Conversation>, SlackError>>> usersConversations(
    ConversationsUserParams params
  ); @Override CompletableFuture<Result<ConversationListResponse, SlackError>> usersConversationsPaginated(
      ConversationsUserParams params
  ); @Override CompletableFuture<Result<ConversationsCreateResponse, SlackError>> createConversation(
    ConversationCreateParams params
  ); @Override CompletableFuture<Result<ConversationsInviteResponse, SlackError>> inviteToConversation(
    ConversationInviteParams params
  ); @Override CompletableFuture<Result<ConversationsUnarchiveResponse, SlackError>> unarchiveConversation(
    ConversationUnarchiveParams params
  ); @Override Iterable<CompletableFuture<Result<List<LiteMessage>, SlackError>>> getConversationHistory(
    ConversationsHistoryParams params
  ); @Override CompletableFuture<Result<ConversationsArchiveResponse, SlackError>> archiveConversation(
    ConversationArchiveParams params
  ); @Override CompletableFuture<Result<ConversationsInfoResponse, SlackError>> getConversationInfo(
    ConversationsInfoParams params
  ); @Override CompletableFuture<Result<Conversation, SlackError>> getConversationByName(
    String conversationName,
    ConversationsFilter conversationsFilter
  ); @Override CompletableFuture<Result<ConversationsOpenResponse, SlackError>> openConversation(
    ConversationOpenParams params
  ); @Override Iterable<CompletableFuture<Result<List<String>, SlackError>>> getConversationMembers(
    ConversationMemberParams params
  ); @Override CompletableFuture<Result<ConversationMemberResponse, SlackError>> getConversationMembersPaginated(
      ConversationMemberParams params
  ); @Override CompletableFuture<Result<UsergroupCreateResponse, SlackError>> createUsergroup(
    UsergroupCreateParams params
  ); @Override Iterable<CompletableFuture<Result<List<SlackUsergroup>, SlackError>>> listUsergroups(
    UsergroupListParams params
  ); @Override CompletableFuture<Result<UsergroupUpdateResponse, SlackError>> updateUsergroup(
    UsergroupUpdateParams params
  ); @Override CompletableFuture<Result<UsergroupEnableResponse, SlackError>> enableUsergroup(
    UsergroupEnableParams params
  ); @Override CompletableFuture<Result<UsergroupDisableResponse, SlackError>> disableUsergroup(
    UsergroupDisableParams params
  ); @Override CompletableFuture<Result<UsergroupUsersUpdateResponse, SlackError>> updateUsergroupUsers(
    UsergroupUsersUpdateParams params
  ); @Override CompletableFuture<Result<DialogOpenResponse, SlackError>> openDialog(
    DialogOpenParams params
  ); @Override CompletableFuture<Result<AddReactionResponse, SlackError>> addReaction(
    ReactionsAddParams params
  ); @Override CompletableFuture<Result<FilesUploadResponse, SlackError>> uploadFile(
    FilesUploadParams params
  ); @Override CompletableFuture<Result<FilesSharedPublicUrlResponse, SlackError>> shareFilePublically(
    FilesSharedPublicUrlParams params
  ); @Override Iterable<CompletableFuture<Result<List<SlackGroup>, SlackError>>> listGroups(
    GroupsListParams filter
  ); @Override CompletableFuture<Result<GroupsKickResponse, SlackError>> kickUserFromGroup(
    GroupsKickParams params
  ); @Override CompletableFuture<Result<TeamInfoResponse, SlackError>> getTeamInfo(); @Override CompletableFuture<Result<EmojiListResponse, SlackError>> listEmoji(); @Override CompletableFuture<Result<ModalViewCommandResponse, SlackError>> openView(
    OpenViewParams params
  ); @Override CompletableFuture<Result<ModalViewCommandResponse, SlackError>> updateView(
    UpdateViewParams params
  ); @Override CompletableFuture<Result<ModalViewCommandResponse, SlackError>> pushView(
    OpenViewParams params
  ); @Override CompletableFuture<Result<HomeTabViewCommandResponse, SlackError>> publishView(
    PublishViewParams params
  ); @Override CompletableFuture<Result<T, SlackError>> postSlackCommand(
    SlackMethod method,
    Object params,
    Class<T> returnClazz
  ); @Override void close(); }
SlackWebClient implements SlackClient { @VisibleForTesting <T extends SlackResponse> CompletableFuture<Result<T, SlackError>> executeLoggedAs( SlackMethod method, HttpRequest request, Class<T> responseType ) { long requestId = REQUEST_COUNTER.getAndIncrement(); requestDebugger.debug(requestId, method, request); Stopwatch timer = Stopwatch.createStarted(); double acquireSeconds = acquirePermit(method); if (acquireSeconds == RATE_LIMIT_SENTINEL_VALUE) { responseDebugger.debugProactiveRateLimit(requestId, method, request); return CompletableFuture.completedFuture( Result.err(SlackError.of(SlackErrorType.RATE_LIMITED.key())) ); } return executeLogged(requestId, method, request, timer) .thenApply( response -> { try { return parseSlackResponse(response, responseType, requestId, method, request); } catch (JsonProcessingException e) { responseDebugger.debugProcessingFailure( requestId, method, request, response, e ); return Result.err( SlackError .builder() .setError(SlackErrorType.JSON_PARSING_FAILED.getCode()) .setType(SlackErrorType.JSON_PARSING_FAILED) .build() ); } catch (RuntimeException ex) { responseDebugger.debugProcessingFailure( requestId, method, request, response, ex ); throw ex; } } ); } @AssistedInject  SlackWebClient(
    DefaultHttpRequestDebugger defaultHttpRequestDebugger,
    DefaultHttpResponseDebugger defaultHttpResponseDebugger,
    NioHttpClient.Factory nioHttpClientFactory,
    ByMethodRateLimiter defaultRateLimiter,
    @Assisted SlackClientRuntimeConfig config
  );   SlackWebClient(
    NioHttpClientFactory nioHttpClientFactory,
    SlackClientRuntimeConfig config
  ); @Override CompletableFuture<Result<AuthTestResponse, SlackError>> testAuth(); @Override CompletableFuture<Result<AuthRevokeResponse, SlackError>> revokeAuth(
    AuthRevokeParams params
  ); @Override CompletableFuture<Result<SearchMessageResponse, SlackError>> searchMessages(
    SearchMessagesParams params
  ); @Override CompletableFuture<Result<FindRepliesResponse, SlackError>> findReplies(
    FindRepliesParams params
  ); @Override CompletableFuture<Result<ConversationsRepliesResponse, SlackError>> getConversationReplies(
    ConversationsRepliesParams params
  ); @Override CompletableFuture<Result<ConversationsInfoResponse, SlackError>> joinConversation(
    ConversationsJoinParams params
  ); @Override CompletableFuture<Result<ConversationSetPurposeResponse, SlackError>> setConversationPurpose(
    ConversationSetPurposeParams params
  ); @Override CompletableFuture<Result<ConversationSetTopicResponse, SlackError>> setConversationTopic(
    ConversationSetTopicParams params
  ); @Override Iterable<CompletableFuture<Result<List<SlackUser>, SlackError>>> listUsers(); @Override CompletableFuture<Result<UsersListResponse, SlackError>> listUsersPaginated(
    UsersListParams params
  ); @Override CompletableFuture<Result<UsersInfoResponse, SlackError>> lookupUserByEmail(
    UserEmailParams params
  ); @Override CompletableFuture<Result<UsersInfoResponse, SlackError>> findUser(
    UsersInfoParams params
  ); @Override CompletableFuture<Result<UsersProfileResponse, SlackError>> getUserProfile(
    UsersInfoParams params
  ); @Override Iterable<CompletableFuture<Result<List<SlackChannel>, SlackError>>> listChannels(
    ChannelsListParams filter
  ); @Override Iterable<CompletableFuture<Result<List<LiteMessage>, SlackError>>> channelHistory(
    ChannelsHistoryParams params
  ); @Override CompletableFuture<Result<SlackChannel, SlackError>> getChannelByName(
    String channelName,
    ChannelsFilter channelsFilter
  ); @Override CompletableFuture<Result<ChannelsInfoResponse, SlackError>> getChannelInfo(
    ChannelsInfoParams params
  ); @Override CompletableFuture<Result<ChannelsKickResponse, SlackError>> kickUserFromChannel(
    ChannelsKickParams params
  ); @Override CompletableFuture<Result<ImOpenResponse, SlackError>> openIm(
    ImOpenParams params
  ); @Override CompletableFuture<Result<ChatPostMessageResponse, SlackError>> postMessage(
    ChatPostMessageParams params
  ); @Override CompletableFuture<Result<ChatPostEphemeralMessageResponse, SlackError>> postEphemeralMessage(
    ChatPostEphemeralMessageParams params
  ); @Override CompletableFuture<Result<ChatUpdateMessageResponse, SlackError>> updateMessage(
    ChatUpdateMessageParams params
  ); @Override CompletableFuture<Result<ChatGetPermalinkResponse, SlackError>> getPermalink(
    ChatGetPermalinkParams params
  ); @Override CompletableFuture<Result<ChatDeleteResponse, SlackError>> deleteMessage(
    ChatDeleteParams params
  ); @Override CompletableFuture<Result<ChatUnfurlResponse, SlackError>> unfurlLinks(
    ChatUnfurlParams params
  ); @Override Iterable<CompletableFuture<Result<List<Conversation>, SlackError>>> listConversations(
    ConversationsListParams params
  ); @Override CompletableFuture<Result<ConversationListResponse, SlackError>> listConversationsPaginated(
    ConversationsListParams params
  ); @Override Iterable<CompletableFuture<Result<List<Conversation>, SlackError>>> usersConversations(
    ConversationsUserParams params
  ); @Override CompletableFuture<Result<ConversationListResponse, SlackError>> usersConversationsPaginated(
      ConversationsUserParams params
  ); @Override CompletableFuture<Result<ConversationsCreateResponse, SlackError>> createConversation(
    ConversationCreateParams params
  ); @Override CompletableFuture<Result<ConversationsInviteResponse, SlackError>> inviteToConversation(
    ConversationInviteParams params
  ); @Override CompletableFuture<Result<ConversationsUnarchiveResponse, SlackError>> unarchiveConversation(
    ConversationUnarchiveParams params
  ); @Override Iterable<CompletableFuture<Result<List<LiteMessage>, SlackError>>> getConversationHistory(
    ConversationsHistoryParams params
  ); @Override CompletableFuture<Result<ConversationsArchiveResponse, SlackError>> archiveConversation(
    ConversationArchiveParams params
  ); @Override CompletableFuture<Result<ConversationsInfoResponse, SlackError>> getConversationInfo(
    ConversationsInfoParams params
  ); @Override CompletableFuture<Result<Conversation, SlackError>> getConversationByName(
    String conversationName,
    ConversationsFilter conversationsFilter
  ); @Override CompletableFuture<Result<ConversationsOpenResponse, SlackError>> openConversation(
    ConversationOpenParams params
  ); @Override Iterable<CompletableFuture<Result<List<String>, SlackError>>> getConversationMembers(
    ConversationMemberParams params
  ); @Override CompletableFuture<Result<ConversationMemberResponse, SlackError>> getConversationMembersPaginated(
      ConversationMemberParams params
  ); @Override CompletableFuture<Result<UsergroupCreateResponse, SlackError>> createUsergroup(
    UsergroupCreateParams params
  ); @Override Iterable<CompletableFuture<Result<List<SlackUsergroup>, SlackError>>> listUsergroups(
    UsergroupListParams params
  ); @Override CompletableFuture<Result<UsergroupUpdateResponse, SlackError>> updateUsergroup(
    UsergroupUpdateParams params
  ); @Override CompletableFuture<Result<UsergroupEnableResponse, SlackError>> enableUsergroup(
    UsergroupEnableParams params
  ); @Override CompletableFuture<Result<UsergroupDisableResponse, SlackError>> disableUsergroup(
    UsergroupDisableParams params
  ); @Override CompletableFuture<Result<UsergroupUsersUpdateResponse, SlackError>> updateUsergroupUsers(
    UsergroupUsersUpdateParams params
  ); @Override CompletableFuture<Result<DialogOpenResponse, SlackError>> openDialog(
    DialogOpenParams params
  ); @Override CompletableFuture<Result<AddReactionResponse, SlackError>> addReaction(
    ReactionsAddParams params
  ); @Override CompletableFuture<Result<FilesUploadResponse, SlackError>> uploadFile(
    FilesUploadParams params
  ); @Override CompletableFuture<Result<FilesSharedPublicUrlResponse, SlackError>> shareFilePublically(
    FilesSharedPublicUrlParams params
  ); @Override Iterable<CompletableFuture<Result<List<SlackGroup>, SlackError>>> listGroups(
    GroupsListParams filter
  ); @Override CompletableFuture<Result<GroupsKickResponse, SlackError>> kickUserFromGroup(
    GroupsKickParams params
  ); @Override CompletableFuture<Result<TeamInfoResponse, SlackError>> getTeamInfo(); @Override CompletableFuture<Result<EmojiListResponse, SlackError>> listEmoji(); @Override CompletableFuture<Result<ModalViewCommandResponse, SlackError>> openView(
    OpenViewParams params
  ); @Override CompletableFuture<Result<ModalViewCommandResponse, SlackError>> updateView(
    UpdateViewParams params
  ); @Override CompletableFuture<Result<ModalViewCommandResponse, SlackError>> pushView(
    OpenViewParams params
  ); @Override CompletableFuture<Result<HomeTabViewCommandResponse, SlackError>> publishView(
    PublishViewParams params
  ); @Override CompletableFuture<Result<T, SlackError>> postSlackCommand(
    SlackMethod method,
    Object params,
    Class<T> returnClazz
  ); @Override void close(); static final int RATE_LIMIT_SENTINEL_VALUE; static final int RATE_LIMIT_LOG_WARNING_THRESHOLD_SECONDS; }