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#[derive(Clone, Debug, Eq, PartialEq)]
31pub enum Change {
32 Create {
36 thing_data: ThingData,
37 uuid: Option<Uuid>,
38 },
39
40 CreateAndSave {
44 thing_data: ThingData,
45 uuid: Option<Uuid>,
46 },
47
48 Delete { uuid: Uuid, name: Name },
52
53 Edit {
57 name: Name,
58 uuid: Option<Uuid>,
59 diff: ThingData,
60 },
61
62 EditAndUnsave {
66 uuid: Uuid,
67 name: Name,
68 diff: ThingData,
69 },
70
71 Save { name: Name, uuid: Option<Uuid> },
75
76 Unsave { uuid: Uuid, name: Name },
80
81 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 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 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 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 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 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 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 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 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 pub async fn modify(&mut self, change: Change) -> Result<Option<Record>, (Change, Error)> {
379 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 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 pub fn undo_history(&self) -> impl Iterator<Item = &Change> {
427 self.undo_history.iter().rev()
428 }
429
430 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 pub fn get_redo(&self) -> Option<&Change> {
449 self.redo_change.as_ref()
450 }
451
452 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 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 pub fn data_store_enabled(&self) -> bool {
625 self.data_store_enabled
626 }
627
628 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn display_undo(&self) -> DisplayUndo {
1038 DisplayUndo(self)
1039 }
1040
1041 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 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 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 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 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 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}