rullst-orm 12.1.1

Fast, un-opinionated, database-agnostic ORM and query builder for Rust
Documentation
#![cfg(not(any(feature = "strict-postgres", feature = "strict-mysql")))]

use std::sync::{Arc, Mutex};

use futures::StreamExt;
use rullst_orm::{Error, Orm};
use tracing::{Event, Subscriber, span};
use tracing_subscriber::{Layer, layer::Context, prelude::*};

#[derive(Debug, Clone, rullst_orm::FromRow, rullst_orm::Orm)]
#[orm(table = "telemetry_probe")]
struct TelemetryProbe {
    id: i32,
    label: String,
}

#[derive(Clone, Default)]
struct CaptureLayer {
    spans: Arc<Mutex<Vec<String>>>,
    event_targets: Arc<Mutex<Vec<String>>>,
    fields: Arc<Mutex<Vec<String>>>,
}

struct FieldVisitor<'a> {
    fields: &'a Arc<Mutex<Vec<String>>>,
}

impl tracing::field::Visit for FieldVisitor<'_> {
    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
        if let Ok(mut fields) = self.fields.lock() {
            fields.push(format!("{}={value:?}", field.name()));
        }
    }
}

impl<S> Layer<S> for CaptureLayer
where
    S: Subscriber,
{
    fn on_new_span(&self, attributes: &span::Attributes<'_>, _id: &span::Id, _ctx: Context<'_, S>) {
        if let Ok(mut spans) = self.spans.lock() {
            spans.push(attributes.metadata().name().to_owned());
        }
        attributes.record(&mut FieldVisitor {
            fields: &self.fields,
        });
    }

    fn on_record(&self, _span: &span::Id, values: &span::Record<'_>, _ctx: Context<'_, S>) {
        values.record(&mut FieldVisitor {
            fields: &self.fields,
        });
    }

    fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
        if let Ok(mut targets) = self.event_targets.lock() {
            targets.push(event.metadata().target().to_owned());
        }
    }
}

#[tokio::test(flavor = "current_thread")]
async fn queries_transactions_and_pool_acquires_emit_redacted_telemetry() {
    let capture = CaptureLayer::default();
    let subscriber = tracing_subscriber::registry().with(capture.clone());
    let _subscriber_guard = tracing::subscriber::set_default(subscriber);

    Orm::init_with_options("sqlite::memory:", 1, 10)
        .await
        .expect("isolated telemetry database should initialize");
    Orm::raw("CREATE TABLE telemetry_probe (id INTEGER PRIMARY KEY, label TEXT NOT NULL)")
        .execute()
        .await
        .expect("telemetry table should be created");

    let mut probe = TelemetryProbe {
        id: 0,
        label: "model-secret-value".to_owned(),
    };
    probe
        .save()
        .await
        .expect("instrumented save should succeed");
    let rows = TelemetryProbe::query()
        .get()
        .await
        .expect("instrumented query should succeed");
    assert_eq!(rows.len(), 1);
    {
        let stream_query = TelemetryProbe::query();
        let mut stream = std::pin::pin!(stream_query.stream());
        assert!(stream.next().await.is_some());
    }

    Orm::transaction(|_| {
        Box::pin(async {
            Orm::raw("INSERT INTO telemetry_probe (label) VALUES (?)")
                .bind("redacted-value")
                .execute()
                .await?;
            Ok::<(), Error>(())
        })
    })
    .await
    .expect("instrumented transaction should commit");

    let rollback = Orm::transaction(|_| {
        Box::pin(async { Err::<(), _>("intentional rollback for telemetry") })
    })
    .await;
    assert!(rollback.is_err());

    let spans = capture.spans.lock().expect("span capture lock");
    assert!(spans.iter().any(|name| name == "rullst.orm.query"));
    assert!(spans.iter().any(|name| name == "rullst.orm.transaction"));
    assert!(
        spans
            .iter()
            .any(|name| name == "rullst.orm.transaction.begin")
    );
    drop(spans);

    let targets = capture
        .event_targets
        .lock()
        .expect("event target capture lock");
    assert!(targets.iter().any(|target| target == "sqlx::pool::acquire"));
    drop(targets);

    let fields = capture.fields.lock().expect("field capture lock");
    assert!(
        fields
            .iter()
            .any(|field| field == "orm.model=\"TelemetryProbe\"")
    );
    assert!(
        fields
            .iter()
            .any(|field| field == "orm.table=\"telemetry_probe\"")
    );
    for operation in ["save", "select_many", "stream"] {
        assert!(
            fields
                .iter()
                .any(|field| field == &format!("orm.operation={operation:?}"))
        );
    }
    assert!(
        fields
            .iter()
            .any(|field| field == "orm.outcome=\"committed\"")
    );
    assert!(
        fields
            .iter()
            .any(|field| field == "orm.outcome=\"rolled_back\"")
    );
    assert!(fields.iter().all(|field| !field.contains("redacted-value")));
    assert!(
        fields
            .iter()
            .all(|field| !field.contains("model-secret-value"))
    );
    assert!(fields.iter().all(|field| !field.contains("CREATE TABLE")));
    assert!(fields.iter().all(|field| !field.contains("INSERT INTO")));
}