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