use bson::Document as BsonDocument;
use serde::{Serialize, de::DeserializeOwned};
use super::error::OdmResult;
pub trait Document: Serialize + DeserializeOwned + Send + Sync + 'static {
type Id: Serialize + DeserializeOwned + Send + Sync;
const COLLECTION_NAME: &'static str;
const DATABASE_NAME: &'static str;
fn id(&self) -> Option<&Self::Id>;
fn set_id(&mut self, id: Self::Id);
fn backend_type() -> crate::nosql::types::NoSQLBackendType;
fn references() -> Vec<(&'static str, &'static str)> {
Vec::new()
}
fn indexes() -> Vec<IndexModel> {
Vec::new()
}
fn validation_schema() -> Option<BsonDocument> {
None
}
fn validate(&self) -> OdmResult<()> {
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexOrder {
Ascending,
Descending,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexKey {
pub field: String,
pub order: IndexOrder,
}
#[non_exhaustive]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct IndexOptions {
pub unique: bool,
pub sparse: bool,
pub name: Option<String>,
pub expire_after_seconds: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexModel {
pub keys: Vec<IndexKey>,
pub options: IndexOptions,
}
impl IndexModel {
pub fn builder() -> IndexModelBuilder {
IndexModelBuilder {
keys: Vec::new(),
options: IndexOptions::default(),
}
}
}
#[derive(Debug)]
pub struct IndexModelBuilder {
keys: Vec<IndexKey>,
options: IndexOptions,
}
impl IndexModelBuilder {
pub fn key(mut self, field: impl Into<String>, order: IndexOrder) -> Self {
self.keys.push(IndexKey {
field: field.into(),
order,
});
self
}
pub fn unique(mut self, unique: bool) -> Self {
self.options.unique = unique;
self
}
pub fn sparse(mut self, sparse: bool) -> Self {
self.options.sparse = sparse;
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.options.name = Some(name.into());
self
}
pub fn expire_after_seconds(mut self, seconds: u64) -> Self {
self.options.expire_after_seconds = Some(seconds);
self
}
pub fn build(self) -> IndexModel {
IndexModel {
keys: self.keys,
options: self.options,
}
}
}
#[cfg(feature = "mongodb")]
impl From<IndexModel> for mongodb::IndexModel {
fn from(model: IndexModel) -> Self {
use std::time::Duration;
let mut keys = bson::Document::new();
for key in &model.keys {
let value = match key.order {
IndexOrder::Ascending => bson::Bson::Int32(1),
IndexOrder::Descending => bson::Bson::Int32(-1),
};
keys.insert(key.field.clone(), value);
}
let mut opts = mongodb::options::IndexOptions::default();
if model.options.unique {
opts.unique = Some(true);
}
if model.options.sparse {
opts.sparse = Some(true);
}
if model.options.name.is_some() {
opts.name.clone_from(&model.options.name);
}
if let Some(seconds) = model.options.expire_after_seconds {
opts.expire_after = Some(Duration::from_secs(seconds));
}
mongodb::IndexModel::builder()
.keys(keys)
.options(opts)
.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
fn build_index_model_with_single_key() {
let builder = IndexModel::builder();
let index = builder
.key("email", IndexOrder::Ascending)
.unique(true)
.build();
assert_eq!(index.keys.len(), 1);
assert_eq!(index.keys[0].field, "email");
assert_eq!(index.keys[0].order, IndexOrder::Ascending);
assert!(index.options.unique);
}
#[rstest]
fn build_index_model_with_multiple_keys() {
let index = IndexModel::builder()
.key("user_id", IndexOrder::Ascending)
.key("created_at", IndexOrder::Descending)
.build();
assert_eq!(index.keys.len(), 2);
assert_eq!(index.keys[0].field, "user_id");
assert_eq!(index.keys[0].order, IndexOrder::Ascending);
assert_eq!(index.keys[1].field, "created_at");
assert_eq!(index.keys[1].order, IndexOrder::Descending);
}
#[rstest]
fn build_index_model_with_default_options() {
let index = IndexModel::builder()
.key("name", IndexOrder::Ascending)
.build();
assert!(!index.options.unique);
assert!(!index.options.sparse);
assert_eq!(index.options.name, None);
assert_eq!(index.options.expire_after_seconds, None);
}
#[rstest]
fn build_index_model_with_all_options() {
let index = IndexModel::builder()
.key("session_token", IndexOrder::Ascending)
.unique(true)
.sparse(true)
.name("idx_session_token")
.expire_after_seconds(3600)
.build();
assert!(index.options.unique);
assert!(index.options.sparse);
assert_eq!(index.options.name.as_deref(), Some("idx_session_token"));
assert_eq!(index.options.expire_after_seconds, Some(3600));
}
}