moniof 1.0.1

Actix middleware to monitor over-fetching and detect N+1 / overfetch database patterns (Mongo + SQL-agnostic)
Documentation
//! Pins the shape of MongoDB command documents, which `extract_collection_op`
//! (instrumentation/mongo_events.rs:35) depends on to build its `collection/op` key.
//!
//! Today that function calls `event.command.get_str("collection")`. These tests show
//! why that is wrong for nearly every command, and what the correct lookup is.
//!
//! Run with: `cargo test --features mongodb`
#![cfg(feature = "mongodb")]

use mongodb::bson::{doc, Document};

/// What `extract_collection_op` does today.
fn current_lookup(command: &Document, db: &str) -> String {
    command
        .get_str("collection")
        .ok()
        .map(|s| s.to_string())
        .unwrap_or_else(|| db.to_string())
}

/// What it should do: the collection is the *value* of the command-name key.
/// `getMore` is the exception — its command value is an i64 cursor id, and it
/// carries a separate `collection` field.
fn correct_lookup(command: &Document, command_name: &str, db: &str) -> String {
    command
        .get_str(command_name)
        .ok()
        .or_else(|| command.get_str("collection").ok())
        .map(|s| s.to_string())
        .unwrap_or_else(|| db.to_string())
}

/// Command documents as the driver actually sends them on the wire.
fn wire_commands() -> Vec<(&'static str, Document, &'static str)> {
    vec![
        ("find", doc! { "find": "users", "filter": { "tenant": 1 }, "$db": "shop" }, "users"),
        ("insert", doc! { "insert": "orders", "documents": [ { "x": 1 } ], "$db": "shop" }, "orders"),
        ("update", doc! { "update": "users", "updates": [ { "q": {}, "u": {} } ], "$db": "shop" }, "users"),
        ("delete", doc! { "delete": "carts", "deletes": [ { "q": {}, "limit": 1 } ], "$db": "shop" }, "carts"),
        ("aggregate", doc! { "aggregate": "events", "pipeline": [], "cursor": {}, "$db": "shop" }, "events"),
        ("count", doc! { "count": "users", "query": {}, "$db": "shop" }, "users"),
    ]
}

#[test]
fn no_ordinary_command_carries_a_top_level_collection_field() {
    for (name, cmd, _) in wire_commands() {
        assert!(
            cmd.get_str("collection").is_err(),
            "`{name}` unexpectedly has a `collection` field: {cmd:?}"
        );
    }
}

/// BUG (documented, not asserted as a failure — the helper is private):
/// because the `collection` lookup always fails, moniof falls back to the database
/// name. Every collection in a database merges into one key, so `x-moniof-slowest-key`
/// reports `shop/find`, not the `users/find` the README advertises — and N+1
/// detection can no longer distinguish `users` from `orders`.
#[test]
fn current_lookup_always_degrades_to_the_database_name() {
    for (name, cmd, expected_collection) in wire_commands() {
        let got = current_lookup(&cmd, "shop");
        assert_eq!(got, "shop", "`{name}` should have degraded to the db name");
        assert_ne!(
            got, expected_collection,
            "`{name}` unexpectedly resolved correctly"
        );
    }
}

#[test]
fn correct_lookup_recovers_the_collection_for_every_command() {
    for (name, cmd, expected_collection) in wire_commands() {
        assert_eq!(
            correct_lookup(&cmd, name, "shop"),
            expected_collection,
            "`{name}` must resolve to its collection"
        );
    }
}

/// `getMore` is the one command where a top-level `collection` field really exists,
/// and where the command value is *not* a string. Any fix must keep both paths.
#[test]
fn get_more_carries_a_collection_field_and_a_non_string_command_value() {
    let cmd = doc! { "getMore": 8_675_309_i64, "collection": "users", "batchSize": 100, "$db": "shop" };

    assert!(cmd.get_str("getMore").is_err(), "getMore's value is an i64 cursor id");
    assert_eq!(cmd.get_str("collection").unwrap(), "users");

    assert_eq!(correct_lookup(&cmd, "getMore", "shop"), "users");
    // The one case today's code gets right, by accident.
    assert_eq!(current_lookup(&cmd, "shop"), "users");
}

/// Admin/handshake commands have no collection at all; the db fallback is correct there.
#[test]
fn commandless_admin_ops_fall_back_to_the_database_name() {
    let ping = doc! { "ping": 1, "$db": "admin" };
    assert_eq!(correct_lookup(&ping, "ping", "admin"), "admin");
}