// This file was autogenerated by some hot garbage in the `uniffi` crate. // Trust me, you don't want to mess with it! @file:Suppress("NAME_SHADOWING") package org.mozilla.experiments.nimbus; // Common helper code. // // Ideally this would live in a separate .kt file where it can be unittested etc // in isolation, and perhaps even published as a re-useable package. // // However, it's important that the detils of how this helper code works (e.g. the // way that different builtin types are passed across the FFI) exactly match what's // expected by the Rust code on the other side of the interface. In practice right // now that means coming from the exact some version of `uniffi` that was used to // compile the Rust component. The easiest way to ensure this is to bundle the Kotlin // helpers directly inline like we're doing here. import com.sun.jna.Library import com.sun.jna.Native import com.sun.jna.Pointer import com.sun.jna.Structure import java.nio.ByteBuffer import java.nio.ByteOrder import java.util.concurrent.atomic.AtomicLong // This is a helper for safely working with byte buffers returned from the Rust code. // A rust-owned buffer is represented by its capacity, its current length, and a // pointer to the underlying data. @Structure.FieldOrder("capacity", "len", "data", "padding") open class RustBuffer : Structure() { @JvmField var capacity: Int = 0 @JvmField var len: Int = 0 @JvmField var data: Pointer? = null // Ref https://github.com/mozilla/uniffi-rs/issues/334 for this weird "padding" field. @JvmField var padding: Long = 0 class ByValue : RustBuffer(), Structure.ByValue companion object { internal fun alloc(size: Int = 0) = rustCall(InternalError.ByReference()) { err -> _UniFFILib.INSTANCE.ffi_nimbus_4952_rustbuffer_alloc(size, err) } internal fun free(buf: RustBuffer.ByValue) = rustCall(InternalError.ByReference()) { err -> _UniFFILib.INSTANCE.ffi_nimbus_4952_rustbuffer_free(buf, err) } internal fun reserve(buf: RustBuffer.ByValue, additional: Int) = rustCall(InternalError.ByReference()) { err -> _UniFFILib.INSTANCE.ffi_nimbus_4952_rustbuffer_reserve(buf, additional, err) } } @Suppress("TooGenericExceptionThrown") fun asByteBuffer() = this.data?.getByteBuffer(0, this.len.toLong())?.also { it.order(ByteOrder.BIG_ENDIAN) } } // This is a helper for safely passing byte references into the rust code. // It's not actually used at the moment, because there aren't many things that you // can take a direct pointer to in the JVM, and if we're going to copy something // then we might as well copy it into a `RustBuffer`. But it's here for API // completeness. @Structure.FieldOrder("len", "data", "padding", "padding2") open class ForeignBytes : Structure() { @JvmField var len: Int = 0 @JvmField var data: Pointer? = null // Ref https://github.com/mozilla/uniffi-rs/issues/334 for these weird "padding" fields. @JvmField var padding: Long = 0 @JvmField var padding2: Int = 0 class ByValue : ForeignBytes(), Structure.ByValue } // A helper for structured writing of data into a `RustBuffer`. // This is very similar to `java.nio.ByteBuffer` but it knows how to grow // the underlying `RustBuffer` on demand. // // TODO: we should benchmark writing things into a `RustBuffer` versus building // up a bytearray and then copying it across. class RustBufferBuilder() { var rbuf = RustBuffer.ByValue() var bbuf: ByteBuffer? = null init { val rbuf = RustBuffer.alloc(16) // Totally arbitrary initial size rbuf.writeField("len", 0) this.setRustBuffer(rbuf) } internal fun setRustBuffer(rbuf: RustBuffer.ByValue) { this.rbuf = rbuf this.bbuf = this.rbuf.data?.getByteBuffer(0, this.rbuf.capacity.toLong())?.also { it.order(ByteOrder.BIG_ENDIAN) it.position(rbuf.len) } } fun finalize() : RustBuffer.ByValue { val rbuf = this.rbuf // Ensure that the JVM-level field is written through to native memory // before turning the buffer, in case its recipient uses it in a context // JNA doesn't apply its automatic synchronization logic. rbuf.writeField("len", this.bbuf!!.position()) this.setRustBuffer(RustBuffer.ByValue()) return rbuf } fun discard() { val rbuf = this.finalize() RustBuffer.free(rbuf) } internal fun reserve(size: Int, write: (ByteBuffer) -> Unit) { // TODO: this will perform two checks to ensure we're not overflowing the buffer: // one here where we check if it needs to grow, and another when we call a write // method on the ByteBuffer. It might be cheaper to use exception-driven control-flow // here, trying the write and growing if it throws a `BufferOverflowException`. // Benchmarking needed. if (this.bbuf!!.position() + size > this.rbuf.capacity) { rbuf.writeField("len", this.bbuf!!.position()) this.setRustBuffer(RustBuffer.reserve(this.rbuf, size)) } write(this.bbuf!!) } fun putByte(v: Byte) { this.reserve(1) { bbuf -> bbuf.put(v) } } fun putShort(v: Short) { this.reserve(2) { bbuf -> bbuf.putShort(v) } } fun putInt(v: Int) { this.reserve(4) { bbuf -> bbuf.putInt(v) } } fun putLong(v: Long) { this.reserve(8) { bbuf -> bbuf.putLong(v) } } fun putFloat(v: Float) { this.reserve(4) { bbuf -> bbuf.putFloat(v) } } fun putDouble(v: Double) { this.reserve(8) { bbuf -> bbuf.putDouble(v) } } fun put(v: ByteArray) { this.reserve(v.size) { bbuf -> bbuf.put(v) } } } // Helpers for reading primitive data types from a bytebuffer. internal fun liftFromRustBuffer(rbuf: RustBuffer.ByValue, readItem: (ByteBuffer) -> T): T { val buf = rbuf.asByteBuffer()!! try { val item = readItem(buf) if (buf.hasRemaining()) { throw RuntimeException("junk remaining in buffer after lifting, something is very wrong!!") } return item } finally { RustBuffer.free(rbuf) } } internal fun lowerIntoRustBuffer(v: T, writeItem: (T, RustBufferBuilder) -> Unit): RustBuffer.ByValue { // TODO: maybe we can calculate some sort of initial size hint? val buf = RustBufferBuilder() try { writeItem(v, buf) return buf.finalize() } catch (e: Throwable) { buf.discard() throw e } } // For every type used in the interface, we provide helper methods for conveniently // lifting and lowering that type from C-compatible data, and for reading and writing // values of that type in a buffer. // Helper functions for pasing values of type List internal fun liftSequenceRecordEnrolledExperiment(rbuf: RustBuffer.ByValue): List { return liftFromRustBuffer(rbuf) { buf -> readSequenceRecordEnrolledExperiment(buf) } } internal fun readSequenceRecordEnrolledExperiment(buf: ByteBuffer): List { val len = buf.getInt() return List(len) { EnrolledExperiment.read(buf) } } internal fun lowerSequenceRecordEnrolledExperiment(v: List): RustBuffer.ByValue { return lowerIntoRustBuffer(v) { v, buf -> writeSequenceRecordEnrolledExperiment(v, buf) } } internal fun writeSequenceRecordEnrolledExperiment(v: List, buf: RustBufferBuilder) { buf.putInt(v.size) v.forEach { it.write(buf) } } // Helper functions for pasing values of type String? internal fun liftOptionalstring(rbuf: RustBuffer.ByValue): String? { return liftFromRustBuffer(rbuf) { buf -> readOptionalstring(buf) } } internal fun readOptionalstring(buf: ByteBuffer): String? { if (buf.get().toInt() == 0) { return null } return String.read(buf) } internal fun lowerOptionalstring(v: String?): RustBuffer.ByValue { return lowerIntoRustBuffer(v) { v, buf -> writeOptionalstring(v, buf) } } internal fun writeOptionalstring(v: String?, buf: RustBufferBuilder) { if (v === null) { buf.putByte(0) } else { buf.putByte(1) v.write(buf) } } internal fun String.Companion.lift(rbuf: RustBuffer.ByValue): String { try { val byteArr = ByteArray(rbuf.len) rbuf.asByteBuffer()!!.get(byteArr) return byteArr.toString(Charsets.UTF_8) } finally { RustBuffer.free(rbuf) } } internal fun String.Companion.read(buf: ByteBuffer): String { val len = buf.getInt() val byteArr = ByteArray(len) buf.get(byteArr) return byteArr.toString(Charsets.UTF_8) } internal fun String.lower(): RustBuffer.ByValue { val byteArr = this.toByteArray(Charsets.UTF_8) // Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us // to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`. val rbuf = RustBuffer.alloc(byteArr.size) rbuf.asByteBuffer()!!.put(byteArr) return rbuf } internal fun String.write(buf: RustBufferBuilder) { val byteArr = this.toByteArray(Charsets.UTF_8) buf.putInt(byteArr.size) buf.put(byteArr) } internal fun Byte.Companion.lift(v: Byte): Byte { return v } internal fun Byte.Companion.read(buf: ByteBuffer): Byte { return buf.get() } internal fun Byte.lower(): Byte { return this } internal fun Byte.write(buf: RustBufferBuilder) { buf.putByte(this) } // Helper functions for pasing values of type RemoteSettingsConfig? internal fun liftOptionalRecordRemoteSettingsConfig(rbuf: RustBuffer.ByValue): RemoteSettingsConfig? { return liftFromRustBuffer(rbuf) { buf -> readOptionalRecordRemoteSettingsConfig(buf) } } internal fun readOptionalRecordRemoteSettingsConfig(buf: ByteBuffer): RemoteSettingsConfig? { if (buf.get().toInt() == 0) { return null } return RemoteSettingsConfig.read(buf) } internal fun lowerOptionalRecordRemoteSettingsConfig(v: RemoteSettingsConfig?): RustBuffer.ByValue { return lowerIntoRustBuffer(v) { v, buf -> writeOptionalRecordRemoteSettingsConfig(v, buf) } } internal fun writeOptionalRecordRemoteSettingsConfig(v: RemoteSettingsConfig?, buf: RustBufferBuilder) { if (v === null) { buf.putByte(0) } else { buf.putByte(1) v.write(buf) } } internal fun Boolean.Companion.lift(v: Byte): Boolean { return v.toInt() != 0 } internal fun Boolean.Companion.read(buf: ByteBuffer): Boolean { return Boolean.lift(buf.get()) } internal fun Boolean.lower(): Byte { return if (this) 1.toByte() else 0.toByte() } internal fun Boolean.write(buf: RustBufferBuilder) { buf.putByte(this.lower()) } @Synchronized fun findLibraryName(componentName: String): String { val libOverride = System.getProperty("uniffi.component.${componentName}.libraryOverride") if (libOverride != null) { return libOverride } return "uniffi_${componentName}" } inline fun loadIndirect( componentName: String ): Lib { return Native.load(findLibraryName(componentName), Lib::class.java) } // A JNA Library to expose the extern-C FFI definitions. // This is an implementation detail which will be called internally by the public API. internal interface _UniFFILib : Library { companion object { internal var INSTANCE: _UniFFILib = loadIndirect(componentName = "nimbus") } fun ffi_nimbus_4952_NimbusClient_object_free(handle: Long , uniffi_out_err: Structure.ByReference ): Unit fun nimbus_4952_NimbusClient_new(app_ctx: RustBuffer.ByValue,dbpath: RustBuffer.ByValue,remote_settings_config: RustBuffer.ByValue,available_randomization_units: RustBuffer.ByValue , uniffi_out_err: Structure.ByReference ): Long fun nimbus_4952_NimbusClient_get_experiment_branch(handle: Long,experiment_slug: RustBuffer.ByValue , uniffi_out_err: Structure.ByReference ): RustBuffer.ByValue fun nimbus_4952_NimbusClient_get_active_experiments(handle: Long , uniffi_out_err: Structure.ByReference ): RustBuffer.ByValue fun nimbus_4952_NimbusClient_get_global_user_participation(handle: Long , uniffi_out_err: Structure.ByReference ): Byte fun nimbus_4952_NimbusClient_set_global_user_participation(handle: Long,opt_in: Byte , uniffi_out_err: Structure.ByReference ): Unit fun nimbus_4952_NimbusClient_update_experiments(handle: Long , uniffi_out_err: Structure.ByReference ): Unit fun nimbus_4952_NimbusClient_opt_in_with_branch(handle: Long,experiment_slug: RustBuffer.ByValue,branch: RustBuffer.ByValue , uniffi_out_err: Structure.ByReference ): Unit fun nimbus_4952_NimbusClient_opt_out(handle: Long,experiment_slug: RustBuffer.ByValue , uniffi_out_err: Structure.ByReference ): Unit fun nimbus_4952_NimbusClient_reset_enrollment(handle: Long,experiment_slug: RustBuffer.ByValue , uniffi_out_err: Structure.ByReference ): Unit fun ffi_nimbus_4952_rustbuffer_alloc(size: Int , uniffi_out_err: Structure.ByReference ): RustBuffer.ByValue fun ffi_nimbus_4952_rustbuffer_from_bytes(bytes: ForeignBytes.ByValue , uniffi_out_err: Structure.ByReference ): RustBuffer.ByValue fun ffi_nimbus_4952_rustbuffer_free(buf: RustBuffer.ByValue , uniffi_out_err: Structure.ByReference ): Unit fun ffi_nimbus_4952_rustbuffer_reserve(buf: RustBuffer.ByValue,additional: Int , uniffi_out_err: Structure.ByReference ): RustBuffer.ByValue fun ffi_nimbus_4952_string_free(cstr: Pointer , uniffi_out_err: Structure.ByReference ): Unit } // 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. abstract class FFIObject( private val handle: AtomicLong ) { open fun destroy() { this.handle.set(0L) } internal inline fun callWithHandle(block: (handle: Long) -> R) = this.handle.get().let { handle -> if (handle != 0L) { block(handle) } else { throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") } } } inline fun T.use(block: (T) -> R) = try { block(this) } finally { try { this.destroy() } catch (e: Throwable) { // swallow } } // Public interface members begin here. // Public facing enums // Error definitions interface RustErrorReference : Structure.ByReference { fun isFailure(): Boolean fun intoException(): E fun ensureConsumed() fun getMessage(): String? fun consumeErrorMessage(): String } @Structure.FieldOrder("code", "message") internal open class RustError : Structure() { open class ByReference: RustError(), RustErrorReference @JvmField var code: Int = 0 @JvmField var message: Pointer? = null /** * Does this represent success? */ fun isSuccess(): Boolean { return code == 0 } /** * Does this represent failure? */ fun isFailure(): Boolean { return code != 0 } @Synchronized fun ensureConsumed() { if (this.message != null) { rustCall(InternalError.ByReference()) { err -> _UniFFILib.INSTANCE.ffi_nimbus_4952_string_free(this.message!!, err) } this.message = null } } /** * Get the error message or null if there is none. */ fun getMessage(): String? { return this.message?.getString(0, "utf8") } /** * Get and consume the error message, or null if there is none. */ @Synchronized fun consumeErrorMessage(): String { val result = this.getMessage() if (this.message != null) { this.ensureConsumed() } if (result == null) { throw NullPointerException("consumeErrorMessage called with null message!") } return result } @Suppress("ReturnCount", "TooGenericExceptionThrown") open fun intoException(): E { if (!isFailure()) { // It's probably a bad idea to throw here! We're probably leaking something if this is // ever hit! (But we shouldn't ever hit it?) throw RuntimeException("[Bug] intoException called on non-failure!") } this.consumeErrorMessage() throw RuntimeException("Generic errors are not implemented yet") } } internal open class InternalError : RustError() { class ByReference: InternalError(), RustErrorReference @Suppress("ReturnCount", "TooGenericExceptionThrown", "UNCHECKED_CAST") override fun intoException(): E { if (!isFailure()) { // It's probably a bad idea to throw here! We're probably leaking something if this is // ever hit! (But we shouldn't ever hit it?) throw RuntimeException("[Bug] intoException called on non-failure!") } val message = this.consumeErrorMessage() return InternalException(message) as E } } class InternalException(message: String) : Exception(message) internal open class Error : RustError() { class ByReference: Error(), RustErrorReference @Suppress("ReturnCount", "TooGenericExceptionThrown", "UNCHECKED_CAST") override fun intoException(): E { if (!isFailure()) { // It's probably a bad idea to throw here! We're probably leaking something if this is // ever hit! (But we shouldn't ever hit it?) throw RuntimeException("[Bug] intoException called on non-failure!") } val message = this.consumeErrorMessage() when (code) { 1 -> return ErrorException.InvalidPersistedData(message) as E 2 -> return ErrorException.RkvError(message) as E 3 -> return ErrorException.IOError(message) as E 4 -> return ErrorException.JSONError(message) as E 5 -> return ErrorException.EvaluationError(message) as E 6 -> return ErrorException.InvalidExpression(message) as E 7 -> return ErrorException.InvalidFraction(message) as E 8 -> return ErrorException.TryFromSliceError(message) as E 9 -> return ErrorException.EmptyRatiosError(message) as E 10 -> return ErrorException.OutOfBoundsError(message) as E 11 -> return ErrorException.UrlParsingError(message) as E 12 -> return ErrorException.RequestError(message) as E 13 -> return ErrorException.ResponseError(message) as E 14 -> return ErrorException.UuidError(message) as E 15 -> return ErrorException.InvalidExperimentResponse(message) as E 16 -> return ErrorException.InvalidPath(message) as E else -> throw RuntimeException("Invalid error received: $code, $message") } } } open class ErrorException(message: String) : Exception(message) { class InvalidPersistedData(msg: String) : ErrorException(msg) class RkvError(msg: String) : ErrorException(msg) class IOError(msg: String) : ErrorException(msg) class JSONError(msg: String) : ErrorException(msg) class EvaluationError(msg: String) : ErrorException(msg) class InvalidExpression(msg: String) : ErrorException(msg) class InvalidFraction(msg: String) : ErrorException(msg) class TryFromSliceError(msg: String) : ErrorException(msg) class EmptyRatiosError(msg: String) : ErrorException(msg) class OutOfBoundsError(msg: String) : ErrorException(msg) class UrlParsingError(msg: String) : ErrorException(msg) class RequestError(msg: String) : ErrorException(msg) class ResponseError(msg: String) : ErrorException(msg) class UuidError(msg: String) : ErrorException(msg) class InvalidExperimentResponse(msg: String) : ErrorException(msg) class InvalidPath(msg: String) : ErrorException(msg) } // Helpers for calling Rust with errors: // In practice we usually need to be synchronized to call this safely, so it doesn't // synchronize itself private inline fun nullableRustCall(callback: (E) -> U?, err: E): U? { try { val ret = callback(err) if (err.isFailure()) { throw err.intoException() } return ret } finally { // This only matters if `callback` throws (or does a non-local return, which // we currently don't do) err.ensureConsumed() } } private inline fun rustCall(err: E, callback: (E) -> U?): U { return nullableRustCall(callback, err)!! } // Public facing records data class AppContext ( val appId: String, val appVersion: String?, val appBuild: String?, val architecture: String?, val deviceManufacturer: String?, val deviceModel: String?, val locale: String?, val os: String?, val osVersion: String?, val androidSdkVersion: String?, val debugTag: String? ) { companion object { // XXX TODO: put this in a superclass maybe? internal fun lift(rbuf: RustBuffer.ByValue): AppContext { return liftFromRustBuffer(rbuf) { buf -> AppContext.read(buf) } } internal fun read(buf: ByteBuffer): AppContext { return AppContext( String.read(buf), readOptionalstring(buf), readOptionalstring(buf), readOptionalstring(buf), readOptionalstring(buf), readOptionalstring(buf), readOptionalstring(buf), readOptionalstring(buf), readOptionalstring(buf), readOptionalstring(buf), readOptionalstring(buf) ) } } internal fun lower(): RustBuffer.ByValue { return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) } internal fun write(buf: RustBufferBuilder) { this.appId.write(buf) writeOptionalstring(this.appVersion, buf) writeOptionalstring(this.appBuild, buf) writeOptionalstring(this.architecture, buf) writeOptionalstring(this.deviceManufacturer, buf) writeOptionalstring(this.deviceModel, buf) writeOptionalstring(this.locale, buf) writeOptionalstring(this.os, buf) writeOptionalstring(this.osVersion, buf) writeOptionalstring(this.androidSdkVersion, buf) writeOptionalstring(this.debugTag, buf) } } data class EnrolledExperiment ( val slug: String, val userFacingName: String, val userFacingDescription: String, val branchSlug: String ) { companion object { // XXX TODO: put this in a superclass maybe? internal fun lift(rbuf: RustBuffer.ByValue): EnrolledExperiment { return liftFromRustBuffer(rbuf) { buf -> EnrolledExperiment.read(buf) } } internal fun read(buf: ByteBuffer): EnrolledExperiment { return EnrolledExperiment( String.read(buf), String.read(buf), String.read(buf), String.read(buf) ) } } internal fun lower(): RustBuffer.ByValue { return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) } internal fun write(buf: RustBufferBuilder) { this.slug.write(buf) this.userFacingName.write(buf) this.userFacingDescription.write(buf) this.branchSlug.write(buf) } } data class RemoteSettingsConfig ( val serverUrl: String, val bucketName: String, val collectionName: String ) { companion object { // XXX TODO: put this in a superclass maybe? internal fun lift(rbuf: RustBuffer.ByValue): RemoteSettingsConfig { return liftFromRustBuffer(rbuf) { buf -> RemoteSettingsConfig.read(buf) } } internal fun read(buf: ByteBuffer): RemoteSettingsConfig { return RemoteSettingsConfig( String.read(buf), String.read(buf), String.read(buf) ) } } internal fun lower(): RustBuffer.ByValue { return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) } internal fun write(buf: RustBufferBuilder) { this.serverUrl.write(buf) this.bucketName.write(buf) this.collectionName.write(buf) } } data class AvailableRandomizationUnits ( val clientId: String?, val dummy: Byte ) { companion object { // XXX TODO: put this in a superclass maybe? internal fun lift(rbuf: RustBuffer.ByValue): AvailableRandomizationUnits { return liftFromRustBuffer(rbuf) { buf -> AvailableRandomizationUnits.read(buf) } } internal fun read(buf: ByteBuffer): AvailableRandomizationUnits { return AvailableRandomizationUnits( readOptionalstring(buf), Byte.read(buf) ) } } internal fun lower(): RustBuffer.ByValue { return lowerIntoRustBuffer(this, {v, buf -> v.write(buf)}) } internal fun write(buf: RustBufferBuilder) { writeOptionalstring(this.clientId, buf) this.dummy.write(buf) } } // Namespace functions // Objects public interface NimbusClientInterface { fun getExperimentBranch(experimentSlug: String ): String? fun getActiveExperiments(): List fun getGlobalUserParticipation(): Boolean fun setGlobalUserParticipation(optIn: Boolean ) fun updateExperiments() fun optInWithBranch(experimentSlug: String, branch: String ) fun optOut(experimentSlug: String ) fun resetEnrollment(experimentSlug: String ) } class NimbusClient( handle: Long ) : FFIObject(AtomicLong(handle)), NimbusClientInterface { constructor(appCtx: AppContext, dbpath: String, remoteSettingsConfig: RemoteSettingsConfig?, availableRandomizationUnits: AvailableRandomizationUnits ) : this(rustCall(Error.ByReference() ) { err -> _UniFFILib.INSTANCE.nimbus_4952_NimbusClient_new(appCtx.lower(), dbpath.lower(), lowerOptionalRecordRemoteSettingsConfig(remoteSettingsConfig), availableRandomizationUnits.lower() ,err) }) /** * Disconnect the object from the underlying Rust object. * * It can be called more than once, but once called, interacting with the object * causes an `IllegalStateException`. * * Clients **must** call this method once done with the object, or cause a memory leak. */ override fun destroy() { try { callWithHandle { super.destroy() // poison the handle so no-one else can use it before we tell rust. rustCall(InternalError.ByReference()) { err -> _UniFFILib.INSTANCE.ffi_nimbus_4952_NimbusClient_object_free(it, err) } } } catch (e: IllegalStateException) { // The user called this more than once. Better than less than once. } } override fun getExperimentBranch(experimentSlug: String ): String? = callWithHandle { rustCall( Error.ByReference() ) { err -> _UniFFILib.INSTANCE.nimbus_4952_NimbusClient_get_experiment_branch(it, experimentSlug.lower() , err) } }.let { liftOptionalstring(it) } override fun getActiveExperiments(): List = callWithHandle { rustCall( Error.ByReference() ) { err -> _UniFFILib.INSTANCE.nimbus_4952_NimbusClient_get_active_experiments(it, err) } }.let { liftSequenceRecordEnrolledExperiment(it) } override fun getGlobalUserParticipation(): Boolean = callWithHandle { rustCall( Error.ByReference() ) { err -> _UniFFILib.INSTANCE.nimbus_4952_NimbusClient_get_global_user_participation(it, err) } }.let { Boolean.lift(it) } override fun setGlobalUserParticipation(optIn: Boolean ) = callWithHandle { rustCall( Error.ByReference() ) { err -> _UniFFILib.INSTANCE.nimbus_4952_NimbusClient_set_global_user_participation(it, optIn.lower() , err) } } override fun updateExperiments() = callWithHandle { rustCall( Error.ByReference() ) { err -> _UniFFILib.INSTANCE.nimbus_4952_NimbusClient_update_experiments(it, err) } } override fun optInWithBranch(experimentSlug: String, branch: String ) = callWithHandle { rustCall( Error.ByReference() ) { err -> _UniFFILib.INSTANCE.nimbus_4952_NimbusClient_opt_in_with_branch(it, experimentSlug.lower(), branch.lower() , err) } } override fun optOut(experimentSlug: String ) = callWithHandle { rustCall( Error.ByReference() ) { err -> _UniFFILib.INSTANCE.nimbus_4952_NimbusClient_opt_out(it, experimentSlug.lower() , err) } } override fun resetEnrollment(experimentSlug: String ) = callWithHandle { rustCall( Error.ByReference() ) { err -> _UniFFILib.INSTANCE.nimbus_4952_NimbusClient_reset_enrollment(it, experimentSlug.lower() , err) } } }