https://kotlinlang.org logo
r

rocketraman

02/10/2022, 2:10 PM
@Robert Jaros Thoughts on making
ServiceException
open so that the client can act based on sub-classes of it?
r

Robert Jaros

02/10/2022, 2:21 PM
The exception type is transferred as a string. Do you know a way to reconstruct the exception on the client side based on the class name?
Can exceptions be serialized with kotlinx.serialization?
r

rocketraman

02/10/2022, 2:25 PM
What about creating a type that requires a serializable entity as a parameter? That entity can then be serialized over the wire and reconstructed on the client side. So the client would not act on the exception type itself, but on the wrapped entity.
r

Robert Jaros

02/10/2022, 2:28 PM
Is it possible to require a serializable parameter?
r

rocketraman

02/10/2022, 2:30 PM
Not sure if it can be made a compile-time error, but I don't see it as a big deal if this throws a runtime exception -- its something a user would have to explicitly implement so presumably they've read the docs 🙂
Hmm, not sure how we would call
serializer()
on it though if its just a generic type.
I asked a question on #serialization
r

Robert Jaros

02/10/2022, 3:29 PM
Do you know if we can use open polymorphism (https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism) with an open class instead of abstract class on the top of hierarchy?
It works for me with abstract class but doesn't work with open
r

rocketraman

02/10/2022, 3:31 PM
I thought you could
r

Robert Jaros

02/10/2022, 3:31 PM
Generally I've made some tests and we could serialize/deserialize hierarchy of exceptions but registration in serializers module is required.
r

rocketraman

02/10/2022, 3:32 PM
Nice. Yeah that's what I would expect.
r

Robert Jaros

02/10/2022, 3:32 PM
Copy code
@Serializable
abstract class MyException(val msg: String): Exception(msg)

@Serializable
@SerialName("mysecond")
class MySecondException : MyException("MySecond")

        val json = Json {
            serializersModule = SerializersModule {
                polymorphic(MyException::class) {
                    subclass(MySecondException::class)
                }
            }
        }
            val e: MyException = MySecondException()
            val serialized = json.encodeToString(e)
            console.log(serialized)
            val deserialized = json.decodeFromString<MyException>(serialized)
            try {
                throw deserialized
            } catch (e: MySecondException) {
                console.log("Cought: ${e.msg}")
            }
👍 1
r

rocketraman

02/10/2022, 3:32 PM
According to docs:
Serialization can work with arbitrary open classes or abstract classes.
r

Robert Jaros

02/10/2022, 3:33 PM
Unfortunately it doesn't work anymore if I change abstract class MyException to open class.
Also deserialized exception is somehow "broken". I can read
e.msg
but
e.message
property is
undefined
Still it should be possible to support hierarchy of exceptions as long as the user registers all the classes with the new
RemoteSerialization.customConfiguration
option I've added recently.
Perhaps it could even work for custom hierarchy without kvision exception at all - I could just try/catch to serialize the backend exception and send serialized form if it can be done, otherwise just send the class name.
And on the fronted side recreate and throw the serialized exception if it was send from the backend, and if there is none - just create ServiceException or general Exception like before.
👍 1
Please fill an issue and I'll try to play with this.
I'm going to winter holiday next week so probably after I return.
r

rocketraman

02/10/2022, 3:46 PM
For now, I'm using this workaround. The backend simply instantiates
ServiceRpcException
instead of
ServiceException
, and the frontend does
RpcError(e)
to unwrap the
RpcError
instance.
Copy code
@Serializable
sealed class RpcError {
  companion object {
    // used by the frontend e.g. RpcError(e) where e is a ServiceException
    operator fun invoke(serviceException: ServiceException) =
      Json.decodeFromString<RpcError>(serviceException.message!!)
  }

  @Serializable
  object Unauthorized : RpcError()
  // ... as many types as needed ...
  @Serializable
  class UnexpectedError(val errorId: String) : RpcError()
}

object ServiceRpcException {
  // used by the backend e.g. ServiceRpcException(RpcError.Unauthorized)
  operator fun invoke(rpcError: RpcError): ServiceException =
    ServiceException(Json.encodeToString(rpcError))
}