A better Rust ATProto crate
at oauth 9.9 kB view raw
1//! `atproto!` macro. 2/// Construct a atproto `Data<'_>` value from a literal. 3/// 4/// ``` 5/// # use jacquard_common::atproto; 6/// # 7/// let value = atproto!({ 8/// "code": 200, 9/// "success": true, 10/// "payload": { 11/// "features": [ 12/// "serde", 13/// "json" 14/// ] 15/// } 16/// }); 17/// ``` 18/// 19/// Variables or expressions can be interpolated into the ATProto literal. Any type 20/// interpolated into an array element or object value must implement Serde's 21/// `Serialize` trait, while any type interpolated into a object key must 22/// implement `Into<String>`. If the `Serialize` implementation of the 23/// interpolated type decides to fail, or if the interpolated type contains a 24/// map with non-string keys, the `atproto!` macro will panic. 25/// 26/// ``` 27/// # use jacquard_common::atproto; 28/// # 29/// let code = 200; 30/// let features = vec!["serde", "json"]; 31/// 32/// let value = atproto!({ 33/// "code": code, 34/// "success": code == 200, 35/// "payload": { 36/// features[0]: features[1] 37/// } 38/// }); 39/// ``` 40/// 41/// Trailing commas are allowed inside both arrays and objects. 42/// 43/// ``` 44/// # use jacquard_common::atproto; 45/// # 46/// let value = atproto!([ 47/// "notice", 48/// "the", 49/// "trailing", 50/// "comma -->", 51/// ]); 52/// ``` 53#[macro_export(local_inner_macros)] 54macro_rules! atproto { 55 // Hide distracting implementation details from the generated rustdoc. 56 ($($atproto:tt)+) => { 57 atproto_internal!($($atproto)+) 58 }; 59} 60 61#[macro_export(local_inner_macros)] 62#[doc(hidden)] 63macro_rules! atproto_internal { 64 ////////////////////////////////////////////////////////////////////////// 65 // TT muncher for parsing the inside of an array [...]. Produces a vec![...] 66 // of the elements. 67 // 68 // Must be invoked as: atproto_internal!(@array [] $($tt)*) 69 ////////////////////////////////////////////////////////////////////////// 70 71 // Done with trailing comma. 72 (@array [$($elems:expr,)*]) => { 73 atproto_internal_vec![$($elems,)*] 74 }; 75 76 // Done without trailing comma. 77 (@array [$($elems:expr),*]) => { 78 atproto_internal_vec![$($elems),*] 79 }; 80 81 // Next element is `null`. 82 (@array [$($elems:expr,)*] null $($rest:tt)*) => { 83 atproto_internal!(@array [$($elems,)* atproto_internal!(null)] $($rest)*) 84 }; 85 86 // Next element is `true`. 87 (@array [$($elems:expr,)*] true $($rest:tt)*) => { 88 atproto_internal!(@array [$($elems,)* atproto_internal!(true)] $($rest)*) 89 }; 90 91 // Next element is `false`. 92 (@array [$($elems:expr,)*] false $($rest:tt)*) => { 93 atproto_internal!(@array [$($elems,)* atproto_internal!(false)] $($rest)*) 94 }; 95 96 // Next element is an array. 97 (@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => { 98 atproto_internal!(@array [$($elems,)* atproto_internal!([$($array)*])] $($rest)*) 99 }; 100 101 // Next element is a map. 102 (@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => { 103 atproto_internal!(@array [$($elems,)* atproto_internal!({$($map)*})] $($rest)*) 104 }; 105 106 // Next element is an expression followed by comma. 107 (@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => { 108 atproto_internal!(@array [$($elems,)* atproto_internal!($next),] $($rest)*) 109 }; 110 111 // Last element is an expression with no trailing comma. 112 (@array [$($elems:expr,)*] $last:expr) => { 113 atproto_internal!(@array [$($elems,)* atproto_internal!($last)]) 114 }; 115 116 // Comma after the most recent element. 117 (@array [$($elems:expr),*] , $($rest:tt)*) => { 118 atproto_internal!(@array [$($elems,)*] $($rest)*) 119 }; 120 121 // Unexpected token after most recent element. 122 (@array [$($elems:expr),*] $unexpected:tt $($rest:tt)*) => { 123 atproto_unexpected!($unexpected) 124 }; 125 126 ////////////////////////////////////////////////////////////////////////// 127 // TT muncher for parsing the inside of an object {...}. Each entry is 128 // inserted into the given map variable. 129 // 130 // Must be invoked as: atproto_internal!(@object $map () ($($tt)*) ($($tt)*)) 131 // 132 // We require two copies of the input tokens so that we can match on one 133 // copy and trigger errors on the other copy. 134 ////////////////////////////////////////////////////////////////////////// 135 136 // Done. 137 (@object $object:ident () () ()) => {}; 138 139 // Insert the current entry followed by trailing comma. 140 (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => { 141 let _ = $object.insert(($($key)+).into(), $value); 142 atproto_internal!(@object $object () ($($rest)*) ($($rest)*)); 143 }; 144 145 // Current entry followed by unexpected token. 146 (@object $object:ident [$($key:tt)+] ($value:expr) $unexpected:tt $($rest:tt)*) => { 147 atproto_unexpected!($unexpected); 148 }; 149 150 // Insert the last entry without trailing comma. 151 (@object $object:ident [$($key:tt)+] ($value:expr)) => { 152 let _ = $object.insert(($($key)+).into(), $value); 153 }; 154 155 // Next value is `null`. 156 (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => { 157 atproto_internal!(@object $object [$($key)+] (atproto_internal!(null)) $($rest)*); 158 }; 159 160 // Next value is `true`. 161 (@object $object:ident ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => { 162 atproto_internal!(@object $object [$($key)+] (atproto_internal!(true)) $($rest)*); 163 }; 164 165 // Next value is `false`. 166 (@object $object:ident ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => { 167 atproto_internal!(@object $object [$($key)+] (atproto_internal!(false)) $($rest)*); 168 }; 169 170 // Next value is an array. 171 (@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => { 172 atproto_internal!(@object $object [$($key)+] (atproto_internal!([$($array)*])) $($rest)*); 173 }; 174 175 // Next value is a map. 176 (@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => { 177 atproto_internal!(@object $object [$($key)+] (atproto_internal!({$($map)*})) $($rest)*); 178 }; 179 180 // Next value is an expression followed by comma. 181 (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => { 182 atproto_internal!(@object $object [$($key)+] (atproto_internal!($value)) , $($rest)*); 183 }; 184 185 // Last value is an expression with no trailing comma. 186 (@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => { 187 atproto_internal!(@object $object [$($key)+] (atproto_internal!($value))); 188 }; 189 190 // Missing value for last entry. Trigger a reasonable error message. 191 (@object $object:ident ($($key:tt)+) (:) $copy:tt) => { 192 // "unexpected end of macro invocation" 193 atproto_internal!(); 194 }; 195 196 // Missing colon and value for last entry. Trigger a reasonable error 197 // message. 198 (@object $object:ident ($($key:tt)+) () $copy:tt) => { 199 // "unexpected end of macro invocation" 200 atproto_internal!(); 201 }; 202 203 // Misplaced colon. Trigger a reasonable error message. 204 (@object $object:ident () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => { 205 // Takes no arguments so "no rules expected the token `:`". 206 atproto_unexpected!($colon); 207 }; 208 209 // Found a comma inside a key. Trigger a reasonable error message. 210 (@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => { 211 // Takes no arguments so "no rules expected the token `,`". 212 atproto_unexpected!($comma); 213 }; 214 215 // Key is fully parenthesized. This avoids clippy double_parens false 216 // positives because the parenthesization may be necessary here. 217 (@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => { 218 atproto_internal!(@object $object ($key) (: $($rest)*) (: $($rest)*)); 219 }; 220 221 // Munch a token into the current key. 222 (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => { 223 atproto_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*)); 224 }; 225 226 ////////////////////////////////////////////////////////////////////////// 227 // The main implementation. 228 // 229 // Must be invoked as: atproto_internal!($($atproto)+) 230 ////////////////////////////////////////////////////////////////////////// 231 232 (null) => { 233 $crate::types::value::Data::Null 234 }; 235 236 (true) => { 237 $crate::types::value::Data::Boolean(true) 238 }; 239 240 (false) => { 241 $crate::types::value::Data::Boolean(false) 242 }; 243 244 ([]) => { 245 $crate::types::value::Data::Array($crate::types::value::Array(atproto_internal_vec![])) 246 }; 247 248 ([ $($tt:tt)+ ]) => { 249 $crate::types::value::Data::Array($crate::types::value::Array(atproto_internal!(@array [] $($tt)+))) 250 }; 251 252 ({}) => { 253 $crate::types::value::Data::Object($crate::types::value::Object(::std::collections::BTreeMap::new())) 254 }; 255 256 ({ $($tt:tt)+ }) => { 257 $crate::types::value::Data::Object($crate::types::value::Object({ 258 let mut object = ::std::collections::BTreeMap::new(); 259 atproto_internal!(@object object () ($($tt)+) ($($tt)+)); 260 object 261 })) 262 }; 263 264 // Any Serialize type: numbers, strings, struct literals, variables etc. 265 // Must be below every other rule. 266 ($other:expr) => { 267 { 268 $crate::types::value::Data::from($other) 269 } 270 }; 271} 272 273// The atproto_internal macro above cannot invoke vec directly because it uses 274// local_inner_macros. A vec invocation there would resolve to $crate::vec. 275// Instead invoke vec here outside of local_inner_macros. 276#[macro_export] 277#[doc(hidden)] 278macro_rules! atproto_internal_vec { 279 ($($content:tt)*) => { 280 ::std::vec![$($content)*] 281 }; 282} 283 284#[macro_export] 285#[doc(hidden)] 286macro_rules! atproto_unexpected { 287 () => {}; 288}