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    /// URL after path/query resolution (may be mutated by plugins).
17    pub url: Url,
18    /// Original path template from the builder.
19    pub path: String,
20    /// HTTP method.
21    pub method: Method,
22    /// Headers before transport.
23    pub headers: HeaderMap,
24}
25
26/// Plugin extension point for better-fetch.
27#[async_trait]
28pub trait Plugin: Send + Sync {
29    /// Unique plugin identifier.
30    fn id(&self) -> &'static str;
31
32    /// Called before lifecycle hooks; may mutate `prepared`.
33    async fn init(&self, _prepared: &mut PreparedRequest) -> Result<()> {
34        Ok(())
35    }
36
37    /// Hooks merged into the client hook chain at build time.
38    fn hooks(&self) -> Hooks {
39        Hooks::default()
40    }
41}
42
43/// Ordered plugin list.
44#[derive(Default)]
45pub struct PluginRegistry {
46    plugins: Vec<Box<dyn Plugin>>,
47}
48
49impl PluginRegistry {
50    /// Creates an empty registry.
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Registers a plugin and returns `self` for chaining.
56    pub fn register<P: Plugin + 'static>(mut self, plugin: P) -> Self {
57        self.plugins.push(Box::new(plugin));
58        self
59    }
60
61    /// Appends a plugin to the registry.
62    pub fn push(&mut self, plugin: Box<dyn Plugin>) {
63        self.plugins.push(plugin);
64    }
65
66    /// Returns registered plugins.
67    pub fn plugins(&self) -> &[Box<dyn Plugin>] {
68        &self.plugins
69    }
70
71    pub(crate) async fn run_init_all(&self, prepared: &mut PreparedRequest) -> Result<()> {
72        for plugin in &self.plugins {
73            plugin.init(prepared).await?;
74        }
75        Ok(())
76    }
77
78    pub(crate) fn merged_hooks(&self) -> Hooks {
79        let mut hooks = Hooks::default();
80        for plugin in &self.plugins {
81            hooks = hooks.merge(plugin.hooks());
82        }
83        hooks
84    }
85}