use std::future::{Future, IntoFuture};
use std::marker::PhantomData;
use std::pin::Pin;
use percent_encoding::{utf8_percent_encode, AsciiSet};
use reqwest::Method;
use serde::de::DeserializeOwned;
use crate::client::{Client, RequestOptions};
use crate::error::Error;
use crate::query::{Query, STRICT};
use crate::request::request_settings;
use crate::Record;
mod admin;
mod custom_api;
mod integrated_accounts;
mod integrations;
mod mcp;
mod proxy_api;
mod unified_api;
pub use admin::*;
pub use custom_api::CustomApi;
pub use integrated_accounts::IntegratedAccounts;
pub use integrations::{Integrations, UnifiedModels};
pub use mcp::Mcp;
pub use proxy_api::ProxyApi;
pub use unified_api::UnifiedApi;
pub(crate) fn seg(s: &str) -> String {
utf8_percent_encode(s, STRICT).to_string()
}
const PATH: &AsciiSet = &percent_encoding::CONTROLS
.add(b' ')
.add(b'"')
.add(b'#')
.add(b'<')
.add(b'>')
.add(b'?')
.add(b'`')
.add(b'{')
.add(b'}');
pub(crate) fn path(s: &str) -> String {
utf8_percent_encode(s.trim_start_matches('/'), PATH).to_string()
}
macro_rules! resource {
($(#[$meta:meta])* $name:ident, $path:literal) => {
$(#[$meta])*
#[doc = ""]
#[doc = concat!("Base path: `/", $path, "`.")]
#[derive(Debug, Clone, Copy)]
pub struct $name<'a> {
client: &'a $crate::Client,
}
impl<'a> $name<'a> {
#[allow(dead_code)]
const PATH: &'static str = $path;
pub(crate) fn new(client: &'a $crate::Client) -> Self {
Self { client }
}
}
};
}
pub(crate) use resource;
macro_rules! list {
($item:ty) => {
pub fn list(&self) -> $crate::Pager<$item> {
self.client.pager(Self::PATH)
}
};
}
pub(crate) use list;
macro_rules! get {
($item:ty) => {
pub fn get(&self, id: &str) -> $crate::Request<$item> {
self.client.request(
reqwest::Method::GET,
format!("{}/{}", Self::PATH, $crate::resources::seg(id)),
)
}
};
}
pub(crate) use get;
macro_rules! create {
(any => $out:ty) => {
pub fn create(&self, body: impl serde::Serialize) -> $crate::Request<$out> {
self.client
.request(reqwest::Method::POST, Self::PATH)
.body(body)
}
};
($body:ty => $out:ty) => {
pub fn create(&self, body: &$body) -> $crate::Request<$out> {
self.client
.request(reqwest::Method::POST, Self::PATH)
.body(body)
}
};
}
pub(crate) use create;
macro_rules! update {
(any => $out:ty) => {
pub fn update(&self, id: &str, body: impl serde::Serialize) -> $crate::Request<$out> {
self.client
.request(
reqwest::Method::PATCH,
format!("{}/{}", Self::PATH, $crate::resources::seg(id)),
)
.body(body)
}
};
($body:ty => $out:ty) => {
pub fn update(&self, id: &str, body: &$body) -> $crate::Request<$out> {
self.client
.request(
reqwest::Method::PATCH,
format!("{}/{}", Self::PATH, $crate::resources::seg(id)),
)
.body(body)
}
};
}
pub(crate) use update;
macro_rules! delete {
($out:ty) => {
pub fn delete(&self, id: &str) -> $crate::Request<$out> {
self.client.request(
reqwest::Method::DELETE,
format!("{}/{}", Self::PATH, $crate::resources::seg(id)),
)
}
};
}
pub(crate) use delete;
macro_rules! schedule {
() => {
pub fn schedule(&self, id: &str) -> $crate::Request<$crate::Record> {
self.client.request(
reqwest::Method::POST,
format!("{}/{}/schedule", Self::PATH, $crate::resources::seg(id)),
)
}
};
}
pub(crate) use schedule;
#[must_use = "an uninstall does nothing until it is awaited"]
pub struct Uninstall<D> {
client: Client,
path: &'static str,
filter_key: &'static str,
id: String,
query: Query,
opts: RequestOptions,
error: Option<Error>,
_marker: PhantomData<fn() -> D>,
}
impl<D> std::fmt::Debug for Uninstall<D> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Uninstall")
.field("path", &self.path)
.field(self.filter_key, &self.id)
.finish()
}
}
impl<D> Uninstall<D> {
pub(crate) fn new(
client: &Client,
path: &'static str,
filter_key: &'static str,
id: &str,
) -> Self {
Self {
client: client.clone(),
path,
filter_key,
id: id.to_string(),
query: Query::new(),
opts: RequestOptions::default(),
error: None,
_marker: PhantomData,
}
}
request_settings!();
}
impl<D: DeserializeOwned + Send + 'static> Uninstall<D> {
pub async fn send(self) -> Result<Vec<D>, Error> {
if let Some(e) = self.error {
return Err(e);
}
let mut query = self.query.clone();
query.insert(self.filter_key, self.id.clone());
let installs: Vec<Record> = self
.client
.pager::<Record>(self.path)
.query(query)
.options(self.opts.clone())
.collect_all()
.await?;
let mut results = Vec::with_capacity(installs.len());
for install in installs {
let id = match install.get("id") {
Some(serde_json::Value::String(s)) => s.clone(),
Some(other) => other.to_string(),
None => continue,
};
let deleted = self
.client
.request::<D>(Method::DELETE, format!("{}/{}", self.path, seg(&id)))
.options(self.opts.clone())
.send()
.await?;
results.push(deleted);
}
Ok(results)
}
}
impl<D: DeserializeOwned + Send + 'static> IntoFuture for Uninstall<D> {
type Output = Result<Vec<D>, Error>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(self.send())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn segments_and_paths_encode() {
assert_eq!(seg("a/b c"), "a%2Fb%20c");
assert_eq!(path("/files/a b/report.csv"), "files/a%20b/report.csv");
}
}
macro_rules! accessors {
($($(#[$meta:meta])* $fn:ident => $ty:ident;)*) => {
impl Client {
$(
$(#[$meta])*
pub fn $fn(&self) -> $ty<'_> {
$ty::new(self)
}
)*
}
};
}
accessors! {
api_tokens => ApiTokens;
environments => Environments;
categories => Categories;
teams => Teams;
users => Users;
tenants => Tenants;
link_tokens => LinkTokens;
session_tokens => SessionTokens;
integrated_accounts => IntegratedAccounts;
sandbox_integrated_accounts => SandboxIntegratedAccounts;
integrations => Integrations;
environment_integrations => EnvironmentIntegrations;
unified_models => UnifiedModels;
environment_unified_models => EnvironmentUnifiedModels;
unified_model_resource_methods => UnifiedModelResourceMethods;
environment_unified_model_resource_methods => EnvironmentUnifiedModelResourceMethods;
sync_jobs => SyncJobs;
sync_job_runs => SyncJobRuns;
sync_job_cron_triggers => SyncJobCronTriggers;
sync_job_templates => SyncJobTemplates;
sync_job_run_states => SyncJobRunStates;
daemons => Daemons;
daemon_jobs => DaemonJobs;
daemon_job_runs => DaemonJobRuns;
daemon_job_cron_triggers => DaemonJobCronTriggers;
alarms => Alarms;
workflows => Workflows;
workflow_runs => WorkflowRuns;
webhooks => Webhooks;
datastores => Datastores;
documentation => DocumentationResource;
logs => Logs;
static_gates => StaticGates;
observability_destinations => ObservabilityDestinations;
analytics => Analytics;
batch_requests => BatchRequests;
environment_integration_webhooks => EnvironmentIntegrationWebhooks;
integrated_account_webhooks => IntegratedAccountWebhooks;
unified_api => UnifiedApi;
proxy_api => ProxyApi;
custom_api => CustomApi;
mcp => Mcp;
}
impl Client {
pub fn ping(&self) -> crate::Request<crate::models::Ping> {
self.request(Method::GET, "ping")
}
}