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}