From d11cf45a7bd7550ace0d2213452f45ee8e246d15 Mon Sep 17 00:00:00 2001 From: xaneets Date: Sat, 29 Nov 2025 21:13:55 +0300 Subject: [PATCH 1/2] improve result types --- Cargo.toml | 5 +- README.md | 2 +- src/client.rs | 329 ++++++++++++++++++++++++++++++-------------------- src/error.rs | 8 +- src/lib.rs | 2 +- src/models.rs | 16 ++- tests/e2e.rs | 236 +++++++++++++++++------------------- 7 files changed, 330 insertions(+), 268 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9e76b25..6263f8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,13 @@ reqwest = { version = "0.12.12", features = ["json", "cookies", "multipart"] } serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.138" serde_path_to_error = "0.1.17" -thiserror = "2.0.11" serde_with = { version = "3.14.0", features = ["json"] } +thiserror = "2.0.11" +uuid = { version = "1", features = ["v4", "serde"] } [dev-dependencies] +anyhow = "1.0.99" dotenv = "0.15.0" env_logger = "0.11.6" tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] } -uuid = { version = "1", features = ["v4"] } diff --git a/README.md b/README.md index 0e85555..df8903e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Provides typed models and high-level methods for common panel operations. ## Implemented endpoints -- ✅ login +- ✅ Login - ✅ Inbounds - ✅ Inbound - ✅ Client traffics with email diff --git a/src/client.rs b/src/client.rs index 154ea8c..a866791 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,15 +4,18 @@ use super::{ ClientIpsResponse, ClientsStatsResponse, ClientsStatsVecResponse, CpuHistoryResponse, DeleteInboundResponse, InboundResponse, InboundsResponse, JsonResponse, NullObjectResponse, OnlineClientsResponse, OptStringVecResponse, Result, StringResponse, StringVecResponse, - UuidRespose, + UuidResponse, }; use crate::error::Error; -use crate::models::{ClientRequest, CreateInboundRequest, Inbounds}; +use crate::models::{ + ClientRequest, ClientStats, CpuHistoryPoint, CreateInboundRequest, Inbounds, Uuid, +}; use crate::response_ext::ResponseJsonVerboseExt; use log::debug; use reqwest::multipart::{Form, Part}; use reqwest::{Client as RClient, IntoUrl, StatusCode, Url}; use serde::Serialize; +use serde_json::Value; type LoginResponse = NullObjectResponse; @@ -98,31 +101,55 @@ impl Client { Ok(response.json().await?) } - pub async fn get_inbounds_list(&self) -> Result { + pub async fn get_inbounds_list(&self) -> Result> { let path = vec!["list"]; - let res = self.client.get(self.gen_inbounds_url(path)?).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: InboundsResponse = self + .client + .get(self.gen_inbounds_url(path)?) + .send() + .await? + .json_verbose() + .await?; + res.into_result() } - pub async fn get_inbound_by_id(&self, inbound_id: u64) -> Result { + pub async fn get_inbound_by_id(&self, inbound_id: u64) -> Result { let id = inbound_id.to_string(); let path = vec!["get", &id]; - let res = self.client.get(self.gen_inbounds_url(path)?).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: InboundResponse = self + .client + .get(self.gen_inbounds_url(path)?) + .send() + .await? + .json_verbose() + .await?; + res.into_result() } - pub async fn get_client_traffic_by_email(&self, email: String) -> Result { - let path = vec!["getClientTraffics", &email]; - let res = self.client.get(self.gen_inbounds_url(path)?).send().await?; // todo check is null return user not found - res.json_verbose().await.map_err(Into::into) + pub async fn get_client_traffic_by_email(&self, email: impl AsRef) -> Result { + let path = vec!["getClientTraffics", email.as_ref()]; + let res: ClientsStatsResponse = self + .client + .get(self.gen_inbounds_url(path)?) + .send() + .await? + .json_verbose() + .await?; // todo check is null return user not found + res.into_result() } - pub async fn get_client_traffic_by_id(&self, id: String) -> Result { + pub async fn get_client_traffic_by_id(&self, id: impl AsRef) -> Result> { // todo id to uuid - let id = id.to_string(); - let path = vec!["getClientTrafficsById", &id]; - let res = self.client.get(self.gen_inbounds_url(path)?).send().await?; - res.json_verbose().await.map_err(Into::into) + let id = id.as_ref(); + let path = vec!["getClientTrafficsById", id]; + let res: ClientsStatsVecResponse = self + .client + .get(self.gen_inbounds_url(path)?) + .send() + .await? + .json_verbose() + .await?; + res.into_result() } pub async fn send_backup_by_bot(&self) -> Result<()> { @@ -135,126 +162,155 @@ impl Client { Ok(()) } - pub async fn get_client_ips(&self, client_email: String) -> Result { + pub async fn get_client_ips(&self, client_email: impl AsRef) -> Result { // todo tests - let path = vec!["clientIps", &client_email]; - let res = self.client.post(self.gen_inbounds_url(path)?).send().await?; + let path = vec!["clientIps", client_email.as_ref()]; + let res = self + .client + .post(self.gen_inbounds_url(path)?) + .send() + .await?; res.json_verbose().await.map_err(Into::into) } - pub async fn add_inbound(&self, req: &CreateInboundRequest) -> Result { + pub async fn add_inbound(&self, req: &CreateInboundRequest) -> Result { let url = self.gen_inbounds_url(vec!["add"])?; - let res = self.client.post(url).json(req).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: InboundResponse = self + .client + .post(url) + .json(req) + .send() + .await? + .json_verbose() + .await?; + res.into_result() } - pub async fn add_client_to_inbound(&self, req: &ClientRequest) -> Result { + pub async fn add_client_to_inbound(&self, req: &ClientRequest) -> Result> { let url = self.gen_inbounds_url(vec!["addClient"])?; - let res = self.client.post(url).json(req).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self + .client + .post(url) + .json(req) + .send() + .await? + .json_verbose() + .await?; + res.into_result() } pub async fn update_inbound( &self, inbound_id: u64, req: &CreateInboundRequest, - ) -> Result { + ) -> Result { let url = self.gen_inbounds_url(vec!["update", &inbound_id.to_string()])?; - let res = self.client.post(url).json(req).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: InboundResponse = self + .client + .post(url) + .json(req) + .send() + .await? + .json_verbose() + .await?; + res.into_result() } - pub async fn update_client( - &self, - uuid: &str, - req: &ClientRequest, - ) -> Result { + pub async fn update_client(&self, uuid: &str, req: &ClientRequest) -> Result> { let url = self.gen_inbounds_url(vec!["updateClient", uuid])?; - let res = self.client.post(url).json(req).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self + .client + .post(url) + .json(req) + .send() + .await? + .json_verbose() + .await?; + res.into_result() } - pub async fn clear_client_ips(&self, email: &str) -> Result { + pub async fn clear_client_ips(&self, email: &str) -> Result> { let url = self.gen_inbounds_url(vec!["clearClientIps", email])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn reset_all_inbound_traffics(&self) -> Result { + pub async fn reset_all_inbound_traffics(&self) -> Result> { let url = self.gen_inbounds_url(vec!["resetAllTraffics"])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn reset_all_client_traffics(&self, inbound_id: u64) -> Result { + pub async fn reset_all_client_traffics(&self, inbound_id: u64) -> Result> { let url = self.gen_inbounds_url(vec!["resetAllClientTraffics", &inbound_id.to_string()])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn reset_client_traffic( - &self, - inbound_id: u64, - email: &str, - ) -> Result { - let url = self.gen_inbounds_url(vec![&inbound_id.to_string(), "resetClientTraffic", email])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + pub async fn reset_client_traffic(&self, inbound_id: u64, email: &str) -> Result> { + let url = + self.gen_inbounds_url(vec![&inbound_id.to_string(), "resetClientTraffic", email])?; + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn delete_client(&self, inbound_id: u64, uuid: &str) -> Result { + pub async fn delete_client(&self, inbound_id: u64, uuid: &str) -> Result> { let url = self.gen_inbounds_url(vec![&inbound_id.to_string(), "delClient", uuid])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn delete_inbound(&self, inbound_id: u64) -> Result { + pub async fn delete_inbound(&self, inbound_id: u64) -> Result { let url = self.gen_inbounds_url(vec!["del", &inbound_id.to_string()])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: DeleteInboundResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn delete_depleted_clients(&self, inbound_id: u64) -> Result { + pub async fn delete_depleted_clients(&self, inbound_id: u64) -> Result> { let url = self.gen_inbounds_url(vec!["delDepletedClients", &inbound_id.to_string()])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn online_clients(&self) -> Result { + pub async fn online_clients(&self) -> Result>> { let url = self.gen_inbounds_url(vec!["onlines"])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: OnlineClientsResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn import_inbound(&self, inbound: &Inbounds) -> Result { + pub async fn import_inbound(&self, inbound: &Inbounds) -> Result { let url = self.gen_inbounds_url(vec!["import"])?; let json_str = serde_json::to_string(inbound) .map_err(|e| Error::OtherError(format!("serialize inbound: {e}")))?; let form = Form::new().text("data", json_str); - let res = self.client.post(url).multipart(form).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: InboundResponse = self + .client + .post(url) + .multipart(form) + .send() + .await? + .json_verbose() + .await?; + res.into_result() } - pub async fn get_last_online(&self) -> Result { + pub async fn get_last_online(&self) -> Result>> { let url = self.gen_inbounds_url(vec!["onlines"])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: OptStringVecResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn del_client_by_email( - &self, - inbound_id: u64, - email: &str, - ) -> Result { - let url = self.gen_inbounds_url(vec![&inbound_id.to_string(), "delClientByEmail", email])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + pub async fn del_client_by_email(&self, inbound_id: u64, email: &str) -> Result> { + let url = + self.gen_inbounds_url(vec![&inbound_id.to_string(), "delClientByEmail", email])?; + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn server_status(&self) -> Result { + pub async fn server_status(&self) -> Result { let url = self.gen_server_url(vec!["status"])?; - let res = self.client.get(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: JsonResponse = self.client.get(url).send().await?.json_verbose().await?; + res.into_result() } pub async fn server_get_db(&self) -> Result> { @@ -263,106 +319,113 @@ impl Client { Ok(res.bytes().await?.to_vec()) } - pub async fn get_xray_version(&self) -> Result { + pub async fn get_xray_version(&self) -> Result> { let url = self.gen_server_url(vec!["getXrayVersion"])?; - let res = self.client.get(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: StringVecResponse = self.client.get(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn get_config_json(&self) -> Result { + pub async fn get_config_json(&self) -> Result { let url = self.gen_server_url(vec!["getConfigJson"])?; - let res = self.client.get(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: JsonResponse = self.client.get(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn cpu_history(&self, minutes: u32) -> Result { + pub async fn cpu_history(&self, minutes: u32) -> Result> { let url = self.gen_server_url(vec!["cpuHistory", &minutes.to_string()])?; - let res = self.client.get(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: CpuHistoryResponse = self.client.get(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn get_new_uuid(&self) -> Result { + pub async fn get_new_uuid(&self) -> Result { let url = self.gen_server_url(vec!["getNewUUID"])?; - let res = self.client.get(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: UuidResponse = self.client.get(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn get_new_x25519_cert(&self) -> Result { + pub async fn get_new_x25519_cert(&self) -> Result { let url = self.gen_server_url(vec!["getNewX25519Cert"])?; - let res = self.client.get(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: JsonResponse = self.client.get(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn get_new_mldsa65(&self) -> Result { + pub async fn get_new_mldsa65(&self) -> Result { let url = self.gen_server_url(vec!["getNewmldsa65"])?; - let res = self.client.get(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: JsonResponse = self.client.get(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn get_new_mlkem768(&self) -> Result { + pub async fn get_new_mlkem768(&self) -> Result { let url = self.gen_server_url(vec!["getNewmlkem768"])?; - let res = self.client.get(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: JsonResponse = self.client.get(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn get_new_vless_enc(&self) -> Result { + pub async fn get_new_vless_enc(&self) -> Result { let url = self.gen_server_url(vec!["getNewVlessEnc"])?; - let res = self.client.get(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: JsonResponse = self.client.get(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn get_new_ech_cert(&self) -> Result { + pub async fn get_new_ech_cert(&self) -> Result { let url = self.gen_server_url(vec!["getNewEchCert"])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: JsonResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn stop_xray_service(&self) -> Result { + pub async fn stop_xray_service(&self) -> Result> { let url = self.gen_server_url(vec!["stopXrayService"])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn restart_xray_service(&self) -> Result { + pub async fn restart_xray_service(&self) -> Result> { let url = self.gen_server_url(vec!["restartXrayService"])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn install_xray_version(&self, version: &str) -> Result { + pub async fn install_xray_version(&self, version: &str) -> Result> { let url = self.gen_server_url(vec!["installXray", version])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn update_geofile(&self) -> Result { + pub async fn update_geofile(&self) -> Result> { let url = self.gen_server_url(vec!["updateGeofile"])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn update_geofile_by_name(&self, file_name: &str) -> Result { + pub async fn update_geofile_by_name(&self, file_name: &str) -> Result> { let url = self.gen_server_url(vec!["updateGeofile", file_name])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: NullObjectResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn logs(&self, count: u32) -> Result { + pub async fn logs(&self, count: u32) -> Result> { let url = self.gen_server_url(vec!["logs", &count.to_string()])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: StringVecResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn xray_logs(&self, count: u32) -> Result { + pub async fn xray_logs(&self, count: u32) -> Result>> { let url = self.gen_server_url(vec!["xraylogs", &count.to_string()])?; - let res = self.client.post(url).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: OptStringVecResponse = self.client.post(url).send().await?.json_verbose().await?; + res.into_result() } - pub async fn import_db_upload(&self, filename: &str, bytes: Vec) -> Result { + pub async fn import_db_upload(&self, filename: &str, bytes: Vec) -> Result { let url = self.gen_server_url(vec!["importDB"])?; let form = Form::new().part("db", Part::bytes(bytes).file_name(filename.to_string())); - let res = self.client.post(url).multipart(form).send().await?; - res.json_verbose().await.map_err(Into::into) + let res: StringResponse = self + .client + .post(url) + .multipart(form) + .send() + .await? + .json_verbose() + .await?; + res.into_result() } } diff --git a/src/error.rs b/src/error.rs index d0cab4f..67f3aaa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,10 +20,10 @@ pub enum Error { impl From for Error { fn from(err: reqwest::Error) -> Self { - if let Some(status) = err.status() { - if status == StatusCode::NOT_FOUND { - return Error::NotFound(err); - } + if let Some(status) = err.status() + && status == StatusCode::NOT_FOUND + { + return Error::NotFound(err); } Error::Connection(err) } diff --git a/src/lib.rs b/src/lib.rs index fb9e177..c43c0e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,4 +27,4 @@ pub type JsonResponse = Response; pub type OptStringVecResponse = Response>>; pub type StringVecResponse = Response>; pub type CpuHistoryResponse = Response>; -pub type UuidRespose = Response; +pub type UuidResponse = Response; diff --git a/src/models.rs b/src/models.rs index e28a0b7..fe789f4 100644 --- a/src/models.rs +++ b/src/models.rs @@ -20,6 +20,14 @@ impl Response { pub fn is_err(&self) -> bool { self.success.not() } + + pub fn into_result(self) -> crate::Result { + if self.success { + Ok(self.object) + } else { + Err(crate::error::Error::OtherError(self.message)) + } + } } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -54,13 +62,13 @@ pub struct Inbounds { pub protocol: InboundProtocols, #[serde( deserialize_with = "de_settings_from_str_or_map", - serialize_with = "se_settings_as_str" + serialize_with = "se_settings_as_str" )] pub settings: Settings, #[serde(rename = "streamSettings")] pub stream_settings: String, // todo pub tag: String, - pub sniffing: String, // todo + pub sniffing: String, // todo pub allocate: Option, // todo } @@ -172,5 +180,5 @@ pub struct CpuHistoryPoint { #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Uuid { - pub uuid: String -} \ No newline at end of file + pub uuid: uuid::Uuid, +} diff --git a/tests/e2e.rs b/tests/e2e.rs index fec0a1f..9a5df0f 100644 --- a/tests/e2e.rs +++ b/tests/e2e.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use dotenv::dotenv; use std::env; use tokio::time::{Duration, sleep}; @@ -11,7 +12,7 @@ use rustix3::{ }; #[tokio::test] -async fn e2e_full_flow() { +async fn e2e_full_flow() -> anyhow::Result<()> { dotenv().ok(); env_logger::init(); @@ -20,12 +21,11 @@ async fn e2e_full_flow() { let user = env::var("PANEL_USERNAME").unwrap_or_else(|_| "admin".into()); let pass = env::var("PANEL_PASSWORD").unwrap_or_else(|_| "admin".into()); - let client = Client::new(user, pass, base).await.expect("login"); + let client = Client::new(user, pass, base).await.context("login")?; log::info!("connected"); - let list_before = client.get_inbounds_list().await.expect("list"); + let list_before = client.get_inbounds_list().await.context("list")?; log::info!("list_before = {:#?}", list_before); - assert!(list_before.is_ok()); let remark = format!("e2e-{}", Uuid::new_v4()); let req = CreateInboundRequest { @@ -48,30 +48,26 @@ async fn e2e_full_flow() { allocate: "{}".into(), }; - let created = client.add_inbound(&req).await.expect("add_inbound"); + let created = client.add_inbound(&req).await.context("add_inbound")?; - assert!(created.is_ok()); - - let inbounds = client.get_inbounds_list().await.expect("list"); + let inbounds = client.get_inbounds_list().await.context("list")?; log::info!("inbounds = {:#?}", inbounds); - let inbound_id = created.object.id; + let inbound_id = created.id; let by_id = client .get_inbound_by_id(inbound_id) .await - .expect("get_by_id"); - assert!(by_id.is_ok()); - assert_eq!(by_id.object.remark, remark); + .context("get_by_id")?; + assert_eq!(by_id.remark, remark); let mut updated_req = req; updated_req.remark = format!("{}-upd", remark); let updated = client .update_inbound(inbound_id, &updated_req) .await - .expect("update_inbound"); - assert!(updated.is_ok()); - assert_eq!(updated.object.remark, updated_req.remark); + .context("update_inbound")?; + assert_eq!(updated.remark, updated_req.remark); let cuuid = Uuid::new_v4().to_string(); let email = format!("{}@example.com", cuuid); @@ -93,13 +89,12 @@ async fn e2e_full_flow() { clients: vec![user_obj.clone()], }, }; - let add_client = client + client .add_client_to_inbound(&add_client_req) .await - .expect("add_client"); - assert!(add_client.is_ok()); + .context("add_client")?; - let inbounds = client.get_inbounds_list().await.expect("list"); + let inbounds = client.get_inbounds_list().await.context("list")?; log::info!("inbounds = {:#?}", inbounds); sleep(Duration::from_millis(200)).await; @@ -107,15 +102,14 @@ async fn e2e_full_flow() { let traffic_by_email = client .get_client_traffic_by_email(email.clone()) .await - .expect("traffic_by_email"); - assert!(traffic_by_email.is_ok()); - assert_eq!(traffic_by_email.object.email, email); + .context("traffic_by_email")?; + assert_eq!(traffic_by_email.email, email); let traffic_by_id = client .get_client_traffic_by_id(cuuid.clone()) .await - .expect("traffic_by_id"); - assert!(traffic_by_id.is_ok()); + .context("traffic_by_id")?; + log::info!("traffic_by_id = {:#?}", traffic_by_id); let mut updated_user = user_obj; updated_user.limit_ip = 1; @@ -125,35 +119,31 @@ async fn e2e_full_flow() { clients: vec![updated_user], }, }; - let upd_client = client + client .update_client(&cuuid, &upd_client_req) .await - .expect("update_client"); - assert!(upd_client.is_ok()); + .context("update_client")?; - let clear_ips = client.clear_client_ips(&email).await.expect("clear_ips"); - assert!(clear_ips.is_ok()); + client.clear_client_ips(&email).await.context("clear_ips")?; - let reset_client = client + client .reset_client_traffic(inbound_id, &email) .await - .expect("reset_client"); - assert!(reset_client.is_ok()); + .context("reset_client")?; - let reset_all_clients = client + client .reset_all_client_traffics(inbound_id) .await - .expect("reset_all_clients"); - assert!(reset_all_clients.is_ok()); + .context("reset_all_clients")?; - let reset_all_inbounds = client + client .reset_all_inbound_traffics() .await - .expect("reset_all_inbounds"); - assert!(reset_all_inbounds.is_ok()); + .context("reset_all_inbounds")?; + + let onlines = client.online_clients().await.context("online_clients")?; - let onlines = client.online_clients().await.expect("online_clients"); - assert!(onlines.is_ok()); + log::info!("onlines = {:#?}", onlines); let cuuid = Uuid::new_v4().to_string(); let email = format!("{}@example.com", cuuid); @@ -175,42 +165,39 @@ async fn e2e_full_flow() { clients: vec![user_obj.clone()], }, }; - let add_client = client + client .add_client_to_inbound(&add_client_req) .await - .expect("add_client"); - assert!(add_client.is_ok()); + .context("add_client")?; - let inbounds = client.get_inbounds_list().await.expect("list"); + let inbounds = client.get_inbounds_list().await.context("list")?; log::info!("inbounds = {:#?}", inbounds); - let del_client = client + client .delete_client(inbound_id, &cuuid) .await - .expect("delete_client"); - assert!(del_client.is_ok()); + .context("delete_client")?; - let inbounds = client.get_inbounds_list().await.expect("list"); + let inbounds = client.get_inbounds_list().await.context("list")?; log::info!("inbounds = {:#?}", inbounds); - let del_depleted = client + client .delete_depleted_clients(inbound_id) .await - .expect("delete_depleted"); - assert!(del_depleted.is_ok()); + .context("delete_depleted")?; let del_inbound = client .delete_inbound(inbound_id) .await - .expect("delete_inbound"); - assert!(del_inbound.is_ok()); + .context("delete_inbound")?; + + log::info!("del_inbound = {:#?}", del_inbound); - let list_after = client.get_inbounds_list().await.expect("list_after"); - assert!(list_after.is_ok()); + let list_after = client.get_inbounds_list().await.context("list_after")?; log::info!("list_after = {:#?}", list_after); - let last_online = client.get_last_online().await.expect("last_online"); - assert!(last_online.is_ok()); + let last_online = client.get_last_online().await.context("last_online")?; + log::info!("last_online = {:#?}", last_online); let cuuid = Uuid::new_v4().to_string(); let email = "testclient".to_string(); @@ -265,110 +252,111 @@ async fn e2e_full_flow() { let tmp_created = client .add_inbound(&tmp_inb_req) .await - .expect("add_inbound_tmp"); - assert!(tmp_created.is_ok()); - let tmp_inbound_id = tmp_created.object.id; + .context("add_inbound_tmp")?; + let tmp_inbound_id = tmp_created.id; - let tmp = client.get_inbounds_list().await.expect("tmp inbound"); - assert!(tmp.is_ok()); + let tmp = client.get_inbounds_list().await.context("tmp inbound")?; log::info!("tmp inbound = {:#?}", tmp); - let del_by_email = client + client .del_client_by_email(tmp_inbound_id, &email) .await - .expect("del_client_by_email"); - - assert!(del_by_email.is_ok()); + .context("del_client_by_email")?; let res = client .delete_inbound(tmp_inbound_id) .await - .expect("del_tmp_inbound"); - - assert!(res.is_ok()); + .context("del_tmp_inbound")?; + log::info!("delete_inbound = {:#?}", res); - let srv_status = client.server_status().await.expect("server_status"); - assert!(srv_status.is_ok()); + let srv_status = client.server_status().await.context("server_status")?; + log::info!("srv_status = {:#?}", srv_status); - let db_bytes = client.server_get_db().await.expect("server_get_db"); + let db_bytes = client.server_get_db().await.context("server_get_db")?; assert!(!db_bytes.is_empty(), "db should not be empty"); let imported_db = client .import_db_upload("file", db_bytes.clone()) .await - .expect("import_db_upload"); - assert!(imported_db.is_ok()); + .context("import_db_upload")?; + log::info!("imported_db = {:#?}", imported_db); - let xver = client.get_xray_version().await.expect("xray_version"); - assert!(xver.is_ok()); - let current_version = xver.object.clone(); + let xver = client.get_xray_version().await.context("xray_version")?; + let current_version = xver.clone(); - let cfg = client.get_config_json().await.expect("get_config_json"); - assert!(cfg.is_ok()); + let cfg = client.get_config_json().await.context("get_config_json")?; + log::info!("cfg = {:#?}", cfg); - let cpu_hist = client.cpu_history(2).await.expect("cpu_history_1min"); // todo bucket - assert!(cpu_hist.is_ok()); + let cpu_hist = client.cpu_history(2).await.context("cpu_history_1min")?; // todo bucket - if let Some(first) = cpu_hist.object.first() { + if let Some(first) = cpu_hist.first() { assert!(first.t > 0, "cpu history timestamp should be > 0"); } - let new_uuid = client.get_new_uuid().await.expect("get_new_uuid"); - assert!(new_uuid.is_ok()); + let new_uuid = client.get_new_uuid().await.context("get_new_uuid")?; - let parsed = Uuid::parse_str(&new_uuid.object.uuid); - assert!(parsed.is_ok(), "server UUID should be valid"); + log::info!("new_uuid = {:#?}", new_uuid); - let x25519 = client.get_new_x25519_cert().await.expect("get_new_x25519"); - assert!(x25519.is_ok()); + let x25519 = client + .get_new_x25519_cert() + .await + .context("get_new_x25519")?; + log::info!("x25519 = {:#?}", x25519); - let mldsa = client.get_new_mldsa65().await.expect("get_new_mldsa65"); - assert!(mldsa.is_ok()); + let mldsa = client.get_new_mldsa65().await.context("get_new_mldsa65")?; + log::info!("mldsa = {:#?}", mldsa); - let mlkem = client.get_new_mlkem768().await.expect("get_new_mlkem768"); - assert!(mlkem.is_ok()); + let mlkem = client + .get_new_mlkem768() + .await + .context("get_new_mlkem768")?; + log::info!("mlkem768 = {:#?}", mlkem); - let venc = client.get_new_vless_enc().await.expect("get_new_vless_enc"); - assert!(venc.is_ok()); + let venc = client + .get_new_vless_enc() + .await + .context("get_new_vless_enc")?; + log::info!("vless enc = {:#?}", venc); - let ech = client.get_new_ech_cert().await.expect("get_new_ech_cert"); - assert!(ech.is_ok()); + let ech = client + .get_new_ech_cert() + .await + .context("get_new_ech_cert")?; + log::info!("ech = {:#?}", ech); - let stopped = client.stop_xray_service().await.expect("stop_xray_service"); - assert!(stopped.is_ok()); + client + .stop_xray_service() + .await + .context("stop_xray_service")?; sleep(Duration::from_secs(1)).await; - let restarted = client + client .restart_xray_service() .await - .expect("restart_xray_service"); - assert!(restarted.is_ok()); + .context("restart_xray_service")?; sleep(Duration::from_secs(2)).await; - log::info!("ver: {:#?}", current_version.get(0).expect("version")); + log::info!("ver: {:#?}", current_version.first().context("version")); - let reinstall = client - .install_xray_version(current_version.get(0).expect("version")) + client + .install_xray_version(current_version.first().context("version")?) .await - .expect("install_xray_version"); - assert!(reinstall.is_ok()); + .context("install_xray_version")?; - let geo_all = client.update_geofile().await.expect("update_geofile"); - assert!(geo_all.is_ok()); + client.update_geofile().await.context("update_geofile")?; - let geo_one = client + client .update_geofile_by_name("geoip.dat") .await - .expect("update_geofile_by_name"); - assert!(geo_one.is_ok()); + .context("update_geofile_by_name")?; - let logs = client.logs(50).await.expect("logs_count"); - assert!(logs.is_ok()); + let logs = client.logs(50).await.context("logs_count")?; + log::info!("logs = {:#?}", logs); - let xlogs = client.xray_logs(50).await.expect("xray_logs_count"); - assert!(xlogs.is_ok()); + let xlogs = client.xray_logs(50).await.context("xray_logs_count")?; + log::info!("xlogs = {:#?}", xlogs); let remark = format!("e2e-{}", Uuid::new_v4()); let req = CreateInboundRequest { @@ -391,20 +379,22 @@ async fn e2e_full_flow() { allocate: "{}".into(), }; - let created = client.add_inbound(&req).await.expect("add_inbound"); - - assert!(created.is_ok()); + let created = client.add_inbound(&req).await.context("add_inbound")?; + log::info!("created = {:#?}", created); - let inbds = client.get_inbounds_list().await.expect("list_for_import"); - assert!(inbds.is_ok()); + let inbds = client + .get_inbounds_list() + .await + .context("list_for_import")?; log::info!("{:#?}", inbds); - let mut import = inbds.object[0].clone(); + let mut import = inbds[0].clone(); import.port = 30222; let import_inb = client .import_inbound(&import) .await - .expect("import_inbounds"); - assert!(import_inb.is_ok()); + .context("import_inbounds")?; + log::info!("import_inbound = {:#?}", import_inb); + Ok(()) } From 6d7c8f93fe4dc19e0f21b01f365db65875997c78 Mon Sep 17 00:00:00 2001 From: xaneets Date: Sat, 29 Nov 2025 21:16:26 +0300 Subject: [PATCH 2/2] up ver --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6263f8f..ca7b758 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustix3" -version = "0.4.1" +version = "0.5.0" edition = "2024" authors = ["Dmitriy Sergeev "] description = "API lib for 3x-ui panel"