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    UpstreamResponse,
31    Response,
32}
33
34/// Core trait that defines the interface all plugins must implement.
35///
36/// Plugins can handle both requests and responses at different processing steps.
37/// The default implementations do nothing and return Ok.
38#[async_trait]
39pub trait Plugin: Sync + Send {
40    /// Returns a unique key that identifies this specific plugin instance.
41    ///
42    /// # Purpose
43    /// - Can be used for caching plugin results
44    /// - Helps differentiate between multiple instances of the same plugin type
45    /// - Useful for tracking and debugging
46    ///
47    /// # Default
48    /// Returns an empty string by default, which means no specific instance identification.
49    fn hash_key(&self) -> String {
50        "".to_string()
51    }
52
53    /// Processes an HTTP request at a specified lifecycle step.
54    ///
55    /// # Parameters
56    /// * `_step` - Current processing step in the request lifecycle (e.g., pre-routing, post-routing)
57    /// * `_session` - Mutable reference to the HTTP session containing request data
58    /// * `_ctx` - Mutable reference to the request context for storing state
59    ///
60    /// # Returns
61    /// * `Ok((executed, response))` where:
62    ///   * `executed` - Boolean flag:
63    ///     - `true`: Plugin performed meaningful logic for this request
64    ///     - `false`: Plugin was skipped or did nothing for this request
65    ///   * `response` - Optional HTTP response:
66    ///     - `Some(response)`: Terminates request processing and returns this response to client
67    ///     - `None`: Allows request to continue to next plugin or upstream
68    /// * `Err` - Returns error if plugin processing failed
69    async fn handle_request(
70        &self,
71        _step: PluginStep,
72        _session: &mut Session,
73        _ctx: &mut Ctx,
74    ) -> pingora::Result<(bool, Option<HttpResponse>)> {
75        Ok((false, None))
76    }
77
78    /// Processes an HTTP response at a specified lifecycle step.
79    ///
80    /// # Parameters
81    /// * `_step` - Current processing step in the response lifecycle
82    /// * `_session` - Mutable reference to the HTTP session
83    /// * `_ctx` - Mutable reference to the request context
84    /// * `_upstream_response` - Mutable reference to the upstream response header
85    ///
86    /// # Returns
87    /// * `Ok(modified)` - Boolean flag:
88    ///   - `true`: Plugin modified the response in some way
89    ///   - `false`: Plugin did not modify the response
90    /// * `Err` - Returns error if plugin processing failed
91    async fn handle_response(
92        &self,
93        _step: PluginStep,
94        _session: &mut Session,
95        _ctx: &mut Ctx,
96        _upstream_response: &mut ResponseHeader,
97    ) -> pingora::Result<bool> {
98        Ok(false)
99    }
100    fn handle_upstream_response(
101        &self,
102        _step: PluginStep,
103        _session: &mut Session,
104        _ctx: &mut Ctx,
105        _upstream_response: &mut ResponseHeader,
106    ) -> pingora::Result<bool> {
107        Ok(false)
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_plugin_step() {
117        let step = "early_request".parse::<PluginStep>().unwrap();
118        assert_eq!(step, PluginStep::EarlyRequest);
119        assert_eq!(step.to_string(), "early_request");
120
121        let step = "request".parse::<PluginStep>().unwrap();
122        assert_eq!(step, PluginStep::Request);
123        assert_eq!(step.to_string(), "request");
124
125        let step = "proxy_upstream".parse::<PluginStep>().unwrap();
126        assert_eq!(step, PluginStep::ProxyUpstream);
127        assert_eq!(step.to_string(), "proxy_upstream");
128
129        let step = "response".parse::<PluginStep>().unwrap();
130        assert_eq!(step, PluginStep::Response);
131        assert_eq!(step.to_string(), "response");
132    }
133}