ordinary-utils 0.10.2

Utils for Ordinary
Documentation
// Copyright (C) 2026 Ordinary Labs, LLC.
//
// SPDX-License-Identifier: AGPL-3.0-only

#[cfg(tracing_unstable)]
use crate::json::JsonValuable;
use async_compression::tokio::write::ZlibDecoder;
use axum::Router;
use axum::http::StatusCode;
use axum::routing::post;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use tokio::io::AsyncWriteExt;
#[cfg(tracing_unstable)]
use valuable::{Mappable, Valuable, Value, Visit};

#[derive(Serialize, Deserialize, Debug)]
pub struct ClientEvent {
    /// timestamp
    pub ts: String,
    /// timezone
    pub tz: String,
    /// log level
    #[serde(skip_serializing)]
    pub lvl: String,
    /// version
    pub v: String,
    /// correlation id
    pub cid: String,
    /// memory id
    pub mid: String,
    /// path
    pub p: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    /// query
    pub q: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    /// fragment
    pub f: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    /// flags
    pub flg: Option<BTreeMap<String, String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    /// fields
    pub fld: Option<BTreeMap<String, serde_json::Value>>,
    #[serde(skip_serializing)]
    /// message
    pub msg: String,
}

#[cfg(tracing_unstable)]
impl Mappable for ClientEvent {
    fn size_hint(&self) -> (usize, Option<usize>) {
        (6, Some(9))
    }
}

#[cfg(tracing_unstable)]
impl Valuable for ClientEvent {
    fn as_value(&self) -> Value<'_> {
        Value::Mappable(self)
    }

    fn visit(&self, visit: &mut dyn Visit) {
        visit.visit_entry("ts".as_value(), self.ts.as_value());
        visit.visit_entry("tz".as_value(), self.tz.as_value());

        visit.visit_entry("v".as_value(), self.v.as_value());
        visit.visit_entry("mid".as_value(), self.mid.as_value());
        visit.visit_entry("cid".as_value(), self.cid.as_value());

        visit.visit_entry("p".as_value(), self.p.as_value());
        if let Some(q) = &self.q {
            visit.visit_entry("q".as_value(), q.as_value());
        }
        if let Some(f) = &self.f {
            visit.visit_entry("f".as_value(), f.as_value());
        }
        if let Some(flg) = &self.flg {
            visit.visit_entry("flg".as_value(), flg.as_value());
        }
        if let Some(fld) = &self.fld {
            for (fld_nm, fld_v) in fld {
                if !fld_v.is_null() {
                    visit.visit_entry(fld_nm.as_value(), JsonValuable(fld_v.to_owned()).as_value());
                }
            }
        }
    }
}

#[must_use]
pub fn setup_routes<S>(client_events: Option<&bool>) -> Option<Router<S>>
where
    S: Clone + Send + Sync + 'static,
{
    if client_events == Some(&true) {
        let router = Router::new().route(
            "/.ordinary/v1/events",
            post(|body: Bytes| async move {
                let mut decoder = ZlibDecoder::new(Vec::new());
                if let Err(err) = decoder.write_all(body.as_ref()).await {
                    tracing::error!(%err);
                    return StatusCode::INTERNAL_SERVER_ERROR;
                }
                if let Err(err) = decoder.shutdown().await {
                    tracing::error!(%err);
                    return StatusCode::INTERNAL_SERVER_ERROR;
                }

                let mut events_slice = decoder.into_inner();

                let events: Vec<ClientEvent> = match simd_json::from_slice(&mut events_slice) {
                    Ok(parsed) => parsed,
                    Err(err) => {
                        tracing::error!(%err);
                        return StatusCode::INTERNAL_SERVER_ERROR;
                    }
                };

                let client_span = tracing::info_span!("client");

                client_span.in_scope(|| {
                    for event in events {
                        #[cfg(tracing_unstable)]
                        {
                            let level = event.lvl.as_str();
                            let message = event.msg.as_str();

                            match level {
                                "TRACE" => tracing::trace!(
                                    _f = tracing::field::valuable(&event),
                                    "{message}"
                                ),
                                "DEBUG" => tracing::debug!(
                                    _f = tracing::field::valuable(&event),
                                    "{message}"
                                ),
                                "INFO" => {
                                    tracing::info!(
                                        _f = tracing::field::valuable(&event),
                                        "{message}"
                                    );
                                }
                                "WARN" => {
                                    tracing::warn!(
                                        _f = tracing::field::valuable(&event),
                                        "{message}"
                                    );
                                }
                                _ => tracing::error!(
                                    _f = tracing::field::valuable(&event),
                                    "{message}"
                                ),
                            }
                        }

                        #[cfg(not(tracing_unstable))]
                        {
                            match event.lvl.as_ref() {
                                "TRACE" => tracing::trace!(evt = debug(&event)),
                                "DEBUG" => tracing::debug!(evt = debug(&event)),
                                "INFO" => tracing::info!(evt = debug(&event)),
                                "WARN" => tracing::warn!(evt = debug(&event)),
                                _ => tracing::error!(evt = debug(&event)),
                            }
                        }
                    }
                });

                StatusCode::OK
            }),
        );
        return Some(router);
    }

    None
}