From 8977f15b993088f30529a40ae95af8978a1c7320 Mon Sep 17 00:00:00 2001 From: brnrs Date: Mon, 27 Apr 2020 19:14:38 +0200 Subject: [PATCH 1/3] context struct first impl --- .vscode/c_cpp_properties.json | 16 +++++++++ src/context.rs | 65 +++++++++++++++++++++++++++++++++++ src/lib.rs | 8 +---- 3 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 src/context.rs diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..ee202d8 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "clang-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..3823048 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,65 @@ +use std::any::{Any, TypeId}; +use std::collections::HashMap; + +#[derive(Default)] +pub struct Context { + container: HashMap>, +} + +impl Context { + pub fn get(&self) -> Option<&T> { + self.container + .get(&TypeId::of::()) + .map(|boxed| boxed.downcast_ref().unwrap()) + } + + pub fn get_mut(&mut self) -> Option<&mut T> { + self.container + .get_mut(&TypeId::of::()) + .map(|boxed| boxed.downcast_mut().unwrap()) + } + + pub fn set(&mut self, t: T) { + self.container.insert(TypeId::of::(), Box::from(t)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_set_and_get() { + // Given Context + let mut context = Context::default(); + // And values + context.set(1_u32); + context.set(String::from("text")); + + // When + let num: &u32 = context.get().unwrap(); + let text: &String = context.get().unwrap(); + + // Then + assert_eq!(*num, 1); + assert_eq!(text, "text"); + } + + #[test] + fn test_set_and_get_mut() { + // Given Context + let mut context = Context::default(); + // And values + context.set(1_u32); + + // When + { + let num: &mut u32 = context.get_mut().unwrap(); + *num = 2; + } + let num: &u32 = context.get().unwrap(); + + // Then + assert_eq!(*num, 2); + } +} diff --git a/src/lib.rs b/src/lib.rs index 31e1bb2..9efb2ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} +pub mod context; From f4848966ee32279c3130d55812c3001b0e13903f Mon Sep 17 00:00:00 2001 From: brnrs Date: Wed, 29 Apr 2020 22:15:29 +0200 Subject: [PATCH 2/3] first implementation of basic functionality - components and factories --- .gitignore | 2 + .vscode/c_cpp_properties.json | 16 ---- src/component.rs | 75 ++++++++++++++++ src/error.rs | 64 +++++++++++++ src/factory.rs | 163 ++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + 6 files changed, 307 insertions(+), 16 deletions(-) delete mode 100644 .vscode/c_cpp_properties.json create mode 100644 src/component.rs create mode 100644 src/error.rs create mode 100644 src/factory.rs diff --git a/.gitignore b/.gitignore index a3283af..e572a85 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ Cargo.lock /target #Cargo.lock + +/.vscode \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json deleted file mode 100644 index ee202d8..0000000 --- a/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "configurations": [ - { - "name": "Linux", - "includePath": [ - "${workspaceFolder}/**" - ], - "defines": [], - "compilerPath": "/usr/bin/clang", - "cStandard": "c11", - "cppStandard": "c++17", - "intelliSenseMode": "clang-x64" - } - ], - "version": 4 -} \ No newline at end of file diff --git a/src/component.rs b/src/component.rs new file mode 100644 index 0000000..4d923a5 --- /dev/null +++ b/src/component.rs @@ -0,0 +1,75 @@ +use std::cell::RefCell; +use std::ops::Deref; +use std::rc::Rc; + +use super::context::Context; +use super::error::{AlreadyRegisteredError, Error, NotRegisteredError}; + +pub struct Component(Rc>); + +impl Component { + pub fn from(t: T) -> Self { + Component(Rc::new(RefCell::new(t))) + } +} + +impl Deref for Component { + type Target = RefCell; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Clone for Component { + fn clone(&self) -> Self { + Component(self.0.clone()) + } +} + +pub trait ContextComponentExtension { + fn get_component(&self) -> Option>; + + fn register_component( + &mut self, + t: T, + ) -> Result, (AlreadyRegisteredError, T)>; + + fn replace_component(&self, t: T) -> Result<(Component, T), (Error, T)>; +} + +impl ContextComponentExtension for Context { + fn get_component(&self) -> Option> { + self.get::>().cloned() + } + + fn register_component( + &mut self, + t: T, + ) -> Result, (AlreadyRegisteredError, T)> { + if self.get::().is_none() { + let component = Component::from(t); + self.set(component.clone()); + Ok(component) + } else { + Err((AlreadyRegisteredError::of_type::>(), t)) + } + } + + fn replace_component(&self, mut t: T) -> Result<(Component, T), (Error, T)> { + if let Some(component) = self.get_component::() { + match component.try_borrow_mut() { + Err(borrow_mut_error) => return Err((Error::Borrowed(borrow_mut_error), t)), + Ok(mut ref_mut) => { + std::mem::swap(&mut *ref_mut, &mut t); + Ok((component.clone(), t)) + } + } + } else { + Err(( + Error::NotRegistered(NotRegisteredError::of_type::>()), + t, + )) + } + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..150334d --- /dev/null +++ b/src/error.rs @@ -0,0 +1,64 @@ +use std::any::{type_name, TypeId}; +use std::cell::{BorrowError, BorrowMutError}; +use std::fmt::{Debug, Formatter, Result}; + +#[derive(Debug)] +pub enum Error { + AlreadyRegistered(AlreadyRegisteredError), + NotRegistered(NotRegisteredError), + BorrowedMutably(BorrowError), + Borrowed(BorrowMutError), + General(String), +} + +pub struct AlreadyRegisteredError(TypeId, &'static str); + +impl AlreadyRegisteredError { + pub fn of_type() -> Self { + AlreadyRegisteredError(TypeId::of::(), type_name::()) + } + + pub fn get_msg(&self) -> String { + format!("Element of type {} already registered", self.1) + } + + pub fn get_type_name(&self) -> &'static str { + self.1 + } + + pub fn get_type_id(&self) -> &TypeId { + &self.0 + } +} + +impl Debug for AlreadyRegisteredError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.write_str(&self.get_msg()) + } +} + +pub struct NotRegisteredError(TypeId, &'static str); + +impl NotRegisteredError { + pub fn of_type() -> Self { + NotRegisteredError(TypeId::of::(), type_name::()) + } + + pub fn get_msg(&self) -> String { + format!("Element of type {} not registered", self.1) + } + + pub fn get_type_name(&self) -> &'static str { + self.1 + } + + pub fn get_type_id(&self) -> &TypeId { + &self.0 + } +} + +impl Debug for NotRegisteredError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.write_str(&self.get_msg()) + } +} diff --git a/src/factory.rs b/src/factory.rs new file mode 100644 index 0000000..72f1012 --- /dev/null +++ b/src/factory.rs @@ -0,0 +1,163 @@ +use std::any::type_name; +use std::fmt::{Debug, Formatter, Result as FmtResult}; +use std::ops::Deref; +use std::rc::Rc; + +use super::component::{Component, ContextComponentExtension}; +use super::context::Context; +use super::error::{AlreadyRegisteredError, Error, NotRegisteredError}; + +pub trait FromContext { + fn from_context(&self, context: &mut Context) -> Result; +} + +impl Result> FromContext for FN { + fn from_context(&self, context: &mut Context) -> Result { + self(context) + } +} + +pub struct Factory(Rc>); + +impl Debug for Factory { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "Factory for component of type {}", type_name::()) + } +} + +impl Factory { + pub fn from + 'static>(from_context: FC) -> Self { + Factory(Rc::new(from_context)) + } +} + +impl Clone for Factory { + fn clone(&self) -> Self { + Factory(self.0.clone()) + } +} + +impl Deref for Factory { + type Target = dyn FromContext; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +pub trait ContextFactoryExtension { + fn get_factory(&self) -> Option>; + + fn register_factory( + &mut self, + factory: Factory, + ) -> Result<(), (AlreadyRegisteredError, Factory)>; + + fn replace_factory( + &mut self, + factory: Factory, + ) -> Result, (NotRegisteredError, Factory)>; + + fn build(&mut self) -> Result; + + fn build_and_register_component(&mut self) -> Result, Error>; + + fn get_or_build_component(&mut self) -> Result, Error>; +} + +impl ContextFactoryExtension for Context { + fn get_factory(&self) -> Option> { + self.get().cloned() + } + + fn register_factory( + &mut self, + factory: Factory, + ) -> Result<(), (AlreadyRegisteredError, Factory)> { + if self.get::>().is_none() { + self.set(factory); + Ok(()) + } else { + Err((AlreadyRegisteredError::of_type::>(), factory)) + } + } + + fn replace_factory( + &mut self, + mut factory: Factory, + ) -> Result, (NotRegisteredError, Factory)> { + match self.get_mut::>() { + Some(mut_ref) => { + std::mem::swap(mut_ref, &mut factory); + Ok(factory) + } + None => Err((NotRegisteredError::of_type::>(), factory)), + } + } + + fn build(&mut self) -> Result { + match self.get_factory::() { + Some(factory) => factory.from_context(self), + None => Err(Error::NotRegistered(NotRegisteredError::of_type::< + Factory, + >())), + } + } + + fn build_and_register_component(&mut self) -> Result, Error> { + if self.get_component::().is_some() { + return Err(Error::AlreadyRegistered(AlreadyRegisteredError::of_type::< + Component, + >())); + } + match self.build::() { + Ok(t) => self + .register_component(t) + .map_err(|(error, _)| Error::AlreadyRegistered(error)), + Err(error) => Err(error), + } + } + + fn get_or_build_component(&mut self) -> Result, Error> { + if let Some(component) = self.get_component::() { + Ok(component) + } else { + self.build_and_register_component() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_factory() { + // Given structs + #[derive(Debug)] + struct Config { + initial_count: u32, + }; + #[derive(Debug)] + struct Counter(u32); + // and context + let mut context = Context::default(); + // and factories registered in context + let config_factory = Factory::from(|_: &mut Context| Ok(Config { initial_count: 5 })); + context.register_factory(config_factory).unwrap(); + let counter_factory = Factory::from(|context: &mut Context| { + let config_component = context.get_or_build_component::()?; + let initial_count = config_component.borrow().initial_count; + Ok(Counter(initial_count)) + }); + context.register_factory(counter_factory).unwrap(); + + // When + let counter_component = context.get_or_build_component::().unwrap(); + let config_component = context.get_component::().unwrap(); + + // Then + assert_eq!(counter_component.borrow().0, 5); + assert_eq!(config_component.borrow().initial_count, 5); + } +} diff --git a/src/lib.rs b/src/lib.rs index 9efb2ab..c784ccc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,4 @@ +pub mod component; pub mod context; +pub mod error; +pub mod factory; From 2857a02744765a55830efc6b6f4a606ecb3b6762 Mon Sep 17 00:00:00 2001 From: brnrs Date: Wed, 29 Apr 2020 22:21:42 +0200 Subject: [PATCH 3/3] badcontext v0.1.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 68afe68..94bee1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "badcontext" -version = "0.1.0-SNAPSHOT" +version = "0.1.0" authors = ["brnrs "] edition = "2018" description = "A small crate for creating Context structs that contain application components"