fieldx_plus/lib.rs
1#![doc(html_root_url = "https://docs.rs/fieldx_plus")]
2//! This crate is intended for implementing some design patterns, based on [`fieldx`](https://crates.io/crates/fieldx)
3//! crate. At the moment it is only Application/Agent and Parent/Child patterns. Both are basically the same thing
4//! essentially where Application/Parent is a reference counted object and agents/children hold references to it. The
5//! difference is in children accessor method names which are `app` and `parent`, correspondingly.
6//!
7//! Unfortunately, lack of time doesn't allow me to create comprehensive documentation for this crate. Consider the
8//! tests as the starting points. But here is, briefly, an idea of how it looks:
9//!
10//! ```
11//! use fieldx_plus::{fx_plus, agent_build};
12//! use thiserror::Error;
13//!
14//! #[derive(Error, Debug)]
15//! enum AppError {
16//! #[error("{0}")]
17//! AdHoc(String),
18//! #[error("Application object is unexpectedly gone")]
19//! AppIsGone
20//! }
21//!
22//! # #[cfg(feature = "sync")]
23//! #[fx_plus(app, sync)]
24//! struct Application {
25//! #[fieldx(lazy, get)]
26//! service: NetService,
27//! }
28//!
29//! # #[cfg(feature = "sync")]
30//! impl Application {
31//! fn build_service(&self) -> NetService {
32//! agent_build!(
33//! self, NetService {
34//! port: 4242,
35//! name: "app service",
36//! }
37//! ).unwrap()
38//! }
39//!
40//! pub fn config(&self, key: &str) -> u32 {
41//! 42
42//! }
43//!
44//! pub fn run() {
45//! let app = Application::new();
46//! app.service().launch().expect("Something went wrong...");
47//! }
48//! }
49//!
50//! # #[cfg(feature = "sync")]
51//! #[fx_plus(agent(Application, unwrap(or(AppError, AppError::AppIsGone))), sync)]
52//! struct NetService {
53//! port: u16,
54//! #[fieldx(into, get)]
55//! name: String,
56//! }
57//!
58//! # #[cfg(feature = "sync")]
59//! impl NetService {
60//! pub fn launch(&self) -> Result<(), AppError> {
61//! let app = self.app()?;
62//! let cfg = app.config("foo");
63//! println!("Launching '{}' service.", self.name());
64//! Ok(())
65//! }
66//! }
67//!
68//! fn main() {
69//! # #[cfg(feature = "sync")]
70//! Application::run();
71//! }
72//! ```
73//!
74//! Here is a quick breakdown for it:
75//!
76//! `fx_plus` is an extender to `fieldx::fxstruct` attribute. As such, it takes all the arguments, `fxstruct` takes and
77//! adds of couple of its own. But be aware that it overrides some of `fxstruct` arguments:
78//!
79//! - with `app` or `parent` `fxstruct(rc)` is enforced
80//! - with `agent` or `child` it sets `fxstruct(no_new, builder)`; setting additional parameters with the `builder` are
81//! allowed, though: `fx_plus(child, builder(into))`
82//!
83//! With any of the four `serde` gets disabled if `serde` feature is enabled.
84//!
85//! Since application/parent struct is always `rc` there is a chance that its reference counter can drop to 0 while an
86//! agent/child with weak reference attempts to call `.app()` or `.parent()`, which internally attempt to upgrade the
87//! reference. `agent(unwrap)` argument controls how this situation is handled:
88//! arguments:
89//!
90//! - `child(unwrap)` results in simple `.unwrap()` applied the upgrade call
91//! - `unwrap(expect("Error message")` commands to use `.expect("Error message")`; with this and the above variant we
92//! always get just the app/parent object
93//! - `unwrap(or(ErrorType, <expression>))` would produce `app`/`parent` methods that return `ErrorType`; the
94//! particular value returned depends on the `<expression>`. Say, `ErrorType::ParentIsGone` can be used to return a
95//! specific error code
96//! - `unwrap(or_else(ErrorType, <expr>))` can be used to invoke a method on `self`. `<expr>` can either be just method name
97//! or something like `map_to_err("argument", 42)` in which case the `map_to_err` method will get the arguments
98//! specified.
99//!
100//! Helper macros `agent_build`, `agent_builder`, `child_build`, and `child_builder` are wrappers around builder
101//! pattern. I.e. `agent_build!(self.app(), Agent { foo: 42, bar: "baz" })` is actually a shortcut for
102//! `Agent::builder().app(self.app()).foo(42).bar("baz").build()`. `agent_builder` is the same but without the final
103//! `build()` call.
104
105pub mod traits;
106
107#[doc(inline)]
108pub use crate::traits::Agent;
109#[doc(inline)]
110pub use crate::traits::Application;
111#[doc(inline)]
112pub use crate::traits::Child;
113#[doc(inline)]
114pub use crate::traits::Parent;
115#[doc(inline)]
116pub use fieldx_plus_macros::fx_plus;
117
118#[macro_export]
119#[doc(hidden)]
120macro_rules! __fxplus_builder {
121 ($method:ident, $conv_method:ident:
122 $self:expr, $ty:ty $(
123 {
124 $( $field:ident $( : $initializer:expr )? ),* $(,)*
125 }
126 )?
127 ) => {
128 <$ty>::builder()
129 .$method( <$ty>::$conv_method( $self.__fxplus_myself_downgrade() ) )
130 $( $( .$field( $crate::__fxplus_builder!(@field_or_expr $field $( : $initializer )? ) ) )* )?
131 };
132
133 (@field_or_expr $field:ident : $initializer:expr) => {
134 $initializer
135 };
136 (@field_or_expr $field:ident) => {
137 $field
138 };
139}
140
141#[macro_export]
142macro_rules! agent_builder {
143 ( $( $args:tt )+ ) => {
144 ::fieldx_plus::__fxplus_builder!(app, __fxplus_app: $( $args )+ )
145 };
146}
147
148#[macro_export]
149macro_rules! agent_build {
150 ( $( $args:tt )+ ) => {
151 ::fieldx_plus::__fxplus_builder!(app, __fxplus_app: $( $args )+ ).build()
152 };
153}
154
155#[macro_export]
156macro_rules! child_builder {
157 ( $( $args:tt )+ ) => {
158 ::fieldx_plus::__fxplus_builder!(parent, __fxplus_parent: $( $args )+ )
159 };
160}
161
162#[macro_export]
163macro_rules! child_build {
164 ( $( $args:tt )+ ) => {
165 ::fieldx_plus::__fxplus_builder!(parent, __fxplus_parent: $( $args )+ ).build()
166 };
167}