lib_efo/
lib.rs

1//! # `lib_efo`
2//!
3//! The **engine** that drives *Epoch Forge*.  It contains a pure, representation of a fantasy world and all the logic needed to manipulate it.
4//!
5//! All public types implement `serde::Serialize / Deserialize` so that they can
6//! be persisted easily.  The `World` struct also offers a convenient
7//! `World::load()` / `World::save()` API that uses the default JSON file
8
9#![warn(missing_docs, clippy::missing_docs_in_private_items)]
10#![deny(clippy::unwrap_used)]
11
12use crate::epoch::EpochInfo;
13
14#[macro_use]
15#[allow(unused_assignments)]
16pub mod error;
17pub mod entity;
18pub mod epoch;
19pub mod naming;
20pub mod stats;
21pub mod utils;
22
23// use std::io::Result as IoResult;
24
25// /// Marker trait for anything that can be persisted via the `Store<T>` abstraction.
26// ///
27// /// It is a thin wrapper around `serde::Serialize` and `serde::de::DeserializeOwned` so that the
28// /// compiler can infer that the type is both serialisable *and* deserialisable.
29// pub trait Persistable: serde::Serialize + serde::de::DeserializeOwned {}
30// impl<T> Persistable for T where T: serde::Serialize + serde::de::DeserializeOwned + 'static {}
31
32// /// The generic store. All domain entities that need persistence should use this trait.
33// /// It can be backed by a file, a DB, an HTTP API, … as long as it knows how to read/write the type `T`.
34// pub trait Store<T>
35// where
36//     T: Persistable,
37// {
38//     /// Read a value identified by `id`.
39//     fn read(&self, id: &str) -> IoResult<Option<T>>;
40
41//     /// Write (or overwrite) a value identified by `id`.
42//     fn write(&self, id: &str, value: &T) -> IoResult<()>;
43
44//     /// Delete the value identified by `id`.
45//     fn delete(&self, id: &str) -> IoResult<()>;
46
47//     /// List all ids known to this store.
48//     fn list_ids(&self) -> IoResult<Vec<String>>;
49// }
50/// A trait for types that can be displayed in a command-line tool.
51///
52/// Implementors should provide three levels of representation:
53/// 1. **Full** - a detailed view suitable for a long-running command.
54/// 2. **Short** - a concise, user-friendly view that fits on a single line.
55/// 3. **Oneline** - a compact, machine-friendly string that can be embedded
56///    inside a `Vec<T>` representation.
57///
58/// ## `Vec<T>` support
59///
60/// The trait is implemented generically for `Vec<T>` as long as `T: Displayable`.
61/// This allows you to call `full_display`, `short_display` or `oneline_display`
62/// directly on a vector of displayable items.
63pub trait Displayable {
64    /// Return a fully-described string representation.
65    fn print_full(&self, epoch_info: &EpochInfo) {
66        println!("{}", self.short_display(epoch_info));
67    }
68
69    /// Return a short, user-friendly string. Resolves Ids
70    fn short_display(&self, _epoch_info: &EpochInfo) -> String {
71        self.oneline_display()
72    }
73
74    /// Return a one-liner string, suitable for embedding in a collection
75    /// representation (e.g. a `Vec<T>`).  Implementations should avoid
76    /// new-lines to keep the output usable in a single line context.
77    fn oneline_display(&self) -> String;
78}
79
80/// Implement `Displayable` for vectors of displayable items.
81///
82/// The implementation simply delegates to the underlying `T::oneline_display`
83/// method and joins the results with a comma.  It works for any `T` that
84/// already implements `Displayable`.  This is a **blanket implementation**,
85/// so you do not need to write a manual impl for each concrete type
86impl<T: Displayable> Displayable for Vec<T> {
87    fn print_full(&self, epoch_info: &EpochInfo) {
88        self.iter().for_each(|e| e.print_full(epoch_info));
89    }
90
91    fn short_display(&self, epoch_info: &EpochInfo) -> String {
92        self.iter()
93            .map(|e| e.short_display(epoch_info))
94            .collect::<Vec<String>>()
95            .join("\n")
96    }
97
98    fn oneline_display(&self) -> String {
99        // No brackets - just a flat, comma-separated list.
100        self.iter()
101            .map(|x| x.oneline_display())
102            .collect::<Vec<_>>()
103            .join("\n")
104    }
105}
106
107// tests/api.rs --------------------------------------------------------------
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use insta::assert_snapshot;
112    struct Dummy {
113        name: String,
114    }
115
116    impl Displayable for Dummy {
117        fn oneline_display(&self) -> String {
118            self.name.clone()
119        }
120    }
121
122    #[test]
123    fn test_displayable_vec() {
124        let vec = vec![
125            Dummy {
126                name: "Alice".to_string(),
127            },
128            Dummy {
129                name: "Bob".to_string(),
130            },
131            Dummy {
132                name: "Eve".to_string(),
133            },
134        ];
135
136        let efo = EpochInfo::default();
137        vec.print_full(&efo);
138        vec.short_display(&efo);
139        assert_snapshot!("dummy_vec_oneline", vec.oneline_display());
140        assert_snapshot!("dummy_vec_oneline", vec.short_display(&efo));
141    }
142}