use apiplant_abi::{FunctionAccess, HttpMethod};
use ntex::http::Method;
use ntex::web::types::{Path, State};
use ntex::web::{HttpRequest, HttpResponse};
use crate::functions::HostBridge;
use crate::response::error;
use crate::state::AppState;
fn expected_method(m: HttpMethod) -> Method {
match m {
HttpMethod::Get => Method::GET,
HttpMethod::Post => Method::POST,
HttpMethod::Put => Method::PUT,
HttpMethod::Delete => Method::DELETE,
}
}
pub async fn invoke(
req: HttpRequest,
state: State<AppState>,
path: Path<String>,
body: String,
) -> HttpResponse {
let name = path.into_inner();
let (method, access) = match state.functions.get(&name) {
Some(f) => (f.manifest.method, f.manifest.access()),
None => return error(404, format!("unknown function `{name}`")),
};
if req.method() != expected_method(method) {
return error(405, "method not allowed");
}
let principal = state.resolve_principal(&req).await;
match &access {
FunctionAccess::Public => {}
FunctionAccess::Private => return error(404, "unknown function"),
FunctionAccess::Authenticated => {
if principal.is_none() {
return error(401, "authentication required");
}
}
FunctionAccess::Member | FunctionAccess::Role(_) => {
if principal.is_none() {
return error(401, "authentication required");
}
let org = state.active_org(&req, &principal);
let ok = match (&access, org, principal.as_ref()) {
(FunctionAccess::Member, Some(org), Some(caller)) => caller.is_member(org),
(FunctionAccess::Role(required), Some(org), Some(caller)) => {
caller.has_role_in(org, required)
}
_ => false,
};
if !ok {
return error(403, "forbidden");
}
}
}
let input = if body.trim().is_empty() {
"{}".to_string()
} else {
body
};
let principal_id = principal
.as_ref()
.map(|p| p.user_id.to_string())
.unwrap_or_default();
let functions = state.functions.clone();
let db = state.db.clone();
let mailer = state.mailer.clone();
let cache = state.cache.clone();
let handle = tokio::runtime::Handle::current();
let name2 = name.clone();
let result = tokio::task::spawn_blocking(move || {
let f = functions.get(&name2).expect("checked above");
let bridge = HostBridge::new(db, handle, f.config_json.clone(), principal_id)
.with_services(mailer, cache);
f.invoke(bridge, &input)
})
.await;
match result {
Ok(Ok(json)) => HttpResponse::Ok()
.content_type("application/json")
.body(json),
Ok(Err(msg)) => match msg.strip_prefix(apiplant_abi::INTERNAL_ERROR_PREFIX) {
Some(detail) => {
tracing::error!(function = %name, detail, "function faulted");
error(500, "internal function error")
}
None => error(400, msg),
},
Err(_) => {
tracing::error!(function = %name, "function task panicked");
error(500, "internal function error")
}
}
}