| Crates.io | mongo-collection-macro |
| lib.rs | mongo-collection-macro |
| version | 0.2.0 |
| created_at | 2025-12-22 01:46:06.468809+00 |
| updated_at | 2025-12-24 12:16:25.173907+00 |
| description | Procedural macros for deriving the Collection trait for MongoDB collections |
| homepage | |
| repository | https://github.com/zhenglongbing/mongo-collection |
| max_upload_size | |
| id | 1998892 |
| size | 25,417 |
Procedural macros for MongoDB collection trait derivation with built-in CRUD operations.
This crate provides two powerful derive macros:
Collection - Automatically implements the Collection trait for MongoDB collection name mappingCollectionRepository - Provides full CRUD operations with pagination supportThe Collection derive macro automatically implements the Collection trait, which identifies MongoDB collection names for your Rust types.
Collection trait with a single attribute#[collection(name = "...")]Send + Sync support for concurrent applicationsuse serde::{Serialize, Deserialize};
use mongo_collection::Collection;
#[derive(Collection, Serialize, Deserialize, Debug, Clone)]
struct User {
id: String,
name: String,
}
// Collection name will automatically be "users" (pluralized)
assert_eq!(User::name(), "users");
use serde::{Serialize, Deserialize};
use mongo_collection::Collection;
#[derive(Collection, Serialize, Deserialize, Debug, Clone)]
#[collection(name = "user_accounts")]
struct User {
id: String,
name: String,
}
// Overrides default naming with custom collection name
assert_eq!(User::name(), "user_accounts");
use serde::{Serialize, Deserialize};
use mongo_collection::Collection;
#[derive(Collection, Serialize, Deserialize, Debug, Clone)]
struct UserProfile {
user_id: String,
bio: String,
avatar_url: Option<String>,
}
// Without specifying name, defaults to "user_profiles" (pluralized snake_case)
assert_eq!(UserProfile::name(), "user_profiles");
When #[collection(name = "...")] is not specified, the macro automatically converts struct names to plural snake_case:
| Struct Name | Default Collection Name | Description |
|---|---|---|
User |
users |
Singular → Plural |
UserProfile |
user_profiles |
CamelCase → snake_case + Plural |
Course |
courses |
Singular → Plural |
Category |
categories |
y → ies |
Book |
books |
Singular → Plural |
CourseEnrollment |
course_enrollments |
CamelCase → snake_case + Plural |
The Collection trait has the following constraints:
Send + Sync + Sized - Required by the trait itself for thread-safe operationsRecommended traits for MongoDB usage:
When working with MongoDB collections, your types should typically implement:
Serialize (from serde) - Required for writing to MongoDBDeserialize (from serde) - Required for reading from MongoDBDebug - Useful for debuggingClone - Often needed for data manipulationNote: The Collection derive macro itself doesn't enforce serde traits, but MongoDB operations require them for serialization/deserialization.
use serde::{Serialize, Deserialize};
use mongo_collection::Collection;
// Example 1: User entity (using default plural naming)
#[derive(Collection, Serialize, Deserialize, Debug, Clone)]
struct User {
#[serde(rename = "_id")]
id: String,
username: String,
email: String,
}
// Example 2: Course entity (using default plural naming)
#[derive(Collection, Serialize, Deserialize, Debug, Clone)]
struct Course {
#[serde(rename = "_id")]
id: String,
title: String,
description: String,
}
// Example 3: Category entity (demonstrating smart pluralization)
#[derive(Collection, Serialize, Deserialize, Debug, Clone)]
struct Category {
#[serde(rename = "_id")]
id: String,
name: String,
}
fn main() {
println!("User collection: {}", User::name()); // Output: users
println!("Course collection: {}", Course::name()); // Output: courses
println!("Category collection: {}", Category::name()); // Output: categories
}
If you attempt to use the Collection macro on a non-struct type, you'll get a compile error:
// ❌ Error: Collection can only be derived for structs
#[derive(Collection)]
enum Status {
Active,
Inactive,
}
The Collection trait provides a convenient collection() method for direct MongoDB integration:
use mongodb::{bson::doc, Database};
use serde::{Serialize, Deserialize};
use mongo_collection::Collection;
#[derive(Collection, Serialize, Deserialize, Debug, Clone)]
struct User {
#[serde(rename = "_id")]
id: String,
username: String,
email: String,
}
async fn example(db: &Database) {
// Get the MongoDB collection directly from the trait
// The collection is automatically named "users" (pluralized)
let users = User::collection(db);
// Insert a document
let new_user = User {
id: "123".to_string(),
username: "john".to_string(),
email: "john@example.com".to_string(),
};
users.insert_one(&new_user).await.unwrap();
// Query documents
let user = users
.find_one(doc! { "username": "john" })
.await
.unwrap();
// Update documents
users
.update_one(
doc! { "_id": "123" },
doc! { "$set": { "email": "newemail@example.com" } },
)
.await
.unwrap();
// The collection name is accessible
println!("Collection name: {}", User::name()); // "users"
}
Add this to your Cargo.toml:
[dependencies]
mongo-collection = "0.1"
serde = { version = "1.0", features = ["derive"] }
mongodb = "2.0"
Types implementing Collection are required to be Send + Sync, making them safe to use across threads. This is particularly important for web servers and concurrent applications:
use std::sync::Arc;
use tokio::task;
use mongo_collection::Collection;
use serde::{Serialize, Deserialize};
#[derive(Collection, Serialize, Deserialize, Debug, Clone)]
struct User {
id: String,
name: String,
}
async fn concurrent_example(db: Arc<mongodb::Database>) {
let handles: Vec<_> = (0..10)
.map(|i| {
let db = Arc::clone(&db);
task::spawn(async move {
let users = User::collection(&db);
// Safe to use across threads
users.find_one(doc! { "id": i.to_string() }).await
})
})
.collect();
for handle in handles {
handle.await.unwrap();
}
}
Use the Collection trait for generic database operations:
use mongodb::{bson::Document, Database};
use mongo_collection::Collection;
async fn count_documents<T: Collection>(db: &Database) -> u64 {
T::collection(db).count_documents(None).await.unwrap()
}
// Usage
let user_count = count_documents::<User>(db).await;
let post_count = count_documents::<Post>(db).await;
Combine with the repository pattern for clean architecture:
pub struct Repository<T: Collection> {
collection: mongodb::Collection<T>,
}
impl<T: Collection + Serialize + DeserializeOwned> Repository<T> {
pub fn new(db: &Database) -> Self {
Self {
collection: T::collection(db),
}
}
pub async fn find_by_id(&self, id: &str) -> Option<T> {
self.collection
.find_one(doc! { "_id": id })
.await
.ok()
.flatten()
}
pub async fn insert(&self, item: &T) -> Result<(), mongodb::error::Error> {
self.collection.insert_one(item).await?;
Ok(())
}
}
// Usage
let user_repo = Repository::<User>::new(db);
let user = user_repo.find_by_id("123").await;
The CollectionRepository derive macro automatically implements the CollectionRepository trait, providing a complete set of CRUD operations for your MongoDB collections.
async-trait for modern async Rustcreate() - Create a single documentcreate_many() - Batch create multiple documentsfind_by_id() - Find document by ObjectId stringfind_one() - Find single document by filterfind_many() - Find multiple documents with optional sorting/paginationfind_all() - Find all documents in collectionfind_paginated() - Paginated query with sorting and metadatacount() - Count documents matching filterexists() - Check if document existsupdate_by_id() - Update document by ObjectId stringupdate_one() - Update single document by filterupdate_many() - Update multiple documents by filterfind_one_and_update() - Find and update, returning the documentdelete_by_id() - Delete document by ObjectId stringdelete_one() - Delete single document by filterdelete_many() - Delete multiple documents by filterfind_one_and_delete() - Find and delete, returning the documentuse mongo_collection::{Collection, CollectionRepository};
use serde::{Deserialize, Serialize};
use mongodb::bson::doc;
#[derive(Collection, CollectionRepository, Serialize, Deserialize, Debug, Clone)]
struct User {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
id: Option<String>,
name: String,
email: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = mongodb::Client::with_uri_str("mongodb://localhost:27017").await?;
let db = client.database("mydb");
// Create
let user = User {
id: None,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
};
let created = User::create(&db, &user).await?;
// Read
let found = User::find_one(&db, doc! { "email": "alice@example.com" }).await?;
// Update
if let Some(user_id) = created.id {
User::update_by_id(&db, &user_id, doc! { "$set": { "name": "Alice Smith" } }).await?;
}
// Delete
User::delete_one(&db, doc! { "email": "alice@example.com" }).await?;
Ok(())
}
use mongo_collection::{Collection, CollectionRepository, PaginatedQuery};
use mongodb::bson::doc;
#[derive(Collection, CollectionRepository, Serialize, Deserialize, Debug, Clone)]
struct Article {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
id: Option<String>,
title: String,
views: u64,
}
async fn list_articles(db: &mongodb::Database) -> Result<(), Box<dyn std::error::Error>> {
// Create pagination query
let query = PaginatedQuery {
page: 1,
page_size: 10,
sort_by: Some("views".to_string()),
sort_order: SortOrder::Desc,
..Default::default()
};
// Execute paginated query
let result = Article::find_paginated(&db, doc! {}, &query).await?;
println!("Page {}/{}", result.page, result.total_pages);
println!("Total: {} articles", result.total_count);
for article in result.items {
println!("- {} ({} views)", article.title, article.views);
}
Ok(())
}
To use CollectionRepository, your struct must:
Collection - Provides collection nameClone - Required for return valuesSerialize + Deserialize - For MongoDB operationsSend + Sync + Unpin - Usually automaticuse mongo_collection::{Collection, CollectionRepository, PaginatedQuery, SortOrder};
use mongodb::bson::doc;
use serde::{Deserialize, Serialize};
#[derive(Collection, CollectionRepository, Serialize, Deserialize, Debug, Clone)]
#[collection(name = "users")]
struct User {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
id: Option<String>,
name: String,
email: String,
age: u32,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = mongodb::Client::with_uri_str("mongodb://localhost:27017").await?;
let db = client.database("myapp");
// 1. Create users
let users = vec![
User { id: None, name: "Alice".into(), email: "alice@example.com".into(), age: 25 },
User { id: None, name: "Bob".into(), email: "bob@example.com".into(), age: 30 },
User { id: None, name: "Charlie".into(), email: "charlie@example.com".into(), age: 35 },
];
User::create_many(&db, users).await?;
// 2. Find all users
let all_users = User::find_all(&db).await?;
println!("Total users: {}", all_users.len());
// 3. Find users over 28
let adults = User::find_many(&db, doc! { "age": { "$gt": 28 } }, None).await?;
println!("Users over 28: {}", adults.len());
// 4. Paginated query with sorting
let query = PaginatedQuery {
page: 1,
page_size: 2,
sort_by: Some("age".to_string()),
sort_order: SortOrder::Desc,
..Default::default()
};
let page = User::find_paginated(&db, doc! {}, &query).await?;
println!("Page {}/{}: {} users", page.page, page.total_pages, page.items.len());
// 5. Count total users
let count = User::count(&db, doc! {}).await?;
println!("Total count: {}", count);
// 6. Check if user exists
let exists = User::exists(&db, doc! { "email": "alice@example.com" }).await?;
println!("Alice exists: {}", exists);
// 7. Update user
User::update_one(&db,
doc! { "email": "alice@example.com" },
doc! { "$set": { "age": 26 } }
).await?;
// 8. Delete users under 30
let deleted = User::delete_many(&db, doc! { "age": { "$lt": 30 } }).await?;
println!("Deleted {} users", deleted);
Ok(())
}
The pagination types support OpenAPI schema generation when the openapi feature is enabled:
[dependencies]
mongo-collection = { version = "0.1", features = ["openapi"] }
With this feature enabled, PaginatedQuery, PaginatedData, ListData, and SortOrder will implement utoipa::ToSchema:
use utoipa::OpenApi;
use mongo_collection::{PaginatedQuery, PaginatedData};
#[derive(OpenApi)]
#[openapi(
components(schemas(PaginatedQuery, PaginatedData<User>))
)]
struct ApiDoc;
MIT