use super::{Ctx, HttpResponse};
use ahash::AHashMap;
use async_trait::async_trait;
use pingora::http::ResponseHeader;
use pingora::proxy::Session;
use std::borrow::Cow;
use std::sync::Arc;
use strum::EnumString;
#[derive(
PartialEq, Debug, Default, Clone, Copy, EnumString, strum::Display,
)]
#[strum(serialize_all = "snake_case")]
pub enum PluginStep {
EarlyRequest,
#[default]
Request,
ProxyUpstream,
UpstreamResponse,
Response,
}
pub enum RequestPluginResult {
Skipped,
Continue,
Respond(HttpResponse),
}
#[derive(Debug, PartialEq, Eq)]
pub enum ResponsePluginResult {
Unchanged,
Modified,
}
#[derive(Debug, PartialEq, Eq)]
pub enum ResponseBodyPluginResult {
Unchanged,
PartialReplaced,
FullyReplaced,
}
impl PartialEq for RequestPluginResult {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(RequestPluginResult::Skipped, RequestPluginResult::Skipped) => {
true
},
(RequestPluginResult::Continue, RequestPluginResult::Continue) => {
true
},
_ => false,
}
}
}
#[async_trait]
pub trait Plugin: Sync + Send {
fn config_key(&self) -> Cow<'_, str> {
Cow::Borrowed("")
}
#[inline]
async fn handle_request(
&self,
_step: PluginStep,
_session: &mut Session,
_ctx: &mut Ctx,
) -> pingora::Result<RequestPluginResult> {
Ok(RequestPluginResult::Skipped)
}
#[inline]
async fn handle_response(
&self,
_session: &mut Session,
_ctx: &mut Ctx,
_upstream_response: &mut ResponseHeader,
) -> pingora::Result<ResponsePluginResult> {
Ok(ResponsePluginResult::Unchanged)
}
#[inline]
fn handle_response_body(
&self,
_session: &mut Session,
_ctx: &mut Ctx,
_body: &mut Option<bytes::Bytes>,
_end_of_stream: bool,
) -> pingora::Result<ResponseBodyPluginResult> {
Ok(ResponseBodyPluginResult::Unchanged)
}
#[inline]
fn handle_upstream_response(
&self,
_session: &mut Session,
_ctx: &mut Ctx,
_upstream_response: &mut ResponseHeader,
) -> pingora::Result<ResponsePluginResult> {
Ok(ResponsePluginResult::Unchanged)
}
#[inline]
fn handle_upstream_response_body(
&self,
_session: &mut Session,
_ctx: &mut Ctx,
_body: &mut Option<bytes::Bytes>,
_end_of_stream: bool,
) -> pingora::Result<ResponseBodyPluginResult> {
Ok(ResponseBodyPluginResult::Unchanged)
}
}
pub trait PluginProvider: Send + Sync {
fn get(&self, name: &str) -> Option<Arc<dyn Plugin>>;
}
pub type Plugins = AHashMap<String, Arc<dyn Plugin>>;
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_plugin_step() {
let step = "early_request".parse::<PluginStep>().unwrap();
assert_eq!(step, PluginStep::EarlyRequest);
assert_eq!(step.to_string(), "early_request");
let step = "request".parse::<PluginStep>().unwrap();
assert_eq!(step, PluginStep::Request);
assert_eq!(step.to_string(), "request");
let step = "proxy_upstream".parse::<PluginStep>().unwrap();
assert_eq!(step, PluginStep::ProxyUpstream);
assert_eq!(step.to_string(), "proxy_upstream");
let step = "response".parse::<PluginStep>().unwrap();
assert_eq!(step, PluginStep::Response);
assert_eq!(step.to_string(), "response");
}
#[test]
fn test_request_plugin_result() {
let skip1 = RequestPluginResult::Skipped;
let skip2 = RequestPluginResult::Skipped;
assert_eq!(true, skip1 == skip2);
let continue1 = RequestPluginResult::Continue;
let continue2 = RequestPluginResult::Continue;
assert_eq!(true, continue1 == continue2);
let respond1 = RequestPluginResult::Respond(HttpResponse::no_content());
let respond2 = RequestPluginResult::Respond(HttpResponse::no_content());
assert_eq!(false, respond1 == respond2);
}
}