initiative_core/storage/
repository.rs

1use crate::storage::{DataStore, MemoryDataStore};
2use crate::time::Time;
3use crate::utils::CaseInsensitiveStr;
4use crate::world::npc::{NpcData, NpcRelations};
5use crate::world::place::{Place, PlaceData, PlaceRelations};
6use crate::world::thing::{Thing, ThingData, ThingRelations};
7use crate::Uuid;
8use futures::join;
9use std::collections::VecDeque;
10use std::fmt;
11
12type Name = String;
13
14const RECENT_MAX_LEN: usize = 100;
15const UNDO_HISTORY_LEN: usize = 10;
16
17pub struct Repository {
18    data_store: Box<dyn DataStore>,
19    data_store_enabled: bool,
20    recent: VecDeque<Thing>,
21    redo_change: Option<Change>,
22    undo_history: VecDeque<Change>,
23}
24
25/// Represents a modification to be applied to the Repository. This is passed to
26/// Repository::modify() to be applied. An object is used to represent the change because every
27/// operation has an opposite; for instance, Unsave is the opposite of Save, and Edit is the
28/// opposite of Edit. This opposite is inserted into the undo history and can be applied using
29/// Repository::undo().
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub enum Change {
32    /// Create a new thing and store it in recent entries.
33    ///
34    /// Reverse: Delete
35    Create {
36        thing_data: ThingData,
37        uuid: Option<Uuid>,
38    },
39
40    /// Create a new thing and store it in the journal.
41    ///
42    /// Reverse: Delete
43    CreateAndSave {
44        thing_data: ThingData,
45        uuid: Option<Uuid>,
46    },
47
48    /// Delete a thing from recent or journal.
49    ///
50    /// Reverse: Create (recent) or CreateAndSave (journal)
51    Delete { uuid: Uuid, name: Name },
52
53    /// Edit fields on a Thing.
54    ///
55    /// Reverse: Edit (already in journal) or EditAndUnsave (in recent)
56    Edit {
57        name: Name,
58        uuid: Option<Uuid>,
59        diff: ThingData,
60    },
61
62    /// Edit a Thing and move it from journal to recent. The reverse of edit with autosave.
63    ///
64    /// Reverse: Edit
65    EditAndUnsave {
66        uuid: Uuid,
67        name: Name,
68        diff: ThingData,
69    },
70
71    /// Transfer a thing from recent to journal.
72    ///
73    /// Reverse: Unsave
74    Save { name: Name, uuid: Option<Uuid> },
75
76    /// Transfer a thing from journal to recent. Only triggerable as the reverse to Save.
77    ///
78    /// Reverse: Save
79    Unsave { uuid: Uuid, name: Name },
80
81    /// Set a value in the key-value store.
82    ///
83    /// Reverse: SetKeyValue
84    SetKeyValue { key_value: KeyValue },
85}
86
87pub struct DisplayUndo<'a>(&'a Change);
88
89pub struct DisplayRedo<'a>(&'a Change);
90
91#[derive(Debug, Eq, PartialEq)]
92pub enum Error {
93    DataStoreFailed,
94    MissingName,
95    UuidAlreadyExists(Thing),
96    NameAlreadyExists(Thing),
97    NotFound,
98}
99
100#[derive(Clone, Debug, Eq, PartialEq)]
101pub enum KeyValue {
102    Time(Option<Time>),
103}
104
105#[derive(Clone, Debug, Eq, PartialEq)]
106pub struct Record {
107    pub status: RecordStatus,
108    pub thing: Thing,
109}
110
111#[derive(Clone, Copy, Debug, Eq, PartialEq)]
112pub enum RecordStatus {
113    Unsaved,
114    Saved,
115    Deleted,
116}
117
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub enum RecordSource {
120    Any,
121    Journal,
122    Recent,
123}
124
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126pub enum ThingType {
127    Any,
128    Npc,
129    Place,
130}
131
132impl Repository {
133    pub fn new(data_store: impl DataStore + 'static) -> Self {
134        Self {
135            data_store: Box::new(data_store),
136            data_store_enabled: false,
137            recent: VecDeque::default(),
138            redo_change: None,
139            undo_history: VecDeque::default(),
140        }
141    }
142
143    /// The data store will not necessarily be available at construct, so we need to check if it's
144    /// healthy or discard it and fall back on a memory data store instead.
145    pub async fn init(&mut self) {
146        if self.data_store.health_check().await.is_ok() {
147            self.data_store_enabled = true;
148        } else {
149            self.data_store = Box::<MemoryDataStore>::default();
150        }
151    }
152
153    /// Get the record associated with a given change, if available.
154    pub async fn get_by_change(&self, change: &Change) -> Result<Record, Error> {
155        let (name, uuid) = match change {
156            Change::Create {
157                uuid: Some(uuid), ..
158            }
159            | Change::CreateAndSave {
160                uuid: Some(uuid), ..
161            }
162            | Change::EditAndUnsave { uuid, .. }
163            | Change::Save {
164                uuid: Some(uuid), ..
165            }
166            | Change::Unsave { uuid, .. }
167            | Change::Delete { uuid, .. }
168            | Change::Edit {
169                uuid: Some(uuid), ..
170            } => (None, Some(uuid)),
171            Change::Create { thing_data, .. } | Change::CreateAndSave { thing_data, .. } => {
172                (thing_data.name().value(), None)
173            }
174            Change::Save { name, .. } | Change::Edit { name, .. } => (Some(name), None),
175            Change::SetKeyValue { .. } => (None, None),
176        };
177
178        if let Some(uuid) = uuid {
179            self.get_by_uuid(uuid).await
180        } else if let Some(name) = name {
181            self.get_by_name(name).await
182        } else {
183            Err(Error::NotFound)
184        }
185    }
186
187    /// Load child and grandchild relations associated with a Thing (eg. location).
188    pub async fn load_relations(&self, thing: &Thing) -> Result<ThingRelations, Error> {
189        let locations = {
190            let parent_uuid = match &thing.data {
191                ThingData::Npc(NpcData { location_uuid, .. }) => location_uuid,
192                ThingData::Place(PlaceData { location_uuid, .. }) => location_uuid,
193            };
194
195            let parent = {
196                let parent_result = if let Some(uuid) = parent_uuid.value() {
197                    self.get_by_uuid(uuid).await.and_then(|record| {
198                        Place::try_from(record.thing).map_err(|_| Error::NotFound)
199                    })
200                } else {
201                    Err(Error::NotFound)
202                };
203
204                match parent_result {
205                    Ok(parent) => Some(parent),
206                    Err(Error::NotFound) => None,
207                    Err(e) => return Err(e),
208                }
209            };
210
211            if let Some(parent) = parent {
212                let grandparent = {
213                    let grandparent_result = if let Some(uuid) = parent.data.location_uuid.value() {
214                        self.get_by_uuid(uuid).await.and_then(|record| {
215                            Place::try_from(record.thing).map_err(|_| Error::NotFound)
216                        })
217                    } else {
218                        Err(Error::NotFound)
219                    };
220
221                    match grandparent_result {
222                        Ok(grandparent) => Some(grandparent),
223                        Err(Error::NotFound) => None,
224                        Err(e) => return Err(e),
225                    }
226                };
227
228                Some((parent, grandparent))
229            } else {
230                None
231            }
232        };
233
234        match thing.data {
235            ThingData::Npc(..) => Ok(NpcRelations {
236                location: locations,
237            }
238            .into()),
239            ThingData::Place(..) => Ok(PlaceRelations {
240                location: locations,
241            }
242            .into()),
243        }
244    }
245
246    /// Get all saved and recent Things beginning with a given (case-insensitive) string, up to an
247    /// optional limit.
248    pub async fn get_by_name_start<'a>(
249        &self,
250        args: impl Into<GetByNameStartArgs<'a>>,
251    ) -> Result<Vec<Record>, Error> {
252        let GetByNameStartArgs {
253            name,
254            record_source,
255            thing_type,
256            limit,
257        } = args.into();
258
259        let recent_iter = if record_source.recent() {
260            Some(
261                self.recent()
262                    .filter(|t| t.is_type(thing_type))
263                    .filter(|t| t.name().value().is_some_and(|s| s.starts_with_ci(name)))
264                    .map(|thing| Record {
265                        status: RecordStatus::Unsaved,
266                        thing: thing.clone(),
267                    }),
268            )
269        } else {
270            None
271        }
272        .into_iter()
273        .flatten();
274
275        let journal_iter = if record_source.journal() {
276            Some(
277                self.data_store
278                    .get_things_by_name_start(name, limit)
279                    .await
280                    .map_err(|_| Error::DataStoreFailed)?
281                    .into_iter()
282                    .filter(|t| t.is_type(thing_type))
283                    .map(|thing| Record {
284                        status: RecordStatus::Saved,
285                        thing,
286                    }),
287            )
288        } else {
289            None
290        }
291        .into_iter()
292        .flatten();
293
294        Ok(journal_iter
295            .chain(recent_iter)
296            .take(limit.unwrap_or(usize::MAX))
297            .collect())
298    }
299
300    /// Get an iterator over all recent Things.
301    pub fn recent(&self) -> impl Iterator<Item = &Thing> {
302        let (a, b) = self.recent.as_slices();
303        a.iter().chain(b.iter())
304    }
305
306    /// Get all Things contained in the journal. This could get heavy, so should not be used
307    /// lightly.
308    pub async fn journal(&self) -> Result<Vec<Thing>, Error> {
309        self.data_store
310            .get_all_the_things()
311            .await
312            .map_err(|_| Error::DataStoreFailed)
313    }
314
315    /// Get the Thing from saved or recent with a given name. (There should be only one.)
316    pub async fn get_by_name<'a>(
317        &self,
318        args: impl Into<GetByNameArgs<'a>>,
319    ) -> Result<Record, Error> {
320        let GetByNameArgs {
321            name,
322            record_source,
323            thing_type,
324        } = args.into();
325
326        let (recent_thing, saved_thing) = join!(
327            async {
328                self.recent()
329                    .find(|t| t.name().value().is_some_and(|s| s.eq_ci(name)))
330            },
331            self.data_store.get_thing_by_name(name)
332        );
333
334        match (recent_thing, saved_thing) {
335            (Some(thing), _) => Ok(Record {
336                status: RecordStatus::Unsaved,
337                thing: thing.clone(),
338            }),
339            (None, Ok(Some(thing))) => Ok(Record {
340                status: RecordStatus::Saved,
341                thing,
342            }),
343            (None, Ok(None)) => Err(Error::NotFound),
344            (None, Err(())) => Err(Error::DataStoreFailed),
345        }
346        .and_then(|record| {
347            if record.thing.is_type(thing_type) && record.is_from_source(record_source) {
348                Ok(record)
349            } else {
350                Err(Error::NotFound)
351            }
352        })
353    }
354
355    /// Get the Thing from saved or recent with a given UUID. (There should be only one.)
356    pub async fn get_by_uuid(&self, uuid: &Uuid) -> Result<Record, Error> {
357        let (recent_thing, saved_thing) = join!(
358            async { self.recent().find(|t| &t.uuid == uuid) },
359            self.data_store.get_thing_by_uuid(uuid)
360        );
361
362        match (recent_thing, saved_thing) {
363            (Some(thing), _) => Ok(Record {
364                status: RecordStatus::Unsaved,
365                thing: thing.clone(),
366            }),
367            (None, Ok(Some(thing))) => Ok(Record {
368                status: RecordStatus::Saved,
369                thing,
370            }),
371            (None, Ok(None)) => Err(Error::NotFound),
372            (None, Err(())) => Err(Error::DataStoreFailed),
373        }
374    }
375
376    /// Apply a given Change, returning the affected Thing (with modifications applied) on success,
377    /// or a tuple of the Change and Error message on failure.
378    pub async fn modify(&mut self, change: Change) -> Result<Option<Record>, (Change, Error)> {
379        // If we're going to delete, we should load the record being deleted first because
380        // otherwise it'll be gone!
381        let mut option_record = if matches!(change, Change::Delete { .. }) {
382            self.get_by_change(&change).await.ok().map(|mut record| {
383                record.status = RecordStatus::Deleted;
384                record
385            })
386        } else {
387            None
388        };
389
390        let undo_change = self.modify_without_undo(change).await?;
391
392        if option_record.is_none() {
393            option_record = self.get_by_change(&undo_change).await.ok();
394        }
395
396        while self.undo_history.len() >= UNDO_HISTORY_LEN {
397            self.undo_history.pop_front();
398        }
399        self.undo_history.push_back(undo_change);
400
401        Ok(option_record)
402    }
403
404    /// Undo the most recent Change. Returns None if the undo history is empty; otherwise returns
405    /// the Result of the modify() operation.
406    pub async fn undo(&mut self) -> Option<Result<Option<Record>, Error>> {
407        if let Some(change) = self.undo_history.pop_back() {
408            match self.modify_without_undo(change).await {
409                Ok(redo_change) => {
410                    let record = self.get_by_change(&redo_change).await.ok();
411                    self.redo_change = Some(redo_change);
412                    Some(Ok(record))
413                }
414                Err((undo_change, e)) => {
415                    self.undo_history.push_back(undo_change);
416                    Some(Err(e))
417                }
418            }
419        } else {
420            None
421        }
422    }
423
424    /// Get an iterator over the Changes currently queued up in the undo history, from newest to
425    /// oldest.
426    pub fn undo_history(&self) -> impl Iterator<Item = &Change> {
427        self.undo_history.iter().rev()
428    }
429
430    /// Redo the most recently undid Change. Returns None if no such change exists; otherwise
431    /// returns the result of the modify() operation. This differs from undo() in that only one
432    /// Change is stored in history at a time.
433    pub async fn redo(&mut self) -> Option<Result<Option<Record>, Error>> {
434        if let Some(change) = self.redo_change.take() {
435            match self.modify(change).await {
436                Ok(option_record) => Some(Ok(option_record)),
437                Err((redo_change, e)) => {
438                    self.redo_change = Some(redo_change);
439                    Some(Err(e))
440                }
441            }
442        } else {
443            None
444        }
445    }
446
447    /// Get the Change currently queued up for redo(), if any.
448    pub fn get_redo(&self) -> Option<&Change> {
449        self.redo_change.as_ref()
450    }
451
452    /// Apply a Change to the Repository without adding the Change to the undo history. Returns
453    /// the reverse operation on success (what would be otherwise inserted into the undo history),
454    /// or a tuple of the failed Change and error message on failure.
455    pub async fn modify_without_undo(&mut self, change: Change) -> Result<Change, (Change, Error)> {
456        match change {
457            Change::Create { thing_data, uuid } => {
458                let name = thing_data.name().to_string();
459                self.create_thing(thing_data, uuid)
460                    .await
461                    .map(|uuid| Change::Delete { uuid, name })
462                    .map_err(|(thing_data, e)| (Change::Create { thing_data, uuid }, e))
463            }
464            Change::CreateAndSave { thing_data, uuid } => {
465                let name = thing_data.name().to_string();
466                self.create_and_save_thing(thing_data, uuid)
467                    .await
468                    .map(|thing| Change::Delete {
469                        uuid: thing.uuid,
470                        name,
471                    })
472                    .map_err(|(thing_data, e)| (Change::CreateAndSave { thing_data, uuid }, e))
473            }
474            Change::Delete { uuid, name } => self
475                .delete_thing_by_uuid(&uuid)
476                .await
477                .map(|Record { thing, status }| {
478                    if status == RecordStatus::Saved {
479                        Change::CreateAndSave {
480                            thing_data: thing.data,
481                            uuid: Some(thing.uuid),
482                        }
483                    } else {
484                        Change::Create {
485                            thing_data: thing.data,
486                            uuid: Some(thing.uuid),
487                        }
488                    }
489                })
490                .map_err(|(_, e)| (Change::Delete { uuid, name }, e)),
491            Change::Edit {
492                name,
493                uuid: None,
494                diff,
495            } => match self.edit_thing_by_name(&name, diff).await {
496                Ok((Record { thing, status }, name)) => {
497                    if status == RecordStatus::Saved {
498                        Ok(Change::Edit {
499                            uuid: Some(thing.uuid),
500                            name,
501                            diff: thing.data,
502                        })
503                    } else {
504                        Ok(Change::EditAndUnsave {
505                            uuid: thing.uuid,
506                            name,
507                            diff: thing.data,
508                        })
509                    }
510                }
511                Err((option_record, diff, e)) => Err((
512                    Change::Edit {
513                        name: option_record
514                            .map(|record| record.thing.name().value().map(String::from))
515                            .unwrap_or(None)
516                            .unwrap_or(name),
517                        uuid: None,
518                        diff,
519                    },
520                    e,
521                )),
522            },
523            Change::Edit {
524                name,
525                uuid: Some(uuid),
526                diff,
527            } => match self.edit_thing_by_uuid(&uuid, diff).await {
528                Ok((Record { thing, status }, name)) => {
529                    let diff = thing.data;
530
531                    if status == RecordStatus::Saved {
532                        let uuid = Some(uuid);
533                        Ok(Change::Edit { uuid, name, diff })
534                    } else {
535                        Ok(Change::EditAndUnsave { uuid, name, diff })
536                    }
537                }
538                Err((option_record, diff, e)) => Err((
539                    Change::Edit {
540                        name: option_record
541                            .map(|record| record.thing.name().value().map(String::from))
542                            .unwrap_or(None)
543                            .unwrap_or(name),
544                        uuid: Some(uuid),
545                        diff,
546                    },
547                    e,
548                )),
549            },
550            Change::EditAndUnsave { uuid, name, diff } => {
551                match self.edit_thing_by_uuid(&uuid, diff).await {
552                    Ok((Record { thing, .. }, name)) => self
553                        .unsave_thing_by_uuid(&uuid)
554                        .await
555                        .map(|name| Change::Edit {
556                            name,
557                            uuid: Some(uuid),
558                            diff: thing.data,
559                        })
560                        .map_err(|(s, e)| {
561                            (
562                                Change::Unsave {
563                                    uuid,
564                                    name: s.unwrap_or(name),
565                                },
566                                e,
567                            )
568                        }),
569                    Err((_, diff, e)) => Err((Change::EditAndUnsave { uuid, name, diff }, e)),
570                }
571            }
572            Change::Save {
573                name,
574                uuid: Some(uuid),
575            } => match self.save_thing_by_uuid(&uuid).await {
576                Ok(thing) => Ok(Change::Unsave {
577                    uuid,
578                    name: thing.name().value().map(String::from).unwrap_or(name),
579                }),
580                Err(e) => Err((
581                    Change::Save {
582                        name,
583                        uuid: Some(uuid),
584                    },
585                    e,
586                )),
587            },
588            Change::Save { name, uuid: None } => match self.save_thing_by_name(&name).await {
589                Ok(thing) => Ok(Change::Unsave {
590                    uuid: thing.uuid,
591                    name: thing.name().value().map(String::from).unwrap_or(name),
592                }),
593                Err(e) => Err((Change::Save { name, uuid: None }, e)),
594            },
595            Change::Unsave { uuid, name } => self
596                .unsave_thing_by_uuid(&uuid)
597                .await
598                .map(|name| Change::Save {
599                    name,
600                    uuid: Some(uuid),
601                })
602                .map_err(|(_, e)| (Change::Unsave { uuid, name }, e)),
603            Change::SetKeyValue { key_value } => self
604                .set_key_value(&key_value)
605                .await
606                .map(|old_kv| Change::SetKeyValue { key_value: old_kv })
607                .map_err(|e| (Change::SetKeyValue { key_value }, e)),
608        }
609    }
610
611    /// Get a value from the key-value store.
612    pub async fn get_key_value(&self, key: &KeyValue) -> Result<KeyValue, Error> {
613        let value_str = self.data_store.get_value(key.key_raw()).await;
614
615        match key {
616            KeyValue::Time(_) => value_str
617                .and_then(|o| o.map(|s| s.parse()).transpose())
618                .map(KeyValue::Time),
619        }
620        .map_err(|_| Error::DataStoreFailed)
621    }
622
623    /// Is the data store currently enabled? Returns false if init() has not yet been called.
624    pub fn data_store_enabled(&self) -> bool {
625        self.data_store_enabled
626    }
627
628    /// Set a value in the key-value store.
629    ///
630    /// Publicly this is done using modify() with Change::SetKeyValue.
631    async fn set_key_value(&mut self, key_value: &KeyValue) -> Result<KeyValue, Error> {
632        let old_key_value = self.get_key_value(key_value).await?;
633
634        match key_value.key_value_raw() {
635            (key, Some(value)) => self.data_store.set_value(key, &value).await,
636            (key, None) => self.data_store.delete_value(key).await,
637        }
638        .map(|_| old_key_value)
639        .map_err(|_| Error::DataStoreFailed)
640    }
641
642    /// Add a Thing to the recent list.
643    fn push_recent(&mut self, thing: Thing) {
644        while self.recent.len() >= RECENT_MAX_LEN {
645            self.recent.pop_front();
646        }
647
648        self.recent.push_back(thing);
649    }
650
651    /// Remove the latest Thing in the recent list, returning it if one is present.
652    fn take_recent<F>(&mut self, f: F) -> Option<Thing>
653    where
654        F: Fn(&Thing) -> bool,
655    {
656        if let Some(index) =
657            self.recent
658                .iter()
659                .enumerate()
660                .find_map(|(i, t)| if f(t) { Some(i) } else { None })
661        {
662            self.recent.remove(index)
663        } else {
664            None
665        }
666    }
667
668    /// Create a Thing, pushing it onto the recent list.
669    ///
670    /// Publicly this is invoked using modify() with Change::Create.
671    async fn create_thing(
672        &mut self,
673        thing_data: ThingData,
674        uuid: Option<Uuid>,
675    ) -> Result<Uuid, (ThingData, Error)> {
676        let thing = self.thing_data_into_thing(thing_data, uuid).await?;
677        let uuid = thing.uuid;
678        self.push_recent(thing);
679        Ok(uuid)
680    }
681
682    /// Create a Thing and save it directly to the journal.
683    ///
684    /// Publicly this is invoked using modify() with Change::CreateAndSave.
685    async fn create_and_save_thing(
686        &mut self,
687        thing_data: ThingData,
688        uuid: Option<Uuid>,
689    ) -> Result<Thing, (ThingData, Error)> {
690        let thing = self.thing_data_into_thing(thing_data, uuid).await?;
691
692        match self.save_thing(&thing).await {
693            Ok(()) => Ok(thing),
694            Err(e) => Err((thing.data, e)),
695        }
696    }
697
698    /// Delete a Thing from recent or journal by its UUID.
699    ///
700    /// Publicly this is invoked using modify() with Change::Delete.
701    async fn delete_thing_by_uuid(
702        &mut self,
703        uuid: &Uuid,
704    ) -> Result<Record, (Option<Record>, Error)> {
705        if let Some(thing) = self.take_recent(|t| &t.uuid == uuid) {
706            Ok(Record {
707                status: RecordStatus::Unsaved,
708                thing,
709            })
710        } else {
711            let record = self.get_by_uuid(uuid).await.map_err(|e| (None, e))?;
712
713            if self.data_store.delete_thing_by_uuid(uuid).await.is_ok() {
714                Ok(record)
715            } else {
716                Err((Some(record), Error::DataStoreFailed))
717            }
718        }
719    }
720
721    /// Transfer a Thing from recent to the journal, referenced by its name. Returns the Thing
722    /// transferred, or an error on failure.
723    ///
724    /// Publicly this is invoked using modify() with Change::Save { uuid: None, .. }
725    async fn save_thing_by_name(&mut self, name: &Name) -> Result<Thing, Error> {
726        if let Some(thing) = self.take_recent(|t| t.name().value().is_some_and(|s| s.eq_ci(name))) {
727            match self.save_thing(&thing).await {
728                Ok(()) => Ok(thing),
729                Err(e) => {
730                    self.push_recent(thing);
731                    Err(e)
732                }
733            }
734        } else {
735            Err(Error::NotFound)
736        }
737    }
738
739    /// Transfer a Thing from recent to the journal, referenced by its UUID. Returns the Thing
740    /// transferred, or an error on failure.
741    ///
742    /// Publicly this is invoked using modify() with Change::Save { uuid: Some(_), .. }
743    async fn save_thing_by_uuid(&mut self, uuid: &Uuid) -> Result<Thing, Error> {
744        if let Some(thing) = self.take_recent(|t| &t.uuid == uuid) {
745            match self.save_thing(&thing).await {
746                Ok(()) => Ok(thing),
747                Err(e) => {
748                    self.push_recent(thing);
749                    Err(e)
750                }
751            }
752        } else {
753            Err(Error::NotFound)
754        }
755    }
756
757    /// Write a Thing to the data store.
758    async fn save_thing(&mut self, thing: &Thing) -> Result<(), Error> {
759        match self.data_store.save_thing(thing).await {
760            Ok(()) => Ok(()),
761            Err(()) => Err(Error::DataStoreFailed),
762        }
763    }
764
765    /// Remove a Thing from the data store and add it to the recent list instead. Returns the name
766    /// of the Thing transferred on success, or a tuple of the optional name and an error on
767    /// failure. This is asymmetric with save_thing_by_* because writing to the recent list takes
768    /// ownership while writing to the data store accepts a reference, so returning a Thing here
769    /// would require an unnecessary clone() call when all we really want is the name of the Thing
770    /// we just unsaved.
771    ///
772    /// Publicly this is invoked using modify() with Change::Unsave.
773    async fn unsave_thing_by_uuid(&mut self, uuid: &Uuid) -> Result<Name, (Option<Name>, Error)> {
774        let thing = match self.data_store.get_thing_by_uuid(uuid).await {
775            Ok(Some(thing)) => Ok(thing),
776            Ok(None) => Err((None, Error::NotFound)),
777            Err(()) => Err((None, Error::DataStoreFailed)),
778        }?;
779
780        let name = thing.name().to_string();
781
782        match self.data_store.delete_thing_by_uuid(uuid).await {
783            Ok(()) => {
784                self.push_recent(thing);
785                Ok(name)
786            }
787            Err(()) => Err((Some(name), Error::DataStoreFailed)),
788        }
789    }
790
791    /// Apply a diff to a Thing matched by name. See edit_thing() for details.
792    ///
793    /// Publicly this is invoked using modify() with Change::Edit { uuid: None, .. }.
794    async fn edit_thing_by_name(
795        &mut self,
796        name: &Name,
797        diff: ThingData,
798    ) -> Result<(Record, Name), (Option<Record>, ThingData, Error)> {
799        match self.get_by_name(name).await {
800            Ok(record) => self
801                .edit_thing(record, diff)
802                .await
803                .map_err(|(record, data, e)| (Some(record), data, e)),
804            Err(e) => Err((None, diff, e)),
805        }
806    }
807
808    /// Apply a diff to a Thing matched by UUID. See edit_thing() for details.
809    ///
810    /// Publicly this is invoked using modify() with Change::Edit { uuid: Some(_), .. }.
811    async fn edit_thing_by_uuid(
812        &mut self,
813        uuid: &Uuid,
814        diff: ThingData,
815    ) -> Result<(Record, Name), (Option<Record>, ThingData, Error)> {
816        match self.get_by_uuid(uuid).await {
817            Ok(record) => self
818                .edit_thing(record, diff)
819                .await
820                .map_err(|(record, data, e)| (Some(record), data, e)),
821            Err(e) => Err((None, diff, e)),
822        }
823    }
824
825    /// Apply a diff to a given Record. Returns a tuple consisting of a Record containing *the
826    /// modified fields* and the matched Thing's actual name on success, or a tuple consisting of
827    /// an optional Record of the matched Thing, the attempted diff, and an error message on
828    /// failure. Note that the successful response includes only the old values of any modified
829    /// fields, so re-applying the diff will revert the Thing back to its original state.
830    ///
831    /// Supports the edit_thing_by_* functions.
832    async fn edit_thing(
833        &mut self,
834        mut record: Record,
835        mut diff: ThingData,
836    ) -> Result<(Record, Name), (Record, ThingData, Error)> {
837        if record.thing.try_apply_diff(&mut diff).is_err() {
838            // This fails when the thing types don't match, eg. applying an Npc diff to a
839            // Place.
840            return Err((record, diff, Error::NotFound));
841        }
842
843        let name = record.thing.name().to_string();
844        let diff_thing = Thing {
845            uuid: record.thing.uuid,
846            data: diff,
847        };
848
849        if record.is_saved() {
850            match self.data_store.edit_thing(&record.thing).await {
851                Ok(()) => Ok((
852                    Record {
853                        status: RecordStatus::Saved,
854                        thing: diff_thing,
855                    },
856                    name,
857                )),
858                Err(()) => Err((record, diff_thing.data, Error::DataStoreFailed)),
859            }
860        } else {
861            let uuid = record.thing.uuid;
862            self.take_recent(|t| t.uuid == uuid);
863
864            if let Ok(()) = self.save_thing(&record.thing).await {
865                Ok((
866                    Record {
867                        status: RecordStatus::Unsaved,
868                        thing: diff_thing,
869                    },
870                    name,
871                ))
872            } else {
873                // Fail forward when implicit save was unsuccessful so that we can at least edit
874                // records in memory when the data store is unavailable.
875                self.push_recent(record.thing);
876                Ok((
877                    Record {
878                        status: RecordStatus::Saved,
879                        thing: diff_thing,
880                    },
881                    name,
882                ))
883            }
884        }
885    }
886
887    /// Creates a new Thing from its data, generating a UUID if necessary and checking for
888    /// name/UUID conflicts.
889    async fn thing_data_into_thing(
890        &self,
891        thing_data: ThingData,
892        uuid: Option<Uuid>,
893    ) -> Result<Thing, (ThingData, Error)> {
894        let uuid = uuid.unwrap_or_else(Uuid::new_v4);
895
896        if let Ok(record) = self.get_by_uuid(&uuid).await {
897            Err((thing_data, Error::UuidAlreadyExists(record.thing)))
898        } else if let Some(name) = thing_data.name().value() {
899            if let Ok(record) = self.get_by_name(name).await {
900                Err((thing_data, Error::NameAlreadyExists(record.thing)))
901            } else {
902                Ok(Thing {
903                    uuid,
904                    data: thing_data,
905                })
906            }
907        } else {
908            Err((thing_data, Error::MissingName))
909        }
910    }
911}
912
913pub struct GetByNameStartArgs<'a> {
914    name: &'a str,
915    record_source: RecordSource,
916    thing_type: ThingType,
917    limit: Option<usize>,
918}
919
920impl<'a> From<&'a str> for GetByNameStartArgs<'a> {
921    fn from(name: &'a str) -> Self {
922        GetByNameStartArgs {
923            name,
924            record_source: RecordSource::Any,
925            thing_type: ThingType::Any,
926            limit: Some(10),
927        }
928    }
929}
930
931impl<'a> From<(&'a str, RecordSource, ThingType)> for GetByNameStartArgs<'a> {
932    fn from(name_source_type: (&'a str, RecordSource, ThingType)) -> Self {
933        let (name, record_source, thing_type) = name_source_type;
934
935        GetByNameStartArgs {
936            name,
937            record_source,
938            thing_type,
939            limit: Some(10),
940        }
941    }
942}
943
944impl<'a> From<(&'a str, RecordSource, ThingType, Option<usize>)> for GetByNameStartArgs<'a> {
945    fn from(name_source_type_limit: (&'a str, RecordSource, ThingType, Option<usize>)) -> Self {
946        let (name, record_source, thing_type, limit) = name_source_type_limit;
947
948        GetByNameStartArgs {
949            name,
950            record_source,
951            thing_type,
952            limit,
953        }
954    }
955}
956
957impl<'a> From<(&'a str, Option<usize>)> for GetByNameStartArgs<'a> {
958    fn from(name_limit: (&'a str, Option<usize>)) -> Self {
959        let (name, limit) = name_limit;
960
961        GetByNameStartArgs {
962            name,
963            record_source: RecordSource::Any,
964            thing_type: ThingType::Any,
965            limit,
966        }
967    }
968}
969
970pub struct GetByNameArgs<'a> {
971    name: &'a str,
972    record_source: RecordSource,
973    thing_type: ThingType,
974}
975
976impl<'a> From<&'a str> for GetByNameArgs<'a> {
977    fn from(name: &'a str) -> Self {
978        GetByNameArgs {
979            name,
980            record_source: RecordSource::Any,
981            thing_type: ThingType::Any,
982        }
983    }
984}
985
986impl<'a> From<&'a String> for GetByNameArgs<'a> {
987    fn from(name: &'a String) -> Self {
988        GetByNameArgs {
989            name,
990            record_source: RecordSource::Any,
991            thing_type: ThingType::Any,
992        }
993    }
994}
995
996impl<'a> From<(&'a str, RecordSource, ThingType)> for GetByNameArgs<'a> {
997    fn from(name_source_type: (&'a str, RecordSource, ThingType)) -> Self {
998        let (name, record_source, thing_type) = name_source_type;
999
1000        GetByNameArgs {
1001            name,
1002            record_source,
1003            thing_type,
1004        }
1005    }
1006}
1007
1008impl KeyValue {
1009    pub const fn key_raw(&self) -> &'static str {
1010        match self {
1011            Self::Time(_) => "time",
1012        }
1013    }
1014
1015    pub fn key_value_raw(&self) -> (&'static str, Option<String>) {
1016        (
1017            self.key_raw(),
1018            match self {
1019                Self::Time(time) => time.as_ref().map(|t| t.display_short().to_string()),
1020            },
1021        )
1022    }
1023
1024    pub const fn time(self) -> Option<Time> {
1025        #[expect(irrefutable_let_patterns)]
1026        if let Self::Time(time) = self {
1027            time
1028        } else {
1029            None
1030        }
1031    }
1032}
1033
1034impl Change {
1035    /// Describe how applying this change from the undo queue will affect the application state
1036    /// ("undo change xyz").
1037    pub fn display_undo(&self) -> DisplayUndo {
1038        DisplayUndo(self)
1039    }
1040
1041    /// Describe how applyifrom the redo queue will affect the application state ("redo change
1042    /// xyz").
1043    pub fn display_redo(&self) -> DisplayRedo {
1044        DisplayRedo(self)
1045    }
1046}
1047
1048impl Record {
1049    pub fn is_saved(&self) -> bool {
1050        self.status == RecordStatus::Saved
1051    }
1052
1053    pub fn is_unsaved(&self) -> bool {
1054        self.status == RecordStatus::Unsaved
1055    }
1056
1057    pub fn is_deleted(&self) -> bool {
1058        self.status == RecordStatus::Deleted
1059    }
1060
1061    pub fn is_from_source(&self, source: RecordSource) -> bool {
1062        matches!(
1063            (self.status, source),
1064            (
1065                RecordStatus::Saved,
1066                RecordSource::Journal | RecordSource::Any
1067            ) | (
1068                RecordStatus::Unsaved,
1069                RecordSource::Recent | RecordSource::Any
1070            ),
1071        )
1072    }
1073}
1074
1075impl RecordSource {
1076    pub fn journal(&self) -> bool {
1077        matches!(self, Self::Journal | Self::Any)
1078    }
1079
1080    pub fn recent(&self) -> bool {
1081        matches!(self, Self::Recent | Self::Any)
1082    }
1083}
1084
1085impl fmt::Display for DisplayUndo<'_> {
1086    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
1087        let change = self.0;
1088
1089        // Note: these descriptions are _backward_ since they describe the reverse, ie. the action
1090        // that this Change will undo. Eg. Change::Create => "undo deleting x"
1091        match change {
1092            Change::Create { thing_data, .. } | Change::CreateAndSave { thing_data, .. } => {
1093                write!(f, "deleting {}", thing_data.name())
1094            }
1095            Change::Delete { name, .. } => write!(f, "creating {}", name),
1096            Change::Save { name, .. } => write!(f, "removing {} from journal", name),
1097            Change::Unsave { name, .. } => write!(f, "saving {} to journal", name),
1098
1099            // These changes are symmetric, so we can provide the same output in both cases.
1100            Change::Edit { .. } | Change::EditAndUnsave { .. } | Change::SetKeyValue { .. } => {
1101                write!(f, "{}", DisplayRedo(change))
1102            }
1103        }
1104    }
1105}
1106
1107impl fmt::Display for DisplayRedo<'_> {
1108    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
1109        let change = self.0;
1110
1111        match change {
1112            Change::Create { thing_data, .. } | Change::CreateAndSave { thing_data, .. } => {
1113                write!(f, "creating {}", thing_data.name())
1114            }
1115            Change::Delete { name, .. } => write!(f, "deleting {}", name),
1116            Change::Edit { name, .. } | Change::EditAndUnsave { name, .. } => {
1117                write!(f, "editing {}", name)
1118            }
1119            Change::Save { name, .. } => write!(f, "saving {} to journal", name),
1120            Change::Unsave { name, .. } => write!(f, "removing {} from journal", name),
1121            Change::SetKeyValue { key_value } => match key_value {
1122                KeyValue::Time(_) => write!(f, "changing the time"),
1123            },
1124        }
1125    }
1126}
1127
1128impl fmt::Debug for Repository {
1129    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1130        write!(
1131            f,
1132            "Repository {{ data_store_enabled: {:?}, recent: {:?} }}",
1133            self.data_store_enabled, self.recent,
1134        )
1135    }
1136}
1137
1138#[cfg(test)]
1139mod test {
1140    use super::*;
1141    use crate::storage::data_store::{MemoryDataStore, NullDataStore};
1142    use crate::test_utils as test;
1143    use crate::world::npc::Npc;
1144    use crate::world::place::Place;
1145    use tokio_test::block_on;
1146    use uuid::Uuid;
1147
1148    const OLYMPUS_UUID: Uuid = Uuid::from_u128(1);
1149    const THESSALY_UUID: Uuid = Uuid::from_u128(2);
1150    const GREECE_UUID: Uuid = Uuid::from_u128(3);
1151    const STYX_UUID: Uuid = Uuid::from_u128(4);
1152    const ODYSSEUS_UUID: Uuid = Uuid::from_u128(5);
1153
1154    macro_rules! assert_change_success {
1155        ($change: expr, $is_changed:expr, $redo_message:expr, $undo_message:expr) => {
1156            let change: Change = $change;
1157            let is_changed: &dyn Fn(&Repository, &dyn DataStore) -> bool = &$is_changed;
1158            let undo_message: &str = $undo_message;
1159            let redo_message: &str = $redo_message;
1160
1161            let (mut repo, data_store) = repo_data_store();
1162            assert_eq!(redo_message, change.display_redo().to_string(), "change.display_redo()");
1163
1164            let (original_recent, original_data_store) = (repo.recent.clone(), data_store.snapshot());
1165
1166            let (modified_recent, modified_data_store) = {
1167                // repo.modify()
1168                block_on(repo.modify(change)).unwrap();
1169                assert!(
1170                    is_changed(&repo, &data_store),
1171                    "`is_changed()` should return true after `repo.modify()`
1172
1173repo.recent = {:?}
1174
1175data_store.snapshot() = {:?}",
1176                    repo.recent,
1177                    data_store.snapshot(),
1178                );
1179                assert!(
1180                    original_recent != repo.recent || original_data_store != data_store.snapshot(),
1181                    "`repo.recent` AND/OR `data_store` should have changed after `repo.modify()`
1182
1183repo.recent = {:?}
1184
1185data_store.snapshot() = {:?}",
1186                    repo.recent,
1187                    data_store.snapshot(),
1188                );
1189
1190                assert_eq!(
1191                    undo_message,
1192                    repo.undo_history()
1193                        .next()
1194                        .unwrap()
1195                        .display_undo()
1196                        .to_string(),
1197                    "`undo_history().display_undo()`",
1198                );
1199
1200                (repo.recent.clone(), data_store.snapshot())
1201            };
1202
1203            {
1204                let undo_change = repo.undo_history().next().cloned();
1205
1206                // repo.undo()
1207                block_on(repo.undo()).unwrap().unwrap();
1208                assert!(
1209                    !is_changed(&repo, &data_store),
1210                    "is_changed() should return false after repo.undo()
1211
1212change = {:?}
1213
1214repo.recent = {:?}
1215
1216data_store.snapshot() = {:?}",
1217                    undo_change,
1218                    repo.recent,
1219                    data_store.snapshot(),
1220                );
1221                assert_eq!(
1222                    original_recent,
1223                    repo.recent,
1224                    "`repo.recent` should reset after `repo.undo()`\n\nchange = {:?}",
1225                    undo_change,
1226                );
1227                assert_eq!(
1228                    original_data_store,
1229                    data_store.snapshot(),
1230                    "`data_store` should reset after `repo.undo()`\n\nchange = {:?}",
1231                    undo_change,
1232                );
1233            }
1234
1235            {
1236                // repo.redo()
1237                block_on(repo.redo());
1238                assert!(
1239                    is_changed(&repo, &data_store),
1240                    "is_changed() should return true after repo.redo()
1241
1242repo.recent = {:?}
1243
1244data_store.snapshot() = {:?}",
1245                    repo.recent,
1246                    data_store.snapshot(),
1247                );
1248                assert_eq!(
1249                    modified_recent,
1250                    repo.recent,
1251                    "`repo.recent` should return to its changed state after `repo.redo()`",
1252                );
1253                assert_eq!(
1254                    modified_data_store,
1255                    data_store.snapshot(),
1256                    "`data_store` should return to its changed state after `repo.redo()`",
1257                );
1258            }
1259        }
1260    }
1261
1262    macro_rules! assert_change_error {
1263        ($repo_data_store: expr, $change:expr, $error:expr) => {
1264            let (mut repo, data_store): (Repository, MemoryDataStore) = $repo_data_store;
1265            let change: Change = $change;
1266            let error: Error = $error;
1267
1268            let (original_recent, original_data_store) =
1269                (repo.recent.clone(), data_store.snapshot());
1270
1271            let result = block_on(repo.modify(change.clone()));
1272
1273            assert_eq!(Err((change, error)), result);
1274            assert_eq!(original_recent, repo.recent);
1275            assert_eq!(original_data_store, data_store.snapshot());
1276        };
1277    }
1278
1279    macro_rules! assert_change_data_store_failed {
1280        ($change:expr) => {
1281            let change: Change = $change;
1282
1283            let mut repo = null_repo();
1284            let original_recent = repo.recent.clone();
1285
1286            let result = block_on(repo.modify(change.clone()));
1287
1288            assert_eq!(Err((change, Error::DataStoreFailed)), result);
1289            assert_eq!(original_recent, repo.recent);
1290        };
1291    }
1292
1293    #[test]
1294    fn recent_test() {
1295        let mut repository = empty_repo();
1296
1297        (0..RECENT_MAX_LEN).for_each(|i| {
1298            repository.push_recent(thing(
1299                Uuid::from_u128(i.try_into().unwrap()),
1300                NpcData {
1301                    name: format!("Thing {}", i).into(),
1302                    ..Default::default()
1303                },
1304            ));
1305            assert_eq!(i + 1, repository.recent.len());
1306        });
1307
1308        assert_eq!(
1309            Some(&"Thing 0".to_string()),
1310            repository
1311                .recent()
1312                .next()
1313                .and_then(|thing| thing.name().value()),
1314        );
1315
1316        repository.push_recent(thing(
1317            Uuid::from_u128(u128::MAX),
1318            NpcData {
1319                name: "The Cat in the Hat".into(),
1320                ..Default::default()
1321            },
1322        ));
1323        assert_eq!(RECENT_MAX_LEN, repository.recent.len());
1324
1325        assert_eq!(
1326            Some(&"Thing 1".to_string()),
1327            repository
1328                .recent()
1329                .next()
1330                .and_then(|thing| thing.name().value()),
1331        );
1332
1333        assert_eq!(
1334            Some(&"The Cat in the Hat".to_string()),
1335            repository
1336                .recent()
1337                .last()
1338                .and_then(|thing| thing.name().value()),
1339        );
1340    }
1341
1342    #[test]
1343    fn journal_recent_test() {
1344        let repo = repo();
1345        assert_eq!(4, block_on(repo.journal()).unwrap().len());
1346        assert_eq!(1, repo.recent().count());
1347    }
1348
1349    #[test]
1350    fn get_by_name_test_from_recent() {
1351        let result = block_on(repo().get_by_name("ODYSSEUS")).unwrap();
1352
1353        assert_eq!(RecordStatus::Unsaved, result.status);
1354        assert_eq!("Odysseus", result.thing.name().to_string());
1355    }
1356
1357    #[test]
1358    fn get_by_name_test_from_journal() {
1359        let result = block_on(repo().get_by_name("OLYMPUS")).unwrap();
1360
1361        assert_eq!(RecordStatus::Saved, result.status);
1362        assert_eq!("Olympus", result.thing.name().to_string());
1363    }
1364
1365    #[test]
1366    fn get_by_name_test_not_found() {
1367        assert_eq!(Err(Error::NotFound), block_on(repo().get_by_name("NOBODY")));
1368    }
1369
1370    #[test]
1371    fn get_by_uuid_test_from_recent() {
1372        let result = block_on(repo().get_by_uuid(&ODYSSEUS_UUID)).unwrap();
1373
1374        assert_eq!(RecordStatus::Unsaved, result.status);
1375        assert_eq!("Odysseus", result.thing.name().to_string());
1376    }
1377
1378    #[test]
1379    fn get_by_uuid_test_from_journal() {
1380        let result = block_on(repo().get_by_uuid(&OLYMPUS_UUID)).unwrap();
1381
1382        assert_eq!(RecordStatus::Saved, result.status);
1383        assert_eq!("Olympus", result.thing.name().to_string());
1384    }
1385
1386    #[test]
1387    fn change_test_delete_from_journal_success() {
1388        assert_change_success!(
1389            Change::Delete {
1390                uuid: OLYMPUS_UUID,
1391                name: "blah".to_string(),
1392            },
1393            |repo, _| block_on(repo.get_by_name("Olympus")) == Err(Error::NotFound),
1394            "deleting blah",
1395            "deleting Olympus"
1396        );
1397    }
1398
1399    #[test]
1400    fn change_test_delete_from_recent_success() {
1401        assert_change_success!(
1402            Change::Delete {
1403                uuid: ODYSSEUS_UUID,
1404                name: "blah".to_string(),
1405            },
1406            |repo, _| block_on(repo.get_by_name("Odysseus")) == Err(Error::NotFound),
1407            "deleting blah",
1408            "deleting Odysseus"
1409        );
1410    }
1411
1412    #[test]
1413    fn change_test_delete_not_found() {
1414        assert_change_error!(
1415            repo_data_store(),
1416            Change::Delete {
1417                uuid: Uuid::nil(),
1418                name: "Nobody".to_string(),
1419            },
1420            Error::NotFound
1421        );
1422    }
1423
1424    #[test]
1425    fn change_test_delete_data_store_failed() {
1426        assert_change_data_store_failed!(Change::Delete {
1427            uuid: OLYMPUS_UUID,
1428            name: "Olympus".to_string(),
1429        });
1430    }
1431
1432    #[test]
1433    fn change_test_edit_by_name_from_recent_success() {
1434        assert_change_success!(
1435            Change::Edit {
1436                name: "ODYSSEUS".into(),
1437                uuid: None,
1438                diff: NpcData {
1439                    name: "Nobody".into(),
1440                    ..Default::default()
1441                }
1442                .into(),
1443            },
1444            |_, ds| {
1445                block_on(ds.get_thing_by_uuid(&ODYSSEUS_UUID))
1446                    .map(|opt_t| opt_t.map(|t| t.name().to_string()))
1447                    == Ok(Some("Nobody".to_string()))
1448            },
1449            "editing ODYSSEUS",
1450            "editing Nobody"
1451        );
1452    }
1453
1454    #[test]
1455    fn change_test_edit_by_name_from_recent_wrong_type() {
1456        assert_change_error!(
1457            repo_data_store(),
1458            Change::Edit {
1459                name: "Odysseus".into(),
1460                uuid: None,
1461                diff: PlaceData::default().into(),
1462            },
1463            Error::NotFound
1464        );
1465    }
1466
1467    #[test]
1468    fn change_test_edit_by_name_from_recent_data_store_failed() {
1469        let mut repo = repo();
1470        repo.data_store = Box::new(NullDataStore);
1471        let change = Change::Edit {
1472            name: "Odysseus".into(),
1473            uuid: None,
1474            diff: NpcData {
1475                name: "Nobody".into(),
1476                ..Default::default()
1477            }
1478            .into(),
1479        };
1480
1481        {
1482            let result = block_on(repo.modify(change));
1483
1484            assert_eq!(
1485                Ok(Some("Nobody".to_string())),
1486                result.map(|opt_r| opt_r.map(|r| r.thing.name().to_string())),
1487            );
1488            assert!(repo.recent().any(|t| t.name().to_string() == "Nobody"));
1489        }
1490
1491        {
1492            let undo_change = repo.undo_history().next().cloned();
1493            let undo_result = block_on(repo.undo());
1494
1495            assert_eq!(
1496                Ok(Some("Odysseus".to_string())),
1497                undo_result
1498                    .unwrap()
1499                    .map(|opt_r| opt_r.map(|r| r.thing.name().to_string())),
1500                "{:?}",
1501                undo_change,
1502            );
1503            assert!(repo.recent().any(|t| t.name().to_string() == "Odysseus"));
1504        }
1505
1506        {
1507            let redo_result = block_on(repo.redo());
1508
1509            assert_eq!(
1510                Ok(Some("Nobody".to_string())),
1511                redo_result
1512                    .unwrap()
1513                    .map(|opt_r| opt_r.map(|r| r.thing.name().to_string())),
1514            );
1515            assert!(repo.recent().any(|t| t.name().to_string() == "Nobody"));
1516        }
1517    }
1518
1519    #[test]
1520    fn change_test_edit_by_name_from_journal_success() {
1521        assert_change_success!(
1522            Change::Edit {
1523                name: "OLYMPUS".into(),
1524                uuid: None,
1525                diff: PlaceData {
1526                    name: "Hades".into(),
1527                    description: "This really is hell!".into(),
1528                    ..Default::default()
1529                }
1530                .into(),
1531            },
1532            |_, ds| {
1533                block_on(ds.get_thing_by_uuid(&OLYMPUS_UUID))
1534                    .map(|opt_t| opt_t.map(|t| t.name().to_string()))
1535                    == Ok(Some("Hades".to_string()))
1536            },
1537            "editing OLYMPUS",
1538            "editing Hades"
1539        );
1540    }
1541
1542    #[test]
1543    fn change_test_edit_by_name_from_journal_wrong_type() {
1544        assert_change_error!(
1545            repo_data_store(),
1546            Change::Edit {
1547                name: "Olympus".into(),
1548                uuid: None,
1549                diff: NpcData::default().into(),
1550            },
1551            Error::NotFound
1552        );
1553    }
1554
1555    #[test]
1556    fn change_test_edit_by_name_from_journal_data_store_failed() {
1557        assert_change_data_store_failed!(Change::Edit {
1558            name: "Olympus".into(),
1559            uuid: None,
1560            diff: PlaceData {
1561                name: "Hades".into(),
1562                ..Default::default()
1563            }
1564            .into(),
1565        });
1566    }
1567
1568    #[test]
1569    fn change_test_edit_by_name_not_found() {
1570        assert_change_error!(
1571            repo_data_store(),
1572            Change::Edit {
1573                name: "Nobody".into(),
1574                uuid: None,
1575                diff: NpcData::default().into(),
1576            },
1577            Error::NotFound
1578        );
1579    }
1580
1581    #[test]
1582    fn change_test_edit_by_uuid_from_recent_success() {
1583        assert_change_success!(
1584            Change::Edit {
1585                name: "blah".into(),
1586                uuid: Some(ODYSSEUS_UUID),
1587                diff: NpcData {
1588                    name: "Nobody".into(),
1589                    ..Default::default()
1590                }
1591                .into(),
1592            },
1593            |repo, ds| {
1594                block_on(ds.get_thing_by_uuid(&ODYSSEUS_UUID))
1595                    .map(|opt_t| opt_t.map(|t| t.name().to_string()))
1596                    == Ok(Some("Nobody".to_string()))
1597                    && !repo.recent().any(|t| t.uuid == ODYSSEUS_UUID)
1598            },
1599            "editing blah",
1600            "editing Nobody"
1601        );
1602    }
1603
1604    #[test]
1605    fn change_test_edit_by_uuid_from_journal_success() {
1606        assert_change_success!(
1607            Change::Edit {
1608                name: "blah".into(),
1609                uuid: Some(OLYMPUS_UUID),
1610                diff: PlaceData {
1611                    name: "Hades".into(),
1612                    description: "This really is hell!".into(),
1613                    ..Default::default()
1614                }
1615                .into(),
1616            },
1617            |_, ds| {
1618                block_on(ds.get_thing_by_uuid(&OLYMPUS_UUID))
1619                    .map(|opt_t| opt_t.map(|t| t.name().to_string()))
1620                    == Ok(Some("Hades".to_string()))
1621            },
1622            "editing blah",
1623            "editing Hades"
1624        );
1625    }
1626
1627    #[test]
1628    fn change_test_edit_by_uuid_wrong_type() {
1629        assert_change_error!(
1630            repo_data_store(),
1631            Change::Edit {
1632                name: "Olympus".into(),
1633                uuid: Some(OLYMPUS_UUID),
1634                diff: NpcData::default().into(),
1635            },
1636            Error::NotFound
1637        );
1638    }
1639
1640    #[test]
1641    fn change_test_edit_by_uuid_not_found() {
1642        assert_change_error!(
1643            repo_data_store(),
1644            Change::Edit {
1645                name: "Nobody".into(),
1646                uuid: Some(Uuid::nil()),
1647                diff: NpcData::default().into(),
1648            },
1649            Error::NotFound
1650        );
1651    }
1652
1653    #[test]
1654    fn change_test_edit_by_uuid_from_journal_data_store_failed() {
1655        assert_change_data_store_failed!(Change::Edit {
1656            name: "Olympus".into(),
1657            uuid: Some(OLYMPUS_UUID),
1658            diff: PlaceData {
1659                name: "Hades".into(),
1660                description: "This really is hell!".into(),
1661                ..Default::default()
1662            }
1663            .into(),
1664        });
1665    }
1666
1667    #[test]
1668    fn change_test_edit_and_unsave_success() {
1669        assert_change_success!(
1670            Change::EditAndUnsave {
1671                uuid: OLYMPUS_UUID,
1672                name: "blah".into(),
1673                diff: PlaceData {
1674                    name: "Hades".into(),
1675                    description: "This really is hell!".into(),
1676                    ..Default::default()
1677                }
1678                .into(),
1679            },
1680            |repo, ds| {
1681                repo.recent().any(|t| t.name().to_string() == "Hades")
1682                    && block_on(ds.get_thing_by_uuid(&OLYMPUS_UUID)) == Ok(None)
1683            },
1684            "editing blah",
1685            "editing Hades"
1686        );
1687    }
1688
1689    #[test]
1690    fn change_test_edit_and_unsave_not_found() {
1691        assert_change_error!(
1692            repo_data_store(),
1693            Change::EditAndUnsave {
1694                name: "Nobody".into(),
1695                uuid: Uuid::nil(),
1696                diff: NpcData::default().into(),
1697            },
1698            Error::NotFound
1699        );
1700    }
1701
1702    #[test]
1703    fn change_test_edit_and_unsave_data_store_failed() {
1704        let mut repo = Repository::new(test::data_store::time_bomb(7));
1705        populate_repo(&mut repo);
1706
1707        let change = Change::EditAndUnsave {
1708            name: "Olympus".into(),
1709            uuid: OLYMPUS_UUID,
1710            diff: PlaceData {
1711                name: "Hades".into(),
1712                description: "This really is hell!".into(),
1713                ..Default::default()
1714            }
1715            .into(),
1716        };
1717
1718        assert_eq!(
1719            Err((
1720                Change::Unsave {
1721                    name: "Hades".into(),
1722                    uuid: OLYMPUS_UUID,
1723                },
1724                Error::DataStoreFailed,
1725            )),
1726            block_on(repo.modify(change)),
1727        );
1728    }
1729
1730    #[test]
1731    fn change_test_create_success() {
1732        assert_change_success!(
1733            Change::Create {
1734                thing_data: NpcData {
1735                    name: "Penelope".into(),
1736                    ..Default::default()
1737                }
1738                .into(),
1739                uuid: None,
1740            },
1741            |repo, _| repo.recent().any(|t| t.name().to_string() == "Penelope"),
1742            "creating Penelope",
1743            "creating Penelope"
1744        );
1745    }
1746
1747    #[test]
1748    fn change_test_create_name_already_exists_in_journal() {
1749        let (repo, data_store) = repo_data_store();
1750        let existing_thing = block_on(data_store.get_thing_by_uuid(&OLYMPUS_UUID))
1751            .unwrap()
1752            .unwrap()
1753            .clone();
1754
1755        assert_change_error!(
1756            (repo, data_store),
1757            Change::Create {
1758                thing_data: NpcData {
1759                    name: "OLYMPUS".into(),
1760                    ..Default::default()
1761                }
1762                .into(),
1763                uuid: None,
1764            },
1765            Error::NameAlreadyExists(existing_thing)
1766        );
1767    }
1768
1769    #[test]
1770    fn change_test_create_name_already_exists_in_recent() {
1771        let (repo, data_store) = repo_data_store();
1772        let existing_thing = repo
1773            .recent()
1774            .find(|t| t.uuid == ODYSSEUS_UUID)
1775            .unwrap()
1776            .clone();
1777
1778        assert_change_error!(
1779            (repo, data_store),
1780            Change::Create {
1781                thing_data: NpcData {
1782                    name: "ODYSSEUS".into(),
1783                    ..Default::default()
1784                }
1785                .into(),
1786                uuid: None,
1787            },
1788            Error::NameAlreadyExists(existing_thing)
1789        );
1790    }
1791
1792    #[test]
1793    fn change_test_save_by_name_success() {
1794        assert_change_success!(
1795            Change::Save {
1796                name: "ODYSSEUS".to_string(),
1797                uuid: None,
1798            },
1799            |repo, ds| {
1800                block_on(ds.get_thing_by_uuid(&ODYSSEUS_UUID))
1801                    .map(|opt_t| opt_t.map(|t| t.name().to_string()))
1802                    == Ok(Some("Odysseus".to_string()))
1803                    && !repo.recent().any(|t| t.uuid == ODYSSEUS_UUID)
1804            },
1805            "saving ODYSSEUS to journal",
1806            "saving Odysseus to journal"
1807        );
1808    }
1809
1810    #[test]
1811    fn change_test_save_data_store_failed() {
1812        let mut repo = null_repo();
1813
1814        block_on(
1815            repo.modify(Change::Create {
1816                thing_data: PlaceData {
1817                    name: "Odysseus".into(),
1818                    ..Default::default()
1819                }
1820                .into(),
1821                uuid: None,
1822            }),
1823        )
1824        .unwrap();
1825
1826        let original_recent = repo.recent.clone();
1827
1828        let change = Change::Save {
1829            name: "ODYSSEUS".to_string(),
1830            uuid: None,
1831        };
1832        assert_eq!(
1833            block_on(repo.modify(change.clone())),
1834            Err((change, Error::DataStoreFailed)),
1835        );
1836
1837        assert_eq!(original_recent, repo.recent);
1838    }
1839
1840    #[test]
1841    fn change_test_save_already_saved() {
1842        assert_change_error!(
1843            repo_data_store(),
1844            Change::Save {
1845                name: "OLYMPUS".to_string(),
1846                uuid: None,
1847            },
1848            Error::NotFound
1849        );
1850    }
1851
1852    #[test]
1853    fn change_test_save_not_found() {
1854        assert_change_error!(
1855            repo_data_store(),
1856            Change::Save {
1857                name: "NOBODY".to_string(),
1858                uuid: None,
1859            },
1860            Error::NotFound
1861        );
1862    }
1863
1864    #[test]
1865    fn change_test_unsave_success() {
1866        assert_change_success!(
1867            Change::Unsave {
1868                uuid: OLYMPUS_UUID,
1869                name: "blah".to_string(),
1870            },
1871            |repo, ds| {
1872                block_on(ds.get_thing_by_uuid(&OLYMPUS_UUID)) == Ok(None)
1873                    && repo.recent().any(|t| t.uuid == OLYMPUS_UUID)
1874            },
1875            "removing blah from journal",
1876            "removing Olympus from journal"
1877        );
1878    }
1879
1880    #[test]
1881    fn change_test_create_and_save_success() {
1882        assert_change_success!(
1883            Change::CreateAndSave {
1884                thing_data: NpcData {
1885                    name: "Penelope".into(),
1886                    ..Default::default()
1887                }
1888                .into(),
1889                uuid: None,
1890            },
1891            |_, ds| block_on(ds.get_thing_by_name("Penelope"))
1892                .unwrap()
1893                .is_some(),
1894            "creating Penelope",
1895            "creating Penelope"
1896        );
1897    }
1898
1899    #[test]
1900    fn change_test_create_and_save_name_already_exists_in_journal() {
1901        let (repo, data_store) = repo_data_store();
1902        let existing_thing = block_on(data_store.get_thing_by_uuid(&OLYMPUS_UUID))
1903            .unwrap()
1904            .unwrap()
1905            .clone();
1906
1907        assert_change_error!(
1908            (repo, data_store),
1909            Change::CreateAndSave {
1910                thing_data: NpcData {
1911                    name: "OLYMPUS".into(),
1912                    ..Default::default()
1913                }
1914                .into(),
1915                uuid: None,
1916            },
1917            Error::NameAlreadyExists(existing_thing)
1918        );
1919    }
1920
1921    #[test]
1922    fn change_test_create_and_save_name_already_exists_in_recent() {
1923        let (repo, data_store) = repo_data_store();
1924        let existing_thing = repo
1925            .recent()
1926            .find(|t| t.uuid == ODYSSEUS_UUID)
1927            .unwrap()
1928            .clone();
1929
1930        assert_change_error!(
1931            (repo, data_store),
1932            Change::CreateAndSave {
1933                thing_data: NpcData {
1934                    name: "ODYSSEUS".into(),
1935                    ..Default::default()
1936                }
1937                .into(),
1938                uuid: None,
1939            },
1940            Error::NameAlreadyExists(existing_thing)
1941        );
1942    }
1943
1944    #[test]
1945    fn change_test_create_and_save_data_store_failed() {
1946        let mut repo = null_repo();
1947
1948        let change = Change::CreateAndSave {
1949            thing_data: NpcData {
1950                name: "Odysseus".into(),
1951                ..Default::default()
1952            }
1953            .into(),
1954            uuid: None,
1955        };
1956
1957        assert_eq!(
1958            block_on(repo.modify(change.clone())),
1959            Err((change, Error::DataStoreFailed)),
1960        );
1961    }
1962
1963    #[test]
1964    fn change_test_set_key_value_success() {
1965        let mut repo = repo();
1966
1967        let one = Time::try_new(1, 0, 0, 0).unwrap();
1968        let two = Time::try_new(2, 0, 0, 0).unwrap();
1969
1970        assert_eq!(
1971            Ok(KeyValue::Time(None)),
1972            block_on(repo.get_key_value(&KeyValue::Time(None)))
1973        );
1974
1975        assert_eq!(
1976            Ok(None),
1977            block_on(repo.modify(Change::SetKeyValue {
1978                key_value: KeyValue::Time(Some(one.clone())),
1979            })),
1980        );
1981
1982        {
1983            let undo_result = repo.undo_history().next().unwrap();
1984
1985            assert_eq!(
1986                &Change::SetKeyValue {
1987                    key_value: KeyValue::Time(None),
1988                },
1989                undo_result,
1990            );
1991            assert_eq!("changing the time", undo_result.display_undo().to_string());
1992            assert_eq!("changing the time", undo_result.display_redo().to_string());
1993        }
1994
1995        block_on(repo.modify(Change::SetKeyValue {
1996            key_value: KeyValue::Time(Some(two.clone())),
1997        }))
1998        .unwrap();
1999
2000        block_on(repo.modify(Change::SetKeyValue {
2001            key_value: KeyValue::Time(None),
2002        }))
2003        .unwrap();
2004
2005        assert_eq!(
2006            Ok(KeyValue::Time(None)),
2007            block_on(repo.get_key_value(&KeyValue::Time(None)))
2008        );
2009
2010        assert_eq!(Some(Ok(None)), block_on(repo.undo()));
2011
2012        assert_eq!(
2013            Ok(KeyValue::Time(Some(two))),
2014            block_on(repo.get_key_value(&KeyValue::Time(None)))
2015        );
2016
2017        block_on(repo.undo());
2018
2019        assert_eq!(
2020            Ok(KeyValue::Time(Some(one))),
2021            block_on(repo.get_key_value(&KeyValue::Time(None)))
2022        );
2023
2024        block_on(repo.undo());
2025
2026        assert_eq!(
2027            Ok(KeyValue::Time(None)),
2028            block_on(repo.get_key_value(&KeyValue::Time(None)))
2029        );
2030    }
2031
2032    #[test]
2033    fn change_test_set_key_value_data_store_failed() {
2034        let change = Change::SetKeyValue {
2035            key_value: KeyValue::Time(Some(Time::default())),
2036        };
2037
2038        assert_eq!(
2039            block_on(null_repo().modify(change.clone())),
2040            Err((change, Error::DataStoreFailed)),
2041        );
2042    }
2043
2044    #[test]
2045    fn load_relations_test_with_parent_success() {
2046        let repo = repo();
2047        let odysseus = block_on(repo.get_by_name("Odysseus")).unwrap().thing;
2048
2049        match block_on(repo.load_relations(&odysseus)) {
2050            Ok(ThingRelations::Npc(NpcRelations {
2051                location: Some((parent, None)),
2052            })) => {
2053                assert_eq!("River Styx", parent.data.name.value().unwrap());
2054            }
2055            r => panic!("{:?}", r),
2056        }
2057    }
2058
2059    #[test]
2060    fn load_relations_test_with_grandparent_success() {
2061        let repo = repo();
2062        let olympus = block_on(repo.get_by_uuid(&OLYMPUS_UUID)).unwrap().thing;
2063
2064        match block_on(repo.load_relations(&olympus)) {
2065            Ok(ThingRelations::Place(PlaceRelations {
2066                location: Some((parent, Some(grandparent))),
2067            })) => {
2068                assert_eq!("Thessaly", parent.data.name.value().unwrap());
2069                assert_eq!("Greece", grandparent.data.name.value().unwrap());
2070            }
2071            r => panic!("{:?}", r),
2072        }
2073    }
2074
2075    #[test]
2076    fn debug_test() {
2077        assert_eq!(
2078            "Repository { data_store_enabled: false, recent: [] }",
2079            format!("{:?}", empty_repo()),
2080        );
2081    }
2082
2083    #[test]
2084    fn data_store_enabled_test_success() {
2085        let mut repo = repo();
2086        block_on(repo.init());
2087        assert!(repo.data_store_enabled());
2088    }
2089
2090    #[test]
2091    fn data_store_enabled_test_failure() {
2092        let mut repo = null_repo();
2093        block_on(repo.init());
2094        assert!(!repo.data_store_enabled());
2095    }
2096
2097    fn thing(uuid: Uuid, data: impl Into<ThingData>) -> Thing {
2098        Thing {
2099            uuid,
2100            data: data.into(),
2101        }
2102    }
2103
2104    fn repo() -> Repository {
2105        repo_data_store().0
2106    }
2107
2108    fn repo_data_store() -> (Repository, MemoryDataStore) {
2109        let data_store = MemoryDataStore::default();
2110        let mut repo = Repository::new(data_store.clone());
2111        populate_repo(&mut repo);
2112        (repo, data_store)
2113    }
2114
2115    fn empty_repo() -> Repository {
2116        Repository::new(MemoryDataStore::default())
2117    }
2118
2119    fn null_repo() -> Repository {
2120        Repository::new(NullDataStore)
2121    }
2122
2123    fn populate_repo(repo: &mut Repository) {
2124        block_on(
2125            repo.data_store.save_thing(
2126                &Place {
2127                    uuid: OLYMPUS_UUID,
2128                    data: PlaceData {
2129                        location_uuid: THESSALY_UUID.into(),
2130                        name: "Olympus".into(),
2131                        ..Default::default()
2132                    },
2133                }
2134                .into(),
2135            ),
2136        )
2137        .unwrap();
2138        block_on(
2139            repo.data_store.save_thing(
2140                &Place {
2141                    uuid: THESSALY_UUID,
2142                    data: PlaceData {
2143                        location_uuid: GREECE_UUID.into(),
2144                        name: "Thessaly".into(),
2145                        ..Default::default()
2146                    },
2147                }
2148                .into(),
2149            ),
2150        )
2151        .unwrap();
2152        block_on(
2153            repo.data_store.save_thing(
2154                &Place {
2155                    uuid: GREECE_UUID,
2156                    data: PlaceData {
2157                        name: "Greece".into(),
2158                        ..Default::default()
2159                    },
2160                }
2161                .into(),
2162            ),
2163        )
2164        .unwrap();
2165        block_on(
2166            repo.data_store.save_thing(
2167                &Place {
2168                    uuid: STYX_UUID,
2169                    data: PlaceData {
2170                        location_uuid: Uuid::nil().into(),
2171                        name: "River Styx".into(),
2172                        ..Default::default()
2173                    },
2174                }
2175                .into(),
2176            ),
2177        )
2178        .unwrap();
2179
2180        repo.recent.push_back(
2181            Npc {
2182                uuid: ODYSSEUS_UUID,
2183                data: NpcData {
2184                    name: "Odysseus".into(),
2185                    location_uuid: STYX_UUID.into(),
2186                    ..Default::default()
2187                },
2188            }
2189            .into(),
2190        );
2191
2192        block_on(repo.init());
2193    }
2194}