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