1use std::{
2 collections::HashMap,
3 fmt::{self, Display, Formatter},
4 num::ParseIntError,
5};
6
7use error::{MissingWord, ProtocolError, WordType};
8use sentence::Sentence;
9use word::{Word, WordAttribute, WordCategory};
10
11pub mod command;
13pub mod error;
15pub mod sentence;
17pub mod word;
19
20pub type FatalResponse = String;
22
23#[derive(Debug, Clone)]
25pub enum CommandResponse {
26 Done(DoneResponse),
28 Reply(ReplyResponse),
30 Trap(TrapResponse),
32 Fatal(FatalResponse),
34}
35
36impl CommandResponse {
37 pub fn tag(&self) -> Option<u16> {
41 match &self {
42 Self::Done(d) => Some(d.tag),
43 Self::Reply(r) => Some(r.tag),
44 Self::Trap(t) => Some(t.tag),
45 Self::Fatal(_) => None,
46 }
47 }
48}
49
50impl TryFrom<Sentence<'_>> for CommandResponse {
51 type Error = ProtocolError;
52
53 fn try_from(mut sentence_iter: Sentence) -> Result<Self, Self::Error> {
54 let word = sentence_iter
55 .next()
56 .ok_or::<ProtocolError>(MissingWord::Category.into())??;
57
58 let category = word.category().ok_or(ProtocolError::WordSequence {
59 word: word.word_type(),
60 expected: vec![WordType::Category],
61 })?;
62
63 match category {
64 WordCategory::Done => {
65 let word = sentence_iter
66 .next()
67 .ok_or::<ProtocolError>(MissingWord::Tag.into())??;
68
69 let tag = word.tag().ok_or(ProtocolError::WordSequence {
71 word: word.into(),
72 expected: vec![WordType::Tag],
73 })?;
74 Ok(CommandResponse::Done(DoneResponse { tag }))
75 }
76 WordCategory::Reply => {
77 let mut tag = None;
80 let mut attributes = HashMap::<String, Option<String>>::new();
81 let mut attributes_raw = HashMap::<String, Option<Vec<u8>>>::new();
82
83 for word in sentence_iter {
84 let word = word?;
85 match word {
86 Word::Tag(t) => tag = Some(t),
87 Word::Attribute(WordAttribute {
88 key,
89 value,
90 value_raw,
91 }) => {
92 attributes.insert(key.to_owned(), value.map(String::from));
93 attributes_raw.insert(key.to_owned(), value_raw.map(Vec::from));
94 }
95 word => {
96 return Err(ProtocolError::WordSequence {
97 word: word.into(),
98 expected: vec![WordType::Tag, WordType::Attribute],
99 });
100 }
101 }
102 }
103
104 let tag = tag.ok_or::<ProtocolError>(MissingWord::Category.into())?;
105
106 Ok(CommandResponse::Reply(ReplyResponse {
107 tag,
108 attributes,
109 attributes_raw,
110 }))
111 }
112 WordCategory::Trap => {
113 let mut tag = None;
117 let mut category = None;
118 let mut message = None;
119
120 for word in sentence_iter {
121 let word = word?;
122 match word {
123 Word::Tag(t) => tag = Some(t),
124 Word::Attribute(WordAttribute {
125 key,
126 value,
127 value_raw: _,
128 }) => match key {
129 "category" => {
130 category = value.map(TrapCategory::try_from).transpose()?;
131 }
132 "message" => {
133 message = value.map(String::from);
134 }
135 key => {
136 return Err(TrapCategoryError::InvalidAttribute {
137 key: key.into(),
138 value: value.map(|v| v.into()),
139 }
140 .into());
141 }
142 },
143 word => {
144 return Err(ProtocolError::WordSequence {
145 word: word.into(),
146 expected: vec![WordType::Tag, WordType::Attribute],
147 });
148 }
149 }
150 }
151
152 let tag = tag.ok_or::<ProtocolError>(MissingWord::Category.into())?;
153 let message = message.ok_or(TrapCategoryError::MissingMessageAttribute)?;
154
155 Ok(CommandResponse::Trap(TrapResponse {
156 tag,
157 category,
158 message,
159 }))
160 }
161 WordCategory::Fatal => {
162 let word = sentence_iter
164 .next()
165 .ok_or::<ProtocolError>(MissingWord::Message.into())??;
166
167 let reason = word.generic().ok_or(ProtocolError::WordSequence {
168 word: word.word_type(),
169 expected: vec![WordType::Message],
170 })?;
171
172 Ok(CommandResponse::Fatal(reason.to_string()))
173 }
174 }
175 }
176}
177
178#[derive(Debug, Clone)]
180pub struct DoneResponse {
181 pub tag: u16,
183}
184
185impl Display for DoneResponse {
186 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
187 write!(f, "DoneResponse {{ tag: {} }}", self.tag)
188 }
189}
190
191#[derive(Debug, Clone)]
193pub struct ReplyResponse {
194 pub tag: u16,
196 pub attributes: HashMap<String, Option<String>>,
198 pub attributes_raw: HashMap<String, Option<Vec<u8>>>,
200}
201
202impl Display for ReplyResponse {
203 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
204 write!(
205 f,
206 "ReplyResponse {{ tag: {}, attributes: {:?} }}",
207 self.tag, self.attributes
208 )
209 }
210}
211
212#[derive(Debug, Clone)]
214pub struct TrapResponse {
215 pub tag: u16,
217 pub category: Option<TrapCategory>,
219 pub message: String,
221}
222
223impl Display for TrapResponse {
224 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
225 write!(
226 f,
227 "TrapResponse {{ tag: {}, category: {:?}, message: \"{}\" }}",
228 self.tag, self.category, self.message
229 )
230 }
231}
232
233#[derive(Debug, Clone)]
235#[repr(u8)]
236pub enum TrapCategory {
237 MissingItemOrCommand = 0,
239 ArgumentValueFailure = 1,
241 CommandExecutionInterrupted = 2,
243 ScriptingFailure = 3,
245 GeneralFailure = 4,
247 APIFailure = 5,
249 TTYFailure = 6,
251 ReturnValue = 7,
253}
254
255impl TryFrom<u8> for TrapCategory {
256 type Error = TrapCategoryError;
257
258 fn try_from(n: u8) -> Result<Self, Self::Error> {
259 match n {
260 0 => Ok(TrapCategory::MissingItemOrCommand),
261 1 => Ok(TrapCategory::ArgumentValueFailure),
262 2 => Ok(TrapCategory::CommandExecutionInterrupted),
263 3 => Ok(TrapCategory::ScriptingFailure),
264 4 => Ok(TrapCategory::GeneralFailure),
265 5 => Ok(TrapCategory::APIFailure),
266 6 => Ok(TrapCategory::TTYFailure),
267 7 => Ok(TrapCategory::ReturnValue),
268 n => Err(TrapCategoryError::OutOfRange(n)),
269 }
270 }
271}
272
273impl TryFrom<&str> for TrapCategory {
274 type Error = ProtocolError;
275
276 fn try_from(s: &str) -> Result<Self, Self::Error> {
277 let n = s
278 .parse::<u8>()
279 .map_err(|e| ProtocolError::TrapCategory(TrapCategoryError::Invalid(e)))?;
280 TrapCategory::try_from(n).map_err(ProtocolError::from)
281 }
282}
283
284#[derive(Debug)]
290pub enum TrapCategoryError {
291 Invalid(ParseIntError),
293 OutOfRange(u8),
295 InvalidAttribute {
297 key: String,
299 value: Option<String>,
301 },
302 MissingMessageAttribute,
304}