Skip to main content

better_fetch/
plugin.rs

1//! Plugin hooks run after URL construction and auth, before request lifecycle hooks.
2//!
3//! Use [`Plugin::init`] to rewrite the target URL (e.g. add a query param) or inspect
4//! [`PreparedRequest::method`] / [`PreparedRequest::headers`] before the request is sent.
5
6use async_trait::async_trait;
7use http::{HeaderMap, Method};
8use url::Url;
9
10use crate::hooks::Hooks;
11use crate::Result;
12
13/// Prepared request state passed to plugin [`Plugin::init`].
14#[derive(Debug, Clone)]
15pub struct PreparedRequest {
16    pub url: Url,
17    pub path: String,
18    pub method: Method,
19    pub headers: HeaderMap,
20}
21
22/// Plugin extension point for better-fetch.
23#[async_trait]
24pub trait Plugin: Send + Sync {
25    fn id(&self) -> &'static str;
26
27    async fn init(&self, _prepared: &mut PreparedRequest) -> Result<()> {
28        Ok(())
29    }
30
31    fn hooks(&self) -> Hooks {
32        Hooks::default()
33    }
34}
35
36/// Ordered plugin list.
37#[derive(Default)]
38pub struct PluginRegistry {
39    plugins: Vec<Box<dyn Plugin>>,
40}
41
42impl PluginRegistry {
43    pub fn new() -> Self {
44        Self::default()
45    }
46
47    pub fn register<P: Plugin + 'static>(mut self, plugin: P) -> Self {
48        self.plugins.push(Box::new(plugin));
49        self
50    }
51
52    pub fn push(&mut self, plugin: Box<dyn Plugin>) {
53        self.plugins.push(plugin);
54    }
55
56    pub fn plugins(&self) -> &[Box<dyn Plugin>] {
57        &self.plugins
58    }
59
60    pub(crate) async fn run_init_all(&self, prepared: &mut PreparedRequest) -> Result<()> {
61        for plugin in &self.plugins {
62            plugin.init(prepared).await?;
63        }
64        Ok(())
65    }
66
67    pub(crate) fn merged_hooks(&self) -> Hooks {
68        let mut hooks = Hooks::default();
69        for plugin in &self.plugins {
70            hooks = hooks.merge(plugin.hooks());
71        }
72        hooks
73    }
74}