sdk

Radroots SDK and bindings
git clone https://radroots.dev/git/sdk.git
Log | Files | Refs | README

commit 6a196b1ff23563d2b58662ed091f85208a995307
parent a752e21babfaf382383b5e1f3b7005d7890d64a0
Author: triesap <tyson@radroots.org>
Date:   Mon,  3 Aug 2026 13:45:00 +0000

ios: regenerate sdk ffi bindings

- Add a governed UniFFI Swift generator routed through the private SDK FFI crate.
- Commit Swift source, C module artifacts, and an exact source-and-output lock.
- Enforce generated binding freshness through the SDK architecture gate.
- Verify repeat generation, Swift typechecking, xtask tests, and locked builds.

Diffstat:
MCargo.lock | 120++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mcrates/ffi/Cargo.toml | 6+++++-
Acrates/ffi/src/bin/bindgen.rs | 3+++
Adocs/platform/swift.md | 14++++++++++++++
Agenerated/swift/radroots_sdk.swift | 1111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agenerated/swift/radroots_sdkFFI.h | 594+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Agenerated/swift/radroots_sdkFFI.modulemap | 7+++++++
Agenerated/swift/source.lock | 14++++++++++++++
Atools/xtask/src/bindings.rs | 253+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/xtask/src/check.rs | 4+++-
Mtools/xtask/src/main.rs | 21++++++++++++++++++++-
11 files changed, 2116 insertions(+), 31 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -45,6 +45,12 @@ dependencies = [ ] [[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] name = "anyhow" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -83,7 +89,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn", + "syn 2.0.117", ] [[package]] @@ -106,7 +112,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -382,6 +388,45 @@ dependencies = [ ] [[package]] +name = "clap" +version = "4.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -508,7 +553,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -559,7 +604,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "syn", + "syn 2.0.117", "toml 0.8.23", ] @@ -571,7 +616,7 @@ checksum = "c3cb9aafc704e13c470498fb9c351353af71c759ba810239bfb82f2d69658934" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1647,7 +1692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -2070,7 +2115,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "syn", + "syn 2.0.117", "tar", "tempfile", "toml 0.8.23", @@ -2448,7 +2493,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2544,7 +2589,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2724,7 +2769,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.117", ] [[package]] @@ -2745,7 +2790,7 @@ dependencies = [ "sha2", "sqlx-core", "sqlx-sqlite", - "syn", + "syn 2.0.117", "tokio", "url", ] @@ -2787,6 +2832,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2804,6 +2855,17 @@ dependencies = [ ] [[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2820,7 +2882,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2882,7 +2944,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2893,7 +2955,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2944,7 +3006,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3111,7 +3173,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3194,7 +3256,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3291800a6b06569f7d3e15bdb6dc235e0f0c8bd3eb07177f430057feb076415f" dependencies = [ "anyhow", + "camino", "cargo_metadata", + "clap", "uniffi_bindgen", "uniffi_core", "uniffi_macros", @@ -3249,7 +3313,7 @@ dependencies = [ "indexmap", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3264,7 +3328,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 2.0.117", "toml 0.5.11", "uniffi_meta", ] @@ -3468,7 +3532,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -3511,7 +3575,7 @@ checksum = "ee997551ce1ad5adda03f7ce37ec34b4140fe9f547fd07b46d55901d1ba1a06b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3653,7 +3717,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3664,7 +3728,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3892,7 +3956,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -3908,7 +3972,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -3985,7 +4049,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -4006,7 +4070,7 @@ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4026,7 +4090,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -4066,7 +4130,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/crates/ffi/Cargo.toml b/crates/ffi/Cargo.toml @@ -14,10 +14,14 @@ readme = "README.md" [lib] crate-type = ["lib", "staticlib", "cdylib"] +[[bin]] +name = "radroots-sdk-bindgen" +path = "src/bin/bindgen.rs" + [dependencies] radroots_sdk = { workspace = true, features = ["memory"] } thiserror = { workspace = true } -uniffi = { workspace = true } +uniffi = { workspace = true, features = ["cli"] } [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt"] } diff --git a/crates/ffi/src/bin/bindgen.rs b/crates/ffi/src/bin/bindgen.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::uniffi_bindgen_main(); +} diff --git a/docs/platform/swift.md b/docs/platform/swift.md @@ -0,0 +1,14 @@ +# Swift SDK FFI bindings + +`radroots_sdk_ffi` is the private Rust source of truth. Generate Swift source, +the C header/module map, and `source.lock` from the SDK root with: + +```sh +cargo extbuild run -- cargo xtask generate bindings swift +``` + +The generator builds the FFI library under the extbuild-owned target root, +extracts UniFFI metadata, writes deterministic text artifacts under +`generated/swift`, and binds every output hash to the exact FFI source hash. +Mobile keychain prompts, background execution, scheduling, and presentation +models are deliberately absent and remain owned by the Apple host. diff --git a/generated/swift/radroots_sdk.swift b/generated/swift/radroots_sdk.swift @@ -0,0 +1,1111 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +// swiftlint:disable all +import Foundation + +// Depending on the consumer's build setup, the low-level FFI code +// might be in a separate module, or it might be compiled inline into +// this module. This is a bit of light hackery to work with both. +#if canImport(radroots_sdkFFI) +import radroots_sdkFFI +#endif + +fileprivate extension RustBuffer { + // Allocate a new buffer, copying the contents of a `UInt8` array. + init(bytes: [UInt8]) { + let rbuf = bytes.withUnsafeBufferPointer { ptr in + RustBuffer.from(ptr) + } + self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) + } + + static func empty() -> RustBuffer { + RustBuffer(capacity: 0, len:0, data: nil) + } + + static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer { + try! rustCall { ffi_radroots_sdk_ffi_rustbuffer_from_bytes(ForeignBytes(bufferPointer: ptr), $0) } + } + + // Frees the buffer in place. + // The buffer must not be used after this is called. + func deallocate() { + try! rustCall { ffi_radroots_sdk_ffi_rustbuffer_free(self, $0) } + } +} + +fileprivate extension ForeignBytes { + init(bufferPointer: UnsafeBufferPointer<UInt8>) { + self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress) + } +} + +// 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 classes/extensions that don't change. +// Someday, this will be in a library of its own. + +fileprivate extension Data { + init(rustBuffer: RustBuffer) { + self.init( + bytesNoCopy: rustBuffer.data!, + count: Int(rustBuffer.len), + deallocator: .none + ) + } +} + +// Define reader functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. +// +// With external types, one swift source file needs to be able to call the read +// method on another source file's FfiConverter, but then what visibility +// should Reader have? +// - If Reader is fileprivate, then this means the read() must also +// be fileprivate, which doesn't work with external types. +// - If Reader is internal/public, we'll get compile errors since both source +// files will try define the same type. +// +// Instead, the read() method and these helper functions input a tuple of data + +fileprivate func createReader(data: Data) -> (data: Data, offset: Data.Index) { + (data: data, offset: 0) +} + +// Reads an integer at the current offset, in big-endian order, and advances +// the offset on success. Throws if reading the integer would move the +// offset past the end of the buffer. +fileprivate func readInt<T: FixedWidthInteger>(_ reader: inout (data: Data, offset: Data.Index)) throws -> T { + let range = reader.offset..<reader.offset + MemoryLayout<T>.size + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = reader.data[reader.offset] + reader.offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value, { reader.data.copyBytes(to: $0, from: range)}) + reader.offset = range.upperBound + return value.bigEndian +} + +// Reads an arbitrary number of bytes, to be used to read +// raw bytes, this is useful when lifting strings +fileprivate func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> Array<UInt8> { + let range = reader.offset..<(reader.offset+count) + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer({ buffer in + reader.data.copyBytes(to: buffer, from: range) + }) + reader.offset = range.upperBound + return value +} + +// Reads a float at the current offset. +fileprivate func readFloat(_ reader: inout (data: Data, offset: Data.Index)) throws -> Float { + return Float(bitPattern: try readInt(&reader)) +} + +// Reads a float at the current offset. +fileprivate func readDouble(_ reader: inout (data: Data, offset: Data.Index)) throws -> Double { + return Double(bitPattern: try readInt(&reader)) +} + +// Indicates if the offset has reached the end of the buffer. +fileprivate func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool { + return reader.offset < reader.data.count +} + +// Define writer functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. See the above discussion on Readers for details. + +fileprivate func createWriter() -> [UInt8] { + return [] +} + +fileprivate func writeBytes<S>(_ writer: inout [UInt8], _ byteArr: S) where S: Sequence, S.Element == UInt8 { + writer.append(contentsOf: byteArr) +} + +// Writes an integer in big-endian order. +// +// Warning: make sure what you are trying to write +// is in the correct type! +fileprivate func writeInt<T: FixedWidthInteger>(_ writer: inout [UInt8], _ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) } +} + +fileprivate func writeFloat(_ writer: inout [UInt8], _ value: Float) { + writeInt(&writer, value.bitPattern) +} + +fileprivate func writeDouble(_ writer: inout [UInt8], _ value: Double) { + writeInt(&writer, value.bitPattern) +} + +// Protocol for types that transfer other types across the FFI. This is +// analogous to the Rust trait of the same name. +fileprivate protocol FfiConverter { + associatedtype FfiType + associatedtype SwiftType + + static func lift(_ value: FfiType) throws -> SwiftType + static func lower(_ value: SwiftType) -> FfiType + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType + static func write(_ value: SwiftType, into buf: inout [UInt8]) +} + +// Types conforming to `Primitive` pass themselves directly over the FFI. +fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { } + +extension FfiConverterPrimitive { +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lift(_ value: FfiType) throws -> SwiftType { + return value + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lower(_ value: SwiftType) -> FfiType { + return value + } +} + +// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`. +// Used for complex types where it's hard to write a custom lift/lower. +fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {} + +extension FfiConverterRustBuffer { +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lift(_ buf: RustBuffer) throws -> SwiftType { + var reader = createReader(data: Data(rustBuffer: buf)) + let value = try read(from: &reader) + if hasRemaining(reader) { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public static func lower(_ value: SwiftType) -> RustBuffer { + var writer = createWriter() + write(value, into: &writer) + return RustBuffer(bytes: writer) + } +} +// An error type for FFI errors. These errors occur at the UniFFI level, not +// the library level. +fileprivate enum UniffiInternalError: LocalizedError { + case bufferOverflow + case incompleteData + case unexpectedOptionalTag + case unexpectedEnumCase + case unexpectedNullPointer + case unexpectedRustCallStatusCode + case unexpectedRustCallError + case unexpectedStaleHandle + case rustPanic(_ message: String) + + public var errorDescription: String? { + switch self { + case .bufferOverflow: return "Reading the requested value would read past the end of the buffer" + case .incompleteData: return "The buffer still has data after lifting its containing value" + case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1" + case .unexpectedEnumCase: return "Raw enum value doesn't match any cases" + case .unexpectedNullPointer: return "Raw pointer value was null" + case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code" + case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified" + case .unexpectedStaleHandle: return "The object in the handle map has been dropped already" + case let .rustPanic(message): return message + } + } +} + +fileprivate extension NSLock { + func withLock<T>(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} + +fileprivate let CALL_SUCCESS: Int8 = 0 +fileprivate let CALL_ERROR: Int8 = 1 +fileprivate let CALL_UNEXPECTED_ERROR: Int8 = 2 +fileprivate let CALL_CANCELLED: Int8 = 3 + +fileprivate extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer.init( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall<T>(_ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T { + let neverThrow: ((RustBuffer) throws -> Never)? = nil + return try makeRustCall(callback, errorHandler: neverThrow) +} + +private func rustCallWithError<T, E: Swift.Error>( + _ errorHandler: @escaping (RustBuffer) throws -> E, + _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T) throws -> T { + try makeRustCall(callback, errorHandler: errorHandler) +} + +private func makeRustCall<T, E: Swift.Error>( + _ callback: (UnsafeMutablePointer<RustCallStatus>) -> T, + errorHandler: ((RustBuffer) throws -> E)? +) throws -> T { + uniffiEnsureRadrootsSdkFfiInitialized() + var callStatus = RustCallStatus.init() + let returnedVal = callback(&callStatus) + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler) + return returnedVal +} + +private func uniffiCheckCallStatus<E: Swift.Error>( + callStatus: RustCallStatus, + errorHandler: ((RustBuffer) throws -> E)? +) throws { + switch callStatus.code { + case CALL_SUCCESS: + return + + case CALL_ERROR: + if let errorHandler = errorHandler { + throw try errorHandler(callStatus.errorBuf) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.unexpectedRustCallError + } + + case CALL_UNEXPECTED_ERROR: + // 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 callStatus.errorBuf.len > 0 { + throw UniffiInternalError.rustPanic(try FfiConverterString.lift(callStatus.errorBuf)) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.rustPanic("Rust panic") + } + + case CALL_CANCELLED: + fatalError("Cancellation not supported yet") + + default: + throw UniffiInternalError.unexpectedRustCallStatusCode + } +} + +private func uniffiTraitInterfaceCall<T>( + callStatus: UnsafeMutablePointer<RustCallStatus>, + makeCall: () throws -> T, + writeReturn: (T) -> () +) { + do { + try writeReturn(makeCall()) + } catch let error { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error)) + } +} + +private func uniffiTraitInterfaceCallWithError<T, E>( + callStatus: UnsafeMutablePointer<RustCallStatus>, + makeCall: () throws -> T, + writeReturn: (T) -> (), + lowerError: (E) -> RustBuffer +) { + do { + try writeReturn(makeCall()) + } catch let error as E { + callStatus.pointee.code = CALL_ERROR + callStatus.pointee.errorBuf = lowerError(error) + } catch { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error)) + } +} +fileprivate final class UniffiHandleMap<T>: @unchecked Sendable { + // All mutation happens with this lock held, which is why we implement @unchecked Sendable. + private let lock = NSLock() + private var map: [UInt64: T] = [:] + private var currentHandle: UInt64 = 1 + + func insert(obj: T) -> UInt64 { + lock.withLock { + let handle = currentHandle + currentHandle += 1 + map[handle] = obj + return handle + } + } + + func get(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map[handle] else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + @discardableResult + func remove(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map.removeValue(forKey: handle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + var count: Int { + get { + map.count + } + } +} + + +// Public interface members begin here. + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterBool : FfiConverter { + typealias FfiType = Int8 + typealias SwiftType = Bool + + public static func lift(_ value: Int8) throws -> Bool { + return value != 0 + } + + public static func lower(_ value: Bool) -> Int8 { + return value ? 1 : 0 + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Bool, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterString: FfiConverter { + typealias SwiftType = String + typealias FfiType = RustBuffer + + public static func lift(_ value: RustBuffer) throws -> String { + defer { + value.deallocate() + } + if value.data == nil { + return String() + } + let bytes = UnsafeBufferPointer<UInt8>(start: value.data!, count: Int(value.len)) + return String(bytes: bytes, encoding: String.Encoding.utf8)! + } + + public static func lower(_ value: String) -> RustBuffer { + return value.utf8CString.withUnsafeBufferPointer { ptr in + // The swift string gives us int8_t, we want uint8_t. + ptr.withMemoryRebound(to: UInt8.self) { ptr in + // The swift string gives us a trailing null byte, we don't want it. + let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1)) + return RustBuffer.from(buf) + } + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String { + let len: Int32 = try readInt(&buf) + return String(bytes: try readBytes(&buf, count: Int(len)), encoding: String.Encoding.utf8)! + } + + public static func write(_ value: String, into buf: inout [UInt8]) { + let len = Int32(value.utf8.count) + writeInt(&buf, len) + writeBytes(&buf, value.utf8) + } +} + + + + +/** + * Thread-safe mobile handle delegating lifecycle to [`radroots_sdk::Client`]. + */ +public protocol MobileClientProtocol: AnyObject, Sendable { + + /** + * Returns stable capability DTOs without probing host resources. + */ + func capabilities() -> [CapabilityStatus] + + /** + * Explicitly closes SDK-owned resources without installing a runtime. + */ + func close() async throws + + /** + * Returns whether explicit SDK close completed across all clones. + */ + func isClosed() -> Bool + +} +/** + * Thread-safe mobile handle delegating lifecycle to [`radroots_sdk::Client`]. + */ +open class MobileClient: MobileClientProtocol, @unchecked Sendable { + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public struct NoPointer { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + // This constructor can be used to instantiate a fake object. + // - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + // + // - Warning: + // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public init(noPointer: NoPointer) { + self.pointer = nil + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_radroots_sdk_ffi_fn_clone_mobileclient(self.pointer, $0) } + } + // No primary constructor declared for this class. + + deinit { + guard let pointer = pointer else { + return + } + + try! rustCall { uniffi_radroots_sdk_ffi_fn_free_mobileclient(pointer, $0) } + } + + + /** + * Creates a deterministic, local-only memory client without I/O. + */ +public static func memory()throws -> MobileClient { + return try FfiConverterTypeMobileClient_lift(try rustCallWithError(FfiConverterTypeError_lift) { + uniffi_radroots_sdk_ffi_fn_constructor_mobileclient_memory($0 + ) +}) +} + + + + /** + * Returns stable capability DTOs without probing host resources. + */ +open func capabilities() -> [CapabilityStatus] { + return try! FfiConverterSequenceTypeCapabilityStatus.lift(try! rustCall() { + uniffi_radroots_sdk_ffi_fn_method_mobileclient_capabilities(self.uniffiClonePointer(),$0 + ) +}) +} + + /** + * Explicitly closes SDK-owned resources without installing a runtime. + */ +open func close()async throws { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_radroots_sdk_ffi_fn_method_mobileclient_close( + self.uniffiClonePointer() + + ) + }, + pollFunc: ffi_radroots_sdk_ffi_rust_future_poll_void, + completeFunc: ffi_radroots_sdk_ffi_rust_future_complete_void, + freeFunc: ffi_radroots_sdk_ffi_rust_future_free_void, + liftFunc: { $0 }, + errorHandler: FfiConverterTypeError_lift + ) +} + + /** + * Returns whether explicit SDK close completed across all clones. + */ +open func isClosed() -> Bool { + return try! FfiConverterBool.lift(try! rustCall() { + uniffi_radroots_sdk_ffi_fn_method_mobileclient_is_closed(self.uniffiClonePointer(),$0 + ) +}) +} + + +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeMobileClient: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = MobileClient + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> MobileClient { + return MobileClient(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: MobileClient) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileClient { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: MobileClient, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMobileClient_lift(_ pointer: UnsafeMutableRawPointer) throws -> MobileClient { + return try FfiConverterTypeMobileClient.lift(pointer) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMobileClient_lower(_ value: MobileClient) -> UnsafeMutableRawPointer { + return FfiConverterTypeMobileClient.lower(value) +} + + + + +/** + * Versioned, presentation-independent capability observation. + */ +public struct CapabilityStatus { + public var id: String + public var compiled: Bool + public var configured: Bool + public var availability: CapabilityAvailability + public var maturity: CapabilityMaturity + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(id: String, compiled: Bool, configured: Bool, availability: CapabilityAvailability, maturity: CapabilityMaturity) { + self.id = id + self.compiled = compiled + self.configured = configured + self.availability = availability + self.maturity = maturity + } +} + +#if compiler(>=6) +extension CapabilityStatus: Sendable {} +#endif + + +extension CapabilityStatus: Equatable, Hashable { + public static func ==(lhs: CapabilityStatus, rhs: CapabilityStatus) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.compiled != rhs.compiled { + return false + } + if lhs.configured != rhs.configured { + return false + } + if lhs.availability != rhs.availability { + return false + } + if lhs.maturity != rhs.maturity { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(compiled) + hasher.combine(configured) + hasher.combine(availability) + hasher.combine(maturity) + } +} + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeCapabilityStatus: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CapabilityStatus { + return + try CapabilityStatus( + id: FfiConverterString.read(from: &buf), + compiled: FfiConverterBool.read(from: &buf), + configured: FfiConverterBool.read(from: &buf), + availability: FfiConverterTypeCapabilityAvailability.read(from: &buf), + maturity: FfiConverterTypeCapabilityMaturity.read(from: &buf) + ) + } + + public static func write(_ value: CapabilityStatus, into buf: inout [UInt8]) { + FfiConverterString.write(value.id, into: &buf) + FfiConverterBool.write(value.compiled, into: &buf) + FfiConverterBool.write(value.configured, into: &buf) + FfiConverterTypeCapabilityAvailability.write(value.availability, into: &buf) + FfiConverterTypeCapabilityMaturity.write(value.maturity, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCapabilityStatus_lift(_ buf: RustBuffer) throws -> CapabilityStatus { + return try FfiConverterTypeCapabilityStatus.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCapabilityStatus_lower(_ value: CapabilityStatus) -> RustBuffer { + return FfiConverterTypeCapabilityStatus.lower(value) +} + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Current side-effect-free capability availability. + */ + +public enum CapabilityAvailability { + + case available + case degraded + case unavailable + case unsupported +} + + +#if compiler(>=6) +extension CapabilityAvailability: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeCapabilityAvailability: FfiConverterRustBuffer { + typealias SwiftType = CapabilityAvailability + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CapabilityAvailability { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .available + + case 2: return .degraded + + case 3: return .unavailable + + case 4: return .unsupported + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: CapabilityAvailability, into buf: inout [UInt8]) { + switch value { + + + case .available: + writeInt(&buf, Int32(1)) + + + case .degraded: + writeInt(&buf, Int32(2)) + + + case .unavailable: + writeInt(&buf, Int32(3)) + + + case .unsupported: + writeInt(&buf, Int32(4)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCapabilityAvailability_lift(_ buf: RustBuffer) throws -> CapabilityAvailability { + return try FfiConverterTypeCapabilityAvailability.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCapabilityAvailability_lower(_ value: CapabilityAvailability) -> RustBuffer { + return FfiConverterTypeCapabilityAvailability.lower(value) +} + + +extension CapabilityAvailability: Equatable, Hashable {} + + + + + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +/** + * Stable capability maturity independent of runtime availability. + */ + +public enum CapabilityMaturity { + + case stable + case preview + case experimental +} + + +#if compiler(>=6) +extension CapabilityMaturity: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeCapabilityMaturity: FfiConverterRustBuffer { + typealias SwiftType = CapabilityMaturity + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CapabilityMaturity { + let variant: Int32 = try readInt(&buf) + switch variant { + + case 1: return .stable + + case 2: return .preview + + case 3: return .experimental + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: CapabilityMaturity, into buf: inout [UInt8]) { + switch value { + + + case .stable: + writeInt(&buf, Int32(1)) + + + case .preview: + writeInt(&buf, Int32(2)) + + + case .experimental: + writeInt(&buf, Int32(3)) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCapabilityMaturity_lift(_ buf: RustBuffer) throws -> CapabilityMaturity { + return try FfiConverterTypeCapabilityMaturity.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeCapabilityMaturity_lower(_ value: CapabilityMaturity) -> RustBuffer { + return FfiConverterTypeCapabilityMaturity.lower(value) +} + + +extension CapabilityMaturity: Equatable, Hashable {} + + + + + + + +/** + * Secret-safe SDK failure at the language boundary. + */ +public enum Error: Swift.Error { + + + + case Sdk(code: String, message: String, retryable: Bool + ) +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeError: FfiConverterRustBuffer { + typealias SwiftType = Error + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Error { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .Sdk( + code: try FfiConverterString.read(from: &buf), + message: try FfiConverterString.read(from: &buf), + retryable: try FfiConverterBool.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: Error, into buf: inout [UInt8]) { + switch value { + + + + + + case let .Sdk(code,message,retryable): + writeInt(&buf, Int32(1)) + FfiConverterString.write(code, into: &buf) + FfiConverterString.write(message, into: &buf) + FfiConverterBool.write(retryable, into: &buf) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeError_lift(_ buf: RustBuffer) throws -> Error { + return try FfiConverterTypeError.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeError_lower(_ value: Error) -> RustBuffer { + return FfiConverterTypeError.lower(value) +} + + +extension Error: Equatable, Hashable {} + + + + +extension Error: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + + + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceTypeCapabilityStatus: FfiConverterRustBuffer { + typealias SwiftType = [CapabilityStatus] + + public static func write(_ value: [CapabilityStatus], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeCapabilityStatus.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [CapabilityStatus] { + let len: Int32 = try readInt(&buf) + var seq = [CapabilityStatus]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeCapabilityStatus.read(from: &buf)) + } + return seq + } +} +private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0 +private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 + +fileprivate let uniffiContinuationHandleMap = UniffiHandleMap<UnsafeContinuation<Int8, Never>>() + +fileprivate func uniffiRustCallAsync<F, T>( + rustFutureFunc: () -> UInt64, + pollFunc: (UInt64, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (), + completeFunc: (UInt64, UnsafeMutablePointer<RustCallStatus>) -> F, + freeFunc: (UInt64) -> (), + liftFunc: (F) throws -> T, + errorHandler: ((RustBuffer) throws -> Swift.Error)? +) async throws -> T { + // Make sure to call the ensure init function since future creation doesn't have a + // RustCallStatus param, so doesn't use makeRustCall() + uniffiEnsureRadrootsSdkFfiInitialized() + let rustFuture = rustFutureFunc() + defer { + freeFunc(rustFuture) + } + var pollResult: Int8; + repeat { + pollResult = await withUnsafeContinuation { + pollFunc( + rustFuture, + uniffiFutureContinuationCallback, + uniffiContinuationHandleMap.insert(obj: $0) + ) + } + } while pollResult != UNIFFI_RUST_FUTURE_POLL_READY + + return try liftFunc(makeRustCall( + { completeFunc(rustFuture, $0) }, + errorHandler: errorHandler + )) +} + +// Callback handlers for an async calls. These are invoked by Rust when the future is ready. They +// lift the return value or error and resume the suspended function. +fileprivate func uniffiFutureContinuationCallback(handle: UInt64, pollResult: Int8) { + if let continuation = try? uniffiContinuationHandleMap.remove(handle: handle) { + continuation.resume(returning: pollResult) + } else { + print("uniffiFutureContinuationCallback invalid handle") + } +} + +private enum InitializationResult { + case ok + case contractVersionMismatch + case apiChecksumMismatch +} +// Use a global variable to perform the versioning checks. Swift ensures that +// the code inside is only computed once. +private let initializationResult: InitializationResult = { + // Get the bindings contract version from our ComponentInterface + let bindings_contract_version = 29 + // Get the scaffolding contract version by calling the into the dylib + let scaffolding_contract_version = ffi_radroots_sdk_ffi_uniffi_contract_version() + if bindings_contract_version != scaffolding_contract_version { + return InitializationResult.contractVersionMismatch + } + if (uniffi_radroots_sdk_ffi_checksum_method_mobileclient_capabilities() != 23938) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_radroots_sdk_ffi_checksum_method_mobileclient_close() != 15210) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_radroots_sdk_ffi_checksum_method_mobileclient_is_closed() != 40205) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_radroots_sdk_ffi_checksum_constructor_mobileclient_memory() != 50243) { + return InitializationResult.apiChecksumMismatch + } + + return InitializationResult.ok +}() + +// Make the ensure init function public so that other modules which have external type references to +// our types can call it. +public func uniffiEnsureRadrootsSdkFfiInitialized() { + switch initializationResult { + case .ok: + break + case .contractVersionMismatch: + fatalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + case .apiChecksumMismatch: + fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} + +// swiftlint:enable all diff --git a/generated/swift/radroots_sdkFFI.h b/generated/swift/radroots_sdkFFI.h @@ -0,0 +1,594 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + uint64_t capacity; + uint64_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H +#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +typedef void (*UniffiForeignFutureFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +typedef void (*UniffiCallbackInterfaceFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE +typedef struct UniffiForeignFuture { + uint64_t handle; + UniffiForeignFutureFree _Nonnull free; +} UniffiForeignFuture; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +typedef struct UniffiForeignFutureStructU8 { + uint8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureStructU8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +typedef struct UniffiForeignFutureStructI8 { + int8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureStructI8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +typedef struct UniffiForeignFutureStructU16 { + uint16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureStructU16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +typedef struct UniffiForeignFutureStructI16 { + int16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureStructI16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +typedef struct UniffiForeignFutureStructU32 { + uint32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureStructU32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +typedef struct UniffiForeignFutureStructI32 { + int32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureStructI32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +typedef struct UniffiForeignFutureStructU64 { + uint64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureStructU64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +typedef struct UniffiForeignFutureStructI64 { + int64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureStructI64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +typedef struct UniffiForeignFutureStructF32 { + float returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureStructF32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +typedef struct UniffiForeignFutureStructF64 { + double returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureStructF64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +typedef struct UniffiForeignFutureStructPointer { + void*_Nonnull returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructPointer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +typedef void (*UniffiForeignFutureCompletePointer)(uint64_t, UniffiForeignFutureStructPointer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +typedef struct UniffiForeignFutureStructRustBuffer { + RustBuffer returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructRustBuffer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureStructRustBuffer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +typedef struct UniffiForeignFutureStructVoid { + RustCallStatus callStatus; +} UniffiForeignFutureStructVoid; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureStructVoid + ); + +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_CLONE_MOBILECLIENT +#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_CLONE_MOBILECLIENT +void*_Nonnull uniffi_radroots_sdk_ffi_fn_clone_mobileclient(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_FREE_MOBILECLIENT +#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_FREE_MOBILECLIENT +void uniffi_radroots_sdk_ffi_fn_free_mobileclient(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_CONSTRUCTOR_MOBILECLIENT_MEMORY +#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_CONSTRUCTOR_MOBILECLIENT_MEMORY +void*_Nonnull uniffi_radroots_sdk_ffi_fn_constructor_mobileclient_memory(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_CAPABILITIES +#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_CAPABILITIES +RustBuffer uniffi_radroots_sdk_ffi_fn_method_mobileclient_capabilities(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_CLOSE +#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_CLOSE +uint64_t uniffi_radroots_sdk_ffi_fn_method_mobileclient_close(void*_Nonnull ptr +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_IS_CLOSED +#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_FN_METHOD_MOBILECLIENT_IS_CLOSED +int8_t uniffi_radroots_sdk_ffi_fn_method_mobileclient_is_closed(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_ALLOC +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_ALLOC +RustBuffer ffi_radroots_sdk_ffi_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_FROM_BYTES +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_FROM_BYTES +RustBuffer ffi_radroots_sdk_ffi_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_FREE +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_FREE +void ffi_radroots_sdk_ffi_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_RESERVE +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUSTBUFFER_RESERVE +RustBuffer ffi_radroots_sdk_ffi_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U8 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U8 +void ffi_radroots_sdk_ffi_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U8 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U8 +void ffi_radroots_sdk_ffi_rust_future_cancel_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U8 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U8 +void ffi_radroots_sdk_ffi_rust_future_free_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U8 +uint8_t ffi_radroots_sdk_ffi_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I8 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I8 +void ffi_radroots_sdk_ffi_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I8 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I8 +void ffi_radroots_sdk_ffi_rust_future_cancel_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I8 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I8 +void ffi_radroots_sdk_ffi_rust_future_free_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I8 +int8_t ffi_radroots_sdk_ffi_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U16 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U16 +void ffi_radroots_sdk_ffi_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U16 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U16 +void ffi_radroots_sdk_ffi_rust_future_cancel_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U16 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U16 +void ffi_radroots_sdk_ffi_rust_future_free_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U16 +uint16_t ffi_radroots_sdk_ffi_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I16 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I16 +void ffi_radroots_sdk_ffi_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I16 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I16 +void ffi_radroots_sdk_ffi_rust_future_cancel_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I16 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I16 +void ffi_radroots_sdk_ffi_rust_future_free_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I16 +int16_t ffi_radroots_sdk_ffi_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U32 +void ffi_radroots_sdk_ffi_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U32 +void ffi_radroots_sdk_ffi_rust_future_cancel_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U32 +void ffi_radroots_sdk_ffi_rust_future_free_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U32 +uint32_t ffi_radroots_sdk_ffi_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I32 +void ffi_radroots_sdk_ffi_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I32 +void ffi_radroots_sdk_ffi_rust_future_cancel_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I32 +void ffi_radroots_sdk_ffi_rust_future_free_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I32 +int32_t ffi_radroots_sdk_ffi_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_U64 +void ffi_radroots_sdk_ffi_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_U64 +void ffi_radroots_sdk_ffi_rust_future_cancel_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_U64 +void ffi_radroots_sdk_ffi_rust_future_free_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_U64 +uint64_t ffi_radroots_sdk_ffi_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_I64 +void ffi_radroots_sdk_ffi_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_I64 +void ffi_radroots_sdk_ffi_rust_future_cancel_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_I64 +void ffi_radroots_sdk_ffi_rust_future_free_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_I64 +int64_t ffi_radroots_sdk_ffi_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_F32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_F32 +void ffi_radroots_sdk_ffi_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_F32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_F32 +void ffi_radroots_sdk_ffi_rust_future_cancel_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_F32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_F32 +void ffi_radroots_sdk_ffi_rust_future_free_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_F32 +float ffi_radroots_sdk_ffi_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_F64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_F64 +void ffi_radroots_sdk_ffi_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_F64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_F64 +void ffi_radroots_sdk_ffi_rust_future_cancel_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_F64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_F64 +void ffi_radroots_sdk_ffi_rust_future_free_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_F64 +double ffi_radroots_sdk_ffi_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_POINTER +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_POINTER +void ffi_radroots_sdk_ffi_rust_future_poll_pointer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_POINTER +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_POINTER +void ffi_radroots_sdk_ffi_rust_future_cancel_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_POINTER +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_POINTER +void ffi_radroots_sdk_ffi_rust_future_free_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_POINTER +void*_Nonnull ffi_radroots_sdk_ffi_rust_future_complete_pointer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_RUST_BUFFER +void ffi_radroots_sdk_ffi_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER +void ffi_radroots_sdk_ffi_rust_future_cancel_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_RUST_BUFFER +void ffi_radroots_sdk_ffi_rust_future_free_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER +RustBuffer ffi_radroots_sdk_ffi_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_VOID +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_POLL_VOID +void ffi_radroots_sdk_ffi_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_VOID +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_CANCEL_VOID +void ffi_radroots_sdk_ffi_rust_future_cancel_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_VOID +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_FREE_VOID +void ffi_radroots_sdk_ffi_rust_future_free_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_RUST_FUTURE_COMPLETE_VOID +void ffi_radroots_sdk_ffi_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_CAPABILITIES +#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_CAPABILITIES +uint16_t uniffi_radroots_sdk_ffi_checksum_method_mobileclient_capabilities(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_CLOSE +#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_CLOSE +uint16_t uniffi_radroots_sdk_ffi_checksum_method_mobileclient_close(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_IS_CLOSED +#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_METHOD_MOBILECLIENT_IS_CLOSED +uint16_t uniffi_radroots_sdk_ffi_checksum_method_mobileclient_is_closed(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_CONSTRUCTOR_MOBILECLIENT_MEMORY +#define UNIFFI_FFIDEF_UNIFFI_RADROOTS_SDK_FFI_CHECKSUM_CONSTRUCTOR_MOBILECLIENT_MEMORY +uint16_t uniffi_radroots_sdk_ffi_checksum_constructor_mobileclient_memory(void + +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_UNIFFI_CONTRACT_VERSION +#define UNIFFI_FFIDEF_FFI_RADROOTS_SDK_FFI_UNIFFI_CONTRACT_VERSION +uint32_t ffi_radroots_sdk_ffi_uniffi_contract_version(void + +); +#endif diff --git a/generated/swift/radroots_sdkFFI.modulemap b/generated/swift/radroots_sdkFFI.modulemap @@ -0,0 +1,7 @@ +module radroots_sdkFFI { + header "radroots_sdkFFI.h" + export * + use "Darwin" + use "_Builtin_stdbool" + use "_Builtin_stdint" +} diff --git a/generated/swift/source.lock b/generated/swift/source.lock @@ -0,0 +1,14 @@ +{ + "schema_version": 1, + "source_package": "radroots_sdk_ffi", + "source_version": "0.1.0-alpha", + "source_sha256": "733c27226e29c3bea820e869e93f3916f1e1dbe9fc1cce30c15d31063ad5a033", + "generator": "uniffi", + "generator_version": "0.29.5", + "language": "swift", + "outputs": { + "radroots_sdk.swift": "0f81256c17243e485d9bca8f2dd4771c0ed2c2c1dc4fb03eb352389aab8599b0", + "radroots_sdkFFI.h": "511d3a22f969ef21d1a6d7357241756c2fda5d96adbb8bb17f2da5e8c65bcc54", + "radroots_sdkFFI.modulemap": "b18fa5af1567a940fc699afe371a3a4653dca206f3233bcdf322062ab8192170" + } +} diff --git a/tools/xtask/src/bindings.rs b/tools/xtask/src/bindings.rs @@ -0,0 +1,253 @@ +use std::{ + collections::BTreeMap, + env, fs, + path::{Path, PathBuf}, + process::Command, +}; + +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +use crate::fs::{workspace_root, write_if_changed}; + +const FFI_PACKAGE: &str = "radroots_sdk_ffi"; +const FFI_VERSION: &str = "0.1.0-alpha"; +const BINDGEN_VERSION: &str = "0.29.5"; + +#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] +struct SourceLock { + schema_version: u16, + source_package: String, + source_version: String, + source_sha256: String, + generator: String, + generator_version: String, + language: String, + outputs: BTreeMap<String, String>, +} + +pub fn generate(args: &[String]) -> Result<(), String> { + match args { + [language] if language == "swift" => generate_language("swift"), + [language] if language == "kotlin" => generate_language("kotlin"), + _ => Err("usage: cargo xtask generate bindings <swift|kotlin>".to_owned()), + } +} + +fn generate_language(language: &'static str) -> Result<(), String> { + let root = workspace_root()?; + build_bindgen(&root)?; + let target_dir = env::var_os("CARGO_TARGET_DIR") + .map(PathBuf::from) + .ok_or_else(|| "CARGO_TARGET_DIR must be set by extbuild for FFI generation".to_owned())?; + let library = target_dir.join("debug").join(format!( + "{}radroots_sdk_ffi{}", + env::consts::DLL_PREFIX, + env::consts::DLL_SUFFIX + )); + if !library.is_file() { + return Err(format!("missing built FFI library: {}", library.display())); + } + let bindgen = target_dir + .join("debug") + .join(format!("radroots-sdk-bindgen{}", env::consts::EXE_SUFFIX)); + if !bindgen.is_file() { + return Err(format!( + "missing FFI bindgen executable: {}", + bindgen.display() + )); + } + let temporary = tempfile::tempdir() + .map_err(|error| format!("failed to create binding output directory: {error}"))?; + let status = Command::new(&bindgen) + .args(["generate", "--library"]) + .arg(&library) + .args(["--crate", FFI_PACKAGE, "--language", language, "--out-dir"]) + .arg(temporary.path()) + .current_dir(&root) + .status() + .map_err(|error| format!("failed to start {}: {error}", bindgen.display()))?; + if !status.success() { + return Err(format!( + "{language} binding generation failed with {status}" + )); + } + install_outputs(&root, language, temporary.path())?; + println!("generated {language} bindings for {FFI_PACKAGE}"); + Ok(()) +} + +fn build_bindgen(root: &Path) -> Result<(), String> { + let cargo = env::var_os("CARGO").unwrap_or_else(|| "cargo".into()); + let status = Command::new(cargo) + .args([ + "build", + "--locked", + "-p", + FFI_PACKAGE, + "--lib", + "--bin", + "radroots-sdk-bindgen", + ]) + .current_dir(root) + .status() + .map_err(|error| format!("failed to start Cargo for FFI generation: {error}"))?; + if !status.success() { + return Err(format!( + "FFI library and bindgen build failed with {status}" + )); + } + Ok(()) +} + +fn install_outputs(root: &Path, language: &'static str, temporary: &Path) -> Result<(), String> { + let output_dir = root.join("generated").join(language); + fs::create_dir_all(&output_dir) + .map_err(|error| format!("failed to create {}: {error}", output_dir.display()))?; + let mut generated = fs::read_dir(temporary) + .map_err(|error| format!("failed to read {}: {error}", temporary.display()))? + .collect::<Result<Vec<_>, _>>() + .map_err(|error| format!("failed to enumerate {}: {error}", temporary.display()))?; + generated.sort_by_key(std::fs::DirEntry::file_name); + let mut outputs = BTreeMap::new(); + for entry in generated { + let path = entry.path(); + if !path.is_file() { + return Err(format!( + "unexpected generated binding entry: {}", + path.display() + )); + } + let name = entry + .file_name() + .into_string() + .map_err(|_| format!("non-UTF-8 generated binding path: {}", path.display()))?; + let contents = fs::read(&path) + .map_err(|error| format!("failed to read {}: {error}", path.display()))?; + let destination = output_dir.join(&name); + let text = String::from_utf8(contents) + .map_err(|error| format!("generated text binding is not UTF-8: {error}"))?; + let text = normalize_generated_text(&text); + outputs.insert( + name.clone(), + format!("{:x}", Sha256::digest(text.as_bytes())), + ); + write_if_changed(&destination, &text)?; + } + if outputs.is_empty() { + return Err(format!("{language} binding generator emitted no files")); + } + let lock = SourceLock { + schema_version: 1, + source_package: FFI_PACKAGE.to_owned(), + source_version: FFI_VERSION.to_owned(), + source_sha256: ffi_source_sha256(root)?, + generator: "uniffi".to_owned(), + generator_version: BINDGEN_VERSION.to_owned(), + language: language.to_owned(), + outputs, + }; + let mut rendered = serde_json::to_string_pretty(&lock) + .map_err(|error| format!("failed to render {language} source lock: {error}"))?; + rendered.push('\n'); + write_if_changed(&output_dir.join("source.lock"), &rendered).map(|_| ()) +} + +fn normalize_generated_text(contents: &str) -> String { + let mut normalized = contents + .lines() + .map(str::trim_end) + .collect::<Vec<_>>() + .join("\n"); + while normalized.ends_with('\n') { + normalized.pop(); + } + normalized.push('\n'); + normalized +} + +pub fn check(root: &Path) -> Result<(), String> { + check_language( + root, + "swift", + &[ + "radroots_sdk.swift", + "radroots_sdkFFI.h", + "radroots_sdkFFI.modulemap", + ], + ) +} + +fn check_language(root: &Path, language: &'static str, expected: &[&str]) -> Result<(), String> { + let output_dir = root.join("generated").join(language); + let lock_path = output_dir.join("source.lock"); + let raw = fs::read_to_string(&lock_path) + .map_err(|error| format!("failed to read {}: {error}", lock_path.display()))?; + let actual: SourceLock = serde_json::from_str(&raw) + .map_err(|error| format!("failed to parse {}: {error}", lock_path.display()))?; + let mut outputs = BTreeMap::new(); + for name in expected { + let path = output_dir.join(name); + let contents = fs::read(&path) + .map_err(|error| format!("failed to read {}: {error}", path.display()))?; + outputs.insert( + (*name).to_owned(), + format!("{:x}", Sha256::digest(contents)), + ); + } + let expected_lock = SourceLock { + schema_version: 1, + source_package: FFI_PACKAGE.to_owned(), + source_version: FFI_VERSION.to_owned(), + source_sha256: ffi_source_sha256(root)?, + generator: "uniffi".to_owned(), + generator_version: BINDGEN_VERSION.to_owned(), + language: language.to_owned(), + outputs, + }; + if actual != expected_lock { + return Err(format!( + "stale generated {language} bindings: {}", + lock_path.display() + )); + } + Ok(()) +} + +fn ffi_source_sha256(root: &Path) -> Result<String, String> { + let crate_dir = root.join("crates/ffi"); + let mut paths = vec![crate_dir.join("Cargo.toml"), crate_dir.join("README.md")]; + collect_files(&crate_dir.join("src"), &mut paths)?; + paths.sort(); + let mut hasher = Sha256::new(); + for path in paths { + let relative = path + .strip_prefix(root) + .map_err(|error| format!("failed to relativize {}: {error}", path.display()))?; + hasher.update(relative.to_string_lossy().as_bytes()); + hasher.update([0]); + hasher.update( + fs::read(&path) + .map_err(|error| format!("failed to read {}: {error}", path.display()))?, + ); + hasher.update([0]); + } + Ok(format!("{:x}", hasher.finalize())) +} + +fn collect_files(dir: &Path, paths: &mut Vec<PathBuf>) -> Result<(), String> { + let mut entries = fs::read_dir(dir) + .map_err(|error| format!("failed to read {}: {error}", dir.display()))? + .collect::<Result<Vec<_>, _>>() + .map_err(|error| format!("failed to enumerate {}: {error}", dir.display()))?; + entries.sort_by_key(std::fs::DirEntry::file_name); + for entry in entries { + let path = entry.path(); + if path.is_dir() { + collect_files(&path, paths)?; + } else if path.is_file() { + paths.push(path); + } + } + Ok(()) +} diff --git a/tools/xtask/src/check.rs b/tools/xtask/src/check.rs @@ -154,6 +154,7 @@ pub fn check() -> Result<(), String> { cli_host::check_cli_host()?; check_package_build_artifacts(&root)?; check_npm_pack_payloads(&root)?; + crate::bindings::check(&root)?; Ok(()) } @@ -166,7 +167,8 @@ pub fn architecture_ci(root: &Path) -> Result<(), String> { check_forbidden_packages(root)?; check_binding_crate_sources(root)?; check_package_source_metadata(root)?; - check_generated_outputs(root) + check_generated_outputs(root)?; + crate::bindings::check(root) } fn check_radroots_facade_conformance(root: &Path) -> Result<(), String> { diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs @@ -1,4 +1,5 @@ mod architecture; +mod bindings; mod check; mod cli_host; mod contracts; @@ -23,6 +24,7 @@ enum CommandAction<'a> { CheckApiBoundaries, CheckDependencyBoundaries, GenerateAll, + GenerateBindings(&'a [String]), GenerateTs, GenerateWasm(&'a [String]), GeneratePackageMetadata, @@ -54,6 +56,7 @@ fn run(args: impl IntoIterator<Item = String>) -> Result<(), String> { architecture::validate_dependency_boundaries(&fs::workspace_root()?) } CommandAction::GenerateAll => generate::generate_all(), + CommandAction::GenerateBindings(rest) => bindings::generate(rest), CommandAction::GenerateTs => generate::generate_ts(), CommandAction::GenerateWasm(rest) => wasm::generate(rest), CommandAction::GeneratePackageMetadata => generate::generate_package_metadata(), @@ -78,6 +81,9 @@ fn command_action(args: &[String]) -> Result<CommandAction<'_>, String> { [command, target, rest @ ..] if command == "generate" && target == "wasm" => { Ok(CommandAction::GenerateWasm(rest)) } + [command, target, rest @ ..] if command == "generate" && target == "bindings" => { + Ok(CommandAction::GenerateBindings(rest)) + } [command, target] if command == "generate" && target == "package-metadata" => { Ok(CommandAction::GeneratePackageMetadata) } @@ -90,7 +96,7 @@ fn command_action(args: &[String]) -> Result<CommandAction<'_>, String> { } fn usage() -> String { - "usage: cargo xtask architecture | cargo xtask architecture-ci | cargo xtask check-api-boundaries | cargo xtask check-dependency-boundaries | cargo xtask generate | cargo xtask generate ts | cargo xtask generate wasm [--package <key>] | cargo xtask generate package-metadata | cargo xtask check | cargo xtask smoke facade-rust-local | sdk-rust-local | front-doors-rust-local | cargo xtask coverage run" + "usage: cargo xtask architecture | cargo xtask architecture-ci | cargo xtask check-api-boundaries | cargo xtask check-dependency-boundaries | cargo xtask generate | cargo xtask generate ts | cargo xtask generate wasm [--package <key>] | cargo xtask generate bindings <swift|kotlin> | cargo xtask generate package-metadata | cargo xtask check | cargo xtask smoke facade-rust-local | sdk-rust-local | front-doors-rust-local | cargo xtask coverage run" .to_owned() } @@ -162,6 +168,19 @@ mod tests { } #[test] + fn accepts_generate_bindings() { + let args = [ + "generate".to_owned(), + "bindings".to_owned(), + "swift".to_owned(), + ]; + assert!(matches!( + command_action(&args).expect("action"), + CommandAction::GenerateBindings(rest) if rest == ["swift"] + )); + } + + #[test] fn accepts_generate_package_metadata() { let args = ["generate".to_owned(), "package-metadata".to_owned()]; assert!(matches!(