dyn_path/
lib.rs

1//! # dyn_path
2//!
3//! dyn_path is a set of macros that permit you access objects
4//! that have `.get()` methods that return `Option<T>` in a nested
5//! way.
6//!
7//! It is as specific as it looks, but most libraries that parse
8//! data interchange languages have a "Value" that contains other
9//! "Value"s inside. And casually all the "Value"s have a `.get()`
10//! method, a generic `.get()` method in fact.
11//!
12//! How does this work? Just like JavaScript.
13//! ```rust
14//! use serde_json::json;
15//! use dyn_path::dyn_access;
16//!
17//! let object = json!({
18//!     "very": {
19//!         "nested": {
20//!             "value": [
21//!                 "hello",
22//!                 "world"
23//!             ]
24//!         }
25//!     }
26//! });
27//!
28//! let hello = dyn_access!(object.very.nested.value[0]).unwrap();
29//! let world = dyn_access!(object.very.nested.value[1]).unwrap();
30//!
31//! assert_eq!(hello, "hello");
32//! assert_eq!(world, "world");
33//! ```
34//! This is also useful for nested `HashMap`s but the difference is
35//! that you will actually get a compile time error if you are wrong
36//! with the type.
37//! ```rust
38//! use std::collections::HashMap;
39//! use dyn_path::dyn_access;
40//!
41//! let map: HashMap<String, HashMap<String, HashMap<i32, ()>>> = HashMap::new();
42//!
43//! dyn_access!(map.nested.value[&0]); // since we don't have any real value this will return None.
44//! ```
45//! Check the available macro documentation to learn more about how to use
46//! the specific macros.
47
48#![cfg_attr(not(feature = "std"), no_std)]
49
50#[cfg(all(not(feature = "std"), feature = "alloc"))]
51pub extern crate alloc;
52
53#[cfg(test)]
54mod test;
55
56/// # dyn_access
57/// The `dyn_access` has a specific use-case, which is
58/// accessing very deeply nested values in parsed structures.
59///
60/// For example, imagine you have an API, which you only need some
61/// data for. Usually in JavaScript you simply fetch a value and
62/// access it dinamically with a path like `value?.nested?.nested[0]`
63/// then simply check for undefined.
64///
65/// This macro permits you access to very nested objects with javascript
66/// like indexers, with the exception you don't need to use `?`, as you
67/// get an `Option<T>` instead.
68///
69/// This macro is recursive and will stop working when the value
70/// doesn't have a `.get` method that returns an Option<T>.
71///
72/// To invoke this macro you just use a path like
73/// ```rust
74/// use serde_json::json;
75/// use dyn_path::dyn_access;
76///
77/// let object = json!({
78///     "very": {
79///         "nested": {
80///             "value": [
81///                 "hello",
82///                 "world"
83///             ]
84///         }
85///     }
86/// });
87///
88/// let hello = dyn_access!(object.very.nested.value[0]).unwrap();
89/// let world = dyn_access!(object.very.nested.value[1]).unwrap();
90///
91/// assert_eq!(hello, "hello");
92/// assert_eq!(world, "world");
93/// ```
94/// You also have indices available to you, whether it is
95/// for an array or an object.
96///
97/// Notice how the first element is the name of the variable,
98/// you can have an expression in there with parenthesis like
99/// `(value.parse::<serde_json::Value>()?).very.nested.value`,
100/// the parenthesis are due to parsing system limitation since
101/// this is a `macro_rules` and not a `proc_macro`.
102#[macro_export]
103macro_rules! dyn_access {
104    ($head:ident $($rest:tt)*) => {{
105        $crate::dyn_access!(($head) $($rest)*)
106    }};
107
108    (($head:expr) $($rest:tt)*) => {{
109        let __ = Some(&($head));
110        $crate::dyn_access!(@recurse __, $($rest)*)
111    }};
112
113    (@recurse $acc:expr, . $field:ident $($rest:tt)*) => {{
114        let __ = $acc.and_then(|v| v.get(::core::stringify!($field)));
115        $crate::dyn_access!(@recurse __, $($rest)*)
116    }};
117
118    (@recurse $acc:expr, [$idx:expr] $($rest:tt)*) => {{
119        let __ = $acc.and_then(|v| v.get($idx));
120        $crate::dyn_access!(@recurse __, $($rest)*)
121    }};
122
123    (@recurse $acc:expr,) => {{ $acc }};
124}
125
126/// # dyn_path
127/// The `dyn_path` macro just acts as a Display for the `dyn_access`
128/// macro, meaning that this just generates a precomputed `String`
129/// of the input path.
130///
131/// The syntax is essentially the same, with the only difference this
132/// doesn't have a "head", meaning that you don't need to specify a source
133/// from where to access something, the path is hypotetical.
134///
135/// An example invocation of this macro is
136/// ```rust
137/// use dyn_path::dyn_path;
138///
139/// let display_path = dyn_path!(nested.path.at[1 + 1].with["no"]["head"]);
140///
141/// assert_eq!(display_path, r#"nested.path.at[2].with["no"]["head"]"#);
142/// ```
143/// Notice how the macro pre-computes the indexes and generates the target string.
144#[cfg(any(feature = "alloc", feature = "std"))]
145#[macro_export]
146macro_rules! dyn_path {
147    ($head:ident $($rest:tt)*) => {{
148        use ::core::fmt::Write;
149        $crate::__import_alloc!();
150        let mut __ = ::core::stringify!($head).to_string();
151        $crate::dyn_path!(@recurse __, $($rest)*)
152    }};
153
154    (@recurse $acc:expr, . $field:ident $($rest:tt)*) => {{
155        let _ = ::core::write!($acc, ".{}", ::core::stringify!($field));
156        $crate::dyn_path!(@recurse $acc, $($rest)*)
157    }};
158
159    (@recurse $acc:expr, [$idx:expr] $($rest:tt)*) => {{
160        let _ = ::core::write!($acc, "[{:?}]", ($idx));
161        $crate::dyn_path!(@recurse $acc, $($rest)*)
162    }};
163
164    (@recurse $acc:expr,) => {{ $acc }};
165}
166
167#[doc(hidden)]
168#[macro_export]
169#[cfg(feature = "alloc")]
170macro_rules! __import_alloc { () => { use $crate::alloc::string::ToString; }; }
171
172#[doc(hidden)]
173#[macro_export]
174#[cfg(not(feature = "alloc"))]
175macro_rules! __import_alloc { () => {}; }