pub struct PluginResult<P: PluginPayload> {
pub continue_processing: bool,
pub modified_payload: Option<P>,
pub modified_extensions: Option<OwnedExtensions>,
pub violation: Option<PluginViolation>,
pub metadata: Option<Value>,
}Expand description
Result returned by a hook handler.
Payload and extension modifications are separate — this is a
core design decision. Extension-only changes (add a label, set a
header) don’t require copying the payload. The payload is only
present in modified_payload when message content actually changed.
The executor interprets the result based on the plugin’s mode:
- Sequential/Transform:
modified_payloadandmodified_extensionsare accepted. - Audit/Concurrent/FireAndForget: modifications are discarded.
- Sequential/Concurrent:
continue_processing = falsehalts the pipeline. - Transform/Audit/FireAndForget: blocks are suppressed.
Mirrors Python’s PluginResult[T] with separate modified_payload
and modified_extensions fields.
§Examples
use cpex_core::hooks::{PluginPayload, PluginResult};
use cpex_core::error::PluginViolation;
// Define a simple payload
#[derive(Debug, Clone)]
struct TestPayload { value: i32 }
cpex_core::impl_plugin_payload!(TestPayload);
// Allow — no changes
let result: PluginResult<TestPayload> = PluginResult::allow();
assert!(result.continue_processing);
assert!(result.modified_payload.is_none());
// Deny
let result: PluginResult<TestPayload> = PluginResult::deny(
PluginViolation::new("forbidden", "not allowed")
);
assert!(!result.continue_processing);
assert!(result.violation.is_some());Fields§
§continue_processing: boolWhether the pipeline should continue processing.
false halts the pipeline (deny). Only respected for
Sequential and Concurrent modes.
modified_payload: Option<P>Modified payload. None means no content modification.
Only accepted from Sequential and Transform mode plugins.
modified_extensions: Option<OwnedExtensions>Modified extensions. None means no extension changes.
Return an OwnedExtensions from extensions.cow_copy().
The executor validates (immutable unchanged, monotonic superset)
and merges back into the pipeline’s Extensions.
violation: Option<PluginViolation>Policy violation. Present when continue_processing is false.
metadata: Option<Value>Optional metadata from the plugin (telemetry, diagnostics). Not used for scheduling or policy decisions.
Implementations§
Source§impl<P: PluginPayload> PluginResult<P>
impl<P: PluginPayload> PluginResult<P>
Sourcepub fn allow() -> Self
pub fn allow() -> Self
Allow — payload continues unchanged, no extension changes.
Examples found in repository?
79 async fn handle(
80 &self,
81 payload: &ToolInvokePayload,
82 _extensions: &Extensions,
83 _ctx: &mut PluginContext,
84 ) -> PluginResult<ToolInvokePayload> {
85 if payload.user.is_empty() {
86 println!(" [identity-resolver] DENIED: no user identity");
87 return PluginResult::deny(PluginViolation::new(
88 "no_identity",
89 "User identity is required",
90 ));
91 }
92 println!(
93 " [identity-resolver] OK: user '{}' identified",
94 payload.user
95 );
96 PluginResult::allow()
97 }
98}
99
100impl HookHandler<ToolPostInvoke> for IdentityResolver {
101 async fn handle(
102 &self,
103 payload: &ToolInvokePayload,
104 _extensions: &Extensions,
105 _ctx: &mut PluginContext,
106 ) -> PluginResult<ToolInvokePayload> {
107 println!(
108 " [identity-resolver] post-invoke: user '{}' completed '{}'",
109 payload.user, payload.tool_name
110 );
111 PluginResult::allow()
112 }
113}
114
115/// PII guard — blocks access to sensitive tools without clearance.
116struct PiiGuard {
117 cfg: PluginConfig,
118}
119
120#[async_trait]
121impl Plugin for PiiGuard {
122 fn config(&self) -> &PluginConfig {
123 &self.cfg
124 }
125 // initialize() and shutdown() use defaults — no setup needed
126}
127
128impl HookHandler<ToolPreInvoke> for PiiGuard {
129 async fn handle(
130 &self,
131 payload: &ToolInvokePayload,
132 _extensions: &Extensions,
133 ctx: &mut PluginContext,
134 ) -> PluginResult<ToolInvokePayload> {
135 // Check if the user has PII clearance (simulated via context)
136 let has_clearance = ctx
137 .get_global("pii_clearance")
138 .and_then(|v| v.as_bool())
139 .unwrap_or(false);
140
141 if !has_clearance {
142 println!(
143 " [pii-guard] DENIED: user '{}' lacks PII clearance for '{}'",
144 payload.user, payload.tool_name
145 );
146 return PluginResult::deny(PluginViolation::new(
147 "pii_access_denied",
148 "PII clearance required",
149 ));
150 }
151
152 println!(
153 " [pii-guard] OK: user '{}' has PII clearance",
154 payload.user
155 );
156 PluginResult::allow()
157 }
158}
159
160/// Audit logger — logs all tool invocations (fire-and-forget).
161struct AuditLogger {
162 cfg: PluginConfig,
163}
164
165#[async_trait]
166impl Plugin for AuditLogger {
167 fn config(&self) -> &PluginConfig {
168 &self.cfg
169 }
170 // initialize() and shutdown() use defaults — no setup needed
171}
172
173impl HookHandler<ToolPreInvoke> for AuditLogger {
174 async fn handle(
175 &self,
176 payload: &ToolInvokePayload,
177 _extensions: &Extensions,
178 _ctx: &mut PluginContext,
179 ) -> PluginResult<ToolInvokePayload> {
180 println!(
181 " [audit-logger] LOG: user='{}' tool='{}' args='{}'",
182 payload.user, payload.tool_name, payload.arguments
183 );
184 PluginResult::allow()
185 }
186}
187
188impl HookHandler<ToolPostInvoke> for AuditLogger {
189 async fn handle(
190 &self,
191 payload: &ToolInvokePayload,
192 _extensions: &Extensions,
193 _ctx: &mut PluginContext,
194 ) -> PluginResult<ToolInvokePayload> {
195 println!(
196 " [audit-logger] LOG: post-invoke user='{}' tool='{}'",
197 payload.user, payload.tool_name
198 );
199 PluginResult::allow()
200 }
201}
202
203// ---------------------------------------------------------------------------
204// Awaiting plugin example — RemoteAuthz
205// ---------------------------------------------------------------------------
206//
207// `HookHandler<H>` is async by design — `handle` is `async fn`.
208// Plugins that don't need to `.await` anything still write
209// `async fn handle` and return synchronously; this plugin shows the
210// other direction, where the body genuinely awaits per-invocation
211// work. The realistic version would call a remote authz service
212// (gRPC, HTTP, OPA, Cedarling, etc.); here we simulate the network
213// round-trip with a small `tokio::time::sleep` so the demo runs
214// offline.
215//
216// Key things this shows:
217// 1. Per-request latency state is *cached at init* — the handler
218// consults the in-memory ACL and only "calls out" on a miss.
219// Hot-path I/O is the most common source of latency regressions
220// in plugins, so prefer initialize-time loading wherever you can.
221// 2. Registration uses the exact same factory pattern as any other
222// plugin — `TypedHandlerAdapter::<H, _>` and the same
223// `register_factory` call. There is no separate async path.
224struct RemoteAuthz {
225 cfg: PluginConfig,
226 /// ACL "fetched" at init. Populated in Plugin::initialize.
227 allowed_users: tokio::sync::RwLock<std::collections::HashSet<String>>,
228}
229
230#[async_trait]
231impl Plugin for RemoteAuthz {
232 fn config(&self) -> &PluginConfig {
233 &self.cfg
234 }
235 /// Pretend we're loading the ACL from a remote service. In a real
236 /// plugin this would be `client.fetch_acl().await`; we simulate
237 /// the round-trip with a small sleep so the demo runs offline.
238 async fn initialize(&self) -> Result<(), Box<PluginError>> {
239 tokio::time::sleep(std::time::Duration::from_millis(2)).await;
240 let mut acl = self.allowed_users.write().await;
241 acl.extend(["alice", "bob"].iter().map(|s| s.to_string()));
242 println!(
243 " [remote-authz] initialized — ACL cached ({} users)",
244 acl.len()
245 );
246 Ok(())
247 }
248 async fn shutdown(&self) -> Result<(), Box<PluginError>> {
249 println!(" [remote-authz] shutdown");
250 Ok(())
251 }
252}
253
254impl HookHandler<ToolPreInvoke> for RemoteAuthz {
255 async fn handle(
256 &self,
257 payload: &ToolInvokePayload,
258 _extensions: &Extensions,
259 _ctx: &mut PluginContext,
260 ) -> PluginResult<ToolInvokePayload> {
261 // Cache hit path — fast.
262 let acl = self.allowed_users.read().await;
263 if acl.contains(&payload.user) {
264 println!(
265 " [remote-authz] OK (cache hit): user '{}' allowed",
266 payload.user
267 );
268 return PluginResult::allow();
269 }
270 drop(acl); // release read lock before the fake remote call
271 // Cache miss path — simulate a remote authz check. In a real
272 // plugin this is where you'd `.await` a gRPC or HTTP call.
273 // The latency cost is real and shows up on the request path.
274 tokio::time::sleep(std::time::Duration::from_millis(1)).await;
275 println!(
276 " [remote-authz] DENIED (cache miss + remote check): user '{}'",
277 payload.user
278 );
279 PluginResult::deny(PluginViolation::new(
280 "remote_authz_denied",
281 format!("User '{}' not in remote ACL", payload.user),
282 ))
283 }More examples
45 async fn handle(
46 &self,
47 payload: &MessagePayload,
48 extensions: &Extensions,
49 _ctx: &mut PluginContext,
50 ) -> PluginResult<MessagePayload> {
51 // Determine if this is pre or post invoke based on message content
52 let is_result = payload.message.is_tool_result();
53
54 if is_result {
55 // POST-INVOKE: verify the tool result came from an authorized call
56 let tool_name = payload
57 .message
58 .get_tool_results()
59 .first()
60 .map(|tr| tr.tool_name.as_str())
61 .unwrap_or("unknown");
62 println!(
63 " [identity-checker] POST-INVOKE: verifying result from '{}'",
64 tool_name
65 );
66
67 if let Some(ref security) = extensions.security {
68 if let Some(ref subject) = security.subject {
69 println!(
70 " [identity-checker] Result authorized for subject: {:?}",
71 subject.id
72 );
73 }
74 }
75 println!(" [identity-checker] POST-INVOKE ALLOWED");
76 } else {
77 // PRE-INVOKE: check caller identity and roles
78 let tool_name = payload
79 .message
80 .get_tool_calls()
81 .first()
82 .map(|tc| tc.name.as_str())
83 .unwrap_or("unknown");
84 println!(
85 " [identity-checker] PRE-INVOKE: checking identity for '{}'",
86 tool_name
87 );
88
89 if let Some(ref security) = extensions.security {
90 let labels: Vec<&String> = security.labels.iter().collect();
91 println!(" [identity-checker] Security labels: {:?}", labels);
92
93 if let Some(ref subject) = security.subject {
94 println!(
95 " [identity-checker] Subject: {:?}, Roles: {:?}",
96 subject.id,
97 subject.roles.iter().collect::<Vec<_>>()
98 );
99
100 if security.has_label("PII") && !subject.roles.contains("hr_admin") {
101 return PluginResult::deny(PluginViolation::new(
102 "insufficient_role",
103 format!("Tool '{}' requires 'hr_admin' role for PII data", tool_name),
104 ));
105 }
106 }
107 }
108
109 if extensions.http.is_some() {
110 println!(" [identity-checker] WARNING: HTTP visible (unexpected!)");
111 } else {
112 println!(" [identity-checker] HTTP: not visible (correct — no read_headers)");
113 }
114 println!(" [identity-checker] PRE-INVOKE ALLOWED");
115 }
116
117 PluginResult::allow()
118 }
119}
120
121// ---------------------------------------------------------------------------
122// Plugin: HeaderInjector
123// Has read_headers, write_headers, append_labels capabilities.
124// Uses COW to add a security label and inject a header.
125// ---------------------------------------------------------------------------
126
127struct HeaderInjector {
128 cfg: PluginConfig,
129}
130
131#[async_trait]
132impl Plugin for HeaderInjector {
133 fn config(&self) -> &PluginConfig {
134 &self.cfg
135 }
136}
137
138impl HookHandler<CmfHook> for HeaderInjector {
139 async fn handle(
140 &self,
141 _payload: &MessagePayload,
142 extensions: &Extensions,
143 _ctx: &mut PluginContext,
144 ) -> PluginResult<MessagePayload> {
145 // Can see HTTP (has read_headers)
146 if let Some(ref http) = extensions.http {
147 println!(
148 " [header-injector] HTTP headers visible: {:?}",
149 http.request_headers
150 );
151 }
152
153 // Can NOT see security subject (no read_subject)
154 if let Some(ref security) = extensions.security {
155 if security.subject.is_some() {
156 println!(" [header-injector] WARNING: Subject visible (unexpected!)");
157 } else {
158 println!(" [header-injector] Security subject: not visible (no read_subject)");
159 }
160 }
161
162 // COW copy to modify — tokens propagate from the executor
163 let mut modified = extensions.cow_copy();
164
165 // Add a label via MonotonicSet (has append_labels)
166 if modified.labels_write_token.is_some() {
167 modified.security.as_mut().unwrap().add_label("PROCESSED");
168 println!(" [header-injector] Added label 'PROCESSED'");
169 }
170
171 // Inject a header via Guarded (has write_headers)
172 if let Some(ref token) = modified.http_write_token {
173 modified
174 .http
175 .as_mut()
176 .unwrap()
177 .write(token)
178 .set_header("X-Processed-By", "header-injector");
179 println!(" [header-injector] Injected header 'X-Processed-By'");
180 }
181
182 PluginResult::modify_extensions(modified)
183 }
184}
185
186// ---------------------------------------------------------------------------
187// Plugin: AuditLogger
188// Has read_headers, read_security, read_labels capabilities.
189// Read-only — just logs what it can see.
190// ---------------------------------------------------------------------------
191
192struct AuditLogger {
193 cfg: PluginConfig,
194}
195
196#[async_trait]
197impl Plugin for AuditLogger {
198 fn config(&self) -> &PluginConfig {
199 &self.cfg
200 }
201}
202
203impl HookHandler<CmfHook> for AuditLogger {
204 async fn handle(
205 &self,
206 payload: &MessagePayload,
207 extensions: &Extensions,
208 _ctx: &mut PluginContext,
209 ) -> PluginResult<MessagePayload> {
210 let is_result = payload.message.is_tool_result();
211 let phase = if is_result { "POST" } else { "PRE" };
212
213 let tool_name = if is_result {
214 payload
215 .message
216 .get_tool_results()
217 .first()
218 .map(|tr| tr.tool_name.as_str())
219 .unwrap_or("unknown")
220 } else {
221 payload
222 .message
223 .get_tool_calls()
224 .first()
225 .map(|tc| tc.name.as_str())
226 .unwrap_or("unknown")
227 };
228
229 print!(" [audit-logger] AUDIT[{}]: tool='{}' ", phase, tool_name);
230
231 if let Some(ref security) = extensions.security {
232 let labels: Vec<&String> = security.labels.iter().collect();
233 print!("labels={:?} ", labels);
234 }
235
236 if let Some(ref http) = extensions.http {
237 if let Some(req_id) = http.get_header("X-Request-ID") {
238 print!("request_id='{}' ", req_id);
239 }
240 }
241
242 if is_result {
243 let is_error = payload
244 .message
245 .get_tool_results()
246 .first()
247 .map(|tr| tr.is_error)
248 .unwrap_or(false);
249 print!("error={} ", is_error);
250 }
251
252 println!();
253 PluginResult::allow()
254 }Sourcepub fn deny(violation: PluginViolation) -> Self
pub fn deny(violation: PluginViolation) -> Self
Deny — pipeline halts with a violation.
Examples found in repository?
79 async fn handle(
80 &self,
81 payload: &ToolInvokePayload,
82 _extensions: &Extensions,
83 _ctx: &mut PluginContext,
84 ) -> PluginResult<ToolInvokePayload> {
85 if payload.user.is_empty() {
86 println!(" [identity-resolver] DENIED: no user identity");
87 return PluginResult::deny(PluginViolation::new(
88 "no_identity",
89 "User identity is required",
90 ));
91 }
92 println!(
93 " [identity-resolver] OK: user '{}' identified",
94 payload.user
95 );
96 PluginResult::allow()
97 }
98}
99
100impl HookHandler<ToolPostInvoke> for IdentityResolver {
101 async fn handle(
102 &self,
103 payload: &ToolInvokePayload,
104 _extensions: &Extensions,
105 _ctx: &mut PluginContext,
106 ) -> PluginResult<ToolInvokePayload> {
107 println!(
108 " [identity-resolver] post-invoke: user '{}' completed '{}'",
109 payload.user, payload.tool_name
110 );
111 PluginResult::allow()
112 }
113}
114
115/// PII guard — blocks access to sensitive tools without clearance.
116struct PiiGuard {
117 cfg: PluginConfig,
118}
119
120#[async_trait]
121impl Plugin for PiiGuard {
122 fn config(&self) -> &PluginConfig {
123 &self.cfg
124 }
125 // initialize() and shutdown() use defaults — no setup needed
126}
127
128impl HookHandler<ToolPreInvoke> for PiiGuard {
129 async fn handle(
130 &self,
131 payload: &ToolInvokePayload,
132 _extensions: &Extensions,
133 ctx: &mut PluginContext,
134 ) -> PluginResult<ToolInvokePayload> {
135 // Check if the user has PII clearance (simulated via context)
136 let has_clearance = ctx
137 .get_global("pii_clearance")
138 .and_then(|v| v.as_bool())
139 .unwrap_or(false);
140
141 if !has_clearance {
142 println!(
143 " [pii-guard] DENIED: user '{}' lacks PII clearance for '{}'",
144 payload.user, payload.tool_name
145 );
146 return PluginResult::deny(PluginViolation::new(
147 "pii_access_denied",
148 "PII clearance required",
149 ));
150 }
151
152 println!(
153 " [pii-guard] OK: user '{}' has PII clearance",
154 payload.user
155 );
156 PluginResult::allow()
157 }
158}
159
160/// Audit logger — logs all tool invocations (fire-and-forget).
161struct AuditLogger {
162 cfg: PluginConfig,
163}
164
165#[async_trait]
166impl Plugin for AuditLogger {
167 fn config(&self) -> &PluginConfig {
168 &self.cfg
169 }
170 // initialize() and shutdown() use defaults — no setup needed
171}
172
173impl HookHandler<ToolPreInvoke> for AuditLogger {
174 async fn handle(
175 &self,
176 payload: &ToolInvokePayload,
177 _extensions: &Extensions,
178 _ctx: &mut PluginContext,
179 ) -> PluginResult<ToolInvokePayload> {
180 println!(
181 " [audit-logger] LOG: user='{}' tool='{}' args='{}'",
182 payload.user, payload.tool_name, payload.arguments
183 );
184 PluginResult::allow()
185 }
186}
187
188impl HookHandler<ToolPostInvoke> for AuditLogger {
189 async fn handle(
190 &self,
191 payload: &ToolInvokePayload,
192 _extensions: &Extensions,
193 _ctx: &mut PluginContext,
194 ) -> PluginResult<ToolInvokePayload> {
195 println!(
196 " [audit-logger] LOG: post-invoke user='{}' tool='{}'",
197 payload.user, payload.tool_name
198 );
199 PluginResult::allow()
200 }
201}
202
203// ---------------------------------------------------------------------------
204// Awaiting plugin example — RemoteAuthz
205// ---------------------------------------------------------------------------
206//
207// `HookHandler<H>` is async by design — `handle` is `async fn`.
208// Plugins that don't need to `.await` anything still write
209// `async fn handle` and return synchronously; this plugin shows the
210// other direction, where the body genuinely awaits per-invocation
211// work. The realistic version would call a remote authz service
212// (gRPC, HTTP, OPA, Cedarling, etc.); here we simulate the network
213// round-trip with a small `tokio::time::sleep` so the demo runs
214// offline.
215//
216// Key things this shows:
217// 1. Per-request latency state is *cached at init* — the handler
218// consults the in-memory ACL and only "calls out" on a miss.
219// Hot-path I/O is the most common source of latency regressions
220// in plugins, so prefer initialize-time loading wherever you can.
221// 2. Registration uses the exact same factory pattern as any other
222// plugin — `TypedHandlerAdapter::<H, _>` and the same
223// `register_factory` call. There is no separate async path.
224struct RemoteAuthz {
225 cfg: PluginConfig,
226 /// ACL "fetched" at init. Populated in Plugin::initialize.
227 allowed_users: tokio::sync::RwLock<std::collections::HashSet<String>>,
228}
229
230#[async_trait]
231impl Plugin for RemoteAuthz {
232 fn config(&self) -> &PluginConfig {
233 &self.cfg
234 }
235 /// Pretend we're loading the ACL from a remote service. In a real
236 /// plugin this would be `client.fetch_acl().await`; we simulate
237 /// the round-trip with a small sleep so the demo runs offline.
238 async fn initialize(&self) -> Result<(), Box<PluginError>> {
239 tokio::time::sleep(std::time::Duration::from_millis(2)).await;
240 let mut acl = self.allowed_users.write().await;
241 acl.extend(["alice", "bob"].iter().map(|s| s.to_string()));
242 println!(
243 " [remote-authz] initialized — ACL cached ({} users)",
244 acl.len()
245 );
246 Ok(())
247 }
248 async fn shutdown(&self) -> Result<(), Box<PluginError>> {
249 println!(" [remote-authz] shutdown");
250 Ok(())
251 }
252}
253
254impl HookHandler<ToolPreInvoke> for RemoteAuthz {
255 async fn handle(
256 &self,
257 payload: &ToolInvokePayload,
258 _extensions: &Extensions,
259 _ctx: &mut PluginContext,
260 ) -> PluginResult<ToolInvokePayload> {
261 // Cache hit path — fast.
262 let acl = self.allowed_users.read().await;
263 if acl.contains(&payload.user) {
264 println!(
265 " [remote-authz] OK (cache hit): user '{}' allowed",
266 payload.user
267 );
268 return PluginResult::allow();
269 }
270 drop(acl); // release read lock before the fake remote call
271 // Cache miss path — simulate a remote authz check. In a real
272 // plugin this is where you'd `.await` a gRPC or HTTP call.
273 // The latency cost is real and shows up on the request path.
274 tokio::time::sleep(std::time::Duration::from_millis(1)).await;
275 println!(
276 " [remote-authz] DENIED (cache miss + remote check): user '{}'",
277 payload.user
278 );
279 PluginResult::deny(PluginViolation::new(
280 "remote_authz_denied",
281 format!("User '{}' not in remote ACL", payload.user),
282 ))
283 }More examples
45 async fn handle(
46 &self,
47 payload: &MessagePayload,
48 extensions: &Extensions,
49 _ctx: &mut PluginContext,
50 ) -> PluginResult<MessagePayload> {
51 // Determine if this is pre or post invoke based on message content
52 let is_result = payload.message.is_tool_result();
53
54 if is_result {
55 // POST-INVOKE: verify the tool result came from an authorized call
56 let tool_name = payload
57 .message
58 .get_tool_results()
59 .first()
60 .map(|tr| tr.tool_name.as_str())
61 .unwrap_or("unknown");
62 println!(
63 " [identity-checker] POST-INVOKE: verifying result from '{}'",
64 tool_name
65 );
66
67 if let Some(ref security) = extensions.security {
68 if let Some(ref subject) = security.subject {
69 println!(
70 " [identity-checker] Result authorized for subject: {:?}",
71 subject.id
72 );
73 }
74 }
75 println!(" [identity-checker] POST-INVOKE ALLOWED");
76 } else {
77 // PRE-INVOKE: check caller identity and roles
78 let tool_name = payload
79 .message
80 .get_tool_calls()
81 .first()
82 .map(|tc| tc.name.as_str())
83 .unwrap_or("unknown");
84 println!(
85 " [identity-checker] PRE-INVOKE: checking identity for '{}'",
86 tool_name
87 );
88
89 if let Some(ref security) = extensions.security {
90 let labels: Vec<&String> = security.labels.iter().collect();
91 println!(" [identity-checker] Security labels: {:?}", labels);
92
93 if let Some(ref subject) = security.subject {
94 println!(
95 " [identity-checker] Subject: {:?}, Roles: {:?}",
96 subject.id,
97 subject.roles.iter().collect::<Vec<_>>()
98 );
99
100 if security.has_label("PII") && !subject.roles.contains("hr_admin") {
101 return PluginResult::deny(PluginViolation::new(
102 "insufficient_role",
103 format!("Tool '{}' requires 'hr_admin' role for PII data", tool_name),
104 ));
105 }
106 }
107 }
108
109 if extensions.http.is_some() {
110 println!(" [identity-checker] WARNING: HTTP visible (unexpected!)");
111 } else {
112 println!(" [identity-checker] HTTP: not visible (correct — no read_headers)");
113 }
114 println!(" [identity-checker] PRE-INVOKE ALLOWED");
115 }
116
117 PluginResult::allow()
118 }Sourcepub fn modify_payload(payload: P) -> Self
pub fn modify_payload(payload: P) -> Self
Modify payload only — extensions unchanged.
Sourcepub fn modify_extensions(extensions: OwnedExtensions) -> Self
pub fn modify_extensions(extensions: OwnedExtensions) -> Self
Modify extensions only — payload unchanged.
Takes an OwnedExtensions from extensions.cow_copy().
Examples found in repository?
139 async fn handle(
140 &self,
141 _payload: &MessagePayload,
142 extensions: &Extensions,
143 _ctx: &mut PluginContext,
144 ) -> PluginResult<MessagePayload> {
145 // Can see HTTP (has read_headers)
146 if let Some(ref http) = extensions.http {
147 println!(
148 " [header-injector] HTTP headers visible: {:?}",
149 http.request_headers
150 );
151 }
152
153 // Can NOT see security subject (no read_subject)
154 if let Some(ref security) = extensions.security {
155 if security.subject.is_some() {
156 println!(" [header-injector] WARNING: Subject visible (unexpected!)");
157 } else {
158 println!(" [header-injector] Security subject: not visible (no read_subject)");
159 }
160 }
161
162 // COW copy to modify — tokens propagate from the executor
163 let mut modified = extensions.cow_copy();
164
165 // Add a label via MonotonicSet (has append_labels)
166 if modified.labels_write_token.is_some() {
167 modified.security.as_mut().unwrap().add_label("PROCESSED");
168 println!(" [header-injector] Added label 'PROCESSED'");
169 }
170
171 // Inject a header via Guarded (has write_headers)
172 if let Some(ref token) = modified.http_write_token {
173 modified
174 .http
175 .as_mut()
176 .unwrap()
177 .write(token)
178 .set_header("X-Processed-By", "header-injector");
179 println!(" [header-injector] Injected header 'X-Processed-By'");
180 }
181
182 PluginResult::modify_extensions(modified)
183 }Sourcepub fn modify(payload: P, extensions: OwnedExtensions) -> Self
pub fn modify(payload: P, extensions: OwnedExtensions) -> Self
Modify both payload and extensions.
Takes an OwnedExtensions from extensions.cow_copy().
Sourcepub fn is_payload_modified(&self) -> bool
pub fn is_payload_modified(&self) -> bool
Whether this result carries a modified payload.
Sourcepub fn is_extensions_modified(&self) -> bool
pub fn is_extensions_modified(&self) -> bool
Whether this result carries modified extensions.