freeswitch_types/commands/
execute_on.rs1use std::fmt;
5
6use super::originate::OriginateError;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct ExecuteOn {
22 app: String,
23 arg: Option<String>,
24}
25
26impl ExecuteOn {
27 pub fn new(
30 app: impl Into<String>,
31 arg: Option<impl Into<String>>,
32 ) -> Result<Self, OriginateError> {
33 let app = app.into();
34 let arg = arg.map(Into::into);
35 if app.is_empty() {
36 return Err(OriginateError::ParseError(
37 "execute_on application name is empty".into(),
38 ));
39 }
40 if app.contains([' ', ':']) {
41 return Err(OriginateError::ParseError(format!(
42 "execute_on application name {app:?} carries a space or colon, where the \
43 switch splits the name from its argument"
44 )));
45 }
46 if let Some(arg) = &arg {
47 if arg.contains("${") {
48 return Err(OriginateError::ParseError(format!(
49 "execute_on argument for {app} carries `${{`, which the switch expands \
50 before the application sees it; pass a path, not content"
51 )));
52 }
53 if arg.contains(['\n', '\r']) {
54 return Err(OriginateError::ParseError(format!(
55 "execute_on argument for {app} carries a line break"
56 )));
57 }
58 }
59 Ok(Self { app, arg })
60 }
61
62 pub fn lua(
65 script: impl AsRef<str>,
66 args: impl IntoIterator<Item = impl AsRef<str>>,
67 ) -> Result<Self, OriginateError> {
68 let mut words = vec![script
69 .as_ref()
70 .to_string()];
71 words.extend(
72 args.into_iter()
73 .map(|a| {
74 a.as_ref()
75 .to_string()
76 }),
77 );
78 if let Some(word) = words
79 .iter()
80 .find(|w| w.is_empty() || w.contains(' '))
81 {
82 return Err(OriginateError::ParseError(format!(
83 "lua argument {word:?} is empty or carries a space, on which mod_lua splits \
84 its argument list"
85 )));
86 }
87 Self::new("lua", Some(words.join(" ")))
88 }
89
90 pub fn app(&self) -> &str {
92 &self.app
93 }
94
95 pub fn arg(&self) -> Option<&str> {
97 self.arg
98 .as_deref()
99 }
100}
101
102impl fmt::Display for ExecuteOn {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 match &self.arg {
105 Some(arg) => write!(f, "{} {}", self.app, arg),
106 None => f.write_str(&self.app),
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn renders_app_and_argument() {
117 let hook = ExecuteOn::new("set", Some("probe=hooked")).unwrap();
118 assert_eq!(hook.to_string(), "set probe=hooked");
119 assert_eq!(hook.app(), "set");
120 assert_eq!(hook.arg(), Some("probe=hooked"));
121
122 let bare = ExecuteOn::new("answer", None::<String>).unwrap();
123 assert_eq!(bare.to_string(), "answer");
124 assert_eq!(bare.arg(), None);
125 }
126
127 #[test]
128 fn lua_joins_script_and_arguments() {
129 let hook = ExecuteOn::lua("/run/app/load.lua", ["/run/app/doc.xml", "pidf"]).unwrap();
130 assert_eq!(
131 hook.to_string(),
132 "lua /run/app/load.lua /run/app/doc.xml pidf"
133 );
134 }
135
136 #[test]
139 fn refuses_a_name_the_switch_would_split() {
140 assert!(ExecuteOn::new("lua:x", None::<String>).is_err());
141 assert!(ExecuteOn::new("my app", None::<String>).is_err());
142 assert!(ExecuteOn::new("", None::<String>).is_err());
143 }
144
145 #[test]
146 fn refuses_an_argument_the_switch_would_expand_or_the_wire_cannot_carry() {
147 assert!(ExecuteOn::new("set", Some("k=${v}")).is_err());
148 assert!(ExecuteOn::new("set", Some("k=a\nb")).is_err());
149 }
150
151 #[test]
152 fn lua_refuses_a_path_with_a_space() {
153 assert!(ExecuteOn::lua("/run/my app/load.lua", ["/run/doc.xml"]).is_err());
154 assert!(ExecuteOn::lua("/run/load.lua", ["/run/my doc.xml"]).is_err());
155 assert!(ExecuteOn::lua("/run/load.lua", [""]).is_err());
156 }
157}