// A handful of classes and functions to support the generated data structures. // This would be a good candidate for isolating in its own ffi-support lib. internal const val UNIFFI_CALL_SUCCESS = 0.toByte() internal const val UNIFFI_CALL_ERROR = 1.toByte() internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte() @Structure.FieldOrder("code", "error_buf") internal open class UniffiRustCallStatus : Structure() { @JvmField var code: Byte = 0 @JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue() class ByValue: UniffiRustCallStatus(), Structure.ByValue fun isSuccess(): Boolean { return code == UNIFFI_CALL_SUCCESS } fun isError(): Boolean { return code == UNIFFI_CALL_ERROR } fun isPanic(): Boolean { return code == UNIFFI_CALL_UNEXPECTED_ERROR } companion object { fun create(code: Byte, errorBuf: RustBuffer.ByValue): UniffiRustCallStatus.ByValue { val callStatus = UniffiRustCallStatus.ByValue() callStatus.code = code callStatus.error_buf = errorBuf return callStatus } } } class InternalException(message: String) : Exception(message) // Each top-level error class has a companion object that can lift the error from the call status's rust buffer interface UniffiRustCallStatusErrorHandler { fun lift(error_buf: RustBuffer.ByValue): E; } // Helpers for calling Rust // In practice we usually need to be synchronized to call this safely, so it doesn't // synchronize itself // Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err private inline fun uniffiRustCallWithError(errorHandler: UniffiRustCallStatusErrorHandler, callback: (UniffiRustCallStatus) -> U): U { var status = UniffiRustCallStatus(); val return_value = callback(status) uniffiCheckCallStatus(errorHandler, status) return return_value } // Check UniffiRustCallStatus and throw an error if the call wasn't successful private fun uniffiCheckCallStatus(errorHandler: UniffiRustCallStatusErrorHandler, status: UniffiRustCallStatus) { if (status.isSuccess()) { return } else if (status.isError()) { throw errorHandler.lift(status.error_buf) } else if (status.isPanic()) { // when the rust code sees a panic, it tries to construct a rustbuffer // with the message. but if that code panics, then it just sends back // an empty buffer. if (status.error_buf.len > 0) { throw InternalException({{ Type::String.borrow()|lift_fn }}(status.error_buf)) } else { throw InternalException("Rust panic") } } else { throw InternalException("Unknown rust call status: $status.code") } } // UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR object UniffiNullRustCallStatusErrorHandler: UniffiRustCallStatusErrorHandler { override fun lift(error_buf: RustBuffer.ByValue): InternalException { RustBuffer.free(error_buf) return InternalException("Unexpected CALL_ERROR") } } // Call a rust function that returns a plain value private inline fun uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U { return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback); } internal inline fun uniffiTraitInterfaceCall( callStatus: UniffiRustCallStatus, makeCall: () -> T, writeReturn: (T) -> Unit, ) { try { writeReturn(makeCall()) } catch(e: Exception) { callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString()) } } internal inline fun uniffiTraitInterfaceCallWithError( callStatus: UniffiRustCallStatus, makeCall: () -> T, writeReturn: (T) -> Unit, lowerError: (E) -> RustBuffer.ByValue ) { try { writeReturn(makeCall()) } catch(e: Exception) { if (e is E) { callStatus.code = UNIFFI_CALL_ERROR callStatus.error_buf = lowerError(e) } else { callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString()) } } }