use alloc::vec::Vec;
use bounded_static_derive::ToStatic;
use chumsky::prelude::*;
use crate::rfc5321::types::{reply_code::ReplyCode, text::Text, vec1::Vec1};
#[derive(Debug, Clone, PartialEq, Eq, Hash, ToStatic)]
pub struct Response<'a> {
pub code: ReplyCode,
pub lines: Vec1<Text<'a>>,
}
impl Response<'_> {
pub fn is_complete(buf: &[u8]) -> bool {
if !buf.ends_with(b"\r\n") {
return false;
}
let body = &buf[..buf.len() - 2]; let line_start = body
.iter()
.rposition(|&b| b == b'\n')
.map(|p| p + 1)
.unwrap_or(0);
let last_line = &body[line_start..];
last_line.len() >= 4 && last_line[3] == b' '
}
pub fn parse<'a>(buf: &'a [u8]) -> Result<Response<'a>, Vec<Rich<'a, u8>>> {
parsers::response().parse(buf).into_result()
}
pub fn new<'a>(code: ReplyCode, text: Text<'a>) -> Response<'a> {
Response {
code,
lines: Vec1::from(text),
}
}
pub fn new_multiline<'a>(code: ReplyCode, lines: Vec1<Text<'a>>) -> Response<'a> {
Response { code, lines }
}
pub fn is_success(&self) -> bool {
self.code.is_success()
}
pub fn is_error(&self) -> bool {
self.code.is_error()
}
pub fn text(&self) -> &Text<'_> {
&self.lines.as_ref()[0]
}
}
pub(crate) mod parsers {
use alloc::{borrow::Cow, vec::Vec};
use chumsky::prelude::*;
use crate::{
rfc5321::types::{
reply_code::parsers::reply_code as reply_code_parser,
response::Response,
text::{Text, parsers::text as text_parser},
vec1::Vec1,
},
utils::parsers::{Extra, crlf, sp},
};
pub(crate) fn response<'a>() -> impl Parser<'a, &'a [u8], Response<'a>, Extra<'a>> + Clone {
let cont = reply_code_parser()
.then_ignore(just(b'-'))
.then(text_parser().or_not())
.then_ignore(crlf());
let last = reply_code_parser()
.then_ignore(sp())
.then(text_parser().or_not())
.then_ignore(crlf());
cont.repeated()
.collect::<Vec<_>>()
.then(last)
.map(|(conts, (code, last_text))| {
let mut lines: Vec<Text> = conts
.into_iter()
.map(|(_, t)| t.unwrap_or_else(|| Text(Cow::Borrowed(""))))
.collect();
lines.push(last_text.unwrap_or_else(|| Text(Cow::Borrowed(""))));
let lines = Vec1::unvalidated(lines);
Response::new_multiline(code, lines)
})
.labelled("SMTP response")
}
}