dwara_core/cel/
everywhere.rs1use std::collections::HashMap;
32
33use super::{value_to_bool, value_to_string, CelContext, CelProgram, Value};
34
35#[derive(Debug, Clone)]
45pub struct RequestContext {
46 pub path: String,
47 pub method: String,
48 pub headers: HashMap<String, String>,
49 pub query: HashMap<String, String>,
50 pub host: String,
51}
52
53impl RequestContext {
54 pub fn new(path: &str, method: &str, host: &str) -> Self {
56 Self {
57 path: path.to_string(),
58 method: method.to_string(),
59 headers: HashMap::new(),
60 query: HashMap::new(),
61 host: host.to_string(),
62 }
63 }
64
65 pub fn with_header(mut self, name: &str, value: &str) -> Self {
67 self.headers.insert(name.to_string(), value.to_string());
68 self
69 }
70
71 pub fn with_query(mut self, name: &str, value: &str) -> Self {
73 self.query.insert(name.to_string(), value.to_string());
74 self
75 }
76
77 pub fn to_cel_context(&self) -> Result<CelContext, String> {
79 let mut ctx = CelContext::new();
80 let request = serde_json::json!({
83 "path": self.path,
84 "method": self.method,
85 "host": self.host,
86 "headers": self.headers,
87 "query": self.query,
88 });
89 ctx.add_var("request", &request)?;
90 Ok(ctx)
91 }
92}
93
94#[derive(Debug)]
103pub struct RouteCondition {
104 program: CelProgram,
105}
106
107impl RouteCondition {
108 pub fn compile(expr: &str) -> Result<Self, String> {
110 let program =
111 CelProgram::compile(expr).map_err(|e| format!("route condition compile: {e}"))?;
112 Ok(Self { program })
113 }
114
115 pub fn matches(&self, ctx: &RequestContext) -> Result<bool, String> {
117 let cel_ctx = ctx.to_cel_context()?;
118 let result = self
119 .program
120 .evaluate(&cel_ctx)
121 .map_err(|e| format!("route condition evaluate: {e}"))?;
122 value_to_bool(&result).ok_or_else(|| "route condition must evaluate to bool".to_string())
123 }
124
125 pub fn source(&self) -> &str {
127 self.program.source()
128 }
129}
130
131#[derive(Debug)]
141pub struct HeaderTransform {
142 program: CelProgram,
143}
144
145impl HeaderTransform {
146 pub fn compile(expr: &str) -> Result<Self, String> {
148 let program =
149 CelProgram::compile(expr).map_err(|e| format!("header transform compile: {e}"))?;
150 Ok(Self { program })
151 }
152
153 pub fn evaluate(&self, ctx: &RequestContext) -> Result<String, String> {
155 let cel_ctx = ctx.to_cel_context()?;
156 let result = self
157 .program
158 .evaluate(&cel_ctx)
159 .map_err(|e| format!("header transform evaluate: {e}"))?;
160 value_to_string(&result)
161 .ok_or_else(|| "header transform must evaluate to string".to_string())
162 }
163
164 pub fn source(&self) -> &str {
166 self.program.source()
167 }
168}
169
170#[derive(Debug)]
180pub struct RateLimitKey {
181 program: CelProgram,
182}
183
184impl RateLimitKey {
185 pub fn compile(expr: &str) -> Result<Self, String> {
187 let program =
188 CelProgram::compile(expr).map_err(|e| format!("rate-limit key compile: {e}"))?;
189 Ok(Self { program })
190 }
191
192 pub fn derive(&self, ctx: &RequestContext) -> Result<String, String> {
194 let cel_ctx = ctx.to_cel_context()?;
195 let result = self
196 .program
197 .evaluate(&cel_ctx)
198 .map_err(|e| format!("rate-limit key evaluate: {e}"))?;
199 value_to_string(&result).ok_or_else(|| "rate-limit key must evaluate to string".to_string())
200 }
201
202 pub fn source(&self) -> &str {
204 self.program.source()
205 }
206}
207
208#[derive(Debug)]
217pub struct PolicyCondition {
218 program: CelProgram,
219}
220
221impl PolicyCondition {
222 pub fn compile(expr: &str) -> Result<Self, String> {
224 let program =
225 CelProgram::compile(expr).map_err(|e| format!("policy condition compile: {e}"))?;
226 Ok(Self { program })
227 }
228
229 pub fn applies(&self, ctx: &RequestContext) -> Result<bool, String> {
231 let cel_ctx = ctx.to_cel_context()?;
232 let result = self
233 .program
234 .evaluate(&cel_ctx)
235 .map_err(|e| format!("policy condition evaluate: {e}"))?;
236 value_to_bool(&result).ok_or_else(|| "policy condition must evaluate to bool".to_string())
237 }
238
239 pub fn source(&self) -> &str {
241 self.program.source()
242 }
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub enum CelUseSite {
252 RouteCondition,
253 HeaderTransform,
254 RateLimitKey,
255 PolicyCondition,
256}
257
258impl CelUseSite {
259 pub fn name(&self) -> &'static str {
261 match self {
262 CelUseSite::RouteCondition => "route condition",
263 CelUseSite::HeaderTransform => "header transform",
264 CelUseSite::RateLimitKey => "rate-limit key",
265 CelUseSite::PolicyCondition => "policy condition",
266 }
267 }
268
269 pub fn expected_type(&self) -> &'static str {
271 match self {
272 CelUseSite::RouteCondition | CelUseSite::PolicyCondition => "bool",
273 CelUseSite::HeaderTransform | CelUseSite::RateLimitKey => "string",
274 }
275 }
276}
277
278pub fn compile_for(use_site: CelUseSite, expr: &str) -> Result<CelProgram, String> {
280 CelProgram::compile(expr).map_err(|e| format!("{} compile: {e}", use_site.name()))
281}
282
283pub fn evaluate_for(
285 use_site: CelUseSite,
286 program: &CelProgram,
287 ctx: &RequestContext,
288) -> Result<Value, String> {
289 let cel_ctx = ctx.to_cel_context()?;
290 let result = program
291 .evaluate(&cel_ctx)
292 .map_err(|e| format!("{} evaluate: {e}", use_site.name()))?;
293
294 let type_ok = match use_site {
296 CelUseSite::RouteCondition | CelUseSite::PolicyCondition => {
297 matches!(result, Value::Bool(_))
298 }
299 CelUseSite::HeaderTransform | CelUseSite::RateLimitKey => {
300 matches!(result, Value::String(_))
301 }
302 };
303
304 if !type_ok {
305 return Err(format!(
306 "{} must evaluate to {}, got {:?}",
307 use_site.name(),
308 use_site.expected_type(),
309 result
310 ));
311 }
312
313 Ok(result)
314}