diff --git a/native/src/lib.rs b/native/src/lib.rs index f252be1..1b2f9b8 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -11,6 +11,7 @@ use log; use log::{error, info, Level}; use once_cell::sync::OnceCell; use std::collections::HashMap; +use std::fmt; use std::future::Future; use std::net::SocketAddr; use std::net::{Ipv4Addr, Ipv6Addr}; @@ -21,8 +22,6 @@ use tokio::runtime::Runtime; include!(concat!(env!("OUT_DIR"), "/talaria.uniffi.rs")); -type Result = std::result::Result>; - const LOG_MIN_LEVEL: Level = Level::Trace; const LOG_TAG: &str = ".native.talari"; const API_ID: i32 = { @@ -46,6 +45,25 @@ static RUNTIME: OnceCell = OnceCell::new(); static CLIENT: OnceCell = OnceCell::new(); static DATABASE: Mutex> = Mutex::new(None); +#[derive(Debug, Clone, Copy)] +pub enum NativeError { + Initialization, + Database, + Network, +} + +impl fmt::Display for NativeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Initialization => write!(f, "a resource could not be initialized correctly"), + Self::Database => write!(f, "a query to the local database failed"), + Self::Network => write!(f, "a network request could not complete successfully"), + } + } +} + +impl std::error::Error for NativeError {} + #[derive(Debug, Clone, Copy)] pub enum MessageAck { Received, @@ -69,6 +87,8 @@ pub struct Dialog { pinned: bool, } +type Result = std::result::Result; + fn block_on(future: F) -> F::Output { if RUNTIME.get().is_none() { RUNTIME @@ -100,7 +120,8 @@ async fn init_client() -> Result<()> { let conn = match guard.as_ref() { Some(c) => c, None => { - return Err("Database was not initialized".into()); + error!("Database was not initialized"); + return Err(NativeError::Initialization); } }; @@ -108,7 +129,7 @@ async fn init_client() -> Result<()> { let session = Session::new(); - let sessions = db::get_sessions(conn)?; + let sessions = db::get_sessions(conn).map_err(|_| NativeError::Database)?; if let Some(s) = sessions.get(0) { match (s.user_id, s.dc_id, s.bot) { (Some(id), Some(dc), Some(bot)) => session.set_user(id, dc, bot), @@ -147,41 +168,51 @@ async fn init_client() -> Result<()> { ..Default::default() }, }) - .await?; + .await + .map_err(|_| NativeError::Network)?; info!("Connected!"); CLIENT .set(client) - .map_err(|_| "Client was already initialized")?; + .map_err(|_| NativeError::Initialization)?; Ok(()) } async fn need_login() -> Result { - let client = CLIENT.get().ok_or("Client not initialized")?; - Ok(client.is_authorized().await?) + let client = CLIENT.get().ok_or(NativeError::Initialization)?; + client + .is_authorized() + .await + .map_err(|_| NativeError::Network) } async fn request_login_code(phone: &str) -> Result { - let client = CLIENT.get().ok_or("Client not initialized")?; - let token = client.request_login_code(&phone, API_ID, API_HASH).await?; - Ok(token) + let client = CLIENT.get().ok_or(NativeError::Initialization)?; + client + .request_login_code(&phone, API_ID, API_HASH) + .await + .map_err(|_| NativeError::Network) } async fn sign_in(token: LoginToken, code: &str) -> Result<()> { - let client = CLIENT.get().ok_or("Client not initialized")?; - client.sign_in(&token, &code).await?; + let client = CLIENT.get().ok_or(NativeError::Initialization)?; + client + .sign_in(&token, &code) + .await + .map_err(|_| NativeError::Network)?; let guard = DATABASE.lock().unwrap(); let conn = match guard.as_ref() { Some(c) => c, None => { - return Err("Database was not initialized".into()); + error!("Database was not initialized"); + return Err(NativeError::Initialization); } }; - let mut session = db::create_session(conn)?; + let mut session = db::create_session(conn).map_err(|_| NativeError::Database)?; let s = client.session(); if let Some(user) = s.get_user() { session.user_id = Some(user.id); @@ -211,79 +242,67 @@ async fn sign_in(token: LoginToken, code: &str) -> Result<()> { } } - db::update_session(conn, &session)?; + db::update_session(conn, &session).map_err(|_| NativeError::Database)?; Ok(()) } async fn get_dialogs() -> Result> { - let client = CLIENT.get().ok_or("Client not initialized")?; + let client = CLIENT.get().ok_or(NativeError::Initialization)?; let mut result = Vec::new(); let mut dialogs = client.iter_dialogs(); - while let Some(dialog) = dialogs.next().await? { + while let Some(dialog) = dialogs.next().await.map_err(|_| NativeError::Network)? { result.push(dialog); } Ok(result) } async fn send_message(chat: PackedChat, text: &str) -> Result<()> { - let client = CLIENT.get().ok_or("Client not initialized")?; - client.send_message(chat, text).await?; + let client = CLIENT.get().ok_or(NativeError::Initialization)?; + client + .send_message(chat, text) + .await + .map_err(|_| NativeError::Network)?; Ok(()) } -pub fn initDatabase(path: String) { +pub fn initDatabase(path: String) -> Result<()> { let mut guard = DATABASE.lock().unwrap(); if guard.is_some() { info!("Database is already initialized"); } match db::init_connection(&path) { - Ok(conn) => *guard = Some(conn), - Err(e) => error!("Failed to initialize database: {}", e), - } -} - -pub fn initClient() { - match block_on(init_client()) { - Ok(_) => info!("Client initialized"), - Err(e) => error!("Failed to initialize client: {}", e), - } -} - -pub fn needLogin() -> bool { - match block_on(need_login()) { - Ok(login) => login.into(), - Err(e) => { - error!("Failed to check if user is authorized: {}", e); - false.into() + Ok(conn) => { + *guard = Some(conn); + Ok(()) } + Err(e) => Err(NativeError::Database), } } -pub fn requestLoginCode(phone: String) -> u64 { - match block_on(request_login_code(&phone)) { - Ok(token) => Box::into_raw(Box::new(token)) as u64, - Err(e) => { - error!("Failed to request login code: {}", e); - 0 as u64 - } - } +pub fn initClient() -> Result<()> { + block_on(init_client()) } -pub fn signIn(token_ptr: u64, code: String) { +pub fn needLogin() -> Result { + block_on(need_login()) +} + +pub fn requestLoginCode(phone: String) -> Result { + block_on(request_login_code(&phone)).map(|token| Box::into_raw(Box::new(token)) as u64) +} + +pub fn signIn(token_ptr: u64, code: String) -> Result<()> { let token = unsafe { *Box::from_raw(token_ptr as *mut LoginToken) }; - match block_on(sign_in(token, &code)) { - Ok(_) => info!("Sign in success"), - Err(e) => error!("Failed to sign in: {}", e), - } + block_on(sign_in(token, &code)) } -pub fn getDialogs() -> Vec { - match block_on(get_dialogs()) { - Ok(dialogs) => dialogs +pub fn getDialogs() -> Result> { + block_on(get_dialogs()).map(|dialogs| { + dialogs .into_iter() .map(|d| Dialog { id: d.chat().pack().to_hex(), @@ -316,18 +335,11 @@ pub fn getDialogs() -> Vec { tl::enums::Dialog::Folder(f) => f.pinned, }, }) - .collect(), - Err(e) => { - error!("Failed to get dialogs: {}", e); - Vec::new() - } - } + .collect() + }) } -pub fn sendMessage(packed: String, text: String) { +pub fn sendMessage(packed: String, text: String) -> Result<()> { let packed = PackedChat::from_hex(&packed).unwrap(); - match block_on(send_message(packed, &text)) { - Ok(_) => info!("Message sent"), - Err(e) => error!("Failed to send message: {}", e), - } + block_on(send_message(packed, &text)) } diff --git a/native/src/talaria.udl b/native/src/talaria.udl index 7918645..41f5157 100644 --- a/native/src/talaria.udl +++ b/native/src/talaria.udl @@ -1,3 +1,10 @@ +[Error] +enum NativeError { + "Initialization", + "Database", + "Network", +}; + enum MessageAck { "Received", "Seen", @@ -19,11 +26,18 @@ dictionary Dialog { }; namespace talaria { + [Throws=NativeError] void initDatabase(string path); + [Throws=NativeError] void initClient(); + [Throws=NativeError] boolean needLogin(); + [Throws=NativeError] u64 requestLoginCode(string phone); + [Throws=NativeError] void signIn(u64 tokenPtr, string code); + [Throws=NativeError] sequence getDialogs(); + [Throws=NativeError] void sendMessage(string packed, string text); };