Skip to main content

coil_runtime/browser/
flash.rs

1use 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}