Macro mongodb_ext::mongo_db [−][src]
macro_rules! mongo_db {
($(#[$additional_db_attr : meta]) * $db_name : ident
{
$($(#[$additional_coll_attr : meta]) * $coll_name : ident
$(< _id : $id_spec : ident >) ?
{
$($(#[$additional_field_attr : meta]) * $field : ident :
$field_type : ty), * $(,) ?
}), + $(,) ?
}) => { ... };
}
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 has an initializer function:
pub async fn new(connection_str: &str) -> Result<Self, String>
- It has a ping function that sends a ping message to the database:
pub async fn ping(&self) -> mongodb::error::Result<mongodb::bson::document::Document>
- 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 in snake_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 also has its own struct which stores all specified fields.
All collections’ structs implement Serialize
and Deserialize
.
By default a field _id
gets added to each collection automatically:
pub _id: Option<DefaultId>
(DefaultId
).
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
).
Additionally the following constants are specified:
mongo::DB_NAME
is set to the database’s name incamelCase
.mongo::schema::{COLLECTION_NAME}
where{COLLECTION_NAME}
represents each collection’s name in screaming snake case. Set to the collection’s name incamelCase
.
Hygiene
All structs / constants / functions are wrapped in a public module called mongo
.
All structs / constants that refer to a collection are wrapped in an additional public module called schema
.
This is done to maintain more hygiene by exposing less items.
A better hygiene creates less interference of the macro and its surrounding items.
In addition to this measure, all paths referred by the code in the macro are full paths, thus there should be no type interference.
Examples
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
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> {
email_address: 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: 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 omitted
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")
}
);
General Examples
use mongodb_ext::mongo_db;
use serde_json::ser;
mongo_db! {
SomeDatabase {
#[derive(Debug, Clone)]
SomeCollection {
first_name: String,
}
}
}
let mut some_document = mongo::schema::SomeCollection {
_id: None,
first_name: String::from("alice")
};
// When serializing, `_id` is skipped only if `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\"}")
);
assert_eq!("someCollection", mongo::schema::SOME_COLLECTION);
assert_eq!("someDatabase", mongo::DB_NAME);
use mongodb_ext::mongo_db;
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::DB_NAME);
assert_eq!("myFirstCollection", mongo::schema::MY_FIRST_COLLECTION);
assert_eq!("anotherCollection", mongo::schema::ANOTHER_COLLECTION);
// initializer function and general usage
// note that `tokio_test::block_on` is just a test function to run `async` code
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());