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 Direct,
40}
41
42impl DialStringCarrier {
43 pub(crate) fn expands(self) -> bool {
46 matches!(self, Self::Dialplan)
47 }
48}
49
50#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
61#[non_exhaustive]
62pub enum BlockParse {
63 #[default]
66 PairSplitCleans,
67}
68
69impl BlockParse {
70 const ALL: &'static [Self] = &[Self::PairSplitCleans];
71
72 fn as_str(self) -> &'static str {
73 match self {
74 Self::PairSplitCleans => "pair_split_cleans",
75 }
76 }
77
78 pub(crate) fn cleanup_passes(self) -> u32 {
79 match self {
80 Self::PairSplitCleans => 2,
81 }
82 }
83
84 pub fn for_version(version: &FreeswitchVersion) -> Result<Self, UnvouchedVersion> {
91 let version = *version;
92 let (first, last) = VOUCHED_PAIR_SPLIT_CLEANS;
93 if version.is_dev() {
94 Err(UnvouchedVersion::Dev { version })
95 } else if version < first {
96 Err(UnvouchedVersion::OlderThanVouched { version })
97 } else if version > last {
98 Err(UnvouchedVersion::NewerThanVouched { version })
99 } else {
100 Ok(Self::PairSplitCleans)
101 }
102 }
103}
104
105const VOUCHED_PAIR_SPLIT_CLEANS: (FreeswitchVersion, FreeswitchVersion) = (
106 FreeswitchVersion::new(1, 10, 0),
107 FreeswitchVersion::new(1, 11, 3),
108);
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112#[non_exhaustive]
113pub enum UnvouchedVersion {
114 Dev {
116 version: FreeswitchVersion,
118 },
119 NewerThanVouched {
121 version: FreeswitchVersion,
123 },
124 OlderThanVouched {
126 version: FreeswitchVersion,
128 },
129}
130
131impl UnvouchedVersion {
132 pub fn version(&self) -> FreeswitchVersion {
134 match *self {
135 Self::Dev { version }
136 | Self::NewerThanVouched { version }
137 | Self::OlderThanVouched { version } => version,
138 }
139 }
140}
141
142impl fmt::Display for UnvouchedVersion {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 let (first, last) = VOUCHED_PAIR_SPLIT_CLEANS;
145 let why = match self {
146 Self::Dev { .. } => "is a development build",
147 Self::NewerThanVouched { .. } => "is newer than any vouched release",
148 Self::OlderThanVouched { .. } => "is older than any vouched release",
149 };
150 write!(
151 f,
152 "FreeSWITCH {} {why}; releases {first} to {last} are vouched for, name a BlockParse explicitly",
153 self.version()
154 )
155 }
156}
157
158impl std::error::Error for UnvouchedVersion {}
159
160impl fmt::Display for BlockParse {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 f.write_str(self.as_str())
163 }
164}
165
166impl FromStr for BlockParse {
167 type Err = ParseBlockParseError;
168
169 fn from_str(s: &str) -> Result<Self, Self::Err> {
170 Self::ALL
171 .iter()
172 .copied()
173 .find(|revision| {
174 revision
175 .as_str()
176 .eq_ignore_ascii_case(s)
177 })
178 .ok_or_else(|| ParseBlockParseError(s.to_string()))
179 }
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct ParseBlockParseError(pub String);
185
186impl fmt::Display for ParseBlockParseError {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 f.write_str("unknown block-parse revision, expected one of:")?;
189 for revision in BlockParse::ALL {
190 write!(f, " {revision}")?;
191 }
192 Ok(())
193 }
194}
195
196impl std::error::Error for ParseBlockParseError {}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub struct DialStringTarget {
202 carrier: DialStringCarrier,
203 block_parse: BlockParse,
204 argument: ArgumentPass,
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209#[non_exhaustive]
210pub enum InvalidArgvSeparator {
211 WrongCarrier(DialStringCarrier),
213 Unusable(char),
215}
216
217impl fmt::Display for InvalidArgvSeparator {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 match self {
220 Self::WrongCarrier(carrier) => {
221 write!(f, "the {carrier:?} carrier takes no argument separator")
222 }
223 Self::Unusable(sep) => write!(f, "{sep:?} cannot separate originate's arguments"),
224 }
225 }
226}
227
228impl std::error::Error for InvalidArgvSeparator {}
229
230impl DialStringTarget {
231 pub fn new(carrier: DialStringCarrier) -> Self {
233 Self {
234 carrier,
235 block_parse: BlockParse::default(),
236 argument: ArgumentPass::Blank,
237 }
238 }
239
240 pub fn with_argv_separator(mut self, sep: char) -> Result<Self, InvalidArgvSeparator> {
250 match self.carrier {
251 DialStringCarrier::EslApi => {}
252 DialStringCarrier::Dialplan | DialStringCarrier::Direct => {
253 return Err(InvalidArgvSeparator::WrongCarrier(self.carrier))
254 }
255 }
256 if !usable_argv_separator(sep) {
257 return Err(InvalidArgvSeparator::Unusable(sep));
258 }
259 self.argument = ArgumentPass::Char(sep);
260 Ok(self)
261 }
262
263 pub fn argv_separator(&self) -> Option<char> {
265 match self.argument {
266 ArgumentPass::Char(sep) => Some(sep),
267 ArgumentPass::Blank | ArgumentPass::Consumed => None,
268 }
269 }
270
271 pub fn escape_argument<'a>(&self, text: &'a str) -> Option<Cow<'a, str>> {
277 self.split_delimiter()
278 .and_then(|sep| escape_argument(text, sep))
279 }
280
281 pub(crate) fn write_argument(
283 self,
284 f: &mut fmt::Formatter<'_>,
285 render: impl Fn(&mut fmt::Formatter<'_>, Self) -> fmt::Result,
286 ) -> fmt::Result {
287 match self.argv_separator() {
288 Some(sep) => write_escaped(
289 f,
290 sep,
291 RenderedAt {
292 target: self.inner(),
293 render: &render,
294 },
295 ),
296 None => render(f, self),
297 }
298 }
299
300 #[cfg(test)]
303 pub(crate) fn with_unchecked_argv_separator(mut self, sep: char) -> Self {
304 self.argument = ArgumentPass::Char(sep);
305 self
306 }
307
308 fn split_delimiter(self) -> Option<char> {
310 match (self.carrier, self.argument) {
311 (DialStringCarrier::EslApi, ArgumentPass::Blank) => Some(' '),
312 (_, ArgumentPass::Char(sep)) => Some(sep),
313 (DialStringCarrier::Dialplan | DialStringCarrier::Direct, ArgumentPass::Blank)
314 | (_, ArgumentPass::Consumed) => None,
315 }
316 }
317
318 pub(crate) fn split_argument(self, text: &[Traced]) -> Option<Result<Option<Token>, ArgvCut>> {
320 self.split_delimiter()
321 .map(|delim| sole_argument(text, delim))
322 }
323
324 pub(crate) fn inner(mut self) -> Self {
326 if self
327 .split_delimiter()
328 .is_some()
329 {
330 self.argument = ArgumentPass::Consumed;
331 }
332 self
333 }
334
335 pub(crate) fn read_argument(self, s: &str) -> Result<(Cow<'_, str>, Self), OriginateError> {
337 let Some(token) = self.split_argument(&trace(s)) else {
338 return Ok((Cow::Borrowed(s), self));
339 };
340 let token = token.map_err(|ArgvCut| {
341 OriginateError::ParseError("originate's argument split cuts the dial string".into())
342 })?;
343 let argument = token.map_or_else(String::new, |token| untrace(&token.text));
344 Ok((Cow::Owned(argument), self.inner()))
345 }
346
347 pub fn with_block_parse(mut self, block_parse: BlockParse) -> Self {
349 self.block_parse = block_parse;
350 self
351 }
352
353 pub fn carrier(&self) -> DialStringCarrier {
355 self.carrier
356 }
357
358 pub fn block_parse(&self) -> BlockParse {
360 self.block_parse
361 }
362
363 pub(crate) fn argument_passes(self) -> u32 {
366 match (self.carrier, self.argument) {
367 (DialStringCarrier::Direct, _) | (_, ArgumentPass::Consumed) => 0,
368 (_, ArgumentPass::Blank | ArgumentPass::Char(_)) => 1,
369 }
370 }
371}
372
373struct RenderedAt<'r, R> {
374 target: DialStringTarget,
375 render: &'r R,
376}
377
378impl<R> fmt::Display for RenderedAt<'_, R>
379where
380 R: Fn(&mut fmt::Formatter<'_>, DialStringTarget) -> fmt::Result,
381{
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 (self.render)(f, self.target)
384 }
385}
386
387impl From<DialStringCarrier> for DialStringTarget {
388 fn from(carrier: DialStringCarrier) -> Self {
389 Self::new(carrier)
390 }
391}