Skip to main content

freeswitch_log_parser/fields/
kind.rs

1//! The span vocabulary: what a located field is, where it lives, and how a
2//! rewrite of it can fail.
3
4use std::fmt;
5use std::ops::Range;
6
7/// What a located span holds.
8///
9/// A kind names the *slot* the value sits in, not the value's shape — a
10/// [`CallerIdName`](FieldKind::CallerIdName) frequently holds a number, and the
11/// consumer decides what that means.
12#[non_exhaustive]
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub enum FieldKind {
15    /// An endpoint channel name (`sofia/<profile>/<user>@<host>`, `loopback/...`).
16    ChannelName,
17    /// Caller-id display name.
18    CallerIdName,
19    /// Caller-id number.
20    CallerIdNumber,
21    /// The number the dialplan is routing to.
22    DestinationNumber,
23    /// A SIP `Call-ID`.
24    CallId,
25    /// A channel UUID appearing anywhere in the text.
26    Uuid,
27    /// A SIP URI. No shape emits one yet — the variant is reserved for URI
28    /// positions FreeSWITCH itself frames; URIs in channel-variable values are
29    /// deliberately not classified here.
30    SipUri,
31    /// An IP address, at positions the classifier frames (a channel name's host,
32    /// an inbound INVITE's source).
33    IpAddr,
34    /// A dump slot's value — a channel variable's or a channel field's — where
35    /// the name names no slot above. The crate locates it and stops there;
36    /// whether the name makes the value sensitive is the consumer's call, from
37    /// the name the classification carries.
38    VariableValue,
39}
40
41impl FieldKind {
42    /// The bare category string.
43    pub fn label(&self) -> &'static str {
44        match self {
45            FieldKind::ChannelName => "channel-name",
46            FieldKind::CallerIdName => "caller-id-name",
47            FieldKind::CallerIdNumber => "caller-id-number",
48            FieldKind::DestinationNumber => "destination-number",
49            FieldKind::CallId => "call-id",
50            FieldKind::Uuid => "uuid",
51            FieldKind::SipUri => "sip-uri",
52            FieldKind::IpAddr => "ip-addr",
53            FieldKind::VariableValue => "variable-value",
54        }
55    }
56}
57
58impl fmt::Display for FieldKind {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        f.pad(self.label())
61    }
62}
63
64/// Which of an entry's texts a range indexes.
65///
66/// An entry has two coordinate systems: [`LogEntry::message`](crate::LogEntry)
67/// is header-stripped, while each attached line is the full physical line,
68/// session-UUID prefix included.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
70pub enum FieldLocation {
71    /// The entry's primary message.
72    Message,
73    /// The i-th attached line, indexed as [`AttachedLines::get`](crate::AttachedLines::get) takes it.
74    Attached(usize),
75}
76
77/// A located byte range and what it holds.
78///
79/// Ranges always index raw line text, never a reassembled [`Block`](crate::Block).
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct Field {
82    pub kind: FieldKind,
83    pub at: FieldLocation,
84    pub range: Range<usize>,
85}
86
87/// Why a rewrite could not be applied.
88///
89/// Every span handed to [`apply_fields`] is validated, replaced or not, so a
90/// malformed one fails the same way regardless of what the callback returns.
91#[non_exhaustive]
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum RenderError {
94    /// The range runs past the end of the text it was applied to.
95    OutOfBounds {
96        at: FieldLocation,
97        range: Range<usize>,
98        len: usize,
99    },
100    /// An endpoint falls inside a multi-byte character.
101    NotOnCharBoundary {
102        at: FieldLocation,
103        range: Range<usize>,
104    },
105    /// Two replaced spans overlap without one containing the other, so there is
106    /// no rewrite that honours both.
107    OverlappingSpans {
108        at: FieldLocation,
109        first: Range<usize>,
110        second: Range<usize>,
111    },
112}
113
114impl fmt::Display for RenderError {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            RenderError::OutOfBounds { at, range, len } => write!(
118                f,
119                "span {}..{} is past the end of {at} ({len} bytes)",
120                range.start, range.end
121            ),
122            RenderError::NotOnCharBoundary { at, range } => write!(
123                f,
124                "span {}..{} splits a character in {at}",
125                range.start, range.end
126            ),
127            RenderError::OverlappingSpans { at, first, second } => write!(
128                f,
129                "replaced spans {}..{} and {}..{} partially overlap in {at}",
130                first.start, first.end, second.start, second.end
131            ),
132        }
133    }
134}
135
136impl std::error::Error for RenderError {}
137
138impl fmt::Display for FieldLocation {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        match self {
141            FieldLocation::Message => f.pad("the message"),
142            FieldLocation::Attached(i) => write!(f, "attached line {i}"),
143        }
144    }
145}
146
147/// An entry's text after a rewrite, one string per render unit.
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct RenderedEntry {
150    pub message: String,
151    pub attached: Vec<String>,
152}
153/// Rank deciding which kind sorts first when two spans start together; the more
154/// specific kind wins, so a contained generic span follows its container.
155pub(super) fn kind_rank(kind: FieldKind) -> u8 {
156    match kind {
157        FieldKind::ChannelName => 0,
158        FieldKind::CallerIdName => 1,
159        FieldKind::CallerIdNumber => 2,
160        FieldKind::DestinationNumber => 3,
161        FieldKind::CallId => 4,
162        FieldKind::SipUri => 5,
163        FieldKind::IpAddr => 6,
164        FieldKind::VariableValue => 7,
165        FieldKind::Uuid => 8,
166    }
167}