Skip to main content

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}