use crate::api::openai_compat::AppState;
use axum::{
body::Bytes,
extract::{Path, State},
http::{HeaderMap, StatusCode},
response::{IntoResponse, Response},
};
pub async fn package_webhook_no_channel(
Path(package_name): Path<String>,
State(state): State<AppState>,
headers: HeaderMap,
body: Bytes,
) -> Response {
do_webhook(&package_name, "", state, headers, body).await
}
pub async fn package_webhook_with_channel(
Path((package_name, channel_type)): Path<(String, String)>,
State(state): State<AppState>,
headers: HeaderMap,
body: Bytes,
) -> Response {
do_webhook(&package_name, &channel_type, state, headers, body).await
}
async fn do_webhook(
package_name: &str,
channel_type: &str,
state: AppState,
headers: HeaderMap,
body: Bytes,
) -> Response {
if package_name.is_empty() {
return (StatusCode::BAD_REQUEST, "Missing package name").into_response();
}
let headers_map: serde_json::Map<String, serde_json::Value> = headers
.iter()
.map(|(k, v)| {
(
k.as_str().to_string(),
serde_json::Value::String(v.to_str().unwrap_or("").to_string()),
)
})
.collect();
let raw_body = String::from_utf8_lossy(&body).to_string();
let payload = serde_json::json!({
"channel": channel_type,
"headers": headers_map,
"body": raw_body,
});
let payload_str = payload.to_string();
let wasm_handle = state.wasm_handle.read().await.clone();
let Some(handle) = wasm_handle else {
return (
StatusCode::INTERNAL_SERVER_ERROR,
"No WASM runtime available",
)
.into_response();
};
match handle.call(package_name, "handle_webhook", &payload_str) {
Ok(result_str) => build_response_from_plugin(&result_str),
Err(e) => {
let msg = e.to_string();
if msg.contains("not loaded") || msg.contains("not found") {
(
StatusCode::NOT_FOUND,
format!("Package '{}' not found: {}", package_name, msg),
)
.into_response()
} else {
tracing::error!(
"[webhook] Package '{}' handle_webhook error: {}",
package_name,
e
);
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Package error: {}", msg),
)
.into_response()
}
}
}
}
fn build_response_from_plugin(result_str: &str) -> Response {
let parsed: serde_json::Value = match serde_json::from_str(result_str) {
Ok(v) => v,
Err(_) => {
return (StatusCode::OK, result_str.to_string()).into_response();
}
};
let status_code = parsed["status_code"].as_u64().unwrap_or(200) as u16;
let body = match &parsed["body"] {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Null => String::new(),
other => other.to_string(),
};
let status = StatusCode::from_u16(status_code).unwrap_or(StatusCode::OK);
(status, body).into_response()
}