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}