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