From 4af1f65d9cbdb6a24c4a1b900e89a4906564d1b8 Mon Sep 17 00:00:00 2001
From: Kubat <maelle.martin@proton.me>
Date: Sat, 19 Apr 2025 13:25:50 +0200
Subject: [PATCH] Organise the Spell things

---
 grimoire_engine_types/src/error.rs           |  8 ++-
 grimoire_engine_types/src/spell.rs           | 54 --------------------
 grimoire_engine_types/src/spell/arguments.rs |  1 +
 grimoire_engine_types/src/spell/factory.rs   | 44 ++++++++++++++++
 grimoire_engine_types/src/spell/mod.rs       |  7 +++
 grimoire_engine_types/src/spell/traits.rs    | 30 +++++++++++
 grimoire_engine_types/src/state.rs           |  3 +-
 7 files changed, 90 insertions(+), 57 deletions(-)
 delete mode 100644 grimoire_engine_types/src/spell.rs
 create mode 100644 grimoire_engine_types/src/spell/arguments.rs
 create mode 100644 grimoire_engine_types/src/spell/factory.rs
 create mode 100644 grimoire_engine_types/src/spell/mod.rs
 create mode 100644 grimoire_engine_types/src/spell/traits.rs

diff --git a/grimoire_engine_types/src/error.rs b/grimoire_engine_types/src/error.rs
index 482b170..dbe8c26 100644
--- a/grimoire_engine_types/src/error.rs
+++ b/grimoire_engine_types/src/error.rs
@@ -1,2 +1,8 @@
 #[derive(Debug, thiserror::Error)]
-pub enum SpellError {}
+pub enum SpellError {
+    #[error("Already defined spell '{0}': {1}")]
+    Redefined(&'static str, &'static str),
+
+    #[error("Undefined spell '{0}'")]
+    Undefined(String),
+}
diff --git a/grimoire_engine_types/src/spell.rs b/grimoire_engine_types/src/spell.rs
deleted file mode 100644
index 8db3e85..0000000
--- a/grimoire_engine_types/src/spell.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-use crate::{error::SpellError, state::State};
-
-pub trait Spell {
-    fn cast(&self, state: State) -> Result<State, SpellError>;
-
-    fn uncast(&self, state: State) -> Result<State, SpellError>;
-}
-
-pub struct SpellArguments {}
-
-pub trait BuildableSpell: Spell + TryFrom<SpellArguments, Error = SpellError> {
-    fn name() -> &'static str;
-
-    fn aliases() -> &'static [&'static str] {
-        &[]
-    }
-
-    fn name_list() -> impl Iterator<Item = &'static str> {
-        [Self::name()]
-            .into_iter()
-            .chain(Self::aliases().iter().copied())
-    }
-
-    fn create(arguments: SpellArguments) -> Result<Box<dyn Spell>, SpellError>;
-}
-
-type SpellBuilderFunction = fn(SpellArguments) -> Result<Box<dyn Spell>, SpellError>;
-
-#[derive(Debug, Default)]
-pub struct SpellFactory {
-    dispatch: hashbrown::HashMap<&'static str, SpellBuilderFunction>,
-}
-
-impl SpellFactory {
-    pub fn register<Spell: BuildableSpell + 'static>(&mut self) -> Result<(), SpellError> {
-        Spell::name_list()
-            .find(|spell_name| self.dispatch.contains_key(spell_name))
-            .map(|_name| -> Result<(), SpellError> { todo!("error") })
-            .unwrap_or(Ok(()))?;
-
-        Spell::name_list().for_each(|name| _ = self.dispatch.insert(name, Spell::create));
-
-        Ok(())
-    }
-
-    pub fn try_build_spell(
-        &self,
-        name: &str,
-        arguments: SpellArguments,
-    ) -> Result<Box<dyn Spell>, SpellError> {
-        let builder_function = self.dispatch.get(name).ok_or_else(|| todo!("error"))?;
-        builder_function(arguments)
-    }
-}
diff --git a/grimoire_engine_types/src/spell/arguments.rs b/grimoire_engine_types/src/spell/arguments.rs
new file mode 100644
index 0000000..6fb7e74
--- /dev/null
+++ b/grimoire_engine_types/src/spell/arguments.rs
@@ -0,0 +1 @@
+pub struct SpellArguments {}
diff --git a/grimoire_engine_types/src/spell/factory.rs b/grimoire_engine_types/src/spell/factory.rs
new file mode 100644
index 0000000..8c9ec57
--- /dev/null
+++ b/grimoire_engine_types/src/spell/factory.rs
@@ -0,0 +1,44 @@
+use crate::{
+    error::SpellError,
+    spell::{BuildableSpell, Spell, SpellArguments},
+};
+use std::collections::HashMap;
+
+/// The builder function, call it with some arguments to try to build the spell
+pub type SpellBuilderFunction = fn(SpellArguments) -> Result<Box<dyn Spell>, SpellError>;
+
+/// The spell factory, you have to register the spell before trying to create them.
+#[derive(Debug, Default)]
+pub struct SpellFactory {
+    /// The dispatch map, to get a builder by an associated spell name.
+    dispatch: HashMap<&'static str, SpellBuilderFunction>,
+}
+
+impl SpellFactory {
+    /// Get the list of all registered spells.
+    pub fn get_registered_spells(&self) -> impl Iterator<Item = &'static str> {
+        self.dispatch.keys().copied()
+    }
+
+    /// Register a spell into the factory. In case of re-registering returns an [Err].
+    pub fn register<Sp: BuildableSpell + 'static>(&mut self) -> Result<(), SpellError> {
+        Sp::name_list().try_for_each(|name| match self.dispatch.entry(name) {
+            std::collections::hash_map::Entry::Occupied(_) => Err(SpellError::Redefined(
+                Sp::name(),
+                "name or alias was already registered",
+            )),
+            std::collections::hash_map::Entry::Vacant(entry) => {
+                _ = entry.insert(Sp::create);
+                Ok(())
+            }
+        })
+    }
+
+    /// Get the builder of a [Spell] by its name. In case of undefined [Spell], returns an [Err].
+    pub fn get_builder(&self, name: impl AsRef<str>) -> Result<SpellBuilderFunction, SpellError> {
+        self.dispatch
+            .get(name.as_ref())
+            .copied()
+            .ok_or(SpellError::Undefined(name.as_ref().to_string()))
+    }
+}
diff --git a/grimoire_engine_types/src/spell/mod.rs b/grimoire_engine_types/src/spell/mod.rs
new file mode 100644
index 0000000..646ce42
--- /dev/null
+++ b/grimoire_engine_types/src/spell/mod.rs
@@ -0,0 +1,7 @@
+mod factory;
+mod arguments;
+mod traits;
+
+pub use factory::*;
+pub use arguments::*;
+pub use traits::*;
diff --git a/grimoire_engine_types/src/spell/traits.rs b/grimoire_engine_types/src/spell/traits.rs
new file mode 100644
index 0000000..8c6c830
--- /dev/null
+++ b/grimoire_engine_types/src/spell/traits.rs
@@ -0,0 +1,30 @@
+use crate::{error::SpellError, spell::SpellArguments, state::State};
+
+pub trait Spell {
+    fn cast(&self, state: State) -> Result<State, SpellError>;
+
+    fn uncast(&self, state: State) -> Result<State, SpellError>;
+}
+
+pub trait BuildableSpell
+where
+    Self: Spell + TryFrom<SpellArguments, Error = SpellError> + 'static,
+{
+    fn name() -> &'static str;
+
+    fn aliases() -> &'static [&'static str] {
+        &[]
+    }
+
+    fn name_list() -> impl Iterator<Item = &'static str> {
+        [Self::name()]
+            .into_iter()
+            .chain(Self::aliases().iter().copied())
+    }
+
+    fn create(arguments: SpellArguments) -> Result<Box<dyn Spell>, SpellError> {
+        Self::try_from(arguments)
+            .map(Box::new)
+            .map(|boxed| boxed as Box<dyn Spell>)
+    }
+}
diff --git a/grimoire_engine_types/src/state.rs b/grimoire_engine_types/src/state.rs
index 815e4be..02d38c7 100644
--- a/grimoire_engine_types/src/state.rs
+++ b/grimoire_engine_types/src/state.rs
@@ -1,2 +1 @@
-pub struct State {
-}
+pub struct State {}
-- 
GitLab