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}