1use std::borrow::Cow;
2use std::sync::Arc;
3
4use serde_json::Value;
5use sofie_core::{Error, FieldError, Instant, Result, Uuid};
6
7use crate::heard::Known;
8use crate::messages::queue_steps;
9use crate::{Dialect, GuardCheck, MessageKind, RaisedEvent, Reach, Reply, Step, UniqueViolation};
10
11pub struct Plan<'d> {
12 dialect: &'d dyn Dialect,
13 reach: Reach,
14 stamp: Stamp,
15 steps: Vec<Step>,
16 leading: usize,
17 guards: Vec<Step>,
18 guard_checks: Vec<Arc<GuardCheck>>,
19 pub(crate) events: Vec<RaisedEvent>,
20 unique_fields: Vec<UniqueField>,
21 queues_messages: bool,
22 pub(crate) heard: Option<Arc<Known>>,
23 #[cfg(feature = "cache")]
24 changed: Vec<crate::ChangedRecord>,
25 #[cfg(feature = "live")]
26 live: Vec<crate::TableChange>,
27 #[cfg(feature = "module-roles")]
28 role: Option<&'static str>,
29 #[cfg(feature = "search-engine")]
30 indexes_searches: bool,
31}
32
33pub(crate) struct Planned {
34 pub(crate) steps: Vec<Step>,
35 pub(crate) guard_checks: Vec<Arc<GuardCheck>>,
36 pub(crate) unique_fields: Vec<UniqueField>,
37 pub(crate) queues_messages: bool,
38 #[cfg(feature = "cache")]
39 pub(crate) changed: Vec<crate::ChangedRecord>,
40 #[cfg(feature = "live")]
41 pub(crate) live: Vec<crate::TableChange>,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct Stamp {
46 pub now: Instant,
47 pub actor: Option<Uuid>,
48 pub action: Option<&'static str>,
49}
50
51const UNIQUE_KEY: &str = "validation.unique";
52#[cfg(feature = "module-roles")]
53pub(crate) const FRAMEWORK_MODULE: &str = "sofie";
54const TAKEN: &str = "is already taken";
55
56pub(crate) struct UniqueField {
57 index: Cow<'static, str>,
58 module: Option<&'static str>,
60 field: &'static str,
61 message: &'static str,
62}
63
64impl<'d> Plan<'d> {
65 pub(crate) fn new(
66 dialect: &'d dyn Dialect,
67 reach: Reach,
68 now: Instant,
69 steps: Vec<Step>,
70 heard: Option<Arc<Known>>,
71 ) -> Self {
72 Self {
73 dialect,
74 reach,
75 stamp: Stamp {
76 now,
77 actor: reach.actor(),
78 action: crate::action::current(),
79 },
80 leading: steps.len(),
81 steps,
82 guards: Vec::new(),
83 guard_checks: Vec::new(),
84 events: Vec::new(),
85 unique_fields: Vec::new(),
86 queues_messages: false,
87 heard,
88 #[cfg(feature = "cache")]
89 changed: Vec::new(),
90 #[cfg(feature = "live")]
91 live: Vec::new(),
92 #[cfg(feature = "module-roles")]
93 role: None,
94 #[cfg(feature = "search-engine")]
95 indexes_searches: false,
96 }
97 }
98
99 #[cfg(feature = "search-engine")]
100 pub(crate) fn index_searches(&mut self) {
101 self.indexes_searches = true;
102 }
103
104 #[cfg(feature = "search-engine")]
105 pub(crate) fn indexes_searches(&self) -> bool {
106 self.indexes_searches
107 }
108
109 #[cfg(feature = "module-roles")]
111 pub(crate) fn act_as(&mut self, module: &'static str) {
112 let role = (module != FRAMEWORK_MODULE).then_some(module);
113 if role == self.role {
114 return;
115 }
116 if let Some(statement) = self.dialect.enter_role(role) {
117 self.steps.push(Step::Script(statement));
118 }
119 self.role = role;
120 }
121
122 #[must_use]
123 pub fn dialect(&self) -> &'d dyn Dialect {
124 self.dialect
125 }
126
127 #[must_use]
128 pub fn reach(&self) -> Reach {
129 self.reach
130 }
131
132 #[must_use]
133 pub fn stamp(&self) -> Stamp {
134 self.stamp
135 }
136
137 pub fn push(&mut self, step: Step) {
138 self.steps.push(step);
139 }
140
141 pub(crate) fn guard(&mut self, steps: Vec<Step>, check: Arc<GuardCheck>) {
142 self.guards.extend(steps);
143 self.guard_checks.push(check);
144 }
145
146 pub fn raise(&mut self, events: &[RaisedEvent]) {
147 self.events.extend_from_slice(events);
148 }
149
150 #[cfg(any(feature = "contracts", feature = "publishing", feature = "retention"))]
151 pub(crate) fn raise_one(&mut self, name: &'static str, payload: Value) {
152 self.events.push(RaisedEvent { name, payload });
153 }
154
155 pub(crate) fn queue<'a>(
156 &mut self,
157 kind: MessageKind,
158 messages: impl IntoIterator<Item = (&'a str, Value)>,
159 ) -> Result<()> {
160 self.queue_at(kind, messages, self.stamp.now.timestamp())
161 }
162
163 pub(crate) fn queue_at<'a>(
164 &mut self,
165 kind: MessageKind,
166 messages: impl IntoIterator<Item = (&'a str, Value)>,
167 at: sofie_core::Timestamp,
168 ) -> Result<()> {
169 let heard = self.heard.as_deref();
170 let queuing = self.stamp.queuing(kind, at);
171 self.queues_messages |=
172 queue_steps(self.dialect, &queuing, messages, heard, &mut self.steps)?;
173 Ok(())
174 }
175
176 pub(crate) fn push_message(&mut self, step: Step) {
177 self.queues_messages = true;
178 self.steps.push(step);
179 }
180
181 #[cfg(feature = "cache")]
182 pub(crate) fn changed(&mut self, record: crate::ChangedRecord) {
183 self.changed.push(record);
184 }
185
186 #[cfg(feature = "live")]
187 pub(crate) fn live_change(&mut self, change: crate::TableChange) {
188 self.live.push(change);
189 }
190
191 pub fn unique(&mut self, index: &'static str, field: &'static str, message: &'static str) {
193 self.unique_fields.push(UniqueField {
194 index: Cow::Borrowed(index),
195 module: None,
196 field,
197 message,
198 });
199 }
200
201 pub(crate) fn unique_column(&mut self, module: &'static str, index: &str, field: &'static str) {
203 self.unique_fields.push(UniqueField {
204 index: Cow::Owned(index.to_owned()),
205 module: Some(module),
206 field,
207 message: TAKEN,
208 });
209 }
210
211 pub(crate) fn into_parts(mut self) -> Result<Planned> {
212 let events = std::mem::take(&mut self.events);
213 #[cfg(feature = "module-roles")]
215 if !events.is_empty() {
216 self.act_as(FRAMEWORK_MODULE);
217 }
218 let named = events.into_iter().map(|event| (event.name, event.payload));
219 self.queue(MessageKind::Event, named)?;
220 let mut steps = self.steps;
221 let writes = steps.split_off(self.leading);
222 steps.extend(self.guards);
223 steps.extend(writes);
224 Ok(Planned {
225 steps,
226 guard_checks: self.guard_checks,
227 unique_fields: self.unique_fields,
228 queues_messages: self.queues_messages,
229 #[cfg(feature = "cache")]
230 changed: self.changed,
231 #[cfg(feature = "live")]
232 live: self.live,
233 })
234 }
235}
236
237pub(crate) fn explain_duplicate(error: Error, unique_fields: &[UniqueField]) -> Error {
238 let Error::Internal {
239 source: Some(source),
240 ..
241 } = &error
242 else {
243 return error;
244 };
245 let Some(violation) = source.downcast_ref::<UniqueViolation>() else {
246 return error;
247 };
248 let index = violation.index.as_str();
249 let index = index.strip_suffix("_key").unwrap_or(index);
250 let prefixed = |unique: &&UniqueField| match unique.module {
252 Some(module) => {
253 index
254 .strip_prefix(module)
255 .and_then(|rest| rest.strip_prefix('_'))
256 == Some(&*unique.index)
257 }
258 None => index.ends_with(&format!("_{}", unique.index)),
259 };
260 let exact = unique_fields.iter().find(|unique| index == unique.index);
261 match exact.or_else(|| unique_fields.iter().find(prefixed)) {
262 Some(unique) => Error::Invalid(vec![FieldError::new(
263 unique.field,
264 UNIQUE_KEY,
265 unique.message,
266 )]),
267 None => error,
268 }
269}
270
271pub trait Outcome: Send {
272 fn plan(&self, plan: &mut Plan<'_>) -> Result<()> {
273 let _ = plan;
274 Ok(())
275 }
276
277 fn committed(&mut self, stamp: Stamp) {
278 let _ = stamp;
279 }
280
281 fn reply(self) -> Result<Reply>
283 where
284 Self: Sized,
285 {
286 Ok(Reply::Empty)
287 }
288}
289
290#[diagnostic::on_unimplemented(
292 message = "a GET, screen or island handler cannot return `{Self}`: a GET never writes",
293 label = "a writable outcome",
294 note = "return a read model, `Paginated`, `Screen` or another reply; a write belongs in a POST, or in a job a domain event triggers"
295)]
296pub trait ReadOnly: Outcome {}
297
298impl Outcome for () {}
299
300impl ReadOnly for () {}
301
302impl<T: ReadOnly> ReadOnly for Vec<T> {}
303
304impl<T: Outcome> Outcome for Vec<T> {
305 fn plan(&self, plan: &mut Plan<'_>) -> Result<()> {
306 self.iter().try_for_each(|outcome| outcome.plan(plan))
307 }
308
309 fn committed(&mut self, stamp: Stamp) {
310 for outcome in self {
311 outcome.committed(stamp);
312 }
313 }
314
315 fn reply(self) -> Result<Reply> {
316 self.into_iter()
317 .map(Outcome::reply)
318 .collect::<Result<_>>()
319 .map(Reply::List)
320 }
321}
322
323impl<T: Outcome> Outcome for Option<T> {
324 fn plan(&self, plan: &mut Plan<'_>) -> Result<()> {
325 self.as_ref().map_or(Ok(()), |outcome| outcome.plan(plan))
326 }
327
328 fn committed(&mut self, stamp: Stamp) {
329 if let Some(outcome) = self {
330 outcome.committed(stamp);
331 }
332 }
333
334 fn reply(self) -> Result<Reply> {
335 self.map_or(Ok(Reply::Empty), Outcome::reply)
336 }
337}
338
339macro_rules! tuple_outcome {
340 ($($name:ident),+) => {
341 #[allow(non_snake_case)]
342 impl<$($name: Outcome),+> Outcome for ($($name,)+) {
343 fn plan(&self, plan: &mut Plan<'_>) -> Result<()> {
344 let ($($name,)+) = self;
345 $($name.plan(plan)?;)+
346 Ok(())
347 }
348
349 fn committed(&mut self, stamp: Stamp) {
350 let ($($name,)+) = self;
351 $($name.committed(stamp);)+
352 }
353
354 fn reply(self) -> Result<Reply> {
356 let ($($name,)+) = self;
357 $(
358 let reply = $name.reply()?;
359 if !matches!(reply, Reply::Empty) {
360 return Ok(reply);
361 }
362 )+
363 Ok(Reply::Empty)
364 }
365 }
366
367 impl<$($name: ReadOnly),+> ReadOnly for ($($name,)+) {}
368 };
369}
370
371tuple_outcome!(A, B);
372tuple_outcome!(A, B, C);
373tuple_outcome!(A, B, C, D);
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378
379 fn email_field() -> Vec<UniqueField> {
380 vec![UniqueField {
381 index: Cow::Borrowed("account_email"),
382 module: Some("shop"),
383 field: "email",
384 message: "is already registered",
385 }]
386 }
387
388 fn violation(index: &str) -> Error {
389 Error::internal(
390 "running an insert",
391 UniqueViolation {
392 index: index.to_owned(),
393 },
394 )
395 }
396
397 #[test]
398 fn an_absent_outcome_replies_empty_and_a_present_one_replies_its_own() {
399 let absent: Option<Vec<()>> = None;
400
401 assert!(matches!(absent.reply().unwrap(), Reply::Empty));
402 assert!(matches!(
403 Some(vec![()]).reply().unwrap(),
404 Reply::List(items) if matches!(items[..], [Reply::Empty])
405 ));
406 }
407
408 struct Saved;
409
410 impl Outcome for Saved {
411 fn reply(self) -> Result<Reply> {
412 Ok(Reply::Json(serde_json::json!({ "id": "1" })))
413 }
414 }
415
416 #[test]
417 fn a_tuple_replies_its_first_part_that_answers_anything() {
418 let reply = ((), Saved, Saved).reply().unwrap();
419
420 assert!(
421 matches!(&reply, Reply::Json(value) if value["id"] == "1"),
422 "{:?}",
423 reply.to_json()
424 );
425 assert!(matches!(((), ()).reply().unwrap(), Reply::Empty));
426 }
427
428 #[test]
429 fn a_violation_named_after_the_module_s_table_is_invalid_on_its_field() {
430 let error = explain_duplicate(violation("shop_account_email"), &email_field());
431
432 assert!(matches!(&error, Error::Invalid(fields) if fields[0].field == "email"));
433 }
434
435 #[test]
436 fn a_violation_on_a_table_whose_name_ends_like_another_s_is_not_put_on_its_field() {
437 let error = explain_duplicate(violation("billing_account_email_key"), &email_field());
438
439 assert!(matches!(error, Error::Internal { .. }), "{error}");
440 }
441
442 #[test]
443 fn a_violation_of_a_named_index_is_invalid_on_its_field() {
444 let error = explain_duplicate(violation("account_email"), &email_field());
445
446 assert!(
447 matches!(&error, Error::Invalid(fields) if fields[0].field == "email"
448 && fields[0].key == "validation.unique"
449 && fields[0].message == "is already registered"),
450 "{error}"
451 );
452 }
453
454 #[test]
455 fn any_other_failure_is_left_as_it_was() {
456 let unnamed = explain_duplicate(violation("other_index"), &email_field());
457 let plain = explain_duplicate(Error::internal_message("lost"), &email_field());
458
459 assert!(matches!(unnamed, Error::Internal { .. }), "{unnamed}");
460 assert!(matches!(plain, Error::Internal { .. }), "{plain}");
461 }
462}