Skip to main content

laminar_connectors/mongodb/change_event/
mod.rs

1//! `MongoDB` change-event classification and canonical Extended JSON rendering.
2//!
3//! Change events stay as raw BSON from the driver until Arrow assembly decodes them once.
4//! Operation names are the server's own `operationType` strings, so history records never
5//! use the engine's `_op` changelog vocabulary.
6
7use mongodb::bson::{Bson, RawBsonRef, RawDocument};
8
9use crate::error::ConnectorError;
10
11/// Semantic class of a change-stream `operationType`.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ChangeOperation {
14    /// A document was inserted.
15    Insert,
16    /// A document was modified; the event carries a delta and optionally a post-image.
17    Update,
18    /// A document was replaced in full.
19    Replace,
20    /// A document was deleted; only its key is present.
21    Delete,
22    /// The change stream was invalidated; only `startAfter` can continue it.
23    Invalidate,
24    /// The watched collection was dropped.
25    Drop,
26    /// The watched collection was renamed.
27    Rename,
28    /// The database containing the collection was dropped.
29    DropDatabase,
30    /// Expanded collection metadata event that leaves documents unchanged.
31    Metadata,
32    /// An operation this connector does not recognize.
33    Unknown,
34}
35
36impl ChangeOperation {
37    /// Classify a server `operationType`.
38    #[must_use]
39    pub fn classify(operation: &str) -> Self {
40        match operation {
41            "insert" => Self::Insert,
42            "update" => Self::Update,
43            "replace" => Self::Replace,
44            "delete" => Self::Delete,
45            "invalidate" => Self::Invalidate,
46            "drop" => Self::Drop,
47            "rename" => Self::Rename,
48            "dropDatabase" => Self::DropDatabase,
49            "create"
50            | "createIndexes"
51            | "dropIndexes"
52            | "modify"
53            | "shardCollection"
54            | "refineCollectionShardKey"
55            | "reshardCollection" => Self::Metadata,
56            _ => Self::Unknown,
57        }
58    }
59}
60
61/// Canonical Extended JSON v2 text for one BSON value. Canonical form keeps every BSON type
62/// distinct (`Int32` versus `Int64`, `ObjectId` versus string, `Decimal128`, dates).
63///
64/// # Errors
65/// Returns an error when the raw value is malformed.
66pub fn canonical_extjson(value: RawBsonRef<'_>) -> Result<String, ConnectorError> {
67    let value = Bson::try_from(value.to_raw_bson()).map_err(|error| {
68        ConnectorError::ReadError(format!("malformed MongoDB BSON value: {error}"))
69    })?;
70    Ok(value.into_canonical_extjson().to_string())
71}
72
73/// Canonical Extended JSON v2 text for one raw document.
74///
75/// # Errors
76/// Returns an error when the document is malformed.
77pub fn canonical_document_extjson(document: &RawDocument) -> Result<String, ConnectorError> {
78    canonical_extjson(RawBsonRef::Document(document))
79}
80
81#[cfg(test)]
82mod tests;