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