sieve/compiler/grammar/tests/
test_envelope.rs1use crate::{
8 Envelope,
9 compiler::{
10 CompileError, ErrorType, Value,
11 grammar::{Capability, Comparator, instruction::CompilerState},
12 lexer::{Token, word::Word},
13 },
14};
15
16use crate::compiler::grammar::{AddressPart, MatchType, test::Test};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(
20 any(test, feature = "serde"),
21 derive(serde::Serialize, serde::Deserialize)
22)]
23#[cfg_attr(
24 feature = "rkyv",
25 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
26)]
27pub(crate) struct TestEnvelope {
28 pub envelope_list: Box<[Envelope]>,
29 pub key_list: Box<[Value]>,
30 pub address_part: AddressPart,
31 pub match_type: MatchType,
32 pub comparator: Comparator,
33 pub zone: Option<i64>,
34 pub is_not: bool,
35}
36
37impl CompilerState<'_> {
38 pub(crate) fn parse_test_envelope(&mut self) -> Result<Test, CompileError> {
39 let mut address_part = AddressPart::All;
40 let mut match_type = MatchType::Is;
41 let mut comparator = Comparator::AsciiCaseMap;
42 let mut envelope_list = None;
43 let key_list;
44 let mut zone = None;
45
46 loop {
47 let mut token_info = self.tokens.unwrap_next()?;
48 match token_info.token {
49 Token::Tag(
50 word @ (Word::LocalPart | Word::Domain | Word::All | Word::User | Word::Detail),
51 ) => {
52 self.validate_argument(
53 1,
54 if matches!(word, Word::User | Word::Detail) {
55 Capability::SubAddress.into()
56 } else {
57 None
58 },
59 token_info.line_num,
60 token_info.line_pos,
61 )?;
62 address_part = word.into();
63 }
64 Token::Tag(
65 word @ (Word::Is
66 | Word::Contains
67 | Word::Matches
68 | Word::Value
69 | Word::Count
70 | Word::Regex
71 | Word::List),
72 ) => {
73 self.validate_argument(
74 2,
75 match word {
76 Word::Value | Word::Count => Capability::Relational.into(),
77 Word::Regex => Capability::Regex.into(),
78 Word::List => Capability::ExtLists.into(),
79 _ => None,
80 },
81 token_info.line_num,
82 token_info.line_pos,
83 )?;
84
85 match_type = self.parse_match_type(word)?;
86 }
87 Token::Tag(Word::Comparator) => {
88 self.validate_argument(3, None, token_info.line_num, token_info.line_pos)?;
89 comparator = self.parse_comparator()?;
90 }
91 Token::Tag(Word::Zone) => {
92 self.validate_argument(
93 4,
94 Capability::EnvelopeDeliverBy.into(),
95 token_info.line_num,
96 token_info.line_pos,
97 )?;
98 zone = self.parse_timezone()?.into();
99 }
100 _ => {
101 if envelope_list.is_none() {
102 let mut envelopes = Vec::new();
103 let line_num = token_info.line_num;
104 let line_pos = token_info.line_pos;
105
106 match token_info.token {
107 Token::StringConstant(s) => {
108 match Envelope::try_from(s.into_string().to_ascii_lowercase()) {
109 Ok(envelope) => {
110 envelopes.push(envelope);
111 }
112 Err(invalid) => {
113 token_info.token = Token::Comma;
114 return Err(
115 token_info.custom(ErrorType::InvalidEnvelope(invalid))
116 );
117 }
118 }
119 }
120 Token::BracketOpen => loop {
121 let mut token_info = self.tokens.unwrap_next()?;
122 match token_info.token {
123 Token::StringConstant(s) => {
124 match Envelope::try_from(
125 s.into_string().to_ascii_lowercase(),
126 ) {
127 Ok(envelope) => {
128 if !envelopes.contains(&envelope) {
129 envelopes.push(envelope);
130 }
131 }
132 Err(invalid) => {
133 token_info.token = Token::Comma;
134 return Err(token_info
135 .custom(ErrorType::InvalidEnvelope(invalid)));
136 }
137 }
138 }
139 Token::Comma => (),
140 Token::BracketClose if !envelopes.is_empty() => break,
141 _ => return Err(token_info.expected("constant string")),
142 }
143 },
144 _ => return Err(token_info.expected("constant string")),
145 }
146
147 for envelope in &envelopes {
148 match envelope {
149 Envelope::ByTimeAbsolute
150 | Envelope::ByTimeRelative
151 | Envelope::ByMode
152 | Envelope::ByTrace => {
153 self.validate_argument(
154 0,
155 Capability::EnvelopeDeliverBy.into(),
156 line_num,
157 line_pos,
158 )?;
159 }
160
161 Envelope::Notify
162 | Envelope::Orcpt
163 | Envelope::Ret
164 | Envelope::Envid => {
165 self.validate_argument(
166 0,
167 Capability::EnvelopeDsn.into(),
168 line_num,
169 line_pos,
170 )?;
171 }
172 _ => (),
173 }
174 }
175
176 envelope_list = envelopes.into();
177 } else {
178 key_list = self.parse_raw_strings_token(token_info)?;
179 break;
180 }
181 }
182 }
183 }
184 let key_list = self.validate_match(&match_type, &comparator, key_list)?;
185
186 Ok(Test::Envelope(Box::new(TestEnvelope {
187 envelope_list: envelope_list.unwrap().into(),
188 key_list: key_list.into(),
189 address_part,
190 match_type,
191 comparator,
192 zone,
193 is_not: false,
194 })))
195 }
196}
197
198impl TryFrom<String> for Envelope {
199 type Error = String;
200
201 fn try_from(value: String) -> Result<Self, Self::Error> {
202 if let Some(envelope) = lookup_envelope(&value) {
203 Ok(envelope)
204 } else {
205 Err(value)
206 }
207 }
208}
209
210impl<'x> TryFrom<&'x str> for Envelope {
211 type Error = &'x str;
212
213 fn try_from(value: &'x str) -> Result<Self, Self::Error> {
214 if let Some(envelope) = lookup_envelope(value) {
215 Ok(envelope)
216 } else {
217 Err(value)
218 }
219 }
220}
221
222fn lookup_envelope(input: &str) -> Option<Envelope> {
223 hashify::tiny_map!(
224 input.as_bytes(),
225 "from" => Envelope::From,
226 "to" => Envelope::To,
227 "bytimeabsolute" => Envelope::ByTimeAbsolute,
228 "bytimerelative" => Envelope::ByTimeRelative,
229 "bymode" => Envelope::ByMode,
230 "bytrace" => Envelope::ByTrace,
231 "notify" => Envelope::Notify,
232 "orcpt" => Envelope::Orcpt,
233 "ret" => Envelope::Ret,
234 "envid" => Envelope::Envid,
235 )
236}