rustev_backend_rules/
check.rs1use rustev_contract::Identified;
4use rustev_contract::definition::{ItemSource, SemanticDecl, StepBody, TypeDecl};
5use rustev_contract::plan::{Plan, PlanExecution, PlanStepDetail};
6
7use crate::RulesBackend;
8use crate::program::{CTask, FieldType};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct PlanMismatch {
13 pub step: String,
14 pub kind: PlanMismatchKind,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum PlanMismatchKind {
19 Descriptor,
21 NoTask(String),
23 TaskDiffers(&'static str),
25 NotProjected { field: String, reference: String },
27 Incompatible { field: String, reference: String },
29}
30
31impl RulesBackend {
32 pub fn check_plan(&self, plan: &Plan) -> Result<(), Vec<PlanMismatch>> {
36 let me = &self.descriptor.backend_id;
37 let my_descriptor = self.descriptor.id().ok();
38 let mut bound: Vec<(&str, bool)> = vec![];
39 for s in &plan.steps {
40 if let PlanStepDetail::Semantic(b) = &s.detail
41 && &b.backend_id == me
42 {
43 bound.push((&s.id, Some(&b.descriptor) == my_descriptor.as_ref()));
44 }
45 }
46 if let PlanExecution::Declared(d) = &plan.execution {
47 for f in d.fallbacks.iter().filter(|f| &f.backend_id == me) {
48 let entry = (
49 f.step.as_str(),
50 Some(&f.descriptor) == my_descriptor.as_ref(),
51 );
52 if !bound.contains(&entry) {
54 bound.push(entry);
55 }
56 }
57 }
58 let mut out = vec![];
59 for (step, same_descriptor) in bound {
60 let mismatch = |kind| PlanMismatch {
61 step: step.to_string(),
62 kind,
63 };
64 if !same_descriptor {
65 out.push(mismatch(PlanMismatchKind::Descriptor));
66 continue;
67 }
68 let decl = plan.definition.steps.iter().find_map(|s| match &s.body {
69 StepBody::Semantic(d) if s.id == step => Some(d),
70 _ => None,
71 });
72 let Some(decl) = decl else { continue };
73 let Some(task) = self.tasks.iter().find(|t| t.name == decl.task) else {
74 out.push(mismatch(PlanMismatchKind::NoTask(decl.task.clone())));
75 continue;
76 };
77 if task.operation != decl.operation {
78 out.push(mismatch(PlanMismatchKind::TaskDiffers("operation")));
79 }
80 if task.question != decl.question {
81 out.push(mismatch(PlanMismatchKind::TaskDiffers("question")));
82 }
83 if task.options != decl.options {
84 out.push(mismatch(PlanMismatchKind::TaskDiffers("options")));
85 }
86 for kind in fields(task, decl, plan) {
87 out.push(mismatch(kind));
88 }
89 }
90 if out.is_empty() { Ok(()) } else { Err(out) }
91 }
92}
93
94fn fields(task: &CTask, decl: &SemanticDecl, plan: &Plan) -> Vec<PlanMismatchKind> {
95 let mut out = vec![];
96 for f in &task.fields {
97 let reference = match &f.member {
98 Some(m) => format!("{}/{m}", f.key),
99 None => f.key.clone(),
100 };
101 if !decl.project.contains(&f.key) {
102 out.push(PlanMismatchKind::NotProjected {
103 field: f.name.clone(),
104 reference,
105 });
106 continue;
107 }
108 let ty = declared_type(&f.key, decl, plan).and_then(|t| match &f.member {
109 None => Some(t),
110 Some(m) => match t {
111 TypeDecl::Record { fields, .. } => {
112 fields.iter().find(|x| &x.name == m).map(|x| &x.ty)
113 }
114 _ => None,
115 },
116 });
117 if !ty.is_some_and(|t| compatible(t, f.ty)) {
118 out.push(PlanMismatchKind::Incompatible {
119 field: f.name.clone(),
120 reference,
121 });
122 }
123 }
124 out
125}
126
127fn declared_type<'p>(key: &str, decl: &SemanticDecl, plan: &'p Plan) -> Option<&'p TypeDecl> {
130 let input = |name: &str| {
131 plan.definition
132 .inputs
133 .iter()
134 .find(|i| i.name == name)
135 .map(|i| &i.ty)
136 };
137 if let Some(name) = key.strip_prefix("input:") {
138 return input(name);
139 }
140 let bind = key.strip_prefix("bind:")?;
141 let f = decl.for_each.iter().find(|f| f.bind == bind)?;
142 let ItemSource::List { list, .. } = &f.items else {
143 return None;
144 };
145 match input(list.strip_prefix("input:")?)? {
146 TypeDecl::List { item, .. } => Some(item),
147 _ => None,
148 }
149}
150
151fn compatible(t: &TypeDecl, f: FieldType) -> bool {
152 matches!(
153 (t, f),
154 (
155 TypeDecl::Text { .. } | TypeDecl::Enum { .. },
156 FieldType::Text
157 ) | (TypeDecl::Bool, FieldType::Bool)
158 | (TypeDecl::Integer | TypeDecl::Timestamp, FieldType::Integer)
159 | (TypeDecl::Decimal, FieldType::Decimal)
160 )
161}