1use super::expression::{BinaryOp, Expression, UnaryOp};
2use super::violation::{Severity, Violation};
3use crate::graph::Graph;
4use crate::policy::ThreeValuedBool;
5use crate::units::get_default_registry;
6use crate::{ConceptId, SemanticVersion};
7use rust_decimal::prelude::{FromPrimitive, FromStr};
8use rust_decimal::Decimal;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum PolicyModality {
13 Obligation,
14 Prohibition,
15 Permission,
16}
17
18#[allow(unused_imports)]
20pub use PolicyModality as DeonticModality;
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub enum PolicyKind {
24 Constraint,
25 Derivation,
26 Obligation,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct Policy {
31 pub id: ConceptId,
32 pub name: String,
33 pub namespace: String,
34 pub version: SemanticVersion,
35 expression: Expression,
36 pub modality: PolicyModality,
37 pub kind: PolicyKind,
38 pub priority: i32,
39 pub rationale: Option<String>,
40 pub tags: Vec<String>,
41 #[serde(skip)]
42 cached_normalized_expr: std::sync::OnceLock<super::NormalizedExpression>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct EvaluationResult {
47 pub is_satisfied: bool,
50 pub is_satisfied_tristate: Option<bool>,
52 pub violations: Vec<Violation>,
53}
54
55impl Policy {
56 #[must_use]
60 pub fn normalized_expression(&self) -> &super::NormalizedExpression {
61 self.cached_normalized_expr
62 .get_or_init(|| self.expression.normalize())
63 }
64
65 pub fn expression(&self) -> &Expression {
67 &self.expression
68 }
69
70 pub fn set_expression(&mut self, expr: Expression) {
72 self.expression = expr;
73 self.cached_normalized_expr = std::sync::OnceLock::new();
75 }
76
77 pub fn new(name: impl Into<String>, expression: Expression) -> Self {
78 let name = name.into();
79 Self {
80 id: ConceptId::from_concept("default", &name),
81 name,
82 namespace: "default".to_string(),
83 version: SemanticVersion::default(),
84 expression,
85 modality: PolicyModality::Obligation,
86 kind: PolicyKind::Constraint,
87 priority: 0,
88 rationale: None,
89 tags: Vec::new(),
90 cached_normalized_expr: std::sync::OnceLock::new(),
91 }
92 }
93
94 pub fn new_with_namespace(
95 name: impl Into<String>,
96 namespace: impl Into<String>,
97 expression: Expression,
98 ) -> Self {
99 let namespace = namespace.into();
100 let name = name.into();
101 let id = ConceptId::from_concept(&namespace, &name);
102
103 Self {
104 id,
105 name,
106 namespace,
107 version: SemanticVersion::default(),
108 expression,
109 modality: PolicyModality::Obligation,
110 kind: PolicyKind::Constraint,
111 priority: 0,
112 rationale: None,
113 tags: Vec::new(),
114 cached_normalized_expr: std::sync::OnceLock::new(),
115 }
116 }
117
118 pub fn with_modality(mut self, modality: PolicyModality) -> Self {
119 self.modality = modality;
120 self
121 }
122
123 pub fn with_version(mut self, version: SemanticVersion) -> Self {
124 self.version = version;
125 self
126 }
127
128 pub fn with_kind(mut self, kind: PolicyKind) -> Self {
129 self.kind = kind;
130 self
131 }
132
133 pub fn with_priority(mut self, priority: i32) -> Self {
134 self.priority = priority;
135 self
136 }
137
138 pub fn with_rationale(mut self, rationale: impl Into<String>) -> Self {
139 self.rationale = Some(rationale.into());
140 self
141 }
142
143 pub fn with_tags<I, S>(mut self, tags: I) -> Self
144 where
145 I: IntoIterator<Item = S>,
146 S: Into<String>,
147 {
148 self.tags = tags.into_iter().map(Into::into).collect();
149 self
150 }
151
152 pub fn with_metadata(
153 mut self,
154 kind: Option<PolicyKind>,
155 modality: Option<PolicyModality>,
156 priority: Option<i32>,
157 rationale: Option<String>,
158 tags: Vec<String>,
159 ) -> Self {
160 if let Some(kind) = kind {
161 self.kind = kind;
162 }
163 if let Some(modality) = modality {
164 self.modality = modality;
165 }
166 if let Some(priority) = priority {
167 self.priority = priority;
168 }
169 self.rationale = rationale;
170 if !tags.is_empty() {
171 self.tags = tags;
172 }
173 self
174 }
175
176 pub fn kind(&self) -> &PolicyKind {
177 &self.kind
178 }
179
180 pub fn evaluate(&self, graph: &Graph) -> Result<EvaluationResult, String> {
181 self.evaluate_with_mode(graph, graph.use_three_valued_logic())
182 }
183
184 pub fn evaluate_with_mode(
185 &self,
186 graph: &Graph,
187 use_three_valued_logic: bool,
188 ) -> Result<EvaluationResult, String> {
189 Self::validate_aggregation_usage(&self.expression, true)?;
190
191 let expanded = self.expression.expand(graph)?;
192
193 let is_satisfied_tristate: Option<bool> = if use_three_valued_logic {
196 match self.evaluate_expression_three_valued(&expanded, graph)? {
197 ThreeValuedBool::True => Some(true),
198 ThreeValuedBool::False => Some(false),
199 ThreeValuedBool::Null => None,
200 }
201 } else {
202 Some(self.evaluate_expression_boolean(&expanded, graph)?)
203 };
204
205 let is_satisfied = is_satisfied_tristate.unwrap_or(false);
206
207 let violations = if is_satisfied_tristate == Some(true) {
208 vec![]
209 } else if is_satisfied_tristate == Some(false) {
210 vec![Violation::new(
211 &self.name,
212 format!("Policy '{}' was violated", self.name),
213 self.modality.to_severity(),
214 )]
215 } else {
216 vec![Violation::new(
218 &self.name,
219 format!("Policy '{}' evaluation is UNKNOWN (NULL)", self.name),
220 self.modality.to_severity(),
221 )]
222 };
223
224 Ok(EvaluationResult {
225 is_satisfied,
226 is_satisfied_tristate,
227 violations,
228 })
229 }
230
231 fn validate_aggregation_usage(
232 expr: &Expression,
233 in_boolean_context: bool,
234 ) -> Result<(), String> {
235 match expr {
236 Expression::Aggregation { .. } | Expression::AggregationComprehension { .. } => {
237 if in_boolean_context {
238 Err("Aggregation in boolean context requires explicit comparison (e.g., COUNT(...) > 0)".to_string())
239 } else {
240 Ok(())
241 }
242 }
243 Expression::Binary { op, left, right } => {
244 let child_boolean = matches!(op, BinaryOp::And | BinaryOp::Or);
245 Self::validate_aggregation_usage(left, child_boolean)?;
246 Self::validate_aggregation_usage(right, child_boolean)?;
247 Ok(())
248 }
249 Expression::Unary { op, operand } => {
250 let child_boolean = matches!(op, UnaryOp::Not);
251 Self::validate_aggregation_usage(operand, child_boolean)
252 }
253 Expression::Quantifier {
254 collection,
255 condition,
256 ..
257 } => {
258 Self::validate_aggregation_usage(collection, false)?;
259 Self::validate_aggregation_usage(condition, true)
260 }
261 Expression::GroupBy {
262 collection,
263 filter,
264 key,
265 condition,
266 ..
267 } => {
268 Self::validate_aggregation_usage(collection, false)?;
269 if let Some(f) = filter {
270 Self::validate_aggregation_usage(f, true)?;
271 }
272 Self::validate_aggregation_usage(key, false)?;
273 Self::validate_aggregation_usage(condition, true)
274 }
275 _ => Ok(()),
276 }
277 }
278
279 fn evaluate_expression_boolean(
280 &self,
281 expr: &Expression,
282 graph: &Graph,
283 ) -> Result<bool, String> {
284 match expr {
285 Expression::Literal(v) => v
286 .as_bool()
287 .ok_or_else(|| format!("Expected boolean literal, got: {}", v)),
288 Expression::Variable(name) => {
289 Err(format!("Cannot evaluate unexpanded variable: {}", name))
290 }
291 Expression::Cast { .. } => {
292 let val = Self::get_runtime_value(expr, graph)?;
293 val.as_bool()
294 .ok_or_else(|| format!("Expected boolean from cast, got: {}", val))
295 }
296 Expression::Binary { op, left, right } => match op {
297 BinaryOp::And | BinaryOp::Or => {
298 let left_val = self.evaluate_expression_boolean(left, graph)?;
299 let right_val = self.evaluate_expression_boolean(right, graph)?;
300
301 Ok(match op {
302 BinaryOp::And => left_val && right_val,
303 BinaryOp::Or => left_val || right_val,
304 _ => unreachable!(),
305 })
306 }
307 BinaryOp::Equal | BinaryOp::NotEqual => {
308 self.compare_values(left, right, graph, |l, r| match op {
309 BinaryOp::Equal => l == r,
310 BinaryOp::NotEqual => l != r,
311 _ => unreachable!(),
312 })
313 }
314 BinaryOp::GreaterThan
315 | BinaryOp::LessThan
316 | BinaryOp::GreaterThanOrEqual
317 | BinaryOp::LessThanOrEqual => {
318 self.compare_numeric(left, right, graph, |l, r| match op {
319 BinaryOp::GreaterThan => l > r,
320 BinaryOp::LessThan => l < r,
321 BinaryOp::GreaterThanOrEqual => l >= r,
322 BinaryOp::LessThanOrEqual => l <= r,
323 _ => unreachable!(),
324 })
325 }
326 BinaryOp::Plus | BinaryOp::Minus | BinaryOp::Multiply | BinaryOp::Divide => {
327 Err("Arithmetic operations not supported in boolean context".to_string())
328 }
329 BinaryOp::Contains | BinaryOp::StartsWith | BinaryOp::EndsWith => self
330 .compare_strings(left, right, graph, |l, r| match op {
331 BinaryOp::Contains => l.contains(r),
332 BinaryOp::StartsWith => l.starts_with(r),
333 BinaryOp::EndsWith => l.ends_with(r),
334 _ => unreachable!(),
335 }),
336 BinaryOp::HasRole => self.evaluate_has_role(left, right, graph),
337 BinaryOp::Matches => self.evaluate_pattern_match(left, right, graph),
338 BinaryOp::Before | BinaryOp::After | BinaryOp::During => {
339 let left_str = self.get_string_value(left, graph)?;
341 let right_str = self.get_string_value(right, graph)?;
342
343 let left_dt = chrono::DateTime::parse_from_rfc3339(&left_str).map_err(|e| {
345 format!("Failed to parse left timestamp '{}': {}", left_str, e)
346 })?;
347 let right_dt =
348 chrono::DateTime::parse_from_rfc3339(&right_str).map_err(|e| {
349 format!("Failed to parse right timestamp '{}': {}", right_str, e)
350 })?;
351
352 let result = match op {
353 BinaryOp::Before => left_dt < right_dt,
354 BinaryOp::After => left_dt > right_dt,
355 BinaryOp::During => {
356 return Err("'during' operator requires interval semantics which are not yet implemented. Use 'before' and 'after' for timestamp comparisons.".to_string())
357 }
358 _ => unreachable!(),
359 };
360 Ok(result)
361 }
362 },
363 Expression::Unary { op, operand } => {
364 let val = self.evaluate_expression_boolean(operand, graph)?;
365 Ok(match op {
366 UnaryOp::Not => !val,
367 UnaryOp::Negate => {
368 return Err("Negate operator not supported in boolean context".to_string())
369 }
370 })
371 }
372 Expression::Quantifier { .. } => {
373 Err("Cannot evaluate non-expanded quantifier".to_string())
374 }
375 Expression::MemberAccess { object, member } => {
376 let value = Self::get_runtime_value(expr, graph)?;
377 match value {
378 serde_json::Value::Bool(v) => Ok(v),
379 serde_json::Value::Null => Ok(false),
380 _ => Err(format!(
381 "Expected boolean value for member '{}.{}', but found {:?}",
382 object, member, value
383 )),
384 }
385 }
386 Expression::Aggregation { .. } => {
387 Err("Cannot evaluate non-expanded aggregation".to_string())
388 }
389 Expression::AggregationComprehension { .. } => {
390 Err("Cannot evaluate non-expanded aggregation comprehension".to_string())
391 }
392 Expression::QuantityLiteral { .. } => {
393 Err("Cannot evaluate quantity literal in boolean context".to_string())
394 }
395 Expression::TimeLiteral(_) => {
396 Err("Cannot evaluate time literal in boolean context".to_string())
397 }
398 Expression::IntervalLiteral { .. } => {
399 Err("Cannot evaluate interval literal in boolean context".to_string())
400 }
401 Expression::GroupBy { .. } => Err("Cannot evaluate non-expanded group_by".to_string()),
402 }
403 }
404
405 fn evaluate_expression_three_valued(
406 &self,
407 expr: &Expression,
408 graph: &Graph,
409 ) -> Result<ThreeValuedBool, String> {
410 use ThreeValuedBool as T;
411
412 match expr {
413 Expression::Literal(v) => Ok(T::from_option_bool(v.as_bool())),
414 Expression::Variable(name) => Err(format!("Cannot evaluate unexpanded variable: {}", name)),
415 Expression::Cast { .. } => {
416 let val = Self::get_runtime_value(expr, graph)?;
417 Ok(T::from_option_bool(val.as_bool()))
418 }
419 Expression::Binary { op, left, right } => match op {
420 BinaryOp::And | BinaryOp::Or => {
421 let l = self.evaluate_expression_three_valued(left, graph)?;
422 let r = self.evaluate_expression_three_valued(right, graph)?;
423 Ok(match op {
424 BinaryOp::And => l.and(r),
425 BinaryOp::Or => l.or(r),
426 _ => unreachable!(),
427 })
428 }
429 BinaryOp::Equal | BinaryOp::NotEqual => {
430 let left_val = Self::get_runtime_value(left, graph);
431 let right_val = Self::get_runtime_value(right, graph);
432 match (left_val, right_val) {
433 (Ok(lv), Ok(rv)) => {
434 if lv.is_null() || rv.is_null() {
436 Ok(T::Null)
437 } else {
438 let numeric_eq = match (
439 Self::parse_numeric_with_unit_value(&lv),
440 Self::parse_numeric_with_unit_value(&rv),
441 ) {
442 (Ok(ln), Ok(rn)) => {
443 self.normalize_units_nullable(ln, rn)?
444 .map(|(l, r)| l == r)
445 }
446 _ => None,
447 };
448
449 let equality = if let Some(eq) = numeric_eq {
450 Some(eq)
451 } else if lv.is_number() || rv.is_number() {
452 None
453 } else {
454 Some(lv == rv)
455 };
456
457 let eq = match (op, equality) {
458 (_, None) => return Ok(T::Null),
459 (BinaryOp::Equal, Some(true)) => true,
460 (BinaryOp::Equal, Some(false)) => false,
461 (BinaryOp::NotEqual, Some(true)) => false,
462 (BinaryOp::NotEqual, Some(false)) => true,
463 _ => unreachable!(),
464 };
465
466 Ok(T::from_option_bool(Some(eq)))
467 }
468 }
469 _ => Ok(T::Null),
470 }
471 }
472 BinaryOp::GreaterThan
473 | BinaryOp::LessThan
474 | BinaryOp::GreaterThanOrEqual
475 | BinaryOp::LessThanOrEqual => {
476 let left_v = Self::get_runtime_value(left, graph);
477 let right_v = Self::get_runtime_value(right, graph);
478 match (left_v, right_v) {
479 (Ok(lv), Ok(rv)) => {
480 if lv.is_null() || rv.is_null() {
481 Ok(T::Null)
482 } else {
483 let numeric = self
484 .normalize_units_nullable(
485 Self::parse_numeric_with_unit_value(&lv)
486 .ok()
487 .flatten(),
488 Self::parse_numeric_with_unit_value(&rv)
489 .ok()
490 .flatten(),
491 )?
492 .map(|(l, r)| match op {
493 BinaryOp::GreaterThan => l > r,
494 BinaryOp::LessThan => l < r,
495 BinaryOp::GreaterThanOrEqual => l >= r,
496 BinaryOp::LessThanOrEqual => l <= r,
497 _ => unreachable!(),
498 });
499
500 Ok(T::from_option_bool(numeric))
501 }
502 }
503 _ => Ok(T::Null),
504 }
505 }
506 BinaryOp::Plus | BinaryOp::Minus | BinaryOp::Multiply | BinaryOp::Divide => {
507 Err("Arithmetic operations not supported in boolean context".to_string())
508 }
509 BinaryOp::Contains | BinaryOp::StartsWith | BinaryOp::EndsWith => {
510 let left_v = Self::get_runtime_value(left, graph);
511 let right_v = Self::get_runtime_value(right, graph);
512 match (left_v, right_v) {
513 (Ok(lv), Ok(rv)) => {
514 if lv.is_null() || rv.is_null() {
515 Ok(T::Null)
516 } else if let (Some(ls), Some(rs)) = (lv.as_str(), rv.as_str()) {
517 let ok = match op {
518 BinaryOp::Contains => ls.contains(rs),
519 BinaryOp::StartsWith => ls.starts_with(rs),
520 BinaryOp::EndsWith => ls.ends_with(rs),
521 _ => unreachable!(),
522 };
523 Ok(T::from_option_bool(Some(ok)))
524 } else {
525 Ok(T::Null)
526 }
527 }
528 _ => Ok(T::Null),
529 }
530 }
531 BinaryOp::HasRole => {
532 let role_check = self.evaluate_has_role(left, right, graph)?;
533 Ok(T::from_option_bool(Some(role_check)))
534 }
535 BinaryOp::Matches => {
536 let left_v = Self::get_runtime_value(left, graph);
537 let right_v = Self::get_runtime_value(right, graph);
538
539 match (left_v, right_v) {
540 (Ok(lv), Ok(rv)) => {
541 if lv.is_null() || rv.is_null() {
542 return Ok(T::Null);
543 }
544
545 if let (Some(candidate), Some(pattern_name)) = (lv.as_str(), rv.as_str()) {
546 let pattern = graph
547 .find_pattern(pattern_name, Some(&self.namespace))
548 .ok_or_else(|| {
549 format!(
550 "Pattern '{}' not found in namespace '{}'",
551 pattern_name, self.namespace
552 )
553 })?;
554 let is_match = pattern.is_match(candidate).map_err(|e| {
555 format!(
556 "Pattern '{}' failed to evaluate: {}",
557 pattern_name, e
558 )
559 })?;
560 Ok(T::from_option_bool(Some(is_match)))
561 } else {
562 Ok(T::Null)
563 }
564 }
565 _ => Ok(T::Null),
566 }
567 }
568 BinaryOp::Before | BinaryOp::After | BinaryOp::During => {
569 if matches!(op, BinaryOp::During) {
571 return Err("'during' operator requires interval semantics which are not yet implemented. Use 'before' and 'after' for timestamp comparisons.".to_string());
572 }
573
574 let left_v = Self::get_runtime_value(left, graph);
576 let right_v = Self::get_runtime_value(right, graph);
577 match (left_v, right_v) {
578 (Ok(lv), Ok(rv)) => {
579 if lv.is_null() || rv.is_null() {
580 Ok(T::Null)
581 } else if let (Some(ls), Some(rs)) = (lv.as_str(), rv.as_str()) {
582 let left_dt = match chrono::DateTime::parse_from_rfc3339(ls) {
584 Ok(dt) => dt,
585 Err(_) => return Ok(T::Null), };
587 let right_dt = match chrono::DateTime::parse_from_rfc3339(rs) {
588 Ok(dt) => dt,
589 Err(_) => return Ok(T::Null), };
591
592 let result = match op {
593 BinaryOp::Before => left_dt < right_dt,
594 BinaryOp::After => left_dt > right_dt,
595 _ => unreachable!(),
596 };
597 Ok(T::from_option_bool(Some(result)))
598 } else {
599 Ok(T::Null)
600 }
601 }
602 _ => Ok(T::Null),
603 }
604 }
605 },
606 Expression::Unary { op, operand } => {
607 let v = self.evaluate_expression_three_valued(operand, graph)?;
608 Ok(match op {
609 UnaryOp::Not => v.not(),
610 UnaryOp::Negate => return Err("Negate operator not supported in boolean context".to_string()),
611 })
612 }
613 Expression::Quantifier { quantifier, variable, collection, condition } => {
614 let items = Expression::get_collection(collection, graph)?;
616 use super::expression::Quantifier as Q;
617
618 let mut saw_true = 0usize;
619 let mut saw_false = 0usize;
620 let mut saw_null = 0usize;
621
622 for item in items {
623 let substituted = condition.substitute(variable, &item)?;
624 let val = self.evaluate_expression_three_valued(&substituted, graph)?;
625 match val {
626 T::True => saw_true += 1,
627 T::False => saw_false += 1,
628 T::Null => saw_null += 1,
629 }
630 }
631
632 match quantifier {
633 Q::ForAll => {
634 if saw_false > 0 { return Ok(T::False); }
635 if saw_null > 0 { return Ok(T::Null); }
636 Ok(T::True)
637 }
638 Q::Exists => {
639 if saw_true > 0 { return Ok(T::True); }
640 if saw_null > 0 { return Ok(T::Null); }
641 Ok(T::False)
642 }
643 Q::ExistsUnique => {
644 if saw_true > 1 { return Ok(T::False); }
645 if saw_true == 1 && saw_null == 0 { return Ok(T::True); }
646 if saw_true == 1 && saw_null > 0 { return Ok(T::Null); }
647 if saw_true == 0 && saw_null > 0 { return Ok(T::Null); }
648 Ok(T::False)
649 }
650 }
651 }
652 Expression::MemberAccess { object: _, member: _ } => {
653 let value = Self::get_runtime_value(expr, graph)?;
655 Ok(T::from_option_bool(value.as_bool()))
656 }
657 Expression::Aggregation { .. } => Err("Aggregation in boolean context requires explicit comparison (e.g., COUNT(...) > 0)".to_string()),
658 Expression::AggregationComprehension { .. } => Err("Aggregation in boolean context requires explicit comparison (e.g., COUNT(...) > 0)".to_string()),
659 Expression::QuantityLiteral { .. } => Err("Cannot convert quantity to boolean; compare against a threshold instead".to_string()),
660 Expression::TimeLiteral(_) => Err("Cannot convert time to boolean; use temporal comparison operators".to_string()),
661 Expression::IntervalLiteral { .. } => Err("Cannot convert interval to boolean; use temporal comparison operators".to_string()),
662 Expression::GroupBy { .. } => Err("Cannot evaluate non-expanded group_by".to_string()),
663 }
664 }
665
666 fn compare_values<F>(
667 &self,
668 left: &Expression,
669 right: &Expression,
670 graph: &Graph,
671 op: F,
672 ) -> Result<bool, String>
673 where
674 F: Fn(&serde_json::Value, &serde_json::Value) -> bool,
675 {
676 let left_val = self
677 .get_literal_value(left)
678 .or_else(|_| Self::get_runtime_value(left, graph))?;
679 let right_val = self
680 .get_literal_value(right)
681 .or_else(|_| Self::get_runtime_value(right, graph))?;
682 Ok(op(&left_val, &right_val))
683 }
684
685 fn compare_numeric<F>(
686 &self,
687 left: &Expression,
688 right: &Expression,
689 graph: &Graph,
690 op: F,
691 ) -> Result<bool, String>
692 where
693 F: Fn(Decimal, Decimal) -> bool,
694 {
695 let left_val = self.resolve_numeric_with_unit(left, graph)?;
696 let right_val = self.resolve_numeric_with_unit(right, graph)?;
697 let (left_aligned, right_aligned) = self.normalize_units_strict(left_val, right_val)?;
698 Ok(op(left_aligned, right_aligned))
699 }
700
701 fn compare_strings<F>(
702 &self,
703 left: &Expression,
704 right: &Expression,
705 graph: &Graph,
706 op: F,
707 ) -> Result<bool, String>
708 where
709 F: Fn(&str, &str) -> bool,
710 {
711 let left_val = self.get_string_value(left, graph)?;
712 let right_val = self.get_string_value(right, graph)?;
713 Ok(op(&left_val, &right_val))
714 }
715
716 fn evaluate_pattern_match(
717 &self,
718 left: &Expression,
719 right: &Expression,
720 graph: &Graph,
721 ) -> Result<bool, String> {
722 let candidate = self.get_string_value(left, graph)?;
723 let pattern_name = self.get_string_value(right, graph)?;
724
725 let pattern = graph
726 .find_pattern(&pattern_name, Some(&self.namespace))
727 .ok_or_else(|| {
728 format!(
729 "Pattern '{}' not found in namespace '{}'",
730 pattern_name, self.namespace
731 )
732 })?;
733
734 pattern
735 .is_match(&candidate)
736 .map_err(|e| format!("Pattern '{}' failed to evaluate: {}", pattern_name, e))
737 }
738
739 fn evaluate_has_role(
740 &self,
741 left: &Expression,
742 right: &Expression,
743 graph: &Graph,
744 ) -> Result<bool, String> {
745 let target_role = self.get_string_value(right, graph)?;
746 let roles = self.collect_roles(left, graph)?;
747
748 Ok(roles
749 .iter()
750 .any(|role| role.eq_ignore_ascii_case(&target_role)))
751 }
752
753 fn collect_roles(&self, expr: &Expression, graph: &Graph) -> Result<Vec<String>, String> {
754 let value = Self::get_runtime_value(expr, graph)?;
755
756 if let Some(arr) = value.as_array() {
757 return Ok(arr
758 .iter()
759 .filter_map(|v| v.as_str().map(|s| s.to_string()))
760 .collect());
761 }
762
763 if let Some(obj) = value.as_object() {
764 if let Some(roles) = obj.get("roles").and_then(|r| r.as_array()) {
765 return Ok(roles
766 .iter()
767 .filter_map(|v| v.as_str().map(|s| s.to_string()))
768 .collect());
769 }
770
771 if let Some(name) = obj.get("name").and_then(|v| v.as_str()) {
772 if let Some(entity_id) = graph.find_entity_by_name(name) {
773 return Ok(graph.role_names_for_entity(&entity_id));
774 }
775 }
776 }
777
778 if let Some(name) = value.as_str() {
779 if let Some(entity_id) = graph.find_entity_by_name(name) {
780 return Ok(graph.role_names_for_entity(&entity_id));
781 }
782
783 if let Some(role_id) = graph.find_role_by_name(name) {
784 if let Some(role) = graph.get_role(&role_id) {
785 return Ok(vec![role.name().to_string()]);
786 }
787 }
788 }
789
790 Ok(Vec::new())
791 }
792
793 fn get_literal_value(&self, expr: &Expression) -> Result<serde_json::Value, String> {
794 match expr {
795 Expression::Literal(v) => Ok(v.clone()),
796 _ => Err("Expected literal value".to_string()),
797 }
798 }
799
800 fn parse_decimal_value(value: &serde_json::Value) -> Result<Decimal, String> {
801 if let Some(s) = value.as_str() {
802 Decimal::from_str(s).map_err(|e| e.to_string())
803 } else if let Some(f) = value.as_f64() {
804 Decimal::from_f64(f).ok_or_else(|| format!("Unable to represent {} as Decimal", f))
805 } else if let Some(i) = value.as_i64() {
806 Ok(Decimal::from(i))
807 } else if let Some(u) = value.as_u64() {
808 Decimal::from_u64(u).ok_or_else(|| format!("Unable to represent {} as Decimal", u))
809 } else {
810 Err(format!("Expected numeric value, got: {}", value))
811 }
812 }
813
814 fn parse_numeric_with_unit_value(
815 value: &serde_json::Value,
816 ) -> Result<Option<(Decimal, Option<String>)>, String> {
817 if let Some(obj) = value.as_object() {
818 match (obj.get("__quantity_value"), obj.get("__quantity_unit")) {
819 (Some(q_val), Some(q_unit)) => {
820 let unit_str = q_unit
821 .as_str()
822 .ok_or_else(|| {
823 format!("Expected __quantity_unit to be string, got: {}", q_unit)
824 })?
825 .to_string();
826 let value_dec = Self::parse_decimal_value(q_val)
827 .map_err(|e| format!("Invalid __quantity_value: {}", e))?;
828 return Ok(Some((value_dec, Some(unit_str))));
829 }
830 (Some(_), None) | (None, Some(_)) => {
831 return Err(
832 "Quantity object must include both __quantity_value and __quantity_unit"
833 .to_string(),
834 )
835 }
836 _ => {}
837 }
838 }
839
840 if value.is_number() || value.is_string() {
841 return Ok(Some((Self::parse_decimal_value(value)?, None)));
842 }
843
844 Ok(None)
845 }
846
847 fn resolve_numeric_with_unit(
848 &self,
849 expr: &Expression,
850 graph: &Graph,
851 ) -> Result<(Decimal, Option<String>), String> {
852 let value = self
853 .get_literal_value(expr)
854 .or_else(|_| Self::get_runtime_value(expr, graph))?;
855 Self::parse_numeric_with_unit_value(&value)?
856 .ok_or_else(|| format!("Expected numeric value, got: {}", value))
857 }
858
859 fn normalize_units_strict(
860 &self,
861 left: (Decimal, Option<String>),
862 right: (Decimal, Option<String>),
863 ) -> Result<(Decimal, Decimal), String> {
864 match (left.1, right.1) {
865 (Some(l_unit), Some(r_unit)) => {
866 let registry = get_default_registry();
867 let registry = registry
868 .read()
869 .map_err(|e| format!("Failed to lock unit registry: {}", e))?;
870
871 if l_unit == r_unit {
872 Ok((left.0, right.0))
873 } else {
874 let from = registry
875 .get_unit(&r_unit)
876 .map_err(|e| format!("Invalid unit '{}': {}", r_unit, e))?;
877 let to = registry
878 .get_unit(&l_unit)
879 .map_err(|e| format!("Invalid unit '{}': {}", l_unit, e))?;
880 let converted = registry
881 .convert(right.0, from, to)
882 .map_err(|e| format!("Unit conversion failed: {}", e))?;
883 Ok((left.0, converted))
884 }
885 }
886 (None, None) => Ok((left.0, right.0)),
887 (Some(l_unit), None) => Err(format!(
888 "Cannot compare quantity with unit '{}' to unitless value",
889 l_unit
890 )),
891 (None, Some(r_unit)) => Err(format!(
892 "Cannot compare unitless value to quantity with unit '{}'",
893 r_unit
894 )),
895 }
896 }
897
898 fn normalize_units_nullable(
899 &self,
900 left: Option<(Decimal, Option<String>)>,
901 right: Option<(Decimal, Option<String>)>,
902 ) -> Result<Option<(Decimal, Decimal)>, String> {
903 let (left, right) = match (left, right) {
904 (Some(l), Some(r)) => (l, r),
905 _ => return Ok(None),
906 };
907
908 match (left.1.clone(), right.1.clone()) {
909 (Some(l_unit), Some(r_unit)) => {
910 let registry = get_default_registry();
911 let registry = registry
912 .read()
913 .map_err(|e| format!("Failed to lock unit registry: {}", e))?;
914 if l_unit == r_unit {
915 Ok(Some((left.0, right.0)))
916 } else {
917 let from = registry.get_unit(&r_unit);
918 let to = registry.get_unit(&l_unit);
919 if let (Ok(from), Ok(to)) = (from, to) {
920 match registry.convert(right.0, from, to) {
921 Ok(converted) => Ok(Some((left.0, converted))),
922 Err(_) => Ok(None),
923 }
924 } else {
925 Ok(None)
926 }
927 }
928 }
929 (None, None) => Ok(Some((left.0, right.0))),
930 _ => Ok(None),
931 }
932 }
933
934 fn get_string_value(&self, expr: &Expression, graph: &Graph) -> Result<String, String> {
935 let v = self
936 .get_literal_value(expr)
937 .or_else(|_| Self::get_runtime_value(expr, graph))?;
938 v.as_str()
939 .map(|s| s.to_string())
940 .ok_or_else(|| "Expected string value".to_string())
941 }
942
943 fn get_runtime_value(expr: &Expression, graph: &Graph) -> Result<serde_json::Value, String> {
944 match expr {
945 Expression::Literal(v) => Ok(v.clone()),
946 Expression::MemberAccess { object, member } => {
947 if let Some(id) = graph.find_entity_by_name(object) {
949 if let Some(entity) = graph.get_entity(&id) {
950 if member == "id" {
952 return Ok(serde_json::json!(entity.id().to_string()));
953 } else if member == "name" {
954 return Ok(serde_json::json!(entity.name()));
955 } else if member == "namespace" {
956 return Ok(serde_json::json!(entity.namespace()));
957 }
958 if let Some(val) = entity.get_attribute(member) {
959 if val.is_null() {
960 log::debug!(
961 "Entity '{}' member '{}' present but NULL; returning Null",
962 object,
963 member
964 );
965 }
966 return Ok(val.clone());
967 }
968 log::debug!(
969 "Entity '{}' found but member '{}' missing; returning Null",
970 object,
971 member
972 );
973 return Ok(serde_json::Value::Null);
974 } else {
975 log::debug!(
976 "Entity lookup for '{}' returned id {} but entity missing; returning Null",
977 object, id
978 );
979 }
980 } else {
981 log::debug!(
982 "Entity '{}' not found while resolving member '{}'; continuing lookup",
983 object,
984 member
985 );
986 }
987
988 if let Some(id) = graph.find_resource_by_name(object) {
989 if let Some(resource) = graph.get_resource(&id) {
990 if member == "id" {
991 return Ok(serde_json::json!(resource.id().to_string()));
992 } else if member == "name" {
993 return Ok(serde_json::json!(resource.name()));
994 } else if member == "unit" {
995 return Ok(serde_json::json!(resource.unit()));
996 }
997 if let Some(val) = resource.get_attribute(member) {
998 if val.is_null() {
999 log::debug!(
1000 "Resource '{}' member '{}' present but NULL; returning Null",
1001 object,
1002 member
1003 );
1004 }
1005 return Ok(val.clone());
1006 }
1007 log::debug!(
1008 "Resource '{}' found but member '{}' missing; returning Null",
1009 object,
1010 member
1011 );
1012 return Ok(serde_json::Value::Null);
1013 } else {
1014 log::debug!(
1015 "Resource lookup for '{}' returned id {} but resource missing; returning Null",
1016 object, id
1017 );
1018 }
1019 } else {
1020 log::debug!(
1021 "Resource '{}' not found while resolving member '{}'; returning Null",
1022 object,
1023 member
1024 );
1025 }
1026
1027 log::debug!(
1029 "Member access '{}.{}' did not resolve to entity or resource; returning Null",
1030 object,
1031 member
1032 );
1033 Ok(serde_json::Value::Null)
1034 }
1035 Expression::Aggregation {
1036 function,
1037 collection,
1038 field,
1039 filter,
1040 } => {
1041 let v =
1042 Expression::evaluate_aggregation(function, collection, field, filter, graph)?;
1043 Ok(v)
1044 }
1045 Expression::AggregationComprehension {
1046 function,
1047 variable,
1048 collection,
1049 window,
1050 predicate,
1051 projection,
1052 target_unit,
1053 } => {
1054 let v = Expression::evaluate_aggregation_comprehension(
1055 function,
1056 variable,
1057 collection,
1058 window,
1059 predicate,
1060 projection,
1061 target_unit.as_deref(),
1062 graph,
1063 )?;
1064 Ok(v)
1065 }
1066 Expression::Cast {
1067 operand,
1068 target_type,
1069 } => {
1070 let val = Self::get_runtime_value(operand, graph)?;
1071 let (value_dec, source_unit) = Self::parse_numeric_with_unit_value(&val)
1072 .map_err(|e| format!("Invalid cast operand: {}", e))?
1073 .ok_or_else(|| {
1074 format!("Cannot cast non-numeric value {} to {}", val, target_type)
1075 })?;
1076
1077 let registry = get_default_registry();
1078 let registry = registry
1079 .read()
1080 .map_err(|e| format!("Failed to lock unit registry: {}", e))?;
1081 let target_unit = registry
1082 .get_unit(target_type)
1083 .map_err(|e| format!("Unknown target unit '{}': {}", target_type, e))?;
1084
1085 let converted_value = if let Some(from_unit_symbol) = source_unit {
1086 let from_unit = registry
1087 .get_unit(&from_unit_symbol)
1088 .map_err(|e| format!("Unknown unit '{}': {}", from_unit_symbol, e))?;
1089 if from_unit.dimension() != target_unit.dimension() {
1090 return Err(format!(
1091 "Cannot cast from '{}' ({:?}) to '{}' ({:?})",
1092 from_unit_symbol,
1093 from_unit.dimension(),
1094 target_type,
1095 target_unit.dimension()
1096 ));
1097 }
1098 registry
1099 .convert(value_dec, from_unit, target_unit)
1100 .map_err(|e| format!("Unit conversion failed: {}", e))?
1101 } else {
1102 value_dec
1103 };
1104
1105 Ok(serde_json::json!({
1106 "__quantity_value": converted_value.to_string(),
1107 "__quantity_unit": target_type
1108 }))
1109 }
1110 Expression::QuantityLiteral { value, unit } => Ok(
1111 serde_json::json!({"__quantity_value": value.to_string(), "__quantity_unit": unit}),
1112 ),
1113 Expression::TimeLiteral(timestamp) => Ok(serde_json::json!(timestamp)),
1114 Expression::IntervalLiteral { start, end } => {
1115 Ok(serde_json::json!({"__interval_start": start, "__interval_end": end}))
1116 }
1117 _ => Err(
1118 "Expected a runtime-resolvable expression (literal, member access, or aggregation)"
1119 .to_string(),
1120 ),
1121 }
1122 }
1123}
1124
1125impl PolicyModality {
1126 pub fn to_severity(&self) -> Severity {
1127 match self {
1128 Self::Obligation => Severity::Error,
1129 Self::Prohibition => Severity::Error,
1130 Self::Permission => Severity::Info,
1131 }
1132 }
1133}
1134
1135impl EvaluationResult {
1136 pub fn has_errors(&self) -> bool {
1137 self.violations
1138 .iter()
1139 .any(|v| v.severity == Severity::Error)
1140 }
1141
1142 pub fn error_count(&self) -> usize {
1143 self.violations
1144 .iter()
1145 .filter(|v| v.severity == Severity::Error)
1146 .count()
1147 }
1148}