use std::borrow::Cow;
use std::fmt;
use std::str::FromStr;
use crate::commands::originate::OriginateError;
use crate::switch_passes::api_argument::{
escape_argument, sole_argument, usable_argv_separator, write_escaped, ArgumentPass, ArgvCut,
};
use crate::switch_passes::separate::Token;
use crate::switch_passes::{trace, untrace, Traced};
use crate::version::FreeswitchVersion;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum DialStringCarrier {
EslApi,
Dialplan,
}
impl DialStringCarrier {
pub(crate) fn expands(self) -> bool {
matches!(self, Self::Dialplan)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[non_exhaustive]
pub enum BlockParse {
#[default]
PairSplitCleans,
}
impl BlockParse {
const ALL: &'static [Self] = &[Self::PairSplitCleans];
fn as_str(self) -> &'static str {
match self {
Self::PairSplitCleans => "pair_split_cleans",
}
}
pub(crate) fn cleanup_passes(self) -> u32 {
match self {
Self::PairSplitCleans => 2,
}
}
pub fn for_version(version: &FreeswitchVersion) -> Result<Self, UnvouchedVersion> {
let version = *version;
let (first, last) = VOUCHED_PAIR_SPLIT_CLEANS;
if version.is_dev() {
Err(UnvouchedVersion::Dev { version })
} else if version < first {
Err(UnvouchedVersion::OlderThanVouched { version })
} else if version > last {
Err(UnvouchedVersion::NewerThanVouched { version })
} else {
Ok(Self::PairSplitCleans)
}
}
}
const VOUCHED_PAIR_SPLIT_CLEANS: (FreeswitchVersion, FreeswitchVersion) = (
FreeswitchVersion::new(1, 10, 0),
FreeswitchVersion::new(1, 11, 3),
);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnvouchedVersion {
Dev {
version: FreeswitchVersion,
},
NewerThanVouched {
version: FreeswitchVersion,
},
OlderThanVouched {
version: FreeswitchVersion,
},
}
impl UnvouchedVersion {
pub fn version(&self) -> FreeswitchVersion {
match *self {
Self::Dev { version }
| Self::NewerThanVouched { version }
| Self::OlderThanVouched { version } => version,
}
}
}
impl fmt::Display for UnvouchedVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (first, last) = VOUCHED_PAIR_SPLIT_CLEANS;
let why = match self {
Self::Dev { .. } => "is a development build",
Self::NewerThanVouched { .. } => "is newer than any vouched release",
Self::OlderThanVouched { .. } => "is older than any vouched release",
};
write!(
f,
"FreeSWITCH {} {why}; releases {first} to {last} are vouched for, name a BlockParse explicitly",
self.version()
)
}
}
impl std::error::Error for UnvouchedVersion {}
impl fmt::Display for BlockParse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for BlockParse {
type Err = ParseBlockParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::ALL
.iter()
.copied()
.find(|revision| {
revision
.as_str()
.eq_ignore_ascii_case(s)
})
.ok_or_else(|| ParseBlockParseError(s.to_string()))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseBlockParseError(pub String);
impl fmt::Display for ParseBlockParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("unknown block-parse revision, expected one of:")?;
for revision in BlockParse::ALL {
write!(f, " {revision}")?;
}
Ok(())
}
}
impl std::error::Error for ParseBlockParseError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DialStringTarget {
carrier: DialStringCarrier,
block_parse: BlockParse,
argument: ArgumentPass,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum InvalidArgvSeparator {
WrongCarrier(DialStringCarrier),
Unusable(char),
}
impl fmt::Display for InvalidArgvSeparator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::WrongCarrier(carrier) => {
write!(f, "the {carrier:?} carrier takes no argument separator")
}
Self::Unusable(sep) => write!(f, "{sep:?} cannot separate originate's arguments"),
}
}
}
impl std::error::Error for InvalidArgvSeparator {}
impl DialStringTarget {
pub fn new(carrier: DialStringCarrier) -> Self {
Self {
carrier,
block_parse: BlockParse::default(),
argument: ArgumentPass::Blank,
}
}
pub fn with_argv_separator(mut self, sep: char) -> Result<Self, InvalidArgvSeparator> {
match self.carrier {
DialStringCarrier::EslApi => {}
DialStringCarrier::Dialplan => {
return Err(InvalidArgvSeparator::WrongCarrier(self.carrier))
}
}
if !usable_argv_separator(sep) {
return Err(InvalidArgvSeparator::Unusable(sep));
}
self.argument = ArgumentPass::Char(sep);
Ok(self)
}
pub fn argv_separator(&self) -> Option<char> {
match self.argument {
ArgumentPass::Char(sep) => Some(sep),
ArgumentPass::Blank | ArgumentPass::Consumed => None,
}
}
pub fn escape_argument<'a>(&self, text: &'a str) -> Option<Cow<'a, str>> {
self.split_delimiter()
.and_then(|sep| escape_argument(text, sep))
}
pub(crate) fn write_argument(
self,
f: &mut fmt::Formatter<'_>,
render: impl Fn(&mut fmt::Formatter<'_>, Self) -> fmt::Result,
) -> fmt::Result {
match self.argv_separator() {
Some(sep) => write_escaped(
f,
sep,
RenderedAt {
target: self.inner(),
render: &render,
},
),
None => render(f, self),
}
}
#[cfg(test)]
pub(crate) fn with_unchecked_argv_separator(mut self, sep: char) -> Self {
self.argument = ArgumentPass::Char(sep);
self
}
fn split_delimiter(self) -> Option<char> {
match (self.carrier, self.argument) {
(DialStringCarrier::EslApi, ArgumentPass::Blank) => Some(' '),
(_, ArgumentPass::Char(sep)) => Some(sep),
(DialStringCarrier::Dialplan, ArgumentPass::Blank) | (_, ArgumentPass::Consumed) => {
None
}
}
}
pub(crate) fn split_argument(self, text: &[Traced]) -> Option<Result<Option<Token>, ArgvCut>> {
self.split_delimiter()
.map(|delim| sole_argument(text, delim))
}
pub(crate) fn inner(mut self) -> Self {
if self
.split_delimiter()
.is_some()
{
self.argument = ArgumentPass::Consumed;
}
self
}
pub(crate) fn read_argument(self, s: &str) -> Result<(Cow<'_, str>, Self), OriginateError> {
let Some(token) = self.split_argument(&trace(s)) else {
return Ok((Cow::Borrowed(s), self));
};
let token = token.map_err(|ArgvCut| {
OriginateError::ParseError("originate's argument split cuts the dial string".into())
})?;
let argument = token.map_or_else(String::new, |token| untrace(&token.text));
Ok((Cow::Owned(argument), self.inner()))
}
pub fn with_block_parse(mut self, block_parse: BlockParse) -> Self {
self.block_parse = block_parse;
self
}
pub fn carrier(&self) -> DialStringCarrier {
self.carrier
}
pub fn block_parse(&self) -> BlockParse {
self.block_parse
}
pub(crate) fn argument_passes(self) -> u32 {
match self.argument {
ArgumentPass::Blank | ArgumentPass::Char(_) => 1,
ArgumentPass::Consumed => 0,
}
}
}
struct RenderedAt<'r, R> {
target: DialStringTarget,
render: &'r R,
}
impl<R> fmt::Display for RenderedAt<'_, R>
where
R: Fn(&mut fmt::Formatter<'_>, DialStringTarget) -> fmt::Result,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(self.render)(f, self.target)
}
}
impl From<DialStringCarrier> for DialStringTarget {
fn from(carrier: DialStringCarrier) -> Self {
Self::new(carrier)
}
}