use reqwest::Method;
use serde_json::{json, Value};
use crate::client::PulseClient;
use crate::error::PulseError;
pub struct AuthResource<'c> {
pub(crate) client: &'c PulseClient,
}
impl AuthResource<'_> {
pub async fn login(&self, username: &str, password: &str) -> Result<Value, PulseError> {
let body = json!({ "username": username, "password": password });
let response = self
.client
.request(Method::POST, "/api/auth/login", Some(&body), false)
.await?;
cache_token(self.client, &response);
Ok(response)
}
pub async fn refresh(&self, refresh_token: &str) -> Result<Value, PulseError> {
let body = json!({ "refreshToken": refresh_token });
let response = self
.client
.request(Method::POST, "/api/auth/refresh", Some(&body), false)
.await?;
cache_token(self.client, &response);
Ok(response)
}
pub async fn organizations(&self) -> Result<Vec<Value>, PulseError> {
let result = self
.client
.request(Method::GET, "/api/auth/organizations", None::<&()>, true)
.await?;
Ok(unwrap_list(&result, "organizations"))
}
pub async fn switch_org(&self, org_id: &str) -> Result<Value, PulseError> {
let body = json!({ "orgId": org_id });
let response = self
.client
.request(Method::POST, "/api/auth/switch-org", Some(&body), true)
.await?;
cache_token(self.client, &response);
Ok(response)
}
}
fn cache_token(client: &PulseClient, response: &Value) {
if let Some(token) = response.get("token").and_then(Value::as_str) {
if !token.is_empty() {
client.set_token(token);
}
}
}
pub struct PipelinesResource<'c> {
pub(crate) client: &'c PulseClient,
}
impl PipelinesResource<'_> {
pub async fn list(&self) -> Result<Vec<Value>, PulseError> {
let result = self
.client
.request(Method::GET, "/api/pulse/pipelines", None::<&()>, true)
.await?;
Ok(unwrap_list(&result, "pipelines"))
}
pub async fn get(&self, pipeline_id: &str) -> Result<Value, PulseError> {
let path = format!("/api/pulse/pipelines/{}", encode_path(pipeline_id));
self.client
.request(Method::GET, &path, None::<&()>, true)
.await
}
pub async fn create(&self, definition: &Value) -> Result<Value, PulseError> {
self.client
.request(Method::POST, "/api/pulse/pipelines", Some(definition), true)
.await
}
pub async fn delete(&self, pipeline_id: &str) -> Result<(), PulseError> {
let path = format!("/api/pulse/pipelines/{}", encode_path(pipeline_id));
self.client
.request(Method::DELETE, &path, None::<&()>, true)
.await?;
Ok(())
}
}
pub struct AgentsResource<'c> {
pub(crate) client: &'c PulseClient,
}
impl AgentsResource<'_> {
pub async fn list(&self) -> Result<Vec<Value>, PulseError> {
let result = self
.client
.request(Method::GET, "/api/pulse/agents", None::<&()>, true)
.await?;
Ok(unwrap_list(&result, "agents"))
}
pub async fn get(&self, agent_id: &str) -> Result<Value, PulseError> {
let path = format!("/api/pulse/agents/{}", encode_path(agent_id));
self.client
.request(Method::GET, &path, None::<&()>, true)
.await
}
pub async fn update(&self, agent_id: &str, config: &Value) -> Result<Value, PulseError> {
let path = format!("/api/pulse/agents/{}", encode_path(agent_id));
self.client
.request(Method::PUT, &path, Some(config), true)
.await
}
pub async fn delete(&self, agent_id: &str) -> Result<(), PulseError> {
let path = format!("/api/pulse/agents/{}", encode_path(agent_id));
self.client
.request::<()>(Method::DELETE, &path, None, true)
.await?;
Ok(())
}
}
pub struct TemplatesResource<'c> {
pub(crate) client: &'c PulseClient,
}
impl TemplatesResource<'_> {
pub async fn list(&self) -> Result<Vec<Value>, PulseError> {
let result = self
.client
.request(Method::GET, "/api/pulse/templates", None::<&()>, true)
.await?;
Ok(unwrap_list(&result, "templates"))
}
}
pub struct UsersResource<'c> {
pub(crate) client: &'c PulseClient,
}
impl UsersResource<'_> {
pub async fn list(&self) -> Result<Vec<Value>, PulseError> {
let result = self
.client
.request(Method::GET, "/api/pulse/users", None::<&()>, true)
.await?;
Ok(unwrap_list(&result, "users"))
}
}
fn unwrap_list(result: &Value, key: &str) -> Vec<Value> {
result
.get(key)
.and_then(Value::as_array)
.cloned()
.unwrap_or_default()
}
fn encode_path(segment: &str) -> String {
let mut out = String::with_capacity(segment.len());
for b in segment.bytes() {
match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
out.push(b as char)
}
_ => out.push_str(&format!("%{b:02X}")),
}
}
out
}