io_smtp/rfc5321/types/
response.rs1use alloc::vec::Vec;
7
8use bounded_static_derive::ToStatic;
9use chumsky::prelude::*;
10
11use crate::rfc5321::types::{reply_code::SmtpReplyCode, text::SmtpText, vec1::SmtpVec1};
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
15pub struct SmtpResponse<'a> {
16 pub code: SmtpReplyCode,
18 pub lines: SmtpVec1<SmtpText<'a>>,
20}
21
22impl SmtpResponse<'_> {
23 pub fn is_complete(buf: &[u8]) -> bool {
28 if !buf.ends_with(b"\r\n") {
29 return false;
30 }
31
32 let body = &buf[..buf.len() - 2];
33 let line_start = body
34 .iter()
35 .rposition(|&b| b == b'\n')
36 .map(|p| p + 1)
37 .unwrap_or(0);
38
39 let last_line = &body[line_start..];
40 last_line.len() >= 4 && last_line[3] == b' '
41 }
42
43 pub fn parse<'a>(buf: &'a [u8]) -> Result<SmtpResponse<'a>, Vec<Rich<'a, u8>>> {
45 parsers::response().parse(buf).into_result()
46 }
47
48 pub fn new<'a>(code: SmtpReplyCode, text: SmtpText<'a>) -> SmtpResponse<'a> {
50 SmtpResponse {
51 code,
52 lines: SmtpVec1::from(text),
53 }
54 }
55
56 pub fn new_multiline<'a>(
58 code: SmtpReplyCode,
59 lines: SmtpVec1<SmtpText<'a>>,
60 ) -> SmtpResponse<'a> {
61 SmtpResponse { code, lines }
62 }
63
64 pub fn is_success(&self) -> bool {
66 self.code.is_success()
67 }
68
69 pub fn is_error(&self) -> bool {
71 self.code.is_error()
72 }
73
74 pub fn text(&self) -> &SmtpText<'_> {
76 &self.lines.as_ref()[0]
77 }
78}
79
80pub(crate) mod parsers {
81 use alloc::{borrow::Cow, vec::Vec};
84
85 use chumsky::prelude::*;
86
87 use crate::{
88 rfc5321::types::{
89 reply_code::parsers::reply_code as reply_code_parser,
90 response::SmtpResponse,
91 text::{SmtpText, parsers::text as text_parser},
92 vec1::SmtpVec1,
93 },
94 utils::parsers::{Extra, crlf, sp},
95 };
96
97 pub(crate) fn response<'a>() -> impl Parser<'a, &'a [u8], SmtpResponse<'a>, Extra<'a>> + Clone {
106 let cont = reply_code_parser()
108 .then_ignore(just(b'-'))
109 .then(text_parser().or_not())
110 .then_ignore(crlf());
111 let last = reply_code_parser()
113 .then_ignore(sp())
114 .then(text_parser().or_not())
115 .then_ignore(crlf());
116
117 cont.repeated()
118 .collect::<Vec<_>>()
119 .then(last)
120 .map(|(conts, (code, last_text))| {
121 let mut lines: Vec<SmtpText> = conts
122 .into_iter()
123 .map(|(_, t)| t.unwrap_or(SmtpText(Cow::Borrowed(""))))
124 .collect();
125 lines.push(last_text.unwrap_or(SmtpText(Cow::Borrowed(""))));
126 let lines = SmtpVec1::unvalidated(lines);
127 SmtpResponse::new_multiline(code, lines)
128 })
129 .labelled("SMTP response")
130 }
131}