use apiplant_core::App;
use apiplant_db::Db;
use crate::functions::{FunctionRegistry, HostBridge};
#[derive(Debug, Clone, Default)]
pub struct Options {
pub input: String,
pub principal: Option<String>,
pub emit_to_stderr: bool,
}
pub async fn call(app: &App, name: &str, options: Options) -> anyhow::Result<String> {
let registry = FunctionRegistry::load(app);
let config_json = match registry.get(name) {
Some(f) => f.config_json.clone(),
None => {
let known = registry
.iter()
.map(|f| f.manifest.name.to_string())
.collect::<Vec<_>>();
anyhow::bail!(
"unknown function `{name}` — this app has: {}",
match known.is_empty() {
true => "none".to_string(),
false => known.join(", "),
}
);
}
};
let db = Db::connect(
&app.config.database.resolved_url(),
app.config.database.max_connections,
)
.await?;
let mailer = apiplant_email::Mailer::from_config(&app.config.email)?;
let cache = apiplant_cache::Cache::connect(&app.config.cache).await?;
let ai = apiplant_ai::Ai::from_config(&app.config.ai)?;
let payments = apiplant_payments::Payments::from_config(
&app.config.payments,
&app.config.server.public_origin(),
)?;
let queue = apiplant_queue::Queue::new(&db, app);
let (printer, chunks) = match options.emit_to_stderr {
true => {
let (chunks, mut receiver) = tokio::sync::mpsc::unbounded_channel::<String>();
let printer = tokio::spawn(async move {
while let Some(chunk) = receiver.recv().await {
eprint!("{chunk}");
}
});
(Some(printer), Some(chunks))
}
false => (None, None),
};
let mut bridge = HostBridge::new(
db,
tokio::runtime::Handle::current(),
config_json,
options.principal.unwrap_or_default(),
)
.with_services(mailer, cache, payments, ai)
.with_queue(queue);
if let Some(chunks) = chunks {
bridge = bridge.streaming(chunks);
}
let input = match options.input.trim().is_empty() {
true => "{}".to_string(),
false => options.input,
};
let name = name.to_string();
let result = tokio::task::spawn_blocking(move || {
let f = registry.get(&name).expect("checked above");
f.invoke(bridge, &input)
})
.await
.map_err(|_| anyhow::anyhow!("the function task panicked"))?;
if let Some(printer) = printer {
let _ = printer.await;
}
result.map_err(|message| {
match message.strip_prefix(apiplant_abi::INTERNAL_ERROR_PREFIX) {
Some(detail) => anyhow::anyhow!("function faulted: {detail}"),
None => anyhow::anyhow!("{message}"),
}
})
}