1use std::str::FromStr;
4
5use nom::{
6 IResult, Parser,
7 character::complete::{char, digit1},
8 combinator::all_consuming,
9 sequence::preceded,
10};
11use serde::{Deserialize, Serialize};
12
13use crate::error::{Error, map_add_intent};
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct WindowId(String);
28
29impl FromStr for WindowId {
30 type Err = Error;
31
32 fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
35 let desc = "WindowId";
36 let intent = "##{window_id}";
37
38 let (_, window_id) = all_consuming(parse::window_id)
39 .parse(input)
40 .map_err(|e| map_add_intent(desc, intent, e))?;
41
42 Ok(window_id)
43 }
44}
45
46impl WindowId {
47 #[must_use]
49 pub fn as_str(&self) -> &str {
50 &self.0
51 }
52}
53
54pub(crate) mod parse {
55 use super::*;
56
57 pub(crate) fn window_id(input: &str) -> IResult<&str, WindowId> {
58 let (input, digit) = preceded(char('@'), digit1).parse(input)?;
59 let id = format!("@{digit}");
60 Ok((input, WindowId(id)))
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn test_parse_window_id_fn() {
70 let actual = parse::window_id("@43");
71 let expected = Ok(("", WindowId("@43".into())));
72 assert_eq!(actual, expected);
73
74 let actual = parse::window_id("@4");
75 let expected = Ok(("", WindowId("@4".into())));
76 assert_eq!(actual, expected);
77 }
78
79 #[test]
80 fn test_parse_window_id_struct() {
81 let actual = WindowId::from_str("@43");
82 assert!(actual.is_ok());
83 assert_eq!(actual.unwrap(), WindowId("@43".into()));
84
85 let actual = WindowId::from_str("4:38");
86 assert!(matches!(
87 actual,
88 Err(Error::ParseError {
89 desc: "WindowId",
90 intent: "##{window_id}",
91 err: _
92 })
93 ));
94 }
95
96 #[test]
97 fn test_parse_window_id_with_large_number() {
98 let window_id = WindowId::from_str("@99999").unwrap();
99 assert_eq!(window_id.as_str(), "@99999");
100 }
101
102 #[test]
103 fn test_parse_window_id_zero() {
104 let window_id = WindowId::from_str("@0").unwrap();
105 assert_eq!(window_id.as_str(), "@0");
106 }
107
108 #[test]
109 fn test_parse_window_id_fails_on_wrong_prefix() {
110 assert!(WindowId::from_str("$1").is_err());
112 assert!(WindowId::from_str("%1").is_err());
113 }
114
115 #[test]
116 fn test_parse_window_id_fails_on_no_prefix() {
117 assert!(WindowId::from_str("123").is_err());
118 }
119
120 #[test]
121 fn test_parse_window_id_fails_on_empty() {
122 assert!(WindowId::from_str("").is_err());
123 assert!(WindowId::from_str("@").is_err());
124 }
125
126 #[test]
127 fn test_parse_window_id_fails_on_non_numeric() {
128 assert!(WindowId::from_str("@abc").is_err());
129 assert!(WindowId::from_str("@12abc").is_err());
130 }
131
132 #[test]
133 fn test_parse_window_id_fails_on_extra_content() {
134 assert!(WindowId::from_str("@12:extra").is_err());
136 }
137
138 #[test]
139 fn test_window_id_as_str() {
140 let window_id = WindowId::from_str("@42").unwrap();
141 assert_eq!(window_id.as_str(), "@42");
142 }
143
144 #[test]
145 fn test_window_id_leaves_remaining_in_parser() {
146 let (remaining, window_id) = parse::window_id("@42:rest").unwrap();
148 assert_eq!(remaining, ":rest");
149 assert_eq!(window_id, WindowId("@42".into()));
150 }
151}