freeswitch_types/commands/variables/
target.rs1use std::borrow::Cow;
8use std::fmt;
9use std::str::FromStr;
10
11use crate::commands::originate::OriginateError;
12use crate::switch_passes::api_argument::{
13 escape_argument, sole_argument, usable_argv_separator, write_escaped, ArgumentPass, ArgvCut,
14};
15use crate::switch_passes::separate::Token;
16use crate::switch_passes::{trace, untrace, Traced};
17use crate::version::FreeswitchVersion;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28#[non_exhaustive]
29pub enum DialStringCarrier {
30 EslApi,
33 Dialplan,
36}
37
38impl DialStringCarrier {
39 pub(crate) fn expands(self) -> bool {
42 matches!(self, Self::Dialplan)
43 }
44}
45
46#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
57#[non_exhaustive]
58pub enum BlockParse {
59 #[default]
62 PairSplitCleans,
63}
64
65impl BlockParse {
66 const ALL: &'static [Self] = &[Self::PairSplitCleans];
67
68 fn as_str(self) -> &'static str {
69 match self {
70 Self::PairSplitCleans => "pair_split_cleans",
71 }
72 }
73
74 pub(crate) fn cleanup_passes(self) -> u32 {
75 match self {
76 Self::PairSplitCleans => 2,
77 }
78 }
79
80 pub fn for_version(version: &FreeswitchVersion) -> Result<Self, UnvouchedVersion> {
87 let version = *version;
88 let (first, last) = VOUCHED_PAIR_SPLIT_CLEANS;
89 if version.is_dev() {
90 Err(UnvouchedVersion::Dev { version })
91 } else if version < first {
92 Err(UnvouchedVersion::OlderThanVouched { version })
93 } else if version > last {
94 Err(UnvouchedVersion::NewerThanVouched { version })
95 } else {
96 Ok(Self::PairSplitCleans)
97 }
98 }
99}
100
101const VOUCHED_PAIR_SPLIT_CLEANS: (FreeswitchVersion, FreeswitchVersion) = (
102 FreeswitchVersion::new(1, 10, 0),
103 FreeswitchVersion::new(1, 11, 3),
104);
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108#[non_exhaustive]
109pub enum UnvouchedVersion {
110 Dev {
112 version: FreeswitchVersion,
114 },
115 NewerThanVouched {
117 version: FreeswitchVersion,
119 },
120 OlderThanVouched {
122 version: FreeswitchVersion,
124 },
125}
126
127impl UnvouchedVersion {
128 pub fn version(&self) -> FreeswitchVersion {
130 match *self {
131 Self::Dev { version }
132 | Self::NewerThanVouched { version }
133 | Self::OlderThanVouched { version } => version,
134 }
135 }
136}
137
138impl fmt::Display for UnvouchedVersion {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 let (first, last) = VOUCHED_PAIR_SPLIT_CLEANS;
141 let why = match self {
142 Self::Dev { .. } => "is a development build",
143 Self::NewerThanVouched { .. } => "is newer than any vouched release",
144 Self::OlderThanVouched { .. } => "is older than any vouched release",
145 };
146 write!(
147 f,
148 "FreeSWITCH {} {why}; releases {first} to {last} are vouched for, name a BlockParse explicitly",
149 self.version()
150 )
151 }
152}
153
154impl std::error::Error for UnvouchedVersion {}
155
156impl fmt::Display for BlockParse {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.write_str(self.as_str())
159 }
160}
161
162impl FromStr for BlockParse {
163 type Err = ParseBlockParseError;
164
165 fn from_str(s: &str) -> Result<Self, Self::Err> {
166 Self::ALL
167 .iter()
168 .copied()
169 .find(|revision| {
170 revision
171 .as_str()
172 .eq_ignore_ascii_case(s)
173 })
174 .ok_or_else(|| ParseBlockParseError(s.to_string()))
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct ParseBlockParseError(pub String);
181
182impl fmt::Display for ParseBlockParseError {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 f.write_str("unknown block-parse revision, expected one of:")?;
185 for revision in BlockParse::ALL {
186 write!(f, " {revision}")?;
187 }
188 Ok(())
189 }
190}
191
192impl std::error::Error for ParseBlockParseError {}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub struct DialStringTarget {
198 carrier: DialStringCarrier,
199 block_parse: BlockParse,
200 argument: ArgumentPass,
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205#[non_exhaustive]
206pub enum InvalidArgvSeparator {
207 WrongCarrier(DialStringCarrier),
209 Unusable(char),
211}
212
213impl fmt::Display for InvalidArgvSeparator {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 match self {
216 Self::WrongCarrier(carrier) => {
217 write!(f, "the {carrier:?} carrier takes no argument separator")
218 }
219 Self::Unusable(sep) => write!(f, "{sep:?} cannot separate originate's arguments"),
220 }
221 }
222}
223
224impl std::error::Error for InvalidArgvSeparator {}
225
226impl DialStringTarget {
227 pub fn new(carrier: DialStringCarrier) -> Self {
229 Self {
230 carrier,
231 block_parse: BlockParse::default(),
232 argument: ArgumentPass::Blank,
233 }
234 }
235
236 pub fn with_argv_separator(mut self, sep: char) -> Result<Self, InvalidArgvSeparator> {
246 match self.carrier {
247 DialStringCarrier::EslApi => {}
248 DialStringCarrier::Dialplan => {
249 return Err(InvalidArgvSeparator::WrongCarrier(self.carrier))
250 }
251 }
252 if !usable_argv_separator(sep) {
253 return Err(InvalidArgvSeparator::Unusable(sep));
254 }
255 self.argument = ArgumentPass::Char(sep);
256 Ok(self)
257 }
258
259 pub fn argv_separator(&self) -> Option<char> {
261 match self.argument {
262 ArgumentPass::Char(sep) => Some(sep),
263 ArgumentPass::Blank | ArgumentPass::Consumed => None,
264 }
265 }
266
267 pub fn escape_argument<'a>(&self, text: &'a str) -> Option<Cow<'a, str>> {
273 self.split_delimiter()
274 .and_then(|sep| escape_argument(text, sep))
275 }
276
277 pub(crate) fn write_argument(
279 self,
280 f: &mut fmt::Formatter<'_>,
281 render: impl Fn(&mut fmt::Formatter<'_>, Self) -> fmt::Result,
282 ) -> fmt::Result {
283 match self.argv_separator() {
284 Some(sep) => write_escaped(
285 f,
286 sep,
287 RenderedAt {
288 target: self.inner(),
289 render: &render,
290 },
291 ),
292 None => render(f, self),
293 }
294 }
295
296 #[cfg(test)]
299 pub(crate) fn with_unchecked_argv_separator(mut self, sep: char) -> Self {
300 self.argument = ArgumentPass::Char(sep);
301 self
302 }
303
304 fn split_delimiter(self) -> Option<char> {
306 match (self.carrier, self.argument) {
307 (DialStringCarrier::EslApi, ArgumentPass::Blank) => Some(' '),
308 (_, ArgumentPass::Char(sep)) => Some(sep),
309 (DialStringCarrier::Dialplan, ArgumentPass::Blank) | (_, ArgumentPass::Consumed) => {
310 None
311 }
312 }
313 }
314
315 pub(crate) fn split_argument(self, text: &[Traced]) -> Option<Result<Option<Token>, ArgvCut>> {
317 self.split_delimiter()
318 .map(|delim| sole_argument(text, delim))
319 }
320
321 pub(crate) fn inner(mut self) -> Self {
323 if self
324 .split_delimiter()
325 .is_some()
326 {
327 self.argument = ArgumentPass::Consumed;
328 }
329 self
330 }
331
332 pub(crate) fn read_argument(self, s: &str) -> Result<(Cow<'_, str>, Self), OriginateError> {
334 let Some(token) = self.split_argument(&trace(s)) else {
335 return Ok((Cow::Borrowed(s), self));
336 };
337 let token = token.map_err(|ArgvCut| {
338 OriginateError::ParseError("originate's argument split cuts the dial string".into())
339 })?;
340 let argument = token.map_or_else(String::new, |token| untrace(&token.text));
341 Ok((Cow::Owned(argument), self.inner()))
342 }
343
344 pub fn with_block_parse(mut self, block_parse: BlockParse) -> Self {
346 self.block_parse = block_parse;
347 self
348 }
349
350 pub fn carrier(&self) -> DialStringCarrier {
352 self.carrier
353 }
354
355 pub fn block_parse(&self) -> BlockParse {
357 self.block_parse
358 }
359
360 pub(crate) fn argument_passes(self) -> u32 {
362 match self.argument {
363 ArgumentPass::Blank | ArgumentPass::Char(_) => 1,
364 ArgumentPass::Consumed => 0,
365 }
366 }
367}
368
369struct RenderedAt<'r, R> {
370 target: DialStringTarget,
371 render: &'r R,
372}
373
374impl<R> fmt::Display for RenderedAt<'_, R>
375where
376 R: Fn(&mut fmt::Formatter<'_>, DialStringTarget) -> fmt::Result,
377{
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 (self.render)(f, self.target)
380 }
381}
382
383impl From<DialStringCarrier> for DialStringTarget {
384 fn from(carrier: DialStringCarrier) -> Self {
385 Self::new(carrier)
386 }
387}