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}