pingap_core/
plugin.rs

1// Copyright 2024-2025 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::{Ctx, HttpResponse};
16use async_trait::async_trait;
17use pingora::http::ResponseHeader;
18use pingora::proxy::Session;
19use strum::EnumString;
20
21#[derive(
22    PartialEq, Debug, Default, Clone, Copy, EnumString, strum::Display,
23)]
24#[strum(serialize_all = "snake_case")]
25pub enum PluginStep {
26    EarlyRequest,
27    #[default]
28    Request,
29    ProxyUpstream,
30    Response,
31}
32
33/// Core trait that defines the interface all plugins must implement.
34///
35/// Plugins can handle both requests and responses at different processing steps.
36/// The default implementations do nothing and return Ok.
37#[async_trait]
38pub trait Plugin: Sync + Send {
39    /// Returns a unique key that identifies this specific plugin instance.
40    ///
41    /// # Purpose
42    /// - Can be used for caching plugin results
43    /// - Helps differentiate between multiple instances of the same plugin type
44    /// - Useful for tracking and debugging
45    ///
46    /// # Default
47    /// Returns an empty string by default, which means no specific instance identification.
48    fn hash_key(&self) -> String {
49        "".to_string()
50    }
51
52    /// Processes an HTTP request at a specified lifecycle step.
53    ///
54    /// # Parameters
55    /// * `_step` - Current processing step in the request lifecycle (e.g., pre-routing, post-routing)
56    /// * `_session` - Mutable reference to the HTTP session containing request data
57    /// * `_ctx` - Mutable reference to the request context for storing state
58    ///
59    /// # Returns
60    /// * `Ok((executed, response))` where:
61    ///   * `executed` - Boolean flag:
62    ///     - `true`: Plugin performed meaningful logic for this request
63    ///     - `false`: Plugin was skipped or did nothing for this request
64    ///   * `response` - Optional HTTP response:
65    ///     - `Some(response)`: Terminates request processing and returns this response to client
66    ///     - `None`: Allows request to continue to next plugin or upstream
67    /// * `Err` - Returns error if plugin processing failed
68    async fn handle_request(
69        &self,
70        _step: PluginStep,
71        _session: &mut Session,
72        _ctx: &mut Ctx,
73    ) -> pingora::Result<(bool, Option<HttpResponse>)> {
74        Ok((false, None))
75    }
76
77    /// Processes an HTTP response at a specified lifecycle step.
78    ///
79    /// # Parameters
80    /// * `_step` - Current processing step in the response lifecycle
81    /// * `_session` - Mutable reference to the HTTP session
82    /// * `_ctx` - Mutable reference to the request context
83    /// * `_upstream_response` - Mutable reference to the upstream response header
84    ///
85    /// # Returns
86    /// * `Ok(modified)` - Boolean flag:
87    ///   - `true`: Plugin modified the response in some way
88    ///   - `false`: Plugin did not modify the response
89    /// * `Err` - Returns error if plugin processing failed
90    async fn handle_response(
91        &self,
92        _step: PluginStep,
93        _session: &mut Session,
94        _ctx: &mut Ctx,
95        _upstream_response: &mut ResponseHeader,
96    ) -> pingora::Result<bool> {
97        Ok(false)
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_plugin_step() {
107        let step = "early_request".parse::<PluginStep>().unwrap();
108        assert_eq!(step, PluginStep::EarlyRequest);
109        assert_eq!(step.to_string(), "early_request");
110
111        let step = "request".parse::<PluginStep>().unwrap();
112        assert_eq!(step, PluginStep::Request);
113        assert_eq!(step.to_string(), "request");
114
115        let step = "proxy_upstream".parse::<PluginStep>().unwrap();
116        assert_eq!(step, PluginStep::ProxyUpstream);
117        assert_eq!(step.to_string(), "proxy_upstream");
118
119        let step = "response".parse::<PluginStep>().unwrap();
120        assert_eq!(step, PluginStep::Response);
121        assert_eq!(step.to_string(), "response");
122    }
123}