pipa-lang 1.0.0-alpha.1

A tiny template language
Documentation
// SPDX-FileCopyrightText: Copyright 2026 olav@occy.org
// SPDX-License-Identifier: MPL-2.0

use crate::value::keys::Keys;
use crate::value::span::Span;
use core::fmt::DebugStruct;
use serde::Deserialize;
use serde::Serialize;

#[derive(Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Tracer {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) source: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) span: Option<Span>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) segment: Option<Segment>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) keys: Option<Keys>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default, with = "crate::serde::bytes_as_base64_opt")]
    pub(crate) raw: Option<Vec<u8>>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Segment {
    Builtin,
    Field,
    Raw,
    Route,
    Setter,
    Static,
}

impl From<Segment> for Tracer {
    fn from(value: Segment) -> Self {
        Tracer::default().with_segment(value)
    }
}

impl From<Span> for Tracer {
    fn from(value: Span) -> Self {
        Tracer::default().with_span(value)
    }
}

impl From<Keys> for Tracer {
    fn from(value: Keys) -> Self {
        Tracer::default().with_keys(value)
    }
}

impl core::fmt::Debug for Tracer {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        if self == &Tracer::default() {
            return write!(f, "Tracer::default()");
        }

        let mut f = f.debug_struct("Tracer");
        let u8l = |n| String::from_utf8_lossy(n).into_owned();
        let raw = self.raw.as_ref().map(|r| u8l(r));

        debug_field(&mut f, "source", self.source.as_ref());
        debug_field(&mut f, "span", self.span.as_ref());
        debug_field(&mut f, "segment", self.segment.as_ref());
        debug_field(&mut f, "keys", self.keys.as_ref());
        debug_field(&mut f, "raw", raw.as_ref());

        f.finish()
    }
}

fn debug_field<T: core::fmt::Debug>(f: &mut DebugStruct<'_, '_>, name: &str, t: Option<&T>) {
    t.as_ref().map(|s| f.field(name, s));
}

impl Tracer {
    pub fn with_source<T: Into<String>>(mut self, t: T) -> Self {
        self.trace_source(t);
        self
    }

    pub fn with_span<T: Into<Span>>(mut self, t: T) -> Self {
        self.trace_span(t);
        self
    }

    pub fn with_segment<T: Into<Segment>>(mut self, t: T) -> Self {
        self.trace_segment(t);
        self
    }

    pub fn with_keys<T: Into<Keys>>(mut self, t: T) -> Self {
        self.trace_keys(t);
        self
    }

    pub fn with_raw<T: Into<Vec<u8>>>(mut self, t: T) -> Self {
        self.trace_raw(t);
        self
    }

    pub fn source(&self) -> Option<&str> {
        self.source.as_deref()
    }

    pub fn span(&self) -> Option<Span> {
        self.span
    }

    pub fn segment(&self) -> Option<Segment> {
        self.segment
    }

    pub fn keys(&self) -> Option<&Keys> {
        self.keys.as_ref()
    }

    pub fn raw(&self) -> Option<&[u8]> {
        self.raw.as_deref()
    }

    pub(crate) fn trace_source<T: Into<String>>(&mut self, t: T) {
        if self.source.is_none() {
            self.source = Some(t.into()).filter(|t| !t.is_empty());
        }
    }

    pub(crate) fn trace_span<T: Into<Span>>(&mut self, t: T) {
        if self.span.is_none() {
            self.span = Some(t.into());
        }
    }

    pub(crate) fn trace_segment<T: Into<Segment>>(&mut self, t: T) {
        if self.segment.is_none() {
            self.segment = Some(t.into());
        }
    }

    pub fn trace_segment_if<T: Into<Segment>, U: Into<Segment>>(&mut self, t: T, u: U) {
        if self.segment == Some(t.into()) {
            self.segment = Some(u.into());
        }
    }

    pub(crate) fn trace_keys<T: Into<Keys>>(&mut self, t: T) {
        if self.keys.is_none() {
            self.keys = Some(t.into()).filter(|t| !t.is_empty());
        }
    }

    pub(crate) fn trace_raw<T: Into<Vec<u8>>>(&mut self, t: T) {
        if self.raw.is_none() {
            self.raw = Some(t.into()).filter(|t| !t.is_empty());
        }
    }

    pub(crate) fn trace<T: Into<Tracer>>(&mut self, tracer: T) {
        let tracer = tracer.into();

        tracer.source.map(|t| self.trace_source(t));
        tracer.span.map(|t| self.trace_span(t));
        tracer.segment.map(|t| self.trace_segment(t));
        tracer.keys.map(|t| self.trace_keys(t));
        tracer.raw.map(|t| self.trace_raw(t));
    }

    pub(crate) fn is_empty(&self) -> bool {
        self.source.is_none()
            && self.span.is_none()
            && self.segment.is_none()
            && self.keys.is_none()
            && self.raw.is_none()
    }
}