ailloli_ui_terminal_core/
warning.rs

1//! Non-fatal parser, policy, encoding, and sizing warnings.
2
3use serde::{Deserialize, Serialize};
4
5/// Stable category for a recoverable terminal warning.
6///
7/// # Examples
8///
9/// ```
10/// use ailloli_ui_terminal_core::TerminalWarningKind;
11/// assert_ne!(TerminalWarningKind::InvalidUtf8, TerminalWarningKind::SizeClamped);
12/// ```
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub enum TerminalWarningKind {
15    /// A recognized escape/control sequence was denied by policy.
16    BlockedSequence,
17    /// A sequence or parameter combination is not implemented.
18    UnsupportedSequence,
19    /// Input bytes could not be decoded as valid UTF-8.
20    InvalidUtf8,
21    /// A zero/invalid terminal dimension was clamped.
22    SizeClamped,
23    /// A broader terminal security-policy condition was reported.
24    SecurityPolicy,
25}
26
27/// One recoverable terminal warning with optional source sequence.
28///
29/// `sequence == None` means no source text was retained; `Some("")` is a
30/// present but empty sequence. Strings are stored verbatim.
31///
32/// # Examples
33///
34/// ```
35/// use ailloli_ui_terminal_core::{TerminalWarning, TerminalWarningKind};
36/// let warning = TerminalWarning::new(TerminalWarningKind::InvalidUtf8, None, "invalid input");
37/// assert_eq!(warning.sequence, None);
38/// ```
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct TerminalWarning {
41    /// Machine-readable warning category.
42    pub kind: TerminalWarningKind,
43    /// Optional offending escape/control sequence.
44    pub sequence: Option<String>,
45    /// Human-readable reason; may be empty.
46    pub reason: String,
47}
48
49impl TerminalWarning {
50    /// Creates a warning, preserving optional sequence and reason verbatim.
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// use ailloli_ui_terminal_core::{TerminalWarning, TerminalWarningKind};
56    /// let warning = TerminalWarning::new(TerminalWarningKind::SizeClamped, Some("0x0".into()), "clamped");
57    /// assert_eq!(warning.kind, TerminalWarningKind::SizeClamped);
58    /// ```
59    pub fn new(
60        kind: TerminalWarningKind,
61        sequence: Option<String>,
62        reason: impl Into<String>,
63    ) -> Self {
64        Self {
65            kind,
66            sequence,
67            reason: reason.into(),
68        }
69    }
70
71    /// Creates a [`TerminalWarningKind::BlockedSequence`] warning.
72    ///
73    /// The supplied sequence is always stored as `Some`, even when empty.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use ailloli_ui_terminal_core::{TerminalWarning, TerminalWarningKind};
79    /// let warning = TerminalWarning::blocked_sequence("OSC 52", "clipboard denied");
80    /// assert_eq!(warning.kind, TerminalWarningKind::BlockedSequence);
81    /// ```
82    pub fn blocked_sequence(sequence: impl Into<String>, reason: impl Into<String>) -> Self {
83        Self::new(
84            TerminalWarningKind::BlockedSequence,
85            Some(sequence.into()),
86            reason,
87        )
88    }
89
90    /// Creates an unsupported-sequence warning with a generated reason.
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// use ailloli_ui_terminal_core::TerminalWarning;
96    /// let warning = TerminalWarning::unsupported_sequence("CSI ? 9999 h");
97    /// assert!(warning.reason.contains("CSI ? 9999 h"));
98    /// ```
99    pub fn unsupported_sequence(sequence: impl Into<String>) -> Self {
100        let sequence = sequence.into();
101        Self::new(
102            TerminalWarningKind::UnsupportedSequence,
103            Some(sequence.clone()),
104            format!("unsupported terminal sequence: {sequence}"),
105        )
106    }
107}