use std::time::Duration;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::{Map, Value};
pub use tocat_wasm_abi::Level as LogLevel;
pub use tocat_wasm_abi::*;
use crate::{
Direction,
channel::{ChannelId, ChannelTarget, HostBuilder},
error::{PluginError, Result},
forgiving::Forgiving,
};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Execution {
#[default]
Inline,
Detached,
}
pub trait EffectSink {
fn write(&mut self, channel: ChannelId, bytes: &[u8]);
fn log(&mut self, level: LogLevel, stage: &str, message: &str);
fn pace(&mut self, delay: Duration) {
let _ = delay;
}
fn halt(&mut self, stage: &str, reason: &str) {
let _ = (stage, reason);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PipelineMeta {
pub direction: Direction,
pub source: String,
pub sink: String,
pub peer: Option<String>,
}
impl PipelineMeta {
pub fn new(direction: Direction, source: impl Into<String>, sink: impl Into<String>) -> Self {
Self {
direction,
source: source.into(),
sink: sink.into(),
peer: None,
}
}
#[must_use]
pub fn with_peer(mut self, peer: Option<impl Into<String>>) -> Self {
self.peer = peer.map(Into::into);
self
}
#[must_use]
pub fn upstream(&self) -> &str {
match self.direction {
Direction::SourceToSink => &self.source,
Direction::SinkToSource => &self.sink,
}
}
#[must_use]
pub fn downstream(&self) -> &str {
match self.direction {
Direction::SourceToSink => &self.sink,
Direction::SinkToSource => &self.source,
}
}
#[must_use]
pub fn label(&self) -> String {
format!("{} -> {}", self.upstream(), self.downstream())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StageInfo<'a> {
pub index: usize,
pub total: usize,
pub name: &'a str,
pub upstream: &'a str,
pub downstream: &'a str,
}
impl StageInfo<'_> {
#[must_use]
pub fn label(&self) -> String {
format!("{} -> {}", self.upstream, self.downstream)
}
#[must_use]
pub fn is_first(&self) -> bool {
self.index == 0
}
#[must_use]
pub fn is_last(&self) -> bool {
self.index + 1 == self.total
}
}
#[derive(Debug, Default)]
pub struct Emission {
pub(crate) out: Vec<u8>,
pub(crate) bounds: Vec<usize>,
pub(crate) emit: Emit,
pub(crate) rearm: bool,
}
impl Emission {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
self.out.clear();
self.bounds.clear();
self.emit = Emit::Pending;
self.rearm = false;
}
pub(crate) fn next_unit(&mut self) {
self.emit = Emit::Pending;
}
pub(crate) fn close(&mut self) {
if self.bounds.last().copied().unwrap_or(0) < self.out.len() {
self.bounds.push(self.out.len());
}
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.out
}
#[must_use]
pub fn bounds(&self) -> &[usize] {
&self.bounds
}
#[must_use]
pub fn emit(&self) -> Emit {
self.emit
}
#[must_use]
pub fn rearm_requested(&self) -> bool {
self.rearm
}
}
pub struct Ctx<'a> {
meta: &'a PipelineMeta,
stage: &'a str,
input: &'a [u8],
emission: &'a mut Emission,
sink: &'a mut dyn EffectSink,
}
impl<'a> Ctx<'a> {
pub fn new(
meta: &'a PipelineMeta,
stage: &'a str,
input: &'a [u8],
emission: &'a mut Emission,
sink: &'a mut dyn EffectSink,
) -> Self {
Self {
meta,
stage,
input,
emission,
sink,
}
}
#[must_use]
pub fn stage(&self) -> &str {
self.stage
}
#[must_use]
pub fn meta(&self) -> &PipelineMeta {
self.meta
}
#[must_use]
pub fn direction(&self) -> Direction {
self.meta.direction
}
#[must_use]
pub fn input(&self) -> &[u8] {
self.input
}
pub fn pass_through(&mut self) {
match self.emission.emit {
Emit::Pending => self.emission.emit = Emit::Passthrough,
Emit::Passthrough => {}
Emit::Buffered => {
let input = self.input;
self.emission.out.extend_from_slice(input);
}
}
}
pub fn forward(&mut self, bytes: &[u8]) {
if self.emission.emit == Emit::Passthrough {
let input = self.input;
self.emission.out.extend_from_slice(input);
}
self.emission.emit = Emit::Buffered;
self.emission.out.extend_from_slice(bytes);
}
pub fn drop_chunk(&mut self) {
if self.emission.emit == Emit::Passthrough {
self.emission.emit = Emit::Pending;
}
}
pub fn boundary(&mut self) {
if self.emission.emit == Emit::Passthrough {
let input = self.input;
self.emission.out.extend_from_slice(input);
self.emission.emit = Emit::Buffered;
}
self.emission.close();
}
pub fn rearm(&mut self) {
self.emission.rearm = true;
}
pub fn side_write(&mut self, channel: ChannelId, bytes: &[u8]) {
self.sink.write(channel, bytes);
}
pub fn log(&mut self, level: LogLevel, message: &str) {
self.sink.log(level, self.stage, message);
}
pub fn pace(&mut self, delay: Duration) {
self.sink.pace(delay);
}
pub fn halt(&mut self, reason: &str) {
self.sink.halt(self.stage, reason);
}
}
pub struct BuildCtx<'a> {
name: &'a str,
config: &'a Map<String, Value>,
meta: &'a PipelineMeta,
stage: StageInfo<'a>,
host: &'a mut dyn HostBuilder,
}
impl<'a> BuildCtx<'a> {
pub fn new(
name: &'a str,
config: &'a Map<String, Value>,
meta: &'a PipelineMeta,
stage: StageInfo<'a>,
host: &'a mut dyn HostBuilder,
) -> Self {
Self {
name,
config,
meta,
stage,
host,
}
}
#[must_use]
pub fn stage(&self) -> StageInfo<'a> {
self.stage
}
#[must_use]
pub fn name(&self) -> &str {
self.name
}
#[must_use]
pub fn meta(&self) -> &PipelineMeta {
self.meta
}
#[must_use]
pub fn direction(&self) -> Direction {
self.meta.direction
}
#[must_use]
pub fn raw_config(&self) -> &Map<String, Value> {
self.config
}
pub fn config<T: DeserializeOwned>(&self) -> Result<T> {
T::deserialize(Forgiving(Value::Object(self.config.clone())))
.map_err(|e| PluginError::config(self.name, e))
}
pub fn open_channel(&mut self, target: ChannelTarget) -> Result<ChannelId> {
self.host.open_channel(target)
}
}
pub enum Stage {
Filter(Box<dyn Plugin>),
External(ExternalStage),
}
impl Stage {
pub fn filter(plugin: impl Plugin + 'static) -> Self {
Self::Filter(Box::new(plugin))
}
}
impl From<Box<dyn Plugin>> for Stage {
fn from(plugin: Box<dyn Plugin>) -> Self {
Self::Filter(plugin)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExternalStage {
pub argv: Vec<String>,
pub shell: bool,
pub stderr: StderrMode,
pub name: String,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum StderrMode {
Inherit,
#[default]
Log,
Null,
}
pub trait Plugin: Send {
fn name(&self) -> &str;
fn on_bytes(&mut self, ctx: &mut Ctx<'_>, input: &[u8]) -> Result<()>;
fn on_eof(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
let _ = ctx;
Ok(())
}
fn tick_interval(&self) -> Option<Duration> {
None
}
fn on_tick(&mut self, ctx: &mut Ctx<'_>) -> Result<()> {
let _ = ctx;
Ok(())
}
fn datagram_safe(&self) -> bool {
false
}
}
pub trait PluginFactory: Send + Sync + 'static {
fn name(&self) -> &str;
fn description(&self) -> &str {
""
}
fn execution(&self) -> Execution {
Execution::Inline
}
fn build(&self, ctx: &mut BuildCtx<'_>) -> Result<Stage>;
}