Skip to content

Commit 36127d8

Browse files
authored
Improve macro syntax support (#83)
1 parent 6c08fd1 commit 36127d8

File tree

9 files changed

+414
-142
lines changed

9 files changed

+414
-142
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bson"
3-
version = "0.9.1"
3+
version = "0.10.0"
44
authors = [
55
"Y. T. Chung <[email protected]>",
66
"Kevin Yeh <[email protected]>"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Deserialize the struct:
6363

6464
```rust
6565
// Read the document from a MongoDB collection
66-
let person_document = mongoCollection.find_one(Some(doc! { "_id" => "12345" }), None)?
66+
let person_document = mongoCollection.find_one(Some(doc! { "_id": "12345" }), None)?
6767
.expect("Document not found");
6868

6969
// Deserialize the document into a Person instance
@@ -87,7 +87,7 @@ fn test_compat_u2f() {
8787

8888
let foo = Foo { x: 20 };
8989
let b = bson::to_bson(&foo).unwrap();
90-
assert_eq!(b, Bson::Document(doc! { "x" => (Bson::FloatingPoint(20.0)) }));
90+
assert_eq!(b, Bson::Document(doc! { "x": Bson::FloatingPoint(20.0) }));
9191

9292
let de_foo = bson::from_bson::<Foo>(b).unwrap();
9393
assert_eq!(de_foo, foo);

src/bson.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -397,52 +397,52 @@ impl Bson {
397397
match *self {
398398
Bson::RegExp(ref pat, ref opt) => {
399399
doc! {
400-
"$regex" => (pat.clone()),
401-
"$options" => (opt.clone())
400+
"$regex": pat.clone(),
401+
"$options": opt.clone(),
402402
}
403403
}
404404
Bson::JavaScriptCode(ref code) => {
405405
doc! {
406-
"$code" => (code.clone())
406+
"$code": code.clone(),
407407
}
408408
}
409409
Bson::JavaScriptCodeWithScope(ref code, ref scope) => {
410410
doc! {
411-
"$code" => (code.clone()),
412-
"$scope" => (scope.clone())
411+
"$code": code.clone(),
412+
"$scope": scope.clone(),
413413
}
414414
}
415415
Bson::TimeStamp(v) => {
416416
let time = (v >> 32) as i32;
417417
let inc = (v & 0xFFFFFFFF) as i32;
418418

419419
doc! {
420-
"t" => time,
421-
"i" => inc
420+
"t": time,
421+
"i": inc
422422
}
423423
}
424424
Bson::Binary(t, ref v) => {
425425
let tval: u8 = From::from(t);
426426
doc! {
427-
"$binary" => (v.to_hex()),
428-
"type" => (tval as i64)
427+
"$binary": v.to_hex(),
428+
"type": tval as i64,
429429
}
430430
}
431431
Bson::ObjectId(ref v) => {
432432
doc! {
433-
"$oid" => (v.to_string())
433+
"$oid": v.to_string(),
434434
}
435435
}
436436
Bson::UtcDatetime(ref v) => {
437437
doc! {
438-
"$date" => {
439-
"$numberLong" => ((v.timestamp() * 1000) + v.nanosecond() as i64 / 1000000)
438+
"$date": {
439+
"$numberLong" => (v.timestamp() * 1000) + v.nanosecond() as i64 / 1000000,
440440
}
441441
}
442442
}
443443
Bson::Symbol(ref v) => {
444444
doc! {
445-
"$symbol" => (v.to_owned())
445+
"$symbol": v.to_owned(),
446446
}
447447
}
448448
_ => panic!("Attempted conversion of invalid data type: {}", self),

src/macros.rs

Lines changed: 235 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,250 @@
1-
/// Construct a BSON value
1+
// BSON macro based on the serde_json json! implementation.
2+
3+
/// Construct a bson::BSON value from a literal.
4+
///
5+
/// ```rust
6+
/// # #[macro_use]
7+
/// # extern crate bson;
8+
/// #
9+
/// # fn main() {
10+
/// let value = bson!({
11+
/// "code": 200,
12+
/// "success": true,
13+
/// "payload": {
14+
/// "some": [
15+
/// "pay",
16+
/// "loads",
17+
/// ]
18+
/// }
19+
/// });
20+
/// # }
21+
/// ```
222
#[macro_export]
323
macro_rules! bson {
4-
([]) => {{ $crate::Bson::Array(Vec::new()) }};
24+
//////////////////////////////////////////////////////////////////////////
25+
// TT muncher for parsing the inside of an array [...]. Produces a vec![...]
26+
// of the elements.
27+
//
28+
// Must be invoked as: bson!(@array [] $($tt)*)
29+
//////////////////////////////////////////////////////////////////////////
530

6-
([$($val:tt),*]) => {{
7-
let mut array = Vec::new();
31+
// Finished with trailing comma.
32+
(@array [$($elems:expr,)*]) => {
33+
vec![$($elems,)*]
34+
};
835

9-
$(
10-
array.push(bson!($val));
11-
)*
36+
// Finished without trailing comma.
37+
(@array [$($elems:expr),*]) => {
38+
vec![$($elems),*]
39+
};
1240

13-
$crate::Bson::Array(array)
14-
}};
41+
// Next element is `null`.
42+
(@array [$($elems:expr,)*] null $($rest:tt)*) => {
43+
bson!(@array [$($elems,)* bson!(null)] $($rest)*)
44+
};
1545

16-
([$val:expr]) => {{
17-
$crate::Bson::Array(vec!(::std::convert::From::from($val)))
18-
}};
46+
// Next element is an array.
47+
(@array [$($elems:expr,)*] [$($array:tt)*] $($rest:tt)*) => {
48+
bson!(@array [$($elems,)* bson!([$($array)*])] $($rest)*)
49+
};
1950

20-
({ $($k:expr => $v:tt),* }) => {{
21-
$crate::Bson::Document(doc! {
22-
$(
23-
$k => $v
24-
),*
25-
})
26-
}};
51+
// Next element is a map.
52+
(@array [$($elems:expr,)*] {$($map:tt)*} $($rest:tt)*) => {
53+
bson!(@array [$($elems,)* bson!({$($map)*})] $($rest)*)
54+
};
2755

28-
($val:expr) => {{
29-
::std::convert::From::from($val)
30-
}};
56+
// Next element is an expression followed by comma.
57+
(@array [$($elems:expr,)*] $next:expr, $($rest:tt)*) => {
58+
bson!(@array [$($elems,)* bson!($next),] $($rest)*)
59+
};
60+
61+
// Last element is an expression with no trailing comma.
62+
(@array [$($elems:expr,)*] $last:expr) => {
63+
bson!(@array [$($elems,)* bson!($last)])
64+
};
65+
66+
// Comma after the most recent element.
67+
(@array [$($elems:expr),*] , $($rest:tt)*) => {
68+
bson!(@array [$($elems,)*] $($rest)*)
69+
};
70+
71+
//////////////////////////////////////////////////////////////////////////
72+
// TT muncher for parsing the inside of an object {...}. Each entry is
73+
// inserted into the given map variable.
74+
//
75+
// Must be invoked as: bson!(@object $map () ($($tt)*) ($($tt)*))
76+
//
77+
// We require two copies of the input tokens so that we can match on one
78+
// copy and trigger errors on the other copy.
79+
//////////////////////////////////////////////////////////////////////////
80+
81+
// Finished.
82+
(@object $object:ident () () ()) => {};
83+
84+
// Insert the current entry followed by trailing comma.
85+
(@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
86+
$object.insert_bson(($($key)+).into(), $value);
87+
bson!(@object $object () ($($rest)*) ($($rest)*));
88+
};
89+
90+
// Insert the last entry without trailing comma.
91+
(@object $object:ident [$($key:tt)+] ($value:expr)) => {
92+
$object.insert_bson(($($key)+).into(), $value);
93+
};
94+
95+
// Next value is `null`.
96+
(@object $object:ident ($($key:tt)+) (=> null $($rest:tt)*) $copy:tt) => {
97+
bson!(@object $object [$($key)+] (bson!(null)) $($rest)*);
98+
};
99+
100+
(@object $object:ident ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
101+
bson!(@object $object [$($key)+] (bson!(null)) $($rest)*);
102+
};
103+
104+
// Next value is an array.
105+
(@object $object:ident ($($key:tt)+) (=> [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
106+
bson!(@object $object [$($key)+] (bson!([$($array)*])) $($rest)*);
107+
};
108+
109+
(@object $object:ident ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
110+
bson!(@object $object [$($key)+] (bson!([$($array)*])) $($rest)*);
111+
};
112+
113+
// Next value is a map.
114+
(@object $object:ident ($($key:tt)+) (=> {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
115+
bson!(@object $object [$($key)+] (bson!({$($map)*})) $($rest)*);
116+
};
117+
118+
(@object $object:ident ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
119+
bson!(@object $object [$($key)+] (bson!({$($map)*})) $($rest)*);
120+
};
121+
122+
// Next value is an expression followed by comma.
123+
(@object $object:ident ($($key:tt)+) (=> $value:expr , $($rest:tt)*) $copy:tt) => {
124+
bson!(@object $object [$($key)+] (bson!($value)) , $($rest)*);
125+
};
126+
127+
(@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
128+
bson!(@object $object [$($key)+] (bson!($value)) , $($rest)*);
129+
};
130+
131+
// Last value is an expression with no trailing comma.
132+
(@object $object:ident ($($key:tt)+) (=> $value:expr) $copy:tt) => {
133+
bson!(@object $object [$($key)+] (bson!($value)));
134+
};
135+
136+
(@object $object:ident ($($key:tt)+) (: $value:expr) $copy:tt) => {
137+
bson!(@object $object [$($key)+] (bson!($value)));
138+
};
139+
140+
// Missing value for last entry. Trigger a reasonable error message.
141+
(@object $object:ident ($($key:tt)+) (=>) $copy:tt) => {
142+
// "unexpected end of macro invocation"
143+
bson!();
144+
};
145+
146+
(@object $object:ident ($($key:tt)+) (:) $copy:tt) => {
147+
// "unexpected end of macro invocation"
148+
bson!();
149+
};
150+
151+
// Missing key-value separator and value for last entry.
152+
// Trigger a reasonable error message.
153+
(@object $object:ident ($($key:tt)+) () $copy:tt) => {
154+
// "unexpected end of macro invocation"
155+
bson!();
156+
};
157+
158+
// Misplaced key-value separator. Trigger a reasonable error message.
159+
(@object $object:ident () (=> $($rest:tt)*) ($kv_separator:tt $($copy:tt)*)) => {
160+
// Takes no arguments so "no rules expected the token `=>`".
161+
unimplemented!($kv_separator);
162+
};
163+
164+
(@object $object:ident () (: $($rest:tt)*) ($kv_separator:tt $($copy:tt)*)) => {
165+
// Takes no arguments so "no rules expected the token `:`".
166+
unimplemented!($kv_separator);
167+
};
168+
169+
// Found a comma inside a key. Trigger a reasonable error message.
170+
(@object $object:ident ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
171+
// Takes no arguments so "no rules expected the token `,`".
172+
unimplemented!($comma);
173+
};
174+
175+
// Key is fully parenthesized. This avoids clippy double_parens false
176+
// positives because the parenthesization may be necessary here.
177+
(@object $object:ident () (($key:expr) => $($rest:tt)*) $copy:tt) => {
178+
bson!(@object $object ($key) (=> $($rest)*) (=> $($rest)*));
179+
};
180+
181+
(@object $object:ident () (($key:expr) : $($rest:tt)*) $copy:tt) => {
182+
bson!(@object $object ($key) (: $($rest)*) (: $($rest)*));
183+
};
184+
185+
// Munch a token into the current key.
186+
(@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
187+
bson!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
188+
};
189+
190+
//////////////////////////////////////////////////////////////////////////
191+
// The main implementation.
192+
//
193+
// Must be invoked as: bson!($($bson)+)
194+
//////////////////////////////////////////////////////////////////////////
195+
196+
(null) => {
197+
$crate::Bson::Null
198+
};
199+
200+
([]) => {
201+
$crate::Bson::Array(vec![])
202+
};
203+
204+
([ $($tt:tt)+ ]) => {
205+
$crate::Bson::Array(bson!(@array [] $($tt)+))
206+
};
207+
208+
({}) => {
209+
$crate::Bson::Document(doc!{})
210+
};
211+
212+
({$($tt:tt)+}) => {
213+
$crate::Bson::Document(doc!{$($tt)+});
214+
};
215+
216+
// Any Serialize type: numbers, strings, struct literals, variables etc.
217+
// Must be below every other rule.
218+
($other:expr) => {
219+
::std::convert::From::from($other)
220+
};
31221
}
32222

33-
/// Construct a BSON Document
223+
/// Construct a bson::Document value.
224+
///
225+
/// ```rust
226+
/// # #[macro_use]
227+
/// # extern crate bson;
228+
/// #
229+
/// # fn main() {
230+
/// let value = doc! {
231+
/// "code": 200,
232+
/// "success": true,
233+
/// "payload": {
234+
/// "some": [
235+
/// "pay",
236+
/// "loads",
237+
/// ]
238+
/// }
239+
/// };
240+
/// # }
241+
/// ```
34242
#[macro_export]
35243
macro_rules! doc {
36244
() => {{ $crate::Document::new() }};
37-
38-
( $($key:expr => $val:tt),* ) => {{
39-
let mut document = $crate::Document::new();
40-
41-
$(
42-
document.insert_bson($key.to_owned(), bson!($val));
43-
)*
44-
45-
document
245+
( $($tt:tt)+ ) => {{
246+
let mut object = $crate::Document::new();
247+
bson!(@object object () ($($tt)+) ($($tt)+));
248+
object
46249
}};
47250
}

0 commit comments

Comments
 (0)