Skip to content

Commit

Permalink
feat(#14 + #5): grpc exceptions to sdk mapping & type-safe creation f…
Browse files Browse the repository at this point in the history
…ailure exceptions (#15)
  • Loading branch information
y9vad9 authored Jun 11, 2023
1 parent f5df119 commit dfa1236
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class DeployPlugin : Plugin<Project> {
version = data.version ?: error("shouldn't be null")

url = uri(
"sftp://${data.host}:22/${data.deployPath}"
"sftp://${data.user}@${data.host}:22/${data.deployPath}"
)

credentials {
Expand Down
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ grpc = "1.3.0"
protobuf = "3.22.0"
protobuf-plugin = "0.9.2"
grpc-netty = "1.55.1"
library-version = "0.0.1-alpha"

[libraries]
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
Expand Down
5 changes: 4 additions & 1 deletion grpc-engine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ protobuf {
}
}

group = "io.timemates"
version = libs.versions.library.version.get()

deployLibrary {
ssh(tag = "maven.timemates.io") {
host = System.getenv("TIMEMATES_SSH_HOST")
Expand All @@ -64,6 +67,6 @@ deployLibrary {

description = "TimeMates grpc adapter for SDK"

version = properties["timemates.sdk.version"] as String
version = libs.versions.library.version.get()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import com.google.protobuf.Empty
import com.google.protobuf.kotlin.toByteString
import io.grpc.ManagedChannelBuilder
import io.grpc.Metadata
import io.grpc.Status
import io.grpc.StatusException
import io.timemates.api.authorizations.AuthorizationServiceGrpcKt
import io.timemates.api.files.FilesServiceGrpcKt
import io.timemates.api.files.requests.UploadFileRequestKt.fileMetadata
import io.timemates.api.grpc.internal.mapException
import io.timemates.api.grpc.mappers.AuthorizationsMapper
import io.timemates.api.grpc.mappers.FilesMapper
import io.timemates.api.grpc.mappers.TimersMapper
Expand All @@ -25,6 +28,13 @@ import io.timemates.sdk.authorization.sessions.requests.TerminateCurrentAuthoriz
import io.timemates.sdk.authorization.types.value.AccessHash
import io.timemates.sdk.common.constructor.createOrThrow
import io.timemates.sdk.common.engine.TimeMatesRequestsEngine
import io.timemates.sdk.common.exceptions.AlreadyExistsException
import io.timemates.sdk.common.exceptions.InternalServerError
import io.timemates.sdk.common.exceptions.InvalidArgumentException
import io.timemates.sdk.common.exceptions.NotFoundException
import io.timemates.sdk.common.exceptions.PermissionDeniedException
import io.timemates.sdk.common.exceptions.UnauthorizedException
import io.timemates.sdk.common.exceptions.UnavailableException
import io.timemates.sdk.common.exceptions.UnsupportedException
import io.timemates.sdk.common.types.TimeMatesEntity
import io.timemates.sdk.common.types.TimeMatesRequest
Expand Down Expand Up @@ -342,6 +352,28 @@ public class GrpcTimeMatesRequestsEngine(

else -> unsupported(request::class)
} as T
}.mapException {
val exception = (it as? StatusException) ?: return@mapException it
val status = exception.status

val message = exception.message ?: NO_MESSAGE

when (status) {
Status.INVALID_ARGUMENT, Status.FAILED_PRECONDITION ->
InvalidArgumentException(message, exception)
Status.UNAUTHENTICATED ->
UnauthorizedException(message, exception)
Status.INTERNAL ->
InternalServerError(message, exception)
Status.NOT_FOUND ->
NotFoundException(message, exception)
Status.ALREADY_EXISTS ->
AlreadyExistsException(message, exception)
Status.PERMISSION_DENIED -> PermissionDeniedException(message, exception)
Status.UNAVAILABLE -> UnavailableException(message, exception)

else -> InternalServerError(message, exception)
}
}

private fun authorizedMetadata(accessHash: AccessHash) = Metadata().apply {
Expand All @@ -352,4 +384,6 @@ public class GrpcTimeMatesRequestsEngine(
throw UnsupportedException("Request of type ${kClass.simpleName} is not supported")

private inline fun <reified T> unsupported(): Nothing = unsupported(T::class)
}
}

private const val NO_MESSAGE = "No message is provided."
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.timemates.api.grpc.internal

/**
* Applies the given transformation function to the exception contained in the `Result` and returns a new `Result`
* with the transformed exception. If the `Result` does not contain an exception,
* it returns the original `Result` unchanged.
*
* @param transform The transformation function to apply to the exception.
* @return A new `Result` with the transformed exception, or the original `Result` if no exception is present.
*/
internal inline fun <T> Result<T>.mapException(transform: (Throwable) -> Throwable): Result<T> {
val exception = exceptionOrNull() ?: return this

return Result.failure(transform(exception))
}
5 changes: 4 additions & 1 deletion sdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ kotlin {
explicitApi()
}

group = "io.timemates"
version = libs.versions.library.version.get()

dependencies {
commonMainImplementation(libs.kotlinx.datetime)
commonMainImplementation(libs.kotlinx.coroutines)
Expand All @@ -28,6 +31,6 @@ deployLibrary {

description = "TimeMates SDK"

version = properties["timemates.sdk.version"] as String
version = libs.versions.library.version.get()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,57 +10,80 @@ import io.timemates.sdk.common.exceptions.TimeMatesException
*
* @property message The error message associated with the creation failure.
*/
public data class CreationFailure(
public override val message: String,
) : TimeMatesException(message) {
public sealed class CreationFailure(message: String) : TimeMatesException(message) {
/**
* Represents a creation failure due to a size range constraint.
*/
public class SizeRangeFailure(public val size: IntRange) : CreationFailure("Constraint failure: size must be in range of $size")

/**
* Represents a creation failure due to an exact size constraint.
*/
public class SizeExactFailure(public val size: Int) : CreationFailure("Constraint failure: size must be exactly $size")

/**
* Represents a creation failure due to a minimum value constraint.
*/
public class MinValueFailure(public val size: Int) : CreationFailure("Constraint failure: minimal value is $size")

/**
* Represents a creation failure due to a blank value constraint.
*/
public class BlankValueFailure : CreationFailure("Constraint failure: provided value is empty")

/**
* Represents a creation failure due to a pattern constraint.
*/
public class PatternFailure(public val regex: Regex) : CreationFailure("Constraint failure: input should match $regex")

public companion object {
/**
* Creates a `CreationFailure` object with a size constraint failure message.
* Creates a [SizeRangeFailure] object with a size constraint failure message.
*
* @param size The size range constraint for the creation failure.
* @return The `CreationFailure` object with the specified size constraint failure message.
* @return The [SizeRangeFailure] object with the specified size constraint failure message.
*/
public fun ofSize(size: IntRange): CreationFailure {
return CreationFailure(
"Constraint failure: size must be in range of $size"
)
public fun ofSizeRange(size: IntRange): CreationFailure {
return SizeRangeFailure(size)
}

/**
* Creates a [CreationFailure] with a constraint failure message based on the provided size.
* Creates a [SizeExactFailure] with a constraint failure message based on the provided size.
*
* @param size The expected size that caused the constraint failure.
* @return A [CreationFailure] object with the constraint failure message.
* @return A [SizeExactFailure] object with the constraint failure message.
*/
public fun ofSize(size: Int): CreationFailure {
return CreationFailure(
"Constraint failure: size must be exactly $size"
)
public fun ofSizeExact(size: Int): CreationFailure {
return SizeExactFailure(size)
}

public fun ofMin(size: Int): CreationFailure {
return CreationFailure(
"Constraint failure: minimal value is the $size"
)
/**
* Creates a [MinValueFailure] with a constraint failure message for a minimum value.
*
* @param size The minimal value that caused the constraint failure.
* @return A [MinValueFailure] object with the constraint failure message.
*/
public fun ofMinValue(size: Int): CreationFailure {
return MinValueFailure(size)
}

/**
* Creates a [CreationFailure] with a constraint failure message for a blank value.
* @return A [CreationFailure] object with the constraint failure message.
* Creates a [BlankValueFailure] with a constraint failure message for a blank value.
*
* @return A [BlankValueFailure] object with the constraint failure message.
*/
public fun ofBlank(): CreationFailure {
return CreationFailure("Constraint failure: provided value is empty")
public fun ofBlankValue(): CreationFailure {
return BlankValueFailure()
}

/**
* Creates a `CreationFailure` object with a pattern constraint failure message.
* Creates a [PatternFailure] object with a pattern constraint failure message.
*
* @param regex The regular expression pattern constraint for the creation failure.
* @return The `CreationFailure` object with the specified pattern constraint failure message.
* @return The [PatternFailure] object with the specified pattern constraint failure message.
*/
public fun ofPattern(regex: Regex): CreationFailure {
return CreationFailure(
"Constraint failure: input should match $regex"
)
return PatternFailure(regex)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.timemates.sdk.common.exceptions

/**
* Exception indicating that an entity already exists.
*
* @param message The detail message for this exception.
* @param cause The cause of this exception.
*/
public data class AlreadyExistsException(
override val message: String,
override val cause: Throwable? = null
) : TimeMatesException(message, cause)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.timemates.sdk.common.exceptions

/**
* Represents internal failure on server.
*/
public class InternalServerError(
message: String,
cause: Throwable?,
) : TimeMatesException(message, cause)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.timemates.sdk.common.exceptions

/**
* Represents failure from the server when given argument
* is invalid.
*
* @param message The detail message for this exception.
* @param cause The cause of this exception.
*/
public data class InvalidArgumentException(
override val message: String,
override val cause: Throwable?
) : TimeMatesException(message, cause)
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ package io.timemates.sdk.common.exceptions
* @property message The error message associated with the not found exception.
*/
public data class NotFoundException(
public override val message: String
) : TimeMatesException(message)
public override val message: String,
public override val cause: Throwable? = null,
) : TimeMatesException(message, cause)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.timemates.sdk.common.exceptions

/**
* Exception indicating that permission is denied.
*
* @param message The detail message for this exception.
* @param cause The cause of this exception.
*/
public data class PermissionDeniedException(
override val message: String,
override val cause: Throwable? = null
) : TimeMatesException(message, cause)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ package io.timemates.sdk.common.exceptions
*
* @property message The error message associated with the unauthorized exception.
*/
public data class UnauthorizedException(override val message: String) : TimeMatesException(message) {
public data class UnauthorizedException(
override val message: String,
override val cause: Throwable? = null
) : TimeMatesException(message, cause) {
public companion object {
/**
* The error message that denotes that client did not provide any token
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ package io.timemates.sdk.common.exceptions
* @property cause The cause of the unavailability, represented as a throwable.
*/
public data class UnavailableException(
override val message: String,
override val cause: Throwable
) : TimeMatesException("Method or service is unavailable.", cause)
) : TimeMatesException("Method or service is unavailable: $message", cause)

0 comments on commit dfa1236

Please sign in to comment.