1use std::str::FromStr;
3
4use crate::chan::ChannelExt;
5use crate::error::MessageParseError;
6use crate::mode::{ChannelMode, Mode, UserMode};
7use crate::response::Response;
8
9#[derive(Clone, Debug, PartialEq)]
14pub enum Command {
15 PASS(String),
18 NICK(String),
20 USER(String, String, String),
22 OPER(String, String),
24 UserMODE(String, Vec<Mode<UserMode>>),
26 SERVICE(String, String, String, String, String, String),
28 QUIT(Option<String>),
30 SQUIT(String, String),
32
33 JOIN(String, Option<String>, Option<String>),
36 PART(String, Option<String>),
38 ChannelMODE(String, Vec<Mode<ChannelMode>>),
40 TOPIC(String, Option<String>),
42 NAMES(Option<String>, Option<String>),
44 LIST(Option<String>, Option<String>),
46 INVITE(String, String),
48 KICK(String, String, Option<String>),
50
51 PRIVMSG(String, String),
66 NOTICE(String, String),
80
81 MOTD(Option<String>),
84 LUSERS(Option<String>, Option<String>),
86 VERSION(Option<String>),
88 STATS(Option<String>, Option<String>),
90 LINKS(Option<String>, Option<String>),
92 TIME(Option<String>),
94 CONNECT(String, String, Option<String>),
96 TRACE(Option<String>),
98 ADMIN(Option<String>),
100 INFO(Option<String>),
102
103 SERVLIST(Option<String>, Option<String>),
106 SQUERY(String, String),
108
109 WHO(Option<String>, Option<bool>),
112 WHOIS(Option<String>, String),
114 WHOWAS(String, Option<String>, Option<String>),
116
117 KILL(String, String),
120 PING(String, Option<String>),
122 PONG(String, Option<String>),
124 ERROR(String),
126
127 AWAY(Option<String>),
130 REHASH,
132 DIE,
134 RESTART,
136 SUMMON(String, Option<String>, Option<String>),
138 USERS(Option<String>),
140 WALLOPS(String),
142 USERHOST(Vec<String>),
144 ISON(Vec<String>),
146
147 SAJOIN(String, String),
150 SAMODE(String, String, Option<String>),
152 SANICK(String, String),
154 SAPART(String, String),
156 SAQUIT(String, String),
158 NICKSERV(Vec<String>),
160 CHANSERV(String),
162 OPERSERV(String),
164 BOTSERV(String),
166 HOSTSERV(String),
168 MEMOSERV(String),
170
171 CAP(
174 Option<String>,
175 CapSubCommand,
176 Option<String>,
177 Option<String>,
178 ),
179
180 AUTHENTICATE(String),
183 ACCOUNT(String),
185 METADATA(String, Option<MetadataSubCommand>, Option<Vec<String>>),
193 MONITOR(String, Option<String>),
195 BATCH(String, Option<BatchSubCommand>, Option<Vec<String>>),
197 CHGHOST(String, String),
199
200 Response(Response, Vec<String>),
203 Raw(String, Vec<String>),
205}
206
207fn stringify(cmd: &str, args: &[&str]) -> String {
208 match args.split_last() {
209 Some((suffix, args)) => {
210 let args = args.join(" ");
211 let sp = if args.is_empty() { "" } else { " " };
212 let co = if suffix.is_empty() || suffix.contains(' ') || suffix.starts_with(':') {
213 ":"
214 } else {
215 ""
216 };
217 format!("{}{}{} {}{}", cmd, sp, args, co, suffix)
218 }
219 None => cmd.to_string(),
220 }
221}
222
223impl<'a> From<&'a Command> for String {
224 fn from(cmd: &'a Command) -> String {
225 match *cmd {
226 Command::PASS(ref p) => stringify("PASS", &[p]),
227 Command::NICK(ref n) => stringify("NICK", &[n]),
228 Command::USER(ref u, ref m, ref r) => stringify("USER", &[u, m, "*", r]),
229 Command::OPER(ref u, ref p) => stringify("OPER", &[u, p]),
230 Command::UserMODE(ref u, ref m) => {
231 m.iter().fold(format!("MODE {u} "), |mut acc, m| {
233 acc.push_str(&m.flag());
234 acc
235 })
236 }
237 Command::SERVICE(ref nick, ref r0, ref dist, ref typ, ref r1, ref info) => {
238 stringify("SERVICE", &[nick, r0, dist, typ, r1, info])
239 }
240 Command::QUIT(Some(ref m)) => stringify("QUIT", &[m]),
241 Command::QUIT(None) => stringify("QUIT", &[]),
242 Command::SQUIT(ref s, ref c) => stringify("SQUIT", &[s, c]),
243 Command::JOIN(ref c, Some(ref k), Some(ref n)) => stringify("JOIN", &[c, k, n]),
244 Command::JOIN(ref c, Some(ref k), None) => stringify("JOIN", &[c, k]),
245 Command::JOIN(ref c, None, Some(ref n)) => stringify("JOIN", &[c, n]),
246 Command::JOIN(ref c, None, None) => stringify("JOIN", &[c]),
247 Command::PART(ref c, Some(ref m)) => stringify("PART", &[c, m]),
248 Command::PART(ref c, None) => stringify("PART", &[c]),
249 Command::ChannelMODE(ref c, ref m) => {
250 let cmd = m.iter().fold(format!("MODE {c} "), |mut acc, m| {
251 acc.push_str(&m.flag());
252 acc
253 });
254 m.iter().filter_map(|m| m.arg()).fold(cmd, |mut acc, arg| {
255 acc.push(' ');
256 acc.push_str(arg);
257 acc
258 })
259 }
260 Command::TOPIC(ref c, Some(ref t)) => stringify("TOPIC", &[c, t]),
261 Command::TOPIC(ref c, None) => stringify("TOPIC", &[c]),
262 Command::NAMES(Some(ref c), Some(ref t)) => stringify("NAMES", &[c, t]),
263 Command::NAMES(Some(ref c), None) => stringify("NAMES", &[c]),
264 Command::NAMES(None, _) => stringify("NAMES", &[]),
265 Command::LIST(Some(ref c), Some(ref t)) => stringify("LIST", &[c, t]),
266 Command::LIST(Some(ref c), None) => stringify("LIST", &[c]),
267 Command::LIST(None, _) => stringify("LIST", &[]),
268 Command::INVITE(ref n, ref c) => stringify("INVITE", &[n, c]),
269 Command::KICK(ref c, ref n, Some(ref r)) => stringify("KICK", &[c, n, r]),
270 Command::KICK(ref c, ref n, None) => stringify("KICK", &[c, n]),
271 Command::PRIVMSG(ref t, ref m) => stringify("PRIVMSG", &[t, m]),
272 Command::NOTICE(ref t, ref m) => stringify("NOTICE", &[t, m]),
273 Command::MOTD(Some(ref t)) => stringify("MOTD", &[t]),
274 Command::MOTD(None) => stringify("MOTD", &[]),
275 Command::LUSERS(Some(ref m), Some(ref t)) => stringify("LUSERS", &[m, t]),
276 Command::LUSERS(Some(ref m), None) => stringify("LUSERS", &[m]),
277 Command::LUSERS(None, _) => stringify("LUSERS", &[]),
278 Command::VERSION(Some(ref t)) => stringify("VERSION", &[t]),
279 Command::VERSION(None) => stringify("VERSION", &[]),
280 Command::STATS(Some(ref q), Some(ref t)) => stringify("STATS", &[q, t]),
281 Command::STATS(Some(ref q), None) => stringify("STATS", &[q]),
282 Command::STATS(None, _) => stringify("STATS", &[]),
283 Command::LINKS(Some(ref r), Some(ref s)) => stringify("LINKS", &[r, s]),
284 Command::LINKS(None, Some(ref s)) => stringify("LINKS", &[s]),
285 Command::LINKS(_, None) => stringify("LINKS", &[]),
286 Command::TIME(Some(ref t)) => stringify("TIME", &[t]),
287 Command::TIME(None) => stringify("TIME", &[]),
288 Command::CONNECT(ref t, ref p, Some(ref r)) => stringify("CONNECT", &[t, p, r]),
289 Command::CONNECT(ref t, ref p, None) => stringify("CONNECT", &[t, p]),
290 Command::TRACE(Some(ref t)) => stringify("TRACE", &[t]),
291 Command::TRACE(None) => stringify("TRACE", &[]),
292 Command::ADMIN(Some(ref t)) => stringify("ADMIN", &[t]),
293 Command::ADMIN(None) => stringify("ADMIN", &[]),
294 Command::INFO(Some(ref t)) => stringify("INFO", &[t]),
295 Command::INFO(None) => stringify("INFO", &[]),
296 Command::SERVLIST(Some(ref m), Some(ref t)) => stringify("SERVLIST", &[m, t]),
297 Command::SERVLIST(Some(ref m), None) => stringify("SERVLIST", &[m]),
298 Command::SERVLIST(None, _) => stringify("SERVLIST", &[]),
299 Command::SQUERY(ref s, ref t) => stringify("SQUERY", &[s, t]),
300 Command::WHO(Some(ref s), Some(true)) => stringify("WHO", &[s, "o"]),
301 Command::WHO(Some(ref s), _) => stringify("WHO", &[s]),
302 Command::WHO(None, _) => stringify("WHO", &[]),
303 Command::WHOIS(Some(ref t), ref m) => stringify("WHOIS", &[t, m]),
304 Command::WHOIS(None, ref m) => stringify("WHOIS", &[m]),
305 Command::WHOWAS(ref n, Some(ref c), Some(ref t)) => stringify("WHOWAS", &[n, c, t]),
306 Command::WHOWAS(ref n, Some(ref c), None) => stringify("WHOWAS", &[n, c]),
307 Command::WHOWAS(ref n, None, _) => stringify("WHOWAS", &[n]),
308 Command::KILL(ref n, ref c) => stringify("KILL", &[n, c]),
309 Command::PING(ref s, Some(ref t)) => stringify("PING", &[s, t]),
310 Command::PING(ref s, None) => stringify("PING", &[s]),
311 Command::PONG(ref s, Some(ref t)) => stringify("PONG", &[s, t]),
312 Command::PONG(ref s, None) => stringify("PONG", &[s]),
313 Command::ERROR(ref m) => stringify("ERROR", &[m]),
314 Command::AWAY(Some(ref m)) => stringify("AWAY", &[m]),
315 Command::AWAY(None) => stringify("AWAY", &[]),
316 Command::REHASH => stringify("REHASH", &[]),
317 Command::DIE => stringify("DIE", &[]),
318 Command::RESTART => stringify("RESTART", &[]),
319 Command::SUMMON(ref u, Some(ref t), Some(ref c)) => stringify("SUMMON", &[u, t, c]),
320 Command::SUMMON(ref u, Some(ref t), None) => stringify("SUMMON", &[u, t]),
321 Command::SUMMON(ref u, None, _) => stringify("SUMMON", &[u]),
322 Command::USERS(Some(ref t)) => stringify("USERS", &[t]),
323 Command::USERS(None) => stringify("USERS", &[]),
324 Command::WALLOPS(ref t) => stringify("WALLOPS", &[t]),
325 Command::USERHOST(ref u) => {
326 stringify("USERHOST", &u.iter().map(|s| &s[..]).collect::<Vec<_>>())
327 }
328 Command::ISON(ref u) => {
329 stringify("ISON", &u.iter().map(|s| &s[..]).collect::<Vec<_>>())
330 }
331
332 Command::SAJOIN(ref n, ref c) => stringify("SAJOIN", &[n, c]),
333 Command::SAMODE(ref t, ref m, Some(ref p)) => stringify("SAMODE", &[t, m, p]),
334 Command::SAMODE(ref t, ref m, None) => stringify("SAMODE", &[t, m]),
335 Command::SANICK(ref o, ref n) => stringify("SANICK", &[o, n]),
336 Command::SAPART(ref c, ref r) => stringify("SAPART", &[c, r]),
337 Command::SAQUIT(ref c, ref r) => stringify("SAQUIT", &[c, r]),
338
339 Command::NICKSERV(ref p) => {
340 stringify("NICKSERV", &p.iter().map(|s| &s[..]).collect::<Vec<_>>())
341 }
342 Command::CHANSERV(ref m) => stringify("CHANSERV", &[m]),
343 Command::OPERSERV(ref m) => stringify("OPERSERV", &[m]),
344 Command::BOTSERV(ref m) => stringify("BOTSERV", &[m]),
345 Command::HOSTSERV(ref m) => stringify("HOSTSERV", &[m]),
346 Command::MEMOSERV(ref m) => stringify("MEMOSERV", &[m]),
347
348 Command::CAP(None, ref s, None, Some(ref p)) => stringify("CAP", &[s.to_str(), p]),
349 Command::CAP(None, ref s, None, None) => stringify("CAP", &[s.to_str()]),
350 Command::CAP(Some(ref k), ref s, None, Some(ref p)) => {
351 stringify("CAP", &[k, s.to_str(), p])
352 }
353 Command::CAP(Some(ref k), ref s, None, None) => stringify("CAP", &[k, s.to_str()]),
354 Command::CAP(None, ref s, Some(ref c), Some(ref p)) => {
355 stringify("CAP", &[s.to_str(), c, p])
356 }
357 Command::CAP(None, ref s, Some(ref c), None) => stringify("CAP", &[s.to_str(), c]),
358 Command::CAP(Some(ref k), ref s, Some(ref c), Some(ref p)) => {
359 stringify("CAP", &[k, s.to_str(), c, p])
360 }
361 Command::CAP(Some(ref k), ref s, Some(ref c), None) => {
362 stringify("CAP", &[k, s.to_str(), c])
363 }
364
365 Command::AUTHENTICATE(ref d) => stringify("AUTHENTICATE", &[d]),
366 Command::ACCOUNT(ref a) => stringify("ACCOUNT", &[a]),
367
368 Command::METADATA(ref t, Some(ref c), None) => {
369 stringify("METADATA", &[&t[..], c.to_str()])
370 }
371 Command::METADATA(ref t, Some(ref c), Some(ref a)) => stringify(
372 "METADATA",
373 &vec![t, &c.to_str().to_owned()]
374 .iter()
375 .map(|s| &s[..])
376 .chain(a.iter().map(|s| &s[..]))
377 .collect::<Vec<_>>(),
378 ),
379
380 Command::METADATA(ref t, None, _) => stringify("METADATA", &[t]),
384
385 Command::MONITOR(ref c, Some(ref t)) => stringify("MONITOR", &[c, t]),
386 Command::MONITOR(ref c, None) => stringify("MONITOR", &[c]),
387 Command::BATCH(ref t, Some(ref c), Some(ref a)) => stringify(
388 "BATCH",
389 &vec![t, &c.to_str().to_owned()]
390 .iter()
391 .map(|s| &s[..])
392 .chain(a.iter().map(|s| &s[..]))
393 .collect::<Vec<_>>(),
394 ),
395 Command::BATCH(ref t, Some(ref c), None) => stringify("BATCH", &[t, c.to_str()]),
396 Command::BATCH(ref t, None, Some(ref a)) => stringify(
397 "BATCH",
398 &vec![t]
399 .iter()
400 .map(|s| &s[..])
401 .chain(a.iter().map(|s| &s[..]))
402 .collect::<Vec<_>>(),
403 ),
404 Command::BATCH(ref t, None, None) => stringify("BATCH", &[t]),
405 Command::CHGHOST(ref u, ref h) => stringify("CHGHOST", &[u, h]),
406
407 Command::Response(ref resp, ref a) => stringify(
408 &format!("{:03}", *resp as u16),
409 &a.iter().map(|s| &s[..]).collect::<Vec<_>>(),
410 ),
411 Command::Raw(ref c, ref a) => {
412 stringify(c, &a.iter().map(|s| &s[..]).collect::<Vec<_>>())
413 }
414 }
415 }
416}
417
418impl Command {
419 pub fn new(cmd: &str, args: Vec<&str>) -> Result<Command, MessageParseError> {
421 Ok(if cmd.eq_ignore_ascii_case("PASS") {
422 if args.len() != 1 {
423 raw(cmd, args)
424 } else {
425 Command::PASS(args[0].to_owned())
426 }
427 } else if cmd.eq_ignore_ascii_case("NICK") {
428 if args.len() != 1 {
429 raw(cmd, args)
430 } else {
431 Command::NICK(args[0].to_owned())
432 }
433 } else if cmd.eq_ignore_ascii_case("USER") {
434 if args.len() != 4 {
435 raw(cmd, args)
436 } else {
437 Command::USER(args[0].to_owned(), args[1].to_owned(), args[3].to_owned())
438 }
439 } else if cmd.eq_ignore_ascii_case("OPER") {
440 if args.len() != 2 {
441 raw(cmd, args)
442 } else {
443 Command::OPER(args[0].to_owned(), args[1].to_owned())
444 }
445 } else if cmd.eq_ignore_ascii_case("MODE") {
446 if args.is_empty() {
447 raw(cmd, args)
448 } else if args[0].is_channel_name() {
449 Command::ChannelMODE(args[0].to_owned(), Mode::as_channel_modes(&args[1..])?)
450 } else {
451 Command::UserMODE(args[0].to_owned(), Mode::as_user_modes(&args[1..])?)
452 }
453 } else if cmd.eq_ignore_ascii_case("SERVICE") {
454 if args.len() != 6 {
455 raw(cmd, args)
456 } else {
457 Command::SERVICE(
458 args[0].to_owned(),
459 args[1].to_owned(),
460 args[2].to_owned(),
461 args[3].to_owned(),
462 args[4].to_owned(),
463 args[5].to_owned(),
464 )
465 }
466 } else if cmd.eq_ignore_ascii_case("QUIT") {
467 if args.is_empty() {
468 Command::QUIT(None)
469 } else if args.len() == 1 {
470 Command::QUIT(Some(args[0].to_owned()))
471 } else {
472 raw(cmd, args)
473 }
474 } else if cmd.eq_ignore_ascii_case("SQUIT") {
475 if args.len() != 2 {
476 raw(cmd, args)
477 } else {
478 Command::SQUIT(args[0].to_owned(), args[1].to_owned())
479 }
480 } else if cmd.eq_ignore_ascii_case("JOIN") {
481 if args.len() == 1 {
482 Command::JOIN(args[0].to_owned(), None, None)
483 } else if args.len() == 2 {
484 Command::JOIN(args[0].to_owned(), Some(args[1].to_owned()), None)
485 } else if args.len() == 3 {
486 Command::JOIN(
487 args[0].to_owned(),
488 Some(args[1].to_owned()),
489 Some(args[2].to_owned()),
490 )
491 } else {
492 raw(cmd, args)
493 }
494 } else if cmd.eq_ignore_ascii_case("PART") {
495 if args.len() == 1 {
496 Command::PART(args[0].to_owned(), None)
497 } else if args.len() == 2 {
498 Command::PART(args[0].to_owned(), Some(args[1].to_owned()))
499 } else {
500 raw(cmd, args)
501 }
502 } else if cmd.eq_ignore_ascii_case("TOPIC") {
503 if args.len() == 1 {
504 Command::TOPIC(args[0].to_owned(), None)
505 } else if args.len() == 2 {
506 Command::TOPIC(args[0].to_owned(), Some(args[1].to_owned()))
507 } else {
508 raw(cmd, args)
509 }
510 } else if cmd.eq_ignore_ascii_case("NAMES") {
511 if args.is_empty() {
512 Command::NAMES(None, None)
513 } else if args.len() == 1 {
514 Command::NAMES(Some(args[0].to_owned()), None)
515 } else if args.len() == 2 {
516 Command::NAMES(Some(args[0].to_owned()), Some(args[1].to_owned()))
517 } else {
518 raw(cmd, args)
519 }
520 } else if cmd.eq_ignore_ascii_case("LIST") {
521 if args.is_empty() {
522 Command::LIST(None, None)
523 } else if args.len() == 1 {
524 Command::LIST(Some(args[0].to_owned()), None)
525 } else if args.len() == 2 {
526 Command::LIST(Some(args[0].to_owned()), Some(args[1].to_owned()))
527 } else {
528 raw(cmd, args)
529 }
530 } else if cmd.eq_ignore_ascii_case("INVITE") {
531 if args.len() != 2 {
532 raw(cmd, args)
533 } else {
534 Command::INVITE(args[0].to_owned(), args[1].to_owned())
535 }
536 } else if cmd.eq_ignore_ascii_case("KICK") {
537 if args.len() == 3 {
538 Command::KICK(
539 args[0].to_owned(),
540 args[1].to_owned(),
541 Some(args[2].to_owned()),
542 )
543 } else if args.len() == 2 {
544 Command::KICK(args[0].to_owned(), args[1].to_owned(), None)
545 } else {
546 raw(cmd, args)
547 }
548 } else if cmd.eq_ignore_ascii_case("PRIVMSG") {
549 if args.len() != 2 {
550 raw(cmd, args)
551 } else {
552 Command::PRIVMSG(args[0].to_owned(), args[1].to_owned())
553 }
554 } else if cmd.eq_ignore_ascii_case("NOTICE") {
555 if args.len() != 2 {
556 raw(cmd, args)
557 } else {
558 Command::NOTICE(args[0].to_owned(), args[1].to_owned())
559 }
560 } else if cmd.eq_ignore_ascii_case("MOTD") {
561 if args.is_empty() {
562 Command::MOTD(None)
563 } else if args.len() == 1 {
564 Command::MOTD(Some(args[0].to_owned()))
565 } else {
566 raw(cmd, args)
567 }
568 } else if cmd.eq_ignore_ascii_case("LUSERS") {
569 if args.is_empty() {
570 Command::LUSERS(None, None)
571 } else if args.len() == 1 {
572 Command::LUSERS(Some(args[0].to_owned()), None)
573 } else if args.len() == 2 {
574 Command::LUSERS(Some(args[0].to_owned()), Some(args[1].to_owned()))
575 } else {
576 raw(cmd, args)
577 }
578 } else if cmd.eq_ignore_ascii_case("VERSION") {
579 if args.is_empty() {
580 Command::VERSION(None)
581 } else if args.len() == 1 {
582 Command::VERSION(Some(args[0].to_owned()))
583 } else {
584 raw(cmd, args)
585 }
586 } else if cmd.eq_ignore_ascii_case("STATS") {
587 if args.is_empty() {
588 Command::STATS(None, None)
589 } else if args.len() == 1 {
590 Command::STATS(Some(args[0].to_owned()), None)
591 } else if args.len() == 2 {
592 Command::STATS(Some(args[0].to_owned()), Some(args[1].to_owned()))
593 } else {
594 raw(cmd, args)
595 }
596 } else if cmd.eq_ignore_ascii_case("LINKS") {
597 if args.is_empty() {
598 Command::LINKS(None, None)
599 } else if args.len() == 1 {
600 Command::LINKS(Some(args[0].to_owned()), None)
601 } else if args.len() == 2 {
602 Command::LINKS(Some(args[0].to_owned()), Some(args[1].to_owned()))
603 } else {
604 raw(cmd, args)
605 }
606 } else if cmd.eq_ignore_ascii_case("TIME") {
607 if args.is_empty() {
608 Command::TIME(None)
609 } else if args.len() == 1 {
610 Command::TIME(Some(args[0].to_owned()))
611 } else {
612 raw(cmd, args)
613 }
614 } else if cmd.eq_ignore_ascii_case("CONNECT") {
615 if args.len() != 2 {
616 raw(cmd, args)
617 } else {
618 Command::CONNECT(args[0].to_owned(), args[1].to_owned(), None)
619 }
620 } else if cmd.eq_ignore_ascii_case("TRACE") {
621 if args.is_empty() {
622 Command::TRACE(None)
623 } else if args.len() == 1 {
624 Command::TRACE(Some(args[0].to_owned()))
625 } else {
626 raw(cmd, args)
627 }
628 } else if cmd.eq_ignore_ascii_case("ADMIN") {
629 if args.is_empty() {
630 Command::ADMIN(None)
631 } else if args.len() == 1 {
632 Command::ADMIN(Some(args[0].to_owned()))
633 } else {
634 raw(cmd, args)
635 }
636 } else if cmd.eq_ignore_ascii_case("INFO") {
637 if args.is_empty() {
638 Command::INFO(None)
639 } else if args.len() == 1 {
640 Command::INFO(Some(args[0].to_owned()))
641 } else {
642 raw(cmd, args)
643 }
644 } else if cmd.eq_ignore_ascii_case("SERVLIST") {
645 if args.is_empty() {
646 Command::SERVLIST(None, None)
647 } else if args.len() == 1 {
648 Command::SERVLIST(Some(args[0].to_owned()), None)
649 } else if args.len() == 2 {
650 Command::SERVLIST(Some(args[0].to_owned()), Some(args[1].to_owned()))
651 } else {
652 raw(cmd, args)
653 }
654 } else if cmd.eq_ignore_ascii_case("SQUERY") {
655 if args.len() != 2 {
656 raw(cmd, args)
657 } else {
658 Command::SQUERY(args[0].to_owned(), args[1].to_owned())
659 }
660 } else if cmd.eq_ignore_ascii_case("WHO") {
661 if args.is_empty() {
662 Command::WHO(None, None)
663 } else if args.len() == 1 {
664 Command::WHO(Some(args[0].to_owned()), None)
665 } else if args.len() == 2 {
666 Command::WHO(Some(args[0].to_owned()), Some(args[1] == "o"))
667 } else {
668 raw(cmd, args)
669 }
670 } else if cmd.eq_ignore_ascii_case("WHOIS") {
671 if args.len() == 1 {
672 Command::WHOIS(None, args[0].to_owned())
673 } else if args.len() == 2 {
674 Command::WHOIS(Some(args[0].to_owned()), args[1].to_owned())
675 } else {
676 raw(cmd, args)
677 }
678 } else if cmd.eq_ignore_ascii_case("WHOWAS") {
679 if args.len() == 1 {
680 Command::WHOWAS(args[0].to_owned(), None, None)
681 } else if args.len() == 2 {
682 Command::WHOWAS(args[0].to_owned(), None, Some(args[1].to_owned()))
683 } else if args.len() == 3 {
684 Command::WHOWAS(
685 args[0].to_owned(),
686 Some(args[1].to_owned()),
687 Some(args[2].to_owned()),
688 )
689 } else {
690 raw(cmd, args)
691 }
692 } else if cmd.eq_ignore_ascii_case("KILL") {
693 if args.len() != 2 {
694 raw(cmd, args)
695 } else {
696 Command::KILL(args[0].to_owned(), args[1].to_owned())
697 }
698 } else if cmd.eq_ignore_ascii_case("PING") {
699 if args.len() == 1 {
700 Command::PING(args[0].to_owned(), None)
701 } else if args.len() == 2 {
702 Command::PING(args[0].to_owned(), Some(args[1].to_owned()))
703 } else {
704 raw(cmd, args)
705 }
706 } else if cmd.eq_ignore_ascii_case("PONG") {
707 if args.len() == 1 {
708 Command::PONG(args[0].to_owned(), None)
709 } else if args.len() == 2 {
710 Command::PONG(args[0].to_owned(), Some(args[1].to_owned()))
711 } else {
712 raw(cmd, args)
713 }
714 } else if cmd.eq_ignore_ascii_case("ERROR") {
715 if args.len() != 1 {
716 raw(cmd, args)
717 } else {
718 Command::ERROR(args[0].to_owned())
719 }
720 } else if cmd.eq_ignore_ascii_case("AWAY") {
721 if args.is_empty() {
722 Command::AWAY(None)
723 } else if args.len() == 1 {
724 Command::AWAY(Some(args[0].to_owned()))
725 } else {
726 raw(cmd, args)
727 }
728 } else if cmd.eq_ignore_ascii_case("REHASH") {
729 if args.is_empty() {
730 Command::REHASH
731 } else {
732 raw(cmd, args)
733 }
734 } else if cmd.eq_ignore_ascii_case("DIE") {
735 if args.is_empty() {
736 Command::DIE
737 } else {
738 raw(cmd, args)
739 }
740 } else if cmd.eq_ignore_ascii_case("RESTART") {
741 if args.is_empty() {
742 Command::RESTART
743 } else {
744 raw(cmd, args)
745 }
746 } else if cmd.eq_ignore_ascii_case("SUMMON") {
747 if args.len() == 1 {
748 Command::SUMMON(args[0].to_owned(), None, None)
749 } else if args.len() == 2 {
750 Command::SUMMON(args[0].to_owned(), Some(args[1].to_owned()), None)
751 } else if args.len() == 3 {
752 Command::SUMMON(
753 args[0].to_owned(),
754 Some(args[1].to_owned()),
755 Some(args[2].to_owned()),
756 )
757 } else {
758 raw(cmd, args)
759 }
760 } else if cmd.eq_ignore_ascii_case("USERS") {
761 if args.len() != 1 {
762 raw(cmd, args)
763 } else {
764 Command::USERS(Some(args[0].to_owned()))
765 }
766 } else if cmd.eq_ignore_ascii_case("WALLOPS") {
767 if args.len() != 1 {
768 raw(cmd, args)
769 } else {
770 Command::WALLOPS(args[0].to_owned())
771 }
772 } else if cmd.eq_ignore_ascii_case("USERHOST") {
773 Command::USERHOST(args.into_iter().map(|s| s.to_owned()).collect())
774 } else if cmd.eq_ignore_ascii_case("ISON") {
775 Command::USERHOST(args.into_iter().map(|s| s.to_owned()).collect())
776 } else if cmd.eq_ignore_ascii_case("SAJOIN") {
777 if args.len() != 2 {
778 raw(cmd, args)
779 } else {
780 Command::SAJOIN(args[0].to_owned(), args[1].to_owned())
781 }
782 } else if cmd.eq_ignore_ascii_case("SAMODE") {
783 if args.len() == 2 {
784 Command::SAMODE(args[0].to_owned(), args[1].to_owned(), None)
785 } else if args.len() == 3 {
786 Command::SAMODE(
787 args[0].to_owned(),
788 args[1].to_owned(),
789 Some(args[2].to_owned()),
790 )
791 } else {
792 raw(cmd, args)
793 }
794 } else if cmd.eq_ignore_ascii_case("SANICK") {
795 if args.len() != 2 {
796 raw(cmd, args)
797 } else {
798 Command::SANICK(args[0].to_owned(), args[1].to_owned())
799 }
800 } else if cmd.eq_ignore_ascii_case("SAPART") {
801 if args.len() != 2 {
802 raw(cmd, args)
803 } else {
804 Command::SAPART(args[0].to_owned(), args[1].to_owned())
805 }
806 } else if cmd.eq_ignore_ascii_case("SAQUIT") {
807 if args.len() != 2 {
808 raw(cmd, args)
809 } else {
810 Command::SAQUIT(args[0].to_owned(), args[1].to_owned())
811 }
812 } else if cmd.eq_ignore_ascii_case("NICKSERV") {
813 if args.len() != 1 {
814 raw(cmd, args)
815 } else {
816 Command::NICKSERV(args[1..].iter().map(|s| s.to_string()).collect())
817 }
818 } else if cmd.eq_ignore_ascii_case("CHANSERV") {
819 if args.len() != 1 {
820 raw(cmd, args)
821 } else {
822 Command::CHANSERV(args[0].to_owned())
823 }
824 } else if cmd.eq_ignore_ascii_case("OPERSERV") {
825 if args.len() != 1 {
826 raw(cmd, args)
827 } else {
828 Command::OPERSERV(args[0].to_owned())
829 }
830 } else if cmd.eq_ignore_ascii_case("BOTSERV") {
831 if args.len() != 1 {
832 raw(cmd, args)
833 } else {
834 Command::BOTSERV(args[0].to_owned())
835 }
836 } else if cmd.eq_ignore_ascii_case("HOSTSERV") {
837 if args.len() != 1 {
838 raw(cmd, args)
839 } else {
840 Command::HOSTSERV(args[0].to_owned())
841 }
842 } else if cmd.eq_ignore_ascii_case("MEMOSERV") {
843 if args.len() != 1 {
844 raw(cmd, args)
845 } else {
846 Command::MEMOSERV(args[0].to_owned())
847 }
848 } else if cmd.eq_ignore_ascii_case("CAP") {
849 if args.len() == 1 {
850 if let Ok(cmd) = args[0].parse() {
851 Command::CAP(None, cmd, None, None)
852 } else {
853 raw(cmd, args)
854 }
855 } else if args.len() == 2 {
856 if let Ok(cmd) = args[0].parse() {
857 Command::CAP(None, cmd, Some(args[1].to_owned()), None)
858 } else if let Ok(cmd) = args[1].parse() {
859 Command::CAP(Some(args[0].to_owned()), cmd, None, None)
860 } else {
861 raw(cmd, args)
862 }
863 } else if args.len() == 3 {
864 if let Ok(cmd) = args[0].parse() {
865 Command::CAP(
866 None,
867 cmd,
868 Some(args[1].to_owned()),
869 Some(args[2].to_owned()),
870 )
871 } else if let Ok(cmd) = args[1].parse() {
872 Command::CAP(
873 Some(args[0].to_owned()),
874 cmd,
875 Some(args[2].to_owned()),
876 None,
877 )
878 } else {
879 raw(cmd, args)
880 }
881 } else if args.len() == 4 {
882 if let Ok(cmd) = args[1].parse() {
883 Command::CAP(
884 Some(args[0].to_owned()),
885 cmd,
886 Some(args[2].to_owned()),
887 Some(args[3].to_owned()),
888 )
889 } else {
890 raw(cmd, args)
891 }
892 } else {
893 raw(cmd, args)
894 }
895 } else if cmd.eq_ignore_ascii_case("AUTHENTICATE") {
896 if args.len() == 1 {
897 Command::AUTHENTICATE(args[0].to_owned())
898 } else {
899 raw(cmd, args)
900 }
901 } else if cmd.eq_ignore_ascii_case("ACCOUNT") {
902 if args.len() == 1 {
903 Command::ACCOUNT(args[0].to_owned())
904 } else {
905 raw(cmd, args)
906 }
907 } else if cmd.eq_ignore_ascii_case("METADATA") {
908 match args.len() {
909 2 => match args[1].parse() {
910 Ok(c) => Command::METADATA(args[0].to_owned(), Some(c), None),
911 Err(_) => raw(cmd, args),
912 },
913 3.. => match args[1].parse() {
914 Ok(c) => Command::METADATA(
915 args[0].to_owned(),
916 Some(c),
917 Some(args.into_iter().skip(1).map(|s| s.to_owned()).collect()),
918 ),
919 Err(_) => {
920 if args.len() == 3 {
921 Command::METADATA(
922 args[0].to_owned(),
923 None,
924 Some(args.into_iter().skip(1).map(|s| s.to_owned()).collect()),
925 )
926 } else {
927 raw(cmd, args)
928 }
929 }
930 },
931 _ => raw(cmd, args),
932 }
933 } else if cmd.eq_ignore_ascii_case("MONITOR") {
934 if args.len() == 2 {
935 Command::MONITOR(args[0].to_owned(), Some(args[1].to_owned()))
936 } else if args.len() == 1 {
937 Command::MONITOR(args[0].to_owned(), None)
938 } else {
939 raw(cmd, args)
940 }
941 } else if cmd.eq_ignore_ascii_case("BATCH") {
942 if args.len() == 1 {
943 Command::BATCH(args[0].to_owned(), None, None)
944 } else if args.len() == 2 {
945 Command::BATCH(args[0].to_owned(), Some(args[1].parse().unwrap()), None)
946 } else if args.len() > 2 {
947 Command::BATCH(
948 args[0].to_owned(),
949 Some(args[1].parse().unwrap()),
950 Some(args.iter().skip(2).map(|&s| s.to_owned()).collect()),
951 )
952 } else {
953 raw(cmd, args)
954 }
955 } else if cmd.eq_ignore_ascii_case("CHGHOST") {
956 if args.len() == 2 {
957 Command::CHGHOST(args[0].to_owned(), args[1].to_owned())
958 } else {
959 raw(cmd, args)
960 }
961 } else if let Ok(resp) = cmd.parse() {
962 Command::Response(resp, args.into_iter().map(|s| s.to_owned()).collect())
963 } else {
964 raw(cmd, args)
965 })
966 }
967}
968
969fn raw(cmd: &str, args: Vec<&str>) -> Command {
971 Command::Raw(
972 cmd.to_owned(),
973 args.into_iter().map(|s| s.to_owned()).collect(),
974 )
975}
976
977#[derive(Clone, Copy, Debug, PartialEq)]
979pub enum CapSubCommand {
980 LS,
982 LIST,
984 REQ,
986 ACK,
988 NAK,
990 END,
992 NEW,
994 DEL,
996}
997
998impl CapSubCommand {
999 pub fn to_str(&self) -> &str {
1001 match *self {
1002 CapSubCommand::LS => "LS",
1003 CapSubCommand::LIST => "LIST",
1004 CapSubCommand::REQ => "REQ",
1005 CapSubCommand::ACK => "ACK",
1006 CapSubCommand::NAK => "NAK",
1007 CapSubCommand::END => "END",
1008 CapSubCommand::NEW => "NEW",
1009 CapSubCommand::DEL => "DEL",
1010 }
1011 }
1012}
1013
1014impl FromStr for CapSubCommand {
1015 type Err = MessageParseError;
1016
1017 fn from_str(s: &str) -> Result<CapSubCommand, Self::Err> {
1018 if s.eq_ignore_ascii_case("LS") {
1019 Ok(CapSubCommand::LS)
1020 } else if s.eq_ignore_ascii_case("LIST") {
1021 Ok(CapSubCommand::LIST)
1022 } else if s.eq_ignore_ascii_case("REQ") {
1023 Ok(CapSubCommand::REQ)
1024 } else if s.eq_ignore_ascii_case("ACK") {
1025 Ok(CapSubCommand::ACK)
1026 } else if s.eq_ignore_ascii_case("NAK") {
1027 Ok(CapSubCommand::NAK)
1028 } else if s.eq_ignore_ascii_case("END") {
1029 Ok(CapSubCommand::END)
1030 } else if s.eq_ignore_ascii_case("NEW") {
1031 Ok(CapSubCommand::NEW)
1032 } else if s.eq_ignore_ascii_case("DEL") {
1033 Ok(CapSubCommand::DEL)
1034 } else {
1035 Err(MessageParseError::InvalidSubcommand {
1036 cmd: "CAP",
1037 sub: s.to_owned(),
1038 })
1039 }
1040 }
1041}
1042
1043#[derive(Clone, Copy, Debug, PartialEq)]
1046pub enum MetadataSubCommand {
1047 GET,
1049 LIST,
1051 SET,
1053 CLEAR,
1055}
1056
1057impl MetadataSubCommand {
1058 pub fn to_str(&self) -> &str {
1060 match *self {
1061 MetadataSubCommand::GET => "GET",
1062 MetadataSubCommand::LIST => "LIST",
1063 MetadataSubCommand::SET => "SET",
1064 MetadataSubCommand::CLEAR => "CLEAR",
1065 }
1066 }
1067}
1068
1069impl FromStr for MetadataSubCommand {
1070 type Err = MessageParseError;
1071
1072 fn from_str(s: &str) -> Result<MetadataSubCommand, Self::Err> {
1073 if s.eq_ignore_ascii_case("GET") {
1074 Ok(MetadataSubCommand::GET)
1075 } else if s.eq_ignore_ascii_case("LIST") {
1076 Ok(MetadataSubCommand::LIST)
1077 } else if s.eq_ignore_ascii_case("SET") {
1078 Ok(MetadataSubCommand::SET)
1079 } else if s.eq_ignore_ascii_case("CLEAR") {
1080 Ok(MetadataSubCommand::CLEAR)
1081 } else {
1082 Err(MessageParseError::InvalidSubcommand {
1083 cmd: "METADATA",
1084 sub: s.to_owned(),
1085 })
1086 }
1087 }
1088}
1089
1090#[derive(Clone, Debug, PartialEq)]
1092pub enum BatchSubCommand {
1093 NETSPLIT,
1095 NETJOIN,
1097 CUSTOM(String),
1099}
1100
1101impl BatchSubCommand {
1102 pub fn to_str(&self) -> &str {
1104 match *self {
1105 BatchSubCommand::NETSPLIT => "NETSPLIT",
1106 BatchSubCommand::NETJOIN => "NETJOIN",
1107 BatchSubCommand::CUSTOM(ref s) => s,
1108 }
1109 }
1110}
1111
1112impl FromStr for BatchSubCommand {
1113 type Err = MessageParseError;
1114
1115 fn from_str(s: &str) -> Result<BatchSubCommand, Self::Err> {
1116 if s.eq_ignore_ascii_case("NETSPLIT") {
1117 Ok(BatchSubCommand::NETSPLIT)
1118 } else if s.eq_ignore_ascii_case("NETJOIN") {
1119 Ok(BatchSubCommand::NETJOIN)
1120 } else {
1121 Ok(BatchSubCommand::CUSTOM(s.to_uppercase()))
1122 }
1123 }
1124}
1125
1126#[cfg(test)]
1127mod test {
1128 use super::Command;
1129 use super::Response;
1130 use crate::Message;
1131
1132 #[test]
1133 fn format_response() {
1134 assert!(
1135 String::from(&Command::Response(
1136 Response::RPL_WELCOME,
1137 vec!["foo".into()],
1138 )) == "001 foo"
1139 );
1140 }
1141
1142 #[test]
1143 fn user_round_trip() {
1144 let cmd = Command::USER("a".to_string(), "b".to_string(), "c".to_string());
1145 let line = Message::from(cmd.clone()).to_string();
1146 let returned_cmd = line.parse::<Message>().unwrap().command;
1147 assert_eq!(cmd, returned_cmd);
1148 }
1149
1150 #[test]
1151 fn parse_user_message() {
1152 let cmd = "USER a 0 * b".parse::<Message>().unwrap().command;
1153 assert_eq!(
1154 Command::USER("a".to_string(), "0".to_string(), "b".to_string()),
1155 cmd
1156 );
1157 }
1158}