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 { () => {}; }