coil_runtime/browser/
flash.rs1use super::support::validate_browser_value;
2use super::*;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum FlashLevel {
6 Info,
7 Success,
8 Warning,
9 Error,
10}
11
12impl FlashLevel {
13 fn as_str(self) -> &'static str {
14 match self {
15 Self::Info => "info",
16 Self::Success => "success",
17 Self::Warning => "warning",
18 Self::Error => "error",
19 }
20 }
21
22 fn parse(value: &str) -> Result<Self, RuntimeBrowserError> {
23 match value {
24 "info" => Ok(Self::Info),
25 "success" => Ok(Self::Success),
26 "warning" => Ok(Self::Warning),
27 "error" => Ok(Self::Error),
28 other => Err(RuntimeBrowserError::InvalidFlashLevel {
29 level: other.to_string(),
30 }),
31 }
32 }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct FlashMessage {
37 pub level: FlashLevel,
38 pub text: String,
39}
40
41impl FlashMessage {
42 pub fn new(level: FlashLevel, text: impl Into<String>) -> Result<Self, RuntimeBrowserError> {
43 let text = validate_browser_value("flash_message", text.into())?;
44 Ok(Self { level, text })
45 }
46}
47
48pub(super) fn serialize_flash_messages(
49 messages: &[FlashMessage],
50) -> Result<String, RuntimeBrowserError> {
51 let mut payload = String::new();
52 for message in messages {
53 if message.text.trim().is_empty() {
54 return Err(RuntimeBrowserError::EmptyValue {
55 field: "flash_message",
56 });
57 }
58
59 payload.push_str(message.level.as_str());
60 payload.push(':');
61 payload.push_str(&message.text.len().to_string());
62 payload.push(':');
63 payload.push_str(&message.text);
64 payload.push('|');
65 }
66
67 Ok(payload)
68}
69
70pub(super) fn deserialize_flash_messages(
71 payload: &str,
72) -> Result<Vec<FlashMessage>, RuntimeBrowserError> {
73 let mut remaining = payload;
74 let mut messages = Vec::new();
75
76 while !remaining.is_empty() {
77 let Some(level_sep) = remaining.find(':') else {
78 return Err(RuntimeBrowserError::InvalidFlashPayload);
79 };
80 let level = &remaining[..level_sep];
81 remaining = &remaining[level_sep + 1..];
82
83 let Some(length_sep) = remaining.find(':') else {
84 return Err(RuntimeBrowserError::InvalidFlashPayload);
85 };
86 let length: usize = remaining[..length_sep]
87 .parse()
88 .map_err(|_| RuntimeBrowserError::InvalidFlashPayload)?;
89 remaining = &remaining[length_sep + 1..];
90
91 if remaining.len() < length + 1 {
92 return Err(RuntimeBrowserError::InvalidFlashPayload);
93 }
94
95 let message = &remaining[..length];
96 let separator = &remaining[length..length + 1];
97 if separator != "|" {
98 return Err(RuntimeBrowserError::InvalidFlashPayload);
99 }
100
101 messages.push(FlashMessage::new(FlashLevel::parse(level)?, message)?);
102 remaining = &remaining[length + 1..];
103 }
104
105 Ok(messages)
106}