rama-http 0.3.0-rc1

rama http layers, services and other utilities
use crate::layer::har::service::HARExportService;
use crate::layer::har::toggle::Toggle;
use rama_core::Layer;

#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct HARExportLayer<R, T> {
    recorder: R,
    toggle: T,

    preserve_sensitive: bool,
}

impl<R, T> HARExportLayer<R, T> {
    pub fn new(recorder: R, toggle: T) -> Self {
        Self {
            recorder,
            toggle,
            preserve_sensitive: false,
        }
    }

    pub fn recorder(&self) -> &R {
        &self.recorder
    }

    pub fn toggle(&self) -> &T {
        &self.toggle
    }

    rama_utils::macros::generate_set_and_with! {
        /// Sets whether to preserve sensitive headers (false by default).
        pub fn preserve_sensitive(mut self) -> Self {
            self.preserve_sensitive = true;
            self
        }
    }
}

impl<R, S, T> Layer<S> for HARExportLayer<R, T>
where
    R: Clone + Send + Sync + 'static,
    T: Toggle + Clone + Send + Sync + 'static,
{
    type Service = HARExportService<R, S, T>;

    fn layer(&self, service: S) -> Self::Service {
        HARExportService {
            service,
            toggle: self.toggle.clone(),
            recorder: self.recorder.clone(),
            preserve_sensitive: self.preserve_sensitive,
        }
    }

    fn into_layer(self, service: S) -> Self::Service {
        HARExportService {
            service,
            toggle: self.toggle,
            recorder: self.recorder,
            preserve_sensitive: self.preserve_sensitive,
        }
    }
}

#[cfg(test)]
mod tests {
    use rama_core::extensions::Extensions;

    use super::*;
    use crate::layer::har::recorder::Recorder;
    use crate::layer::har::spec::Log;
    use crate::layer::har::toggle::mpsc_unbounded_toggle;
    use parking_lot::Mutex;
    use std::future::ready;
    use std::sync::{Arc, atomic::Ordering};

    // simple alternative implementation

    #[derive(Clone, Default)]
    pub struct InMemoryRecorder {
        logs: Arc<Mutex<Vec<Log>>>,
    }

    impl InMemoryRecorder {
        #[must_use]
        #[inline]
        pub fn new() -> Self {
            Self::default()
        }
    }

    impl Recorder for InMemoryRecorder {
        async fn record(&self, log: Log) -> Option<Extensions> {
            let mut lock = self.logs.lock();
            lock.push(log);
            None
        }

        fn stop_record(&self) -> impl Future<Output = ()> + Send {
            ready(())
        }
    }

    impl<T: Toggle> HARExportLayer<InMemoryRecorder, T> {
        pub fn new_test(toggle: T) -> Self {
            Self::new(InMemoryRecorder::new(), toggle)
        }
    }

    #[tokio::test]
    // Test showing flag on/off working with a manual recorder
    async fn in_memory_recorder_records_logs() {
        let (flag, tx) = mpsc_unbounded_toggle(std::future::pending::<()>());
        let layer = HARExportLayer::new_test(flag.clone());
        // initially the flag is set false
        assert!(!flag.load(Ordering::Relaxed));

        // flip once
        tx.send(()).unwrap();
        tokio::task::yield_now().await;
        assert!(flag.load(Ordering::Relaxed));

        layer.recorder.record(Log::default()).await;

        // flip it manually
        tx.send(()).unwrap();
        tokio::task::yield_now().await;
        assert!(!flag.load(Ordering::Relaxed));

        // Check that the recorder captured something
        let data = layer.recorder.logs.lock();
        assert!(
            !data.is_empty(),
            "Expected recorder to have recorded at least one HAR log"
        );
    }
}