Macro mongodb_ext::mongo_db
source · [−]macro_rules! mongo_db {
(
$({
$($outer_tokens:tt)+
})?
$(#[$additional_db_attr:meta])*
$db_name:ident {
$({
$($inner_tokens:tt)+
})?
$(
$(#[$additional_coll_attr:meta])*
$coll_name:ident$(<_id: $id_spec:ident>)? {
$(
$(#[$additional_field_attr:meta])*
$field:ident: $field_type:ty
),*$(,)?
}
$(-{
$($inner_impl:tt)+
})?
);+$(;)?
}
$(-{
$($outer_impl:tt)+
})?
) => { ... };
}
Expand description
Model a mongodb database.
This macro creates structs / functions / constants / modules that represent a mongoDB database. Being a macro (which is expanded at compile time) there is no run time performance penalty when using this macro.
Structure
This macro wraps everything in a module called mongo
.
The main database handler has the following attributes:
- Its name represents the database’s name (eg. a database named
MyDatabase
has a structmongo::MyDatabase
). - It implements the
MongoClient
trait. - It contains handles to all given collections inside the database.
These handles have the format
{collection_name}_coll
where{collection_name}
represents the collection’s name insnake_case
. - It also contains a
client
and adatabase
field for you to use.
All collections are wrapped in an additional public module named schema
.
Each collection has its own struct which stores all specified fields.
All collection structs implement Serialize
, Deserialize
and MongoCollection
.
By default a field _id
gets added to each collection automatically:
pub _id: Option<DefaultId>
(see DefaultId
for more info).
This field needs to exist for you to be able to obtain an _id
field from the database.
When serializing, _id
gets skipped if it is None
.
All fields except _id
get renamed to camelCase
when serializing (converting _id
to camelCase
results in id
).
Note: All structs’ names in camelCase
can be accessed via the MongoClient
/ MongoCollection
trait.
Examples
General Examples
use mongodb_ext::{mongo_db, MongoClient, MongoCollection};
use serde_json::ser;
mongo_db! {
// database name
SomeDatabase {
// additional attributes for the collection
#[derive(Debug, Clone)]
// collection name
SomeCollection {
// collection fields
first_name: String,
}
}
}
let mut some_document = mongo::schema::SomeCollection {
_id: None,
first_name: String::from("alice")
};
// When serializing, `_id` is skipped only if it is `None`.
// Note the key conversion to `camelCase`.
assert_eq!(
ser::to_string(&some_document).unwrap(),
String::from("{\"firstName\":\"alice\"}")
);
// update `_id` field to include in serialization.
some_document._id = Some(String::from("my-custom-ID"));
assert_eq!(
ser::to_string(&some_document).unwrap(),
String::from("{\"_id\":\"my-custom-ID\",\"firstName\":\"alice\"}")
);
// constants store the collection / database names in `camelCase`
assert_eq!("someCollection", mongo::schema::SomeCollection::NAME);
assert_eq!("someDatabase", mongo::SomeDatabase::NAME);
Multiple collections need to be separated by ;
, a trailing ;
is optional:
use mongodb_ext::{mongo_db, MongoCollection, MongoClient};
mongo_db! {
#[derive(Debug, Clone)]
MyDatabase {
#[derive(Debug, Clone)]
MyFirstCollection {
first_name: String,
last_name: String,
age: u8,
};
#[derive(Debug)]
AnotherCollection {
some_field: String
};
}
}
// all constants that were defined
assert_eq!("myDatabase", mongo::MyDatabase::NAME);
assert_eq!("myFirstCollection", mongo::schema::MyFirstCollection::NAME);
assert_eq!("anotherCollection", mongo::schema::AnotherCollection::NAME);
// initializer function and general usage
// note that `tokio_test::block_on` is just a test function to run `async` code in doc tests
let mongo = tokio_test::block_on(mongo::MyDatabase::new("mongodb://example.com"))
.expect("Could not create mongoDB client");
let bob = mongo::schema::MyFirstCollection {
_id: None,
first_name: String::from("Bob"),
last_name: String::from("Bob's last name"),
age: 255,
};
// This should fail beause there is no actual mongoDB service running at the specified connection.
assert!(tokio_test::block_on(
mongo.my_first_collection_coll.insert_one(bob, None)
).is_err());
Manipulating / Removing _id
You can specify any type (that implements Serialize
and Deserialize
) to be used inside the _id
Option
by specifying it in <
/ >
after the collection name:
use mongodb_ext::mongo_db;
mongo_db! {
SomeDatabase {
SomeCollection<_id: u128> {
first_name: String,
}
}
}
// _id is now u128 instead of `DefaultId`
let some_document = mongo::schema::SomeCollection {
_id: Some(255),
first_name: String::from("Bob")
};
It is also possible to disable the generation of an _id
field all together by using <_id: none>
.
use mongodb_ext::mongo_db;
mongo_db! {
SomeDatabase {
SomeCollection<_id: none> {
#[serde(skip_serializing_if = "Option::is_none")]
email_address: Option<String>,
first_name: String,
}
}
}
// no `_id` exists, this example assumes that users are addressed via their email address
let some_document = mongo::schema::SomeCollection {
email_address: Some(String::from("bob@example.com")),
first_name: String::from("Bob")
};
These features are unique for each collection:
use mongodb_ext::mongo_db;
mongo_db! {
SomeDatabase {
SomeCollection<_id: u128> {
first_name: String,
};
Another {
some_field: u32,
};
AndYetAnother<_id: none> {
email: String,
name: String,
}
}
}
// `_id` type changed to `u128`
let some_document = mongo::schema::SomeCollection {
_id: Some(255),
first_name: String::from("Bob")
};
// `_id` type default, eg. `DefaultId`
let another_document = mongo::schema::Another {
_id: Some(String::from("my_id")),
some_field: 1,
};
// `_id` field disabled
let and_yet_another_document = mongo::schema::AndYetAnother {
name: String::from("Bob"),
email: String::from("bob@example.com")
};
Serializing from json!
and doc!
use mongodb_ext::mongo_db;
use serde_json::{json, Value};
use mongodb::{bson::{doc, Document}, bson};
mongo_db! {
#[derive(Debug, Clone)]
DatabaseOfItems {
#[derive(Debug, Clone, PartialEq)]
Items {
counter: u16,
name: String
};
}
}
// Note that `_id` is not specified here
let my_item: Value = json! ({
"counter": 0,
"name": "my_special_item"
});
let my_collection_entry: mongo::schema::Items =
serde_json::from_value(my_item)
.expect("Could not convert json Value to collection document");
assert_eq!(
my_collection_entry,
mongo::schema::Items {
_id: None,
counter: 0,
name: String::from("my_special_item")
}
);
// Note that `_id` is not specified here
let my_item: Document = doc! {
"counter": 0,
"name": "my_special_item"
};
let my_collection_entry: mongo::schema::Items = bson::de::from_document(my_item)
.expect("Could not convert mongodb bson Document to collection document");
assert_eq!(
my_collection_entry,
mongo::schema::Items {
_id: None,
counter: 0,
name: String::from("my_special_item")
}
);
Adding your own code
Additional code for the mongo
and schema
modules can be specified in curly braces ({
/ }
).
use mongodb_ext::mongo_db;
mongo_db! {
// specify code to be in `mongo` here:
{
pub fn this_is_a_function_in_mongo() -> bool { true }
}
SomeDatabase {
// specify code to be in `schema` here:
{
pub fn this_is_a_function_in_schema() -> bool { true }
use std::collections::HashMap;
}
SomeCollection {
dict: HashMap<String, u32>,
}
}
}
assert!(mongo::this_is_a_function_in_mongo());
assert!(mongo::schema::this_is_a_function_in_schema());
Code positioning
Impl
ementations can be easily added by using the preset feature:
use mongodb_ext::mongo_db;
mongo_db! {
// specify globally needed code in `mongo` here:
{
use std::collections::HashMap;
}
SomeDatabase {
// specify globally needed code in `schema` here:
{
use std::collections::HashMap;
}
// specify collection-dependent code in an additional block below the collection connected with a `-`:
SomeCollection {
dict: HashMap<String, u32>,
}-{
pub fn some_collection_function() -> bool { true }
};
AnotherCollection {}-{
pub fn id(&self) -> &Option<String> { &self._id }
}
}-{
/// specify `mongo` implementations here:
pub fn give_bool() -> bool { true }
}
}
assert!(mongo::SomeDatabase::give_bool());
assert!(mongo::schema::SomeCollection::some_collection_function());
let another_collection = mongo::schema::AnotherCollection {
_id: Some(String::from("id")),
};
assert_eq!(*another_collection.id(), Some(String::from("id")));