110 lines
4.3 KiB
Rust
110 lines
4.3 KiB
Rust
//! # `chanora_bridge`
|
|
//!
|
|
//! Typed Flutter/Rust bridge — schema-controlled DTOs for commands,
|
|
//! results, and events. Backed by `flutter_rust_bridge` 2.x per
|
|
//! DEC-014.
|
|
//!
|
|
//! ## Alpha scope
|
|
//!
|
|
//! Exposes three commands that target the Alpha release goal:
|
|
//!
|
|
//! * `bridge_init()` — one-time process initialisation. Sets up
|
|
//! logging.
|
|
//! * `connect(host, nickname)` — connects to a TS3-compatible
|
|
//! server and returns a typed [`ConnectResultDto`] containing the
|
|
//! server snapshot.
|
|
//! * `disconnect()` — clean disconnect.
|
|
//! * `snapshot()` — re-fetch the current server snapshot.
|
|
//!
|
|
//! No audio commands cross the bridge in Alpha; audio is Beta scope.
|
|
//!
|
|
//! ## Boundary discipline
|
|
//!
|
|
//! Every type in this module is `Serialize + Deserialize` over owned
|
|
//! primitives or `String`s. No `tsclientlib`, `cpal`, or backend
|
|
//! types may appear in the public surface (SAD-067, SDD-079).
|
|
//!
|
|
//! Note: this crate cannot use `#![forbid(unsafe_code)]` because the
|
|
//! FRB-generated glue (in `frb_generated`) legitimately uses unsafe
|
|
//! for the FFI boundary. Hand-written code in this crate must
|
|
//! nonetheless avoid `unsafe` and is held to that standard by review.
|
|
|
|
#![warn(missing_docs)]
|
|
|
|
pub mod api;
|
|
mod frb_generated;
|
|
|
|
#[cfg(target_os = "android")]
|
|
mod android_init;
|
|
|
|
use thiserror::Error;
|
|
|
|
/// Errors raised at the bridge boundary. Production code must keep
|
|
/// these user-safe — no secrets, no protocol details, no path
|
|
/// information beyond what the redaction policy permits.
|
|
#[derive(Debug, Error, Clone, serde::Serialize, serde::Deserialize)]
|
|
pub enum BridgeError {
|
|
/// The caller submitted a malformed command DTO.
|
|
#[error("invalid command: {0}")]
|
|
InvalidCommand(String),
|
|
/// Hostname resolution failed. Distinct from `Connection` so the
|
|
/// UI can show a meaningful "Server not found" message.
|
|
#[error("dns: could not resolve '{host}': {reason}")]
|
|
DnsFailed {
|
|
/// The hostname (or `host:port`) the caller submitted.
|
|
host: String,
|
|
/// Reason from the platform resolver.
|
|
reason: String,
|
|
},
|
|
/// Connection layer failure (typed-mapped from CoreError).
|
|
#[error("connection: {0}")]
|
|
Connection(String),
|
|
/// Operation requires an active connection.
|
|
#[error("not connected")]
|
|
NotConnected,
|
|
/// Already connected; DEC-006 forbids a second concurrent
|
|
/// connection.
|
|
#[error("already connected")]
|
|
AlreadyConnected,
|
|
/// A server command was rejected by the TeamSpeak server. The
|
|
/// `code` is the canonical TS3 error number (see
|
|
/// https://github.com/ReSpeak/tsdeclarations Errors.csv); the
|
|
/// `message` is the server-supplied text. The UI uses `code`
|
|
/// to look up a localised explanation.
|
|
#[error("server rejected (code {code}): {message}")]
|
|
ServerRejected {
|
|
/// Raw TS3 error code.
|
|
code: u32,
|
|
/// Server-supplied message text.
|
|
message: String,
|
|
},
|
|
/// An unmapped error escaped the subsystem boundary. Production
|
|
/// callers should never see this; if they do, it is a mapping
|
|
/// bug here.
|
|
#[error("unmapped: {0}")]
|
|
Unmapped(String),
|
|
}
|
|
|
|
impl From<chanora_core::CoreError> for BridgeError {
|
|
fn from(e: chanora_core::CoreError) -> Self {
|
|
match e {
|
|
chanora_core::CoreError::NotConnected => BridgeError::NotConnected,
|
|
chanora_core::CoreError::AlreadyConnected => BridgeError::AlreadyConnected,
|
|
chanora_core::CoreError::AudioNotStarted => {
|
|
BridgeError::InvalidCommand("audio not started".to_string())
|
|
}
|
|
chanora_core::CoreError::Protocol(chanora_protocol::ProtocolError::DnsFailed {
|
|
host,
|
|
reason,
|
|
}) => BridgeError::DnsFailed { host, reason },
|
|
chanora_core::CoreError::Protocol(
|
|
chanora_protocol::ProtocolError::ServerRejected { code, message },
|
|
) => BridgeError::ServerRejected { code, message },
|
|
chanora_core::CoreError::Protocol(p) => BridgeError::Connection(format!("{p}")),
|
|
chanora_core::CoreError::Audio(a) => BridgeError::Connection(format!("audio: {a}")),
|
|
chanora_core::CoreError::Storage(s) => BridgeError::Connection(format!("storage: {s}")),
|
|
other => BridgeError::Unmapped(format!("{other}")),
|
|
}
|
|
}
|
|
}
|