mongodb_ext/lib.rs
1//! This crate provides the macro [`mongo_db`] to model a mongoDB database.
2//!
3//! # Features
4//!
5//! Feature flags are documented here.
6//!
7//! ## `default`
8//!
9//! This feature enables the following feature(s):
10//!
11//! - `mongodb-gridfs`
12//!
13//! ## `mongodb-gridfs`
14//!
15//! Enabling this feature creates automatic implementations of the then-available trait `GridFSDb`.
16
17/// To make [`mongo_db`] work reliably a couple of re-exports are needed, these are not relevant for using the macro.
18#[doc(hidden)]
19pub use {async_trait, mongodb, mongodb_ext_derive, paste, serde, typed_builder};
20
21#[doc(hidden)]
22pub mod traits;
23
24#[doc(hidden)]
25pub use crate::mongodb_ext_derive::case;
26
27#[cfg(feature = "mongodb-gridfs")]
28pub use crate::traits::GridFSDb;
29
30pub use crate::traits::{MongoClient, MongoCollection};
31
32/// Defines the default type inside an [`Option`] for the `_id` field.
33///
34/// Re-export from [`mongodb::bson::oid::ObjectId`].
35///
36pub use mongodb::bson::oid::ObjectId as DefaultId;
37
38/// Defines the default value used as schema version in [`MongoCollection::SCHEMA_VERSION`] if not specified otherwise.
39pub const DEFAULT_SCHEMA_VERSION: i32 = 1;
40
41/// This macro parses the per-collection parameters in a more usable format.
42#[macro_export]
43#[doc(hidden)]
44macro_rules! parse_collection_params {
45 (
46 version: $version:literal,
47 _id: $id:ident
48 $($rest:tt)*
49 ) => {
50 $crate::expand_collection_version! {
51 version = $version;
52 id = $id;
53 $($rest)*
54 }
55 };
56 (
57 _id: $id:ident,
58 version: $version:literal
59 $($rest:tt)*
60 ) => {
61 $crate::expand_collection_version! {
62 version = $version;
63 id = $id;
64 $($rest)*
65 }
66 };
67 (
68 version: $version:literal
69 $($rest:tt)*
70 ) => {
71 $crate::expand_collection_version! {
72 version = $version;
73 id = ;
74 $($rest)*
75 }
76 };
77 (
78 _id: $id:ident
79 $($rest:tt)*
80 ) => {
81 $crate::expand_collection_version! {
82 version = ;
83 id = $id;
84 $($rest)*
85 }
86 };
87 (
88 $($rest:tt)*
89 ) => {
90 $crate::expand_collection_version! {
91 version = ;
92 id = ;
93 $($rest)*
94 }
95 };
96}
97
98/// Expands schema version that is given in `<` / `>` behind each collection.
99#[macro_export]
100#[doc(hidden)]
101macro_rules! expand_collection_version {
102 (
103 version = ;
104 $($rest:tt)*
105 ) => {
106 $crate::expand_collection_id!{
107 version = $crate::DEFAULT_SCHEMA_VERSION;
108 $($rest)*
109 }
110 };
111 (
112 version = $version:literal;
113 $($rest:tt)*
114 ) => {
115 $crate::expand_collection_id!{
116 version = $version;
117 $($rest)*
118 }
119 };
120}
121
122/// Expands collection _id that is given in `<` / `>` behind each collection.
123#[macro_export]
124#[doc(hidden)]
125macro_rules! expand_collection_id {
126 (
127 version = $version:expr;
128 id = ;
129 $($rest:tt)*
130 ) => {
131 $crate::expand_collection!{
132 @add_id
133 version = $version;
134 id = $crate::DefaultId;
135 $($rest)*
136 }
137 };
138 (
139 version = $version:expr;
140 id = none;
141 $($rest:tt)*
142 ) => {
143 $crate::expand_collection!{
144 @final
145 version = $version;
146 id = none;
147 $($rest)*
148 }
149 };
150 (
151 version = $version:expr;
152 id = $id:ty;
153 $($rest:tt)*
154 ) => {
155 $crate::expand_collection!{
156 @add_id
157 version = $version;
158 id = $id;
159 $($rest)*
160 }
161 };
162}
163
164/// Expands one collection.
165///
166/// Needed internally, but has no big use on its own.
167/// Thus hidden from documentation.
168#[macro_export]
169#[doc(hidden)]
170macro_rules! expand_collection {
171 // invoked with `_id: none`, thus assume `_id` is added already and finally expand to collection
172 (
173 @final
174 version = $schema_version:expr;
175 id = none;
176 $(#[$additional_coll_attr:meta])*
177 $coll_name:ident {
178 $(
179 $(#[$additional_field_attr:meta])*
180 $field:ident: $field_type:ty
181 ),*$(,)?
182 }
183 $(-{
184 $($inner_tokens2:tt)+
185 })?
186 ) => {
187 $crate::paste::paste! {
188 #[doc = "Represents the [`" $coll_name "`] collection in mongodb."]
189 #[derive($crate::serde::Deserialize, $crate::serde::Serialize, $crate::typed_builder::TypedBuilder)]
190 #[serde(rename_all = "camelCase")]
191 $(#[$additional_coll_attr])*
192 pub struct $coll_name {
193 $(
194 $(#[$additional_field_attr])*
195 pub $field: $field_type
196 ),*
197 }
198
199 impl $crate::MongoCollection for $coll_name {
200 const NAME: &'static str = $crate::case!($coll_name => Camel);
201 const SCHEMA_VERSION: i32 = $schema_version;
202 }
203
204 $(
205 impl $coll_name {
206 $($inner_tokens2)+
207 }
208 )?
209 }
210 };
211 // specific type for `_id` given, add it and invoke again with `_id: none` to avoid adding the `_id` field again
212 (
213 @add_id
214 version = $schema_version:expr;
215 id = $explicit_id_type:ty;
216 $(#[$additional_coll_attr:meta])*
217 $coll_name:ident {
218 $(
219 $(#[$additional_field_attr:meta])*
220 $field:ident: $field_type:ty
221 ),*$(,)?
222 }
223 $(-{
224 $($inner_tokens2:tt)+
225 })?
226 ) => {
227 $crate::expand_collection! {
228 @final
229 version = $schema_version;
230 id = none;
231 $(#[$additional_coll_attr])*
232 $coll_name {
233 #[serde(skip_serializing_if = "std::option::Option::is_none")]
234 #[serde(rename = "_id")]
235 #[builder(default)]
236 _id: std::option::Option<$explicit_id_type>,
237 $(
238 $(#[$additional_field_attr])*
239 $field: $field_type
240 ),*
241 }-{
242 #[doc = "Returns a reference to the `_id` field."]
243 #[allow(dead_code)]
244 pub fn id(&self) -> &Option<$explicit_id_type> {
245 &self._id
246 }
247 $($($inner_tokens2)+)?
248 }
249 }
250 };
251}
252
253/// Expands the main database client.
254///
255/// Needed internally, but has no big use on its own.
256/// Thus hidden from documentation.
257#[macro_export]
258#[doc(hidden)]
259macro_rules! expand_main_client {
260 (
261 $(#[$additional_db_attr:meta])*
262 $db_name:ident {
263 $(
264 $(#[$additional_coll_attr:meta])*
265 $coll_name:ident<_id: none> {
266 $(
267 $(#[$additional_field_attr:meta])*
268 $field:ident: $field_type:ty
269 ),*$(,)?
270 }
271 ),+
272 }
273 $(-{
274 $($impl:tt)+
275 })?
276 ) => {
277 $crate::paste::paste! {
278 #[doc = "Client to interact with the `" $db_name "` database."]
279 $(#[$additional_db_attr])*
280 pub struct $db_name {
281 pub client: $crate::mongodb::Client,
282 pub database: $crate::mongodb::Database,
283 $(
284 #[doc = "Handle to the `" $coll_name "` collection"]
285 pub [<$coll_name:snake:lower _coll>]: $crate::mongodb::Collection<schema::$coll_name>
286 ),+
287 }
288
289 #[$crate::async_trait::async_trait]
290 impl $crate::MongoClient for $db_name {
291 const NAME: &'static str = $crate::case!($db_name => Camel);
292
293 async fn new(connection_str: &str) -> $crate::mongodb::error::Result<Self> {
294 let client = match $crate::mongodb::Client::with_uri_str(connection_str).await {
295 $crate::mongodb::error::Result::Ok(client) => client,
296 $crate::mongodb::error::Result::Err(e) => return $crate::mongodb::error::Result::Err(e),
297 };
298 <Self as $crate::MongoClient>::new_with_client(client).await
299 }
300
301 async fn new_with_client(client: $crate::mongodb::Client) -> $crate::mongodb::error::Result<Self> {
302 let database = client.database(<Self as $crate::MongoClient>::NAME);
303 $(
304 let [<$coll_name:snake:lower _coll>] = database.collection(<schema::$coll_name as $crate::MongoCollection>::NAME);
305 )+
306 $crate::mongodb::error::Result::Ok(Self {
307 client,
308 database,
309 $([<$coll_name:snake:lower _coll>]),+
310 })
311 }
312
313 async fn ping(&self) -> $crate::mongodb::error::Result<$crate::mongodb::bson::document::Document> {
314 self.database.run_command($crate::mongodb::bson::doc!{"ping": 1}, std::option::Option::None).await
315 }
316
317 fn database(&self) -> &$crate::mongodb::Database {
318 &self.database
319 }
320 fn client(&self) -> &$crate::mongodb::Client {
321 &self.client
322 }
323 }
324 $(
325 impl $db_name {
326 $($impl)+
327 }
328 )?
329 }
330 };
331}
332
333/// Model a mongodb database.
334///
335/// This macro creates structs / functions / constants / modules that represent a mongoDB database.
336/// Being a macro (which is expanded at compile time) there is no run time performance penalty when using this macro.
337///
338/// For a detailled syntax demonstration see [Examples](#examples).
339///
340/// # Structure
341///
342/// This macro wraps everything in a module called `mongo`.
343///
344/// The main database handler has the following attributes:
345/// - Its name represents the database's name (eg. a database named `MyDatabase` has a struct `mongo::MyDatabase`).
346/// - It implements the [`MongoClient`] trait.
347/// - It contains handles to all given collections inside the database.
348/// These handles have the format `{collection_name}_coll` where `{collection_name}` represents the collection's name in `snake_case`.
349/// - It also contains a [`client`](mongodb::Client) and a [`database`](mongodb::Database) field for you to use.
350///
351/// All collections are wrapped in an additional public module named `schema`.
352///
353/// Each collection has its own struct which stores all specified fields.
354/// All collection structs implement [`Serialize`](serde::Serialize), [`Deserialize`](serde::Deserialize) and [`MongoCollection`].
355///
356/// By default a field `_id` gets added to each collection automatically:
357/// `pub _id: Option<DefaultId>` (see [`DefaultId`] for more info).
358/// This field needs to exist for you to be able to obtain an `_id` field from the database.
359/// When serializing, `_id` gets skipped if it is [`None`].
360/// All fields except `_id` get renamed to `camelCase` when serializing (converting `_id` to `camelCase` results in `id`).
361///
362/// _Note_: All structs' names in `camelCase` can be accessed via the [`MongoClient`] / [`MongoCollection`] trait.
363///
364/// # Examples
365///
366/// ## General Examples
367///
368/// ```rust
369/// use mongodb_ext::{mongo_db, MongoClient, MongoCollection, DefaultId};
370/// use serde_json::ser;
371///
372/// mongo_db! {
373/// // database name
374/// SomeDatabase {
375/// // additional attributes for the collection
376/// #[derive(Debug, Clone)]
377/// // collection name
378/// SomeCollection {
379/// // collection fields
380/// first_name: String,
381/// }
382/// }
383/// }
384///
385/// let mut some_document = mongo::schema::SomeCollection {
386/// _id: None,
387/// first_name: String::from("alice")
388/// };
389///
390/// // When serializing, `_id` is skipped only if it is `None`.
391/// // Note the key conversion to `camelCase`.
392/// assert_eq!(
393/// ser::to_string(&some_document).unwrap(),
394/// String::from("{\"firstName\":\"alice\"}")
395/// );
396///
397/// // update `_id` field to include in serialization.
398/// let oid = DefaultId::parse_str("0123456789ABCDEF01234567").unwrap();
399/// some_document._id = Some(oid);
400/// assert_eq!(
401/// ser::to_string(&some_document).unwrap(),
402/// String::from("{\"_id\":{\"$oid\":\"0123456789abcdef01234567\"},\"firstName\":\"alice\"}")
403/// );
404///
405/// // constants store the collection / database names in `camelCase` + collection version
406/// assert_eq!("someCollection", mongo::schema::SomeCollection::NAME);
407/// assert_eq!(1, mongo::schema::SomeCollection::SCHEMA_VERSION);
408/// assert_eq!("someDatabase", mongo::SomeDatabase::NAME);
409/// ```
410///
411/// Multiple collections need to be separated by `;`, a trailing `;` is optional:
412///
413/// ```rust
414/// use mongodb_ext::{mongo_db, MongoCollection, MongoClient};
415///
416/// mongo_db! {
417/// #[derive(Debug, Clone)]
418/// MyDatabase {
419/// #[derive(Debug, Clone)]
420/// MyFirstCollection {
421/// first_name: String,
422/// last_name: String,
423/// age: u8,
424/// };
425/// #[derive(Debug)]
426/// AnotherCollection {
427/// some_field: String
428/// };
429/// }
430/// }
431///
432/// // all constants that were defined
433/// assert_eq!("myDatabase", mongo::MyDatabase::NAME);
434/// assert_eq!("myFirstCollection", mongo::schema::MyFirstCollection::NAME);
435/// assert_eq!(1, mongo::schema::MyFirstCollection::SCHEMA_VERSION);
436/// assert_eq!("anotherCollection", mongo::schema::AnotherCollection::NAME);
437/// assert_eq!(1, mongo::schema::AnotherCollection::SCHEMA_VERSION);
438///
439/// // initializer function and general usage
440/// // note that `tokio_test::block_on` is just a test function to run `async` code in doc tests
441///
442/// let mongo = tokio_test::block_on(mongo::MyDatabase::new("mongodb://example.com"))
443/// .expect("Could not create mongoDB client");
444///
445/// let bob = mongo::schema::MyFirstCollection {
446/// _id: None,
447/// first_name: String::from("Bob"),
448/// last_name: String::from("Bob's last name"),
449/// age: 255,
450/// };
451///
452/// // This should fail beause there is no actual mongoDB service running at the specified
453/// // connection.
454/// assert!(tokio_test::block_on(
455/// mongo.my_first_collection_coll.insert_one(bob, None)
456/// ).is_err());
457/// ```
458///
459/// ## Manipulating / Removing `_id`
460///
461/// You can specify any type (that implements [`Serialize`](serde::Serialize) and [`Deserialize`](serde::Deserialize)) to be used inside the `_id` [`Option`] by specifying it in `<` / `>` after the collection name:
462///
463/// ```rust
464/// use mongodb_ext::mongo_db;
465///
466/// mongo_db! {
467/// SomeDatabase {
468/// SomeCollection<_id: u128> {
469/// first_name: String,
470/// }
471/// }
472/// }
473///
474/// // _id is now `u128` instead of `DefaultId`
475/// let some_document = mongo::schema::SomeCollection {
476/// _id: Some(255),
477/// first_name: String::from("Bob")
478/// };
479/// ```
480///
481/// It is also possible to disable the generation of an `_id` field all together by using `<_id: none>`.
482///
483/// ```rust
484/// use mongodb_ext::mongo_db;
485///
486/// mongo_db! {
487/// SomeDatabase {
488/// SomeCollection<_id: none> {
489/// #[serde(skip_serializing_if = "Option::is_none")]
490/// email_address: Option<String>,
491/// first_name: String,
492/// }
493/// }
494/// }
495///
496/// // no `_id` exists, this example assumes that users are addressed via their email address
497/// let some_document = mongo::schema::SomeCollection {
498/// email_address: Some(String::from("bob@example.com")),
499/// first_name: String::from("Bob")
500/// };
501/// ```
502///
503/// These features are unique for each collection:
504///
505/// ```rust
506/// use mongodb_ext::{mongo_db, DefaultId};
507///
508/// mongo_db! {
509/// SomeDatabase {
510/// SomeCollection<_id: u128> {
511/// first_name: String,
512/// };
513/// Another {
514/// some_field: u32,
515/// };
516/// AndYetAnother<_id: none> {
517/// email: String,
518/// name: String,
519/// }
520/// }
521/// }
522///
523/// // `_id` type changed to `u128`
524/// let some_document = mongo::schema::SomeCollection {
525/// _id: Some(255),
526/// first_name: String::from("Bob")
527/// };
528/// // `_id` type default, eg. `DefaultId`
529/// let oid = DefaultId::parse_str("0123456789ABCDEF01234567").unwrap();
530/// let another_document = mongo::schema::Another {
531/// _id: Some(oid),
532/// some_field: 1,
533/// };
534/// // `_id` field disabled
535/// let and_yet_another_document = mongo::schema::AndYetAnother {
536/// name: String::from("Bob"),
537/// email: String::from("bob@example.com")
538/// };
539/// ```
540///
541/// Each collection that does not have a parameter of `id: none` implements a function `id(&self)` that returns a reference to its ID:
542///
543/// ```rust
544/// use mongodb_ext::{mongo_db, DefaultId};
545///
546/// mongo_db! {
547/// SomeDatabase {
548/// SomeCollection<_id: u128> {};
549/// Another {};
550/// }
551/// }
552///
553/// // `id` returns `&Option<u128>`
554/// let some_collection = mongo::schema::SomeCollection {
555/// _id: Some(255),
556/// };
557/// assert_eq!(
558/// *some_collection.id(),
559/// Some(255)
560/// );
561///
562/// // `id` returns `&Option<DefaultId>`
563/// let oid = DefaultId::parse_str("0123456789ABCDEF01234567").unwrap();
564/// let another = mongo::schema::Another {
565/// _id: Some(oid.clone()),
566/// };
567/// assert_eq!(
568/// *another.id(),
569/// Some(oid)
570/// );
571/// ```
572///
573/// ## Versioning of your schema
574///
575/// Your database schema version is managed via [`MongoCollection::SCHEMA_VERSION`].
576///
577/// This can be modified like so:
578///
579/// ```rust
580/// use mongodb_ext::{mongo_db, MongoCollection};
581/// use serde_json::ser;
582///
583/// mongo_db! {
584/// SomeDatabase {
585/// // no schema version defaults to const `DEFAULT_SCHEMA_VERSION`
586/// Items {
587/// name: String,
588/// };
589/// // schema version of 200
590/// Queue<version: 200> {
591/// item: i32,
592/// };
593/// // schema version of 4
594/// SomeCollection<version: 4, _id: none> {
595/// first_name: String,
596/// };
597/// // schema version of 5
598/// FourthCollection<_id: String, version: 5> {};
599/// }
600/// }
601///
602/// // default schema version is 1
603/// assert_eq!(1, mongodb_ext::DEFAULT_SCHEMA_VERSION);
604///
605/// assert_eq!(mongo::schema::Items::SCHEMA_VERSION, 1);
606/// assert_eq!(mongo::schema::Queue::SCHEMA_VERSION, 200);
607/// assert_eq!(mongo::schema::SomeCollection::SCHEMA_VERSION, 4);
608/// assert_eq!(mongo::schema::FourthCollection::SCHEMA_VERSION, 5);
609/// ```
610///
611/// ## Serializing from [`json!`](serde_json::json) and [`doc!`](mongodb::bson::doc) macros
612///
613/// ```rust
614/// use mongodb_ext::mongo_db;
615/// use serde_json::{json, Value};
616/// use mongodb::{bson::{doc, Document}, bson};
617///
618/// mongo_db! {
619/// #[derive(Debug, Clone)]
620/// DatabaseOfItems {
621/// #[derive(Debug, Clone, PartialEq)]
622/// Items {
623/// counter: u16,
624/// name: String
625/// };
626/// }
627/// }
628///
629/// // Note that `_id` is not specified here
630/// let my_item: Value = json! ({
631/// "counter": 0,
632/// "name": "my_special_item"
633/// });
634///
635/// let my_collection_entry: mongo::schema::Items =
636/// serde_json::from_value(my_item)
637/// .expect("Could not convert json Value to collection document");
638///
639/// assert_eq!(
640/// my_collection_entry,
641/// mongo::schema::Items {
642/// _id: None,
643/// counter: 0,
644/// name: String::from("my_special_item")
645/// }
646/// );
647///
648/// // Note that `_id` is not specified here
649/// let my_item: Document = doc! {
650/// "counter": 0,
651/// "name": "my_special_item"
652/// };
653///
654/// let my_collection_entry: mongo::schema::Items = bson::de::from_document(my_item)
655/// .expect("Could not convert mongodb bson Document to collection document");
656///
657/// assert_eq!(
658/// my_collection_entry,
659/// mongo::schema::Items {
660/// _id: None,
661/// counter: 0,
662/// name: String::from("my_special_item")
663/// }
664/// );
665/// ```
666///
667/// ## Adding your own code
668///
669/// Additional code for the `mongo` and `schema` modules can be specified in curly braces (`{` / `}`).
670///
671/// ```rust
672/// use mongodb_ext::mongo_db;
673///
674/// mongo_db! {
675/// // specify code to be in `mongo` here:
676/// {
677/// pub fn this_is_a_function_in_mongo() -> bool { true }
678/// }
679/// SomeDatabase {
680/// // specify code to be in `schema` here:
681/// {
682/// pub fn this_is_a_function_in_schema() -> bool { true }
683/// use std::collections::HashMap;
684/// }
685/// SomeCollection {
686/// dict: HashMap<String, u32>,
687/// }
688/// }
689/// }
690///
691/// assert!(mongo::this_is_a_function_in_mongo());
692/// assert!(mongo::schema::this_is_a_function_in_schema());
693/// ```
694///
695/// ### Code positioning
696///
697/// `Impl`ementations can be easily added by using the preset feature:
698///
699/// ```rust
700/// use mongodb_ext::{mongo_db, DefaultId};
701///
702/// mongo_db! {
703/// // specify globally needed code in `mongo` here:
704/// {
705/// use std::collections::HashMap;
706/// }
707/// SomeDatabase {
708/// // specify globally needed code in `schema` here:
709/// {
710/// use {
711/// std::collections::HashMap,
712/// mongodb::bson::oid::ObjectId
713/// };
714/// }
715///
716/// // specify collection-dependent code in an additional block below the
717/// // collection connected with a `-`:
718/// SomeCollection {
719/// dict: HashMap<String, u32>,
720/// }-{
721/// pub fn some_collection_function() -> bool { true }
722/// };
723/// #[derive(Debug, PartialEq)]
724/// AnotherCollection {}-{
725/// pub fn from_id(id: ObjectId) -> Self { Self { _id: Some(id) } }
726/// }
727/// }-{
728/// // specify implementations on the database handler here:
729/// pub fn give_bool() -> bool { true }
730/// }
731/// }
732///
733/// assert!(mongo::SomeDatabase::give_bool());
734/// assert!(mongo::schema::SomeCollection::some_collection_function());
735///
736/// let oid = DefaultId::parse_str("0123456789ABCDEF01234567").unwrap();
737/// assert_eq!(
738/// mongo::schema::AnotherCollection::from_id(oid.clone()),
739/// mongo::schema::AnotherCollection {
740/// _id: Some(oid),
741/// },
742/// );
743/// ```
744///
745/// ## [`TypedBuilder`](typed_builder::TypedBuilder)
746///
747/// Each schema implements [`TypedBuilder`](typed_builder::TypedBuilder) which lets you create a collection more easily.
748///
749/// If `_id` is not set to `none`, the `_id` field will have a `builder` attribute set to `default`.
750/// This enables you to skip specifying `_id` as [`None`].
751///
752/// ```rust
753/// use mongodb_ext::{mongo_db, MongoClient, MongoCollection};
754///
755/// mongo_db! {
756/// MyDatabase {
757/// #[derive(Debug, PartialEq)]
758/// MyCollection<version: 2, _id: u128> {
759/// name: String,
760/// counter: u32,
761/// schema_version: i32
762/// }
763/// }
764/// }
765///
766/// use mongo::schema::MyCollection;
767///
768/// assert_eq!(
769/// // constructing using the builder
770/// // note that no field `_id` is specified, thus `None` is used
771/// MyCollection::builder()
772/// .name("Alice".to_string())
773/// .counter(1)
774/// .schema_version(MyCollection::SCHEMA_VERSION)
775/// .build(),
776/// // constructing normally
777/// MyCollection {
778/// _id: None,
779/// name: "Alice".to_string(),
780/// counter: 1,
781/// schema_version: MyCollection::SCHEMA_VERSION
782/// }
783/// );
784/// ```
785///
786/// Combining the schema version with the typed builder can be very useful:
787///
788/// ```rust
789/// use mongodb_ext::{mongo_db, MongoClient, MongoCollection};
790///
791/// mongo_db! {
792/// MyDatabase {
793/// {
794/// use mongodb_ext::MongoCollection;
795/// }
796/// #[derive(Debug, PartialEq)]
797/// MyCollection<version: 2, _id: u128> {
798/// name: String,
799/// counter: u32,
800/// #[builder(default = <MyCollection as MongoCollection>::SCHEMA_VERSION)]
801/// schema_version: i32
802/// }
803/// }
804/// }
805///
806/// use mongo::schema::MyCollection;
807///
808/// assert_eq!(
809/// // specifying no version takes version constant by default
810/// MyCollection::builder()
811/// .name("Alice".to_string())
812/// .counter(255)
813/// .build(),
814/// MyCollection {
815/// _id: None,
816/// name: "Alice".to_string(),
817/// counter: 255,
818/// schema_version: 2
819/// }
820/// );
821/// ```
822#[macro_export]
823macro_rules! mongo_db {
824 // only one match, the real magic happens in `expand_collection` and `expand_main_client`
825 (
826 $({
827 $($outer_tokens:tt)+
828 })?
829
830 $(#[$additional_db_attr:meta])*
831 $db_name:ident {
832
833 $({
834 $($inner_tokens:tt)+
835 })?
836
837 $(
838 $(#[$additional_coll_attr:meta])*
839 $coll_name:ident$(<$($collection_param_name:ident: $collection_param_value:tt),+>)? {
840 $(
841 $(#[$additional_field_attr:meta])*
842 $field:ident: $field_type:ty
843 ),*$(,)?
844 }
845 $(-{
846 $($inner_impl:tt)+
847 })?
848 );+$(;)?
849 }
850 $(-{
851 $($outer_impl:tt)+
852 })?
853 ) => {
854 pub mod mongo {
855 $($($outer_tokens)*)?
856
857 pub mod schema {
858 $($($inner_tokens)*)?
859
860 $(
861 $crate::parse_collection_params! {
862 $(
863 $($collection_param_name: $collection_param_value),+
864 )?
865
866 $(#[$additional_coll_attr])*
867
868 $coll_name {
869 $(
870 $(#[$additional_field_attr])*
871 $field: $field_type
872 ),*
873 }
874 $(-{
875 $($inner_impl)+
876 })?
877 }
878 )+
879 }
880
881 $crate::expand_main_client ! {
882 $(#[$additional_db_attr])*
883 $db_name {
884 $(
885 $(#[$additional_coll_attr])*
886 $coll_name<_id: none> {
887 $(
888 $(#[$additional_field_attr])*
889 $field: $field_type
890 ),*
891 }
892 ),+
893 }
894 $(-{
895 $($outer_impl)+
896 })?
897 }
898 }
899 };
900}