Skip to content

Commit

Permalink
(turms-client-kotlin) Wrap the response data as Response<?> on servic…
Browse files Browse the repository at this point in the history
…e layers for better extendibility + more useful information turms-im#594
  • Loading branch information
JamesChenX committed Jun 1, 2022
1 parent da0e123 commit 3b3eb87
Show file tree
Hide file tree
Showing 22 changed files with 529 additions and 332 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class TurmsClient constructor(
val notificationService: NotificationService = NotificationService(this)

suspend fun close() {
return this.driver.close();
return this.driver.close()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import com.google.protobuf.MessageLite
import im.turms.client.driver.service.ConnectionService
import im.turms.client.driver.service.HeartbeatService
import im.turms.client.driver.service.MessageService
import im.turms.client.extension.isSuccessful
import im.turms.client.model.proto.notification.TurmsNotification
import im.turms.client.model.proto.request.TurmsRequest
import im.turms.client.transport.Pin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class ConnectionService(
if (host == stateStore.tcp?.host && port == stateStore.tcp?.port) {
return
} else {
throw ResponseException(ResponseStatusCode.CLIENT_SESSION_ALREADY_ESTABLISHED)
throw ResponseException.from(ResponseStatusCode.CLIENT_SESSION_ALREADY_ESTABLISHED)
}
}
resetStates()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class HeartbeatService(

suspend fun send() = suspendCoroutine<Unit> { cont ->
if (!stateStore.isConnected || !stateStore.isSessionOpen) {
cont.resumeWithException(ResponseException(ResponseStatusCode.CLIENT_SESSION_HAS_BEEN_CLOSED))
cont.resumeWithException(ResponseException.from(ResponseStatusCode.CLIENT_SESSION_HAS_BEEN_CLOSED))
return@suspendCoroutine
}
launch {
Expand All @@ -94,7 +94,7 @@ class HeartbeatService(

fun rejectHeartbeatPromisesIfFail(notification: TurmsNotification): Boolean {
if (notification.hasRequestId() && notification.requestId == HEARTBEAT_FAILURE_REQUEST_ID) {
rejectHeartbeatRequests(ResponseException.get(notification))
rejectHeartbeatRequests(ResponseException.from(notification))
return true
}
return false
Expand All @@ -112,7 +112,7 @@ class HeartbeatService(

override fun onDisconnected() {
stop()
rejectHeartbeatRequests(ResponseException(ResponseStatusCode.CLIENT_SESSION_HAS_BEEN_CLOSED))
rejectHeartbeatRequests(ResponseException.from(ResponseStatusCode.CLIENT_SESSION_HAS_BEEN_CLOSED))
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package im.turms.client.driver.service

import im.turms.client.driver.StateStore
import im.turms.client.exception.ResponseException
import im.turms.client.extension.isSuccessful
import im.turms.client.extension.isSuccess
import im.turms.client.extension.tryResumeWithException
import im.turms.client.model.ResponseStatusCode
import im.turms.client.model.proto.notification.TurmsNotification
Expand Down Expand Up @@ -70,18 +70,18 @@ class MessageService(
suspend fun sendRequest(requestBuilder: TurmsRequest.Builder): TurmsNotification = suspendCoroutine { cont ->
if (requestBuilder.hasCreateSessionRequest()) {
if (stateStore.isSessionOpen) {
cont.resumeWithException(ResponseException(ResponseStatusCode.CLIENT_SESSION_ALREADY_ESTABLISHED))
cont.resumeWithException(ResponseException.from(ResponseStatusCode.CLIENT_SESSION_ALREADY_ESTABLISHED))
return@suspendCoroutine
}
} else if (!stateStore.isConnected || !stateStore.isSessionOpen) {
cont.resumeWithException(ResponseException(ResponseStatusCode.CLIENT_SESSION_HAS_BEEN_CLOSED))
cont.resumeWithException(ResponseException.from(ResponseStatusCode.CLIENT_SESSION_HAS_BEEN_CLOSED))
return@suspendCoroutine
}
val now = Date()
val difference = now.time - stateStore.lastRequestDate
val isFrequent = minRequestInterval > 0 && difference <= minRequestInterval
if (isFrequent) {
cont.resumeWithException(ResponseException(ResponseStatusCode.CLIENT_REQUESTS_TOO_FREQUENT))
cont.resumeWithException(ResponseException.from(ResponseStatusCode.CLIENT_REQUESTS_TOO_FREQUENT))
return@suspendCoroutine
}
val requestContext = TurmsRequestContext(cont, null)
Expand All @@ -107,7 +107,7 @@ class MessageService(
delay(requestTimeout.toLong())
requestMap.remove(requestId)?.let {
if (!requestContext.timeoutDeferred!!.isCompleted) {
cont.tryResumeWithException(ResponseException(ResponseStatusCode.REQUEST_TIMEOUT))
cont.tryResumeWithException(ResponseException.from(ResponseStatusCode.REQUEST_TIMEOUT))
}
}
}
Expand All @@ -123,14 +123,14 @@ class MessageService(
val cont = it.cont
if (notification.hasCode()) {
it.timeoutDeferred?.cancel()
if (notification.isSuccessful()) {
if (notification.isSuccess) {
cont.resume(notification)
} else {
cont.resumeWithException(ResponseException.get(notification))
cont.resumeWithException(ResponseException.from(notification))
}
} else {
cont.resumeWithException(
ResponseException(
ResponseException.from(
ResponseStatusCode.INVALID_NOTIFICATION,
"The code is missing"
)
Expand Down Expand Up @@ -160,7 +160,7 @@ class MessageService(
override suspend fun close() = onDisconnected()

override fun onDisconnected() =
rejectRequests(ResponseException(ResponseStatusCode.CLIENT_SESSION_HAS_BEEN_CLOSED))
rejectRequests(ResponseException.from(ResponseStatusCode.CLIENT_SESSION_HAS_BEEN_CLOSED))

private data class TurmsRequestContext(
val cont: Continuation<TurmsNotification>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import im.turms.client.model.proto.notification.TurmsNotification
* @author James Chen
*/
data class ResponseException internal constructor(
val requestId: Long?,
val code: Int,
val reason: String? = null,
override val cause: Throwable? = null
Expand All @@ -36,12 +37,16 @@ data class ResponseException internal constructor(
}
}

fun get(notification: TurmsNotification): ResponseException {
fun from(notification: TurmsNotification): ResponseException {
val code = notification.code
return if (notification.hasReason())
ResponseException(code, notification.reason)
ResponseException(null, code, notification.reason)
else
ResponseException(code)
ResponseException(null, code)
}

fun from(code: Int, reason: String? = null): ResponseException {
return ResponseException(null, code, reason)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@

package im.turms.client.extension

import im.turms.client.model.Response
import im.turms.client.model.ResponseStatusCode
import im.turms.client.model.proto.notification.TurmsNotification

/**
* @author James Chen
*/
fun TurmsNotification.isSuccessful(): Boolean =
hasCode() && ResponseStatusCode.isSuccessCode(this.code)
val TurmsNotification.isSuccess: Boolean
get() = hasCode() && ResponseStatusCode.isSuccessCode(code)

fun TurmsNotification.isServerError(): Boolean =
hasCode() && ResponseStatusCode.isServerError(this.code)
val TurmsNotification.isError: Boolean
get() = hasCode() && ResponseStatusCode.isErrorCode(code)

fun <T> TurmsNotification.toResponse(
dataTransformer: ((TurmsNotification.Data) -> T)? = null
): Response<T> = Response.fromNotification(this, dataTransformer)
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,8 @@ class GroupWithVersion {
var lastUpdatedDate: Long = 0

companion object {
fun from(notification: TurmsNotification?): GroupWithVersion? {
if (notification == null) {
return null
}
val data = notification.data
if (!notification.hasData() || !data.hasGroupsWithVersion()) {
fun from(data: TurmsNotification.Data): GroupWithVersion? {
if (!data.hasGroupsWithVersion()) {
return null
}
val groupsWithVersion = data.groupsWithVersion
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2019 The Turms Project
* https://github.com/turms-im/turms
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.turms.client.model

import im.turms.client.exception.ResponseException
import im.turms.client.extension.isError
import im.turms.client.model.proto.notification.TurmsNotification
import java.util.Collections

data class Response<T>(
val requestId: Long?,
val code: Int,
val data: T
) {
companion object {

@JvmStatic
fun <T> value(data: T): Response<T> = Response(null, ResponseStatusCode.OK, data)

@JvmStatic
fun unitValue(): Response<Unit> = Response(null, ResponseStatusCode.OK, Unit)

@JvmStatic
fun <T> emptyList(): Response<List<T>> = Response(null, ResponseStatusCode.OK, Collections.emptyList())

@JvmStatic
fun <T> fromNotification(
notification: TurmsNotification,
dataTransformer: ((TurmsNotification.Data) -> T)? = null
): Response<T> {
if (!notification.hasCode()) {
throw ResponseException.from(
ResponseStatusCode.INVALID_NOTIFICATION,
"Cannot parse a success response from a notification without code"
)
}
if (notification.isError) {
throw ResponseException.from(
ResponseStatusCode.INVALID_NOTIFICATION,
"Cannot parse a success response from non-success notification"
)
}
val data = if (dataTransformer == null) Unit as T else dataTransformer.invoke(notification.data)
return Response(
if (notification.hasRequestId()) notification.requestId else null,
notification.code,
data
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ object ResponseStatusCode {
}

@JvmStatic
fun isServerError(businessCode: Int): Boolean {
return businessCode in 1200..1299 || businessCode in 200..299
fun isErrorCode(businessCode: Int): Boolean {
return !isSuccessCode(businessCode)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ class UserInfoWithVersion {

companion object {
@JvmStatic
fun from(notification: TurmsNotification?): UserInfoWithVersion? {
if (notification == null || !notification.hasData()) {
return null
}
val data = notification.data
fun from(data: TurmsNotification.Data): UserInfoWithVersion? {
if (!data.hasUsersInfosWithVersion()) {
return null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,62 +17,71 @@
package im.turms.client.service

import im.turms.client.TurmsClient
import im.turms.client.extension.toResponse
import im.turms.client.model.Response
import im.turms.client.model.proto.model.conversation.GroupConversation
import im.turms.client.model.proto.model.conversation.PrivateConversation
import im.turms.client.model.proto.request.conversation.QueryConversationsRequest
import im.turms.client.model.proto.request.conversation.UpdateConversationRequest
import im.turms.client.model.proto.request.conversation.UpdateTypingStatusRequest
import im.turms.client.util.Validator
import java.util.Collections
import java.util.Date

/**
* @author James Chen
*/
class ConversationService(private val turmsClient: TurmsClient) {

suspend fun queryPrivateConversations(targetIds: Set<Long>?): List<PrivateConversation> =
suspend fun queryPrivateConversations(targetIds: Set<Long>?): Response<List<PrivateConversation>> =
if (Validator.areAllFalsy(targetIds)) {
Collections.emptyList()
Response.emptyList()
} else turmsClient.driver
.send(QueryConversationsRequest.newBuilder().apply {
addAllTargetIds(targetIds)
}).data.conversations.privateConversationsList
}).toResponse {
it.conversations.privateConversationsList
}

suspend fun queryGroupConversations(groupIds: Set<Long>?): List<GroupConversation> =
suspend fun queryGroupConversations(groupIds: Set<Long>?): Response<List<GroupConversation>> =
if (Validator.areAllFalsy(groupIds)) {
Collections.emptyList()
Response.emptyList()
} else turmsClient.driver
.send(QueryConversationsRequest.newBuilder().apply {
addAllGroupIds(groupIds)
}).data.conversations.groupConversationsList
}).toResponse {
it.conversations.groupConversationsList
}

suspend fun updatePrivateConversationReadDate(targetId: Long, readDate: Date? = null) =
suspend fun updatePrivateConversationReadDate(targetId: Long, readDate: Date? = null): Response<Unit> =
turmsClient.driver
.send(UpdateConversationRequest.newBuilder().apply {
this.targetId = targetId
this.readDate = readDate?.time ?: Date().time
}).run {}
})
.toResponse()

suspend fun updateGroupConversationReadDate(groupId: Long, readDate: Date? = null) =
suspend fun updateGroupConversationReadDate(groupId: Long, readDate: Date? = null): Response<Unit> =
turmsClient.driver
.send(UpdateConversationRequest.newBuilder().apply {
this.groupId = groupId
this.readDate = readDate?.time ?: Date().time
}).run {}
})
.toResponse()

suspend fun updatePrivateConversationTypingStatus(targetId: Long) =
suspend fun updatePrivateConversationTypingStatus(targetId: Long): Response<Unit> =
turmsClient.driver
.send(UpdateTypingStatusRequest.newBuilder().apply {
toId = targetId
isGroupMessage = false
}).run {}
})
.toResponse()

suspend fun updateGroupConversationTypingStatus(groupId: Long) =
suspend fun updateGroupConversationTypingStatus(groupId: Long): Response<Unit> =
turmsClient.driver
.send(UpdateTypingStatusRequest.newBuilder().apply {
toId = groupId
isGroupMessage = true
}).run {}
})
.toResponse()

}
Loading

0 comments on commit 3b3eb87

Please sign in to comment.