pub mod wasm_types;
mod macros;
mod plugin_ctx;
mod wasm_ctx;
pub use crate::plugin_ctx::{
FormPart, HttpRequest, HttpRequestBuilder, HttpResponse, LOG_DEBUG, LOG_ERROR, LOG_INFO,
LOG_TRACE, LOG_WARN, PluginContext, PluginContextExt,
};
pub use async_trait::async_trait;
pub use bincode;
pub use bytes::Bytes;
pub use http;
pub use semver::Version;
use serde::de::DeserializeOwned;
pub use serde_json;
use serde_json::Value;
pub use wasm_ctx::{respond_to_host, WasmHttpContext};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PluginInfo {
pub version: Version,
pub default_config: Value,
pub description: String,
pub readme: Option<String>,
}
#[derive(Debug)]
pub enum Outcome {
Continue,
Respond(Response),
}
#[derive(Debug)]
pub struct Response {
pub status: u16,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
#[derive(Debug)]
pub enum PluginError {
ExecuteError(String),
NotFound(String),
LoadError(String),
SerdeError(String),
HttpError(String),
}
pub type PluginResult = Result<Outcome, PluginError>;
impl Outcome {
pub fn goon() -> PluginResult {
Ok(Outcome::Continue)
}
pub fn respond(status: u16, headers: Vec<(String, String)>, body: Vec<u8>) -> PluginResult {
Ok(Outcome::Respond(Response {
status,
headers,
body,
}))
}
pub fn reject(status: u16, msg: impl Into<String>) -> PluginResult {
Ok(Outcome::Respond(Response {
status,
headers: Vec::new(),
body: msg.into().into_bytes(),
}))
}
pub fn execute_error(msg: impl Into<String>) -> PluginResult {
Err(PluginError::ExecuteError(msg.into()))
}
pub fn not_found(msg: impl Into<String>) -> PluginResult {
Err(PluginError::NotFound(msg.into()))
}
}
impl std::fmt::Display for PluginError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PluginError::ExecuteError(msg) => write!(f, "{}", msg),
PluginError::NotFound(msg) => write!(f, "{}", msg),
PluginError::LoadError(msg) => write!(f, "{}", msg),
PluginError::SerdeError(msg) => write!(f, "{}", msg),
PluginError::HttpError(msg) => write!(f, "{}", msg),
}
}
}
#[async_trait]
pub trait Plugin: Send + Sync {
fn name(&self) -> &str;
fn info(&self) -> PluginInfo;
async fn on_request(&self, _ctx: &mut dyn PluginContext) -> PluginResult {
Ok(Outcome::Continue)
}
async fn on_request_body(&self, _ctx: &mut dyn PluginContext) -> PluginResult {
Ok(Outcome::Continue)
}
async fn on_response(&self, _ctx: &mut dyn PluginContext) -> PluginResult {
Ok(Outcome::Continue)
}
async fn on_response_body(&self, _ctx: &mut dyn PluginContext) -> PluginResult {
Ok(Outcome::Continue)
}
async fn on_logging(&self, _: &mut dyn PluginContext) {}
}
pub trait PluginConfigExt: Plugin {
fn parse_config<T>(&self, config: &Value) -> Result<T, PluginError>
where
T: DeserializeOwned,
{
serde_json::from_value(config.clone()).map_err(|e| {
PluginError::SerdeError(format!("[{}] pase plugin config error: {}", self.name(), e))
})
}
}
impl<T: Plugin> PluginConfigExt for T {}
pub fn block_on<F: Future>(f: F) -> F::Output {
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
fn noop_clone(_: *const ()) -> RawWaker {
RawWaker::new(std::ptr::null(), &VTABLE)
}
fn noop(_: *const ()) {}
static VTABLE: RawWakerVTable = RawWakerVTable::new(noop_clone, noop, noop, noop);
let raw_waker = RawWaker::new(std::ptr::null(), &VTABLE);
let waker = unsafe { Waker::from_raw(raw_waker) };
let mut cx = Context::from_waker(&waker);
let mut f = std::pin::pin!(f);
match f.as_mut().poll(&mut cx) {
Poll::Ready(val) => val,
Poll::Pending => panic!(
"plugin future returned Pending in WASM context; \
WASM plugins must not perform real async I/O"
),
}
}