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}