1use super::*;
4
5#[derive(Clone, Copy, Debug, Default, serde::Deserialize, Eq, PartialEq, Serialize)]
8pub enum PhoneKeypadTarget {
9 #[default]
10 #[serde(rename = "application")]
11 Application,
12 #[serde(rename = "applicationCall")]
13 ApplicationCall,
14 #[serde(rename = "activeCall")]
15 ActiveCall,
16}
17
18#[derive(Clone, Copy, Debug, serde::Deserialize, Eq, PartialEq, Serialize)]
20pub enum PhoneXmlKey {
21 KeyPad0,
22 KeyPad1,
23 KeyPad2,
24 KeyPad3,
25 KeyPad4,
26 KeyPad5,
27 KeyPad6,
28 KeyPad7,
29 KeyPad8,
30 KeyPad9,
31 KeyPadStar,
32 KeyPadPound,
33 NavUp,
34 NavDown,
35 NavLeft,
36 NavRight,
37 NavSelect,
38 NavBack,
39 PushToTalk,
40}
41
42#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, Serialize)]
44#[serde(deny_unknown_fields)]
45pub struct CiscoIpPhoneSoftKeyItem {
46 #[serde(rename = "Name", default, skip_serializing_if = "Option::is_none")]
47 pub name: Option<String>,
48 #[serde(rename = "Position")]
49 pub position: PhoneSoftKeyPosition,
50 #[serde(rename = "URL", default, skip_serializing_if = "Option::is_none")]
51 pub url: Option<String>,
52 #[serde(rename = "URLDown", default, skip_serializing_if = "Option::is_none")]
53 pub url_down: Option<String>,
54}
55
56#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
60pub struct PhoneSoftKeyPosition(i8);
61
62impl PhoneSoftKeyPosition {
63 pub const APPLICATION: Self = Self(-1);
65
66 pub fn new(value: i16) -> Result<Self, PhoneXmlError> {
68 if value == -1 || (1..=16).contains(&value) {
69 i8::try_from(value)
70 .map(Self)
71 .map_err(|_| PhoneXmlError::InvalidField {
72 field: "phone soft-key position",
73 expected: "-1 or between 1 and 16",
74 })
75 } else {
76 Err(PhoneXmlError::InvalidField {
77 field: "phone soft-key position",
78 expected: "-1 or between 1 and 16",
79 })
80 }
81 }
82
83 pub const fn get(self) -> i8 {
85 self.0
86 }
87}
88
89impl Serialize for PhoneSoftKeyPosition {
90 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
91 where
92 S: serde::Serializer,
93 {
94 serializer.serialize_i8(self.0)
95 }
96}
97
98impl<'de> serde::Deserialize<'de> for PhoneSoftKeyPosition {
99 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
100 where
101 D: serde::Deserializer<'de>,
102 {
103 let value = <i16 as serde::Deserialize>::deserialize(deserializer)?;
104 Self::new(value).map_err(serde::de::Error::custom)
105 }
106}
107
108#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, Serialize)]
110#[serde(deny_unknown_fields)]
111pub struct CiscoIpPhoneKeyItem {
112 #[serde(rename = "Key")]
113 pub key: PhoneXmlKey,
114 #[serde(rename = "URL", default, skip_serializing_if = "Option::is_none")]
115 pub url: Option<String>,
116 #[serde(rename = "URLDown", default, skip_serializing_if = "Option::is_none")]
117 pub url_down: Option<String>,
118}
119
120#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
122pub struct PhoneServicePriority(u8);
123
124impl PhoneServicePriority {
125 pub const LOW: Self = Self(0);
126 pub const NORMAL: Self = Self(1);
127 pub const HIGH: Self = Self(2);
128
129 pub fn new(value: u32) -> Result<Self, PhoneXmlError> {
131 if value > u32::from(Self::HIGH.0) {
132 return Err(PhoneXmlError::InvalidField {
133 field: "phone-service display priority",
134 expected: "between 0 and 2",
135 });
136 }
137 u8::try_from(value)
138 .map(Self)
139 .map_err(|_| PhoneXmlError::InvalidField {
140 field: "phone-service display priority",
141 expected: "between 0 and 2",
142 })
143 }
144
145 pub fn wire(self) -> u32 {
146 u32::from(self.0)
147 }
148}
149
150impl Default for PhoneServicePriority {
151 fn default() -> Self {
152 Self::NORMAL
153 }
154}
155
156#[derive(Clone, Debug, Eq, PartialEq)]
158pub struct PhoneXmlRefresh {
159 delay_seconds: u32,
160 url: String,
161}
162
163impl PhoneXmlRefresh {
164 pub fn new(delay_seconds: u32, url: impl Into<String>) -> Result<Self, PhoneXmlError> {
166 let refresh = Self {
167 delay_seconds,
168 url: url.into(),
169 };
170 validate_optional_text(
171 "phone XML refresh URL",
172 Some(&refresh.url),
173 1,
174 PHONE_XML_URL_MAX_CHARS,
175 )?;
176 if !refresh.url.is_ascii()
177 || refresh
178 .url
179 .chars()
180 .any(|character| character.is_ascii_whitespace() || character.is_ascii_control())
181 {
182 return Err(PhoneXmlError::InvalidField {
183 field: "phone XML refresh URL",
184 expected: "an ASCII URL between 1 and 256 characters",
185 });
186 }
187 Ok(refresh)
188 }
189
190 pub const fn delay_seconds(&self) -> u32 {
191 self.delay_seconds
192 }
193
194 pub fn url(&self) -> &str {
195 &self.url
196 }
197
198 pub fn http_header_value(&self) -> String {
200 format!("{};url={}", self.delay_seconds, self.url)
201 }
202}
203
204#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, Serialize)]
206#[serde(rename = "CiscoIPPhoneText", deny_unknown_fields)]
207pub struct CiscoIpPhoneText {
208 #[serde(
209 rename = "@keypadTarget",
210 default,
211 skip_serializing_if = "Option::is_none"
212 )]
213 pub keypad_target: Option<PhoneKeypadTarget>,
214 #[serde(rename = "@appId", default, skip_serializing_if = "Option::is_none")]
215 pub application_id: Option<String>,
216 #[serde(
217 rename = "@onAppFocusLost",
218 default,
219 skip_serializing_if = "Option::is_none"
220 )]
221 pub on_focus_lost: Option<String>,
222 #[serde(
223 rename = "@onAppFocusGained",
224 default,
225 skip_serializing_if = "Option::is_none"
226 )]
227 pub on_focus_gained: Option<String>,
228 #[serde(
229 rename = "@onAppMinimized",
230 default,
231 skip_serializing_if = "Option::is_none"
232 )]
233 pub on_minimized: Option<String>,
234 #[serde(
235 rename = "@onAppClosed",
236 default,
237 skip_serializing_if = "Option::is_none"
238 )]
239 pub on_closed: Option<String>,
240 #[serde(rename = "Title", default, skip_serializing_if = "Option::is_none")]
241 pub title: Option<String>,
242 #[serde(rename = "Prompt", default, skip_serializing_if = "Option::is_none")]
243 pub prompt: Option<String>,
244 #[serde(rename = "SoftKeyItem", default)]
245 pub soft_keys: Vec<CiscoIpPhoneSoftKeyItem>,
246 #[serde(rename = "KeyItem", default)]
247 pub key_items: Vec<CiscoIpPhoneKeyItem>,
248 #[serde(rename = "Text", default, skip_serializing_if = "Option::is_none")]
249 pub text: Option<String>,
250}
251
252impl CiscoIpPhoneText {
253 pub fn new(
255 title: impl Into<String>,
256 prompt: impl Into<String>,
257 text: impl Into<String>,
258 ) -> Result<Self, PhoneXmlError> {
259 let document = Self {
260 keypad_target: None,
261 application_id: None,
262 on_focus_lost: None,
263 on_focus_gained: None,
264 on_minimized: None,
265 on_closed: None,
266 title: Some(title.into()),
267 prompt: Some(prompt.into()),
268 soft_keys: Vec::new(),
269 key_items: Vec::new(),
270 text: Some(text.into()),
271 };
272 document.validate()?;
273 Ok(document)
274 }
275
276 pub fn validate(&self) -> Result<(), PhoneXmlError> {
278 validate_displayable(
279 self.title.as_deref(),
280 self.prompt.as_deref(),
281 self.application_id.as_deref(),
282 [
283 self.on_focus_lost.as_deref(),
284 self.on_focus_gained.as_deref(),
285 self.on_minimized.as_deref(),
286 self.on_closed.as_deref(),
287 ],
288 &self.soft_keys,
289 &self.key_items,
290 )?;
291 validate_optional_text(
292 "phone text body",
293 self.text.as_deref(),
294 0,
295 PHONE_TEXT_MAX_CHARS,
296 )
297 }
298}
299
300#[derive(Clone, Copy, Debug, serde::Deserialize, Eq, PartialEq, Serialize)]
305pub enum PhoneInputFlags {
306 #[serde(rename = "A")]
307 Alphabetic,
308 #[serde(rename = "T")]
309 Telephone,
310 #[serde(rename = "N")]
311 Numeric,
312 #[serde(rename = "E")]
313 Equation,
314 #[serde(rename = "U")]
315 Uppercase,
316 #[serde(rename = "L")]
317 Lowercase,
318 #[serde(rename = "AP")]
319 AlphabeticPassword,
320 #[serde(rename = "TP")]
321 TelephonePassword,
322 #[serde(rename = "NP")]
323 NumericPassword,
324 #[serde(rename = "EP")]
325 EquationPassword,
326 #[serde(rename = "UP")]
327 UppercasePassword,
328 #[serde(rename = "LP")]
329 LowercasePassword,
330 #[serde(rename = "PA")]
331 PasswordAlphabetic,
332 #[serde(rename = "PT")]
333 PasswordTelephone,
334 #[serde(rename = "PN")]
335 PasswordNumeric,
336 #[serde(rename = "PE")]
337 PasswordEquation,
338 #[serde(rename = "PU")]
339 PasswordUppercase,
340 #[serde(rename = "PL")]
341 PasswordLowercase,
342}
343
344impl PhoneInputFlags {
345 pub const ALL: [Self; 18] = [
347 Self::Alphabetic,
348 Self::Telephone,
349 Self::Numeric,
350 Self::Equation,
351 Self::Uppercase,
352 Self::Lowercase,
353 Self::AlphabeticPassword,
354 Self::TelephonePassword,
355 Self::NumericPassword,
356 Self::EquationPassword,
357 Self::UppercasePassword,
358 Self::LowercasePassword,
359 Self::PasswordAlphabetic,
360 Self::PasswordTelephone,
361 Self::PasswordNumeric,
362 Self::PasswordEquation,
363 Self::PasswordUppercase,
364 Self::PasswordLowercase,
365 ];
366}
367
368#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
370#[serde(transparent)]
371pub struct PhoneInputParameterName(String);
372
373impl PhoneInputParameterName {
374 pub fn new(value: impl Into<String>) -> Result<Self, PhoneXmlError> {
376 let value = value.into();
377 validate_optional_text("phone input parameter name", Some(&value), 1, 32)?;
378 Ok(Self(value))
379 }
380
381 pub fn as_str(&self) -> &str {
382 &self.0
383 }
384}
385
386impl<'de> serde::Deserialize<'de> for PhoneInputParameterName {
387 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
388 where
389 D: serde::Deserializer<'de>,
390 {
391 let value = <String as serde::Deserialize>::deserialize(deserializer)?;
392 Self::new(value).map_err(serde::de::Error::custom)
393 }
394}
395
396#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, Serialize)]
398#[serde(deny_unknown_fields)]
399pub struct CiscoIpPhoneInputItem {
400 #[serde(
401 rename = "DisplayName",
402 default,
403 skip_serializing_if = "Option::is_none"
404 )]
405 pub display_name: Option<String>,
406 #[serde(rename = "QueryStringParam")]
407 pub parameter: PhoneInputParameterName,
408 #[serde(rename = "InputFlags")]
409 pub flags: PhoneInputFlags,
410 #[serde(
411 rename = "DefaultValue",
412 default,
413 skip_serializing_if = "Option::is_none"
414 )]
415 pub default_value: Option<String>,
416}
417
418#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, Serialize)]
420#[serde(rename = "CiscoIPPhoneInput", deny_unknown_fields)]
421pub struct CiscoIpPhoneInput {
422 #[serde(
423 rename = "@keypadTarget",
424 default,
425 skip_serializing_if = "Option::is_none"
426 )]
427 pub keypad_target: Option<PhoneKeypadTarget>,
428 #[serde(rename = "@appId", default, skip_serializing_if = "Option::is_none")]
429 pub application_id: Option<String>,
430 #[serde(
431 rename = "@onAppFocusLost",
432 default,
433 skip_serializing_if = "Option::is_none"
434 )]
435 pub on_focus_lost: Option<String>,
436 #[serde(
437 rename = "@onAppFocusGained",
438 default,
439 skip_serializing_if = "Option::is_none"
440 )]
441 pub on_focus_gained: Option<String>,
442 #[serde(
443 rename = "@onAppMinimized",
444 default,
445 skip_serializing_if = "Option::is_none"
446 )]
447 pub on_minimized: Option<String>,
448 #[serde(
449 rename = "@onAppClosed",
450 default,
451 skip_serializing_if = "Option::is_none"
452 )]
453 pub on_closed: Option<String>,
454 #[serde(rename = "Title", default, skip_serializing_if = "Option::is_none")]
455 pub title: Option<String>,
456 #[serde(rename = "Prompt", default, skip_serializing_if = "Option::is_none")]
457 pub prompt: Option<String>,
458 #[serde(rename = "SoftKeyItem", default)]
459 pub soft_keys: Vec<CiscoIpPhoneSoftKeyItem>,
460 #[serde(rename = "KeyItem", default)]
461 pub key_items: Vec<CiscoIpPhoneKeyItem>,
462 #[serde(rename = "URL")]
463 pub url: String,
464 #[serde(rename = "InputItem", default)]
465 pub items: Vec<CiscoIpPhoneInputItem>,
466}
467
468impl CiscoIpPhoneInput {
469 pub fn new(
471 title: impl Into<String>,
472 prompt: impl Into<String>,
473 url: impl Into<String>,
474 items: Vec<CiscoIpPhoneInputItem>,
475 ) -> Result<Self, PhoneXmlError> {
476 let document = Self {
477 keypad_target: None,
478 application_id: None,
479 on_focus_lost: None,
480 on_focus_gained: None,
481 on_minimized: None,
482 on_closed: None,
483 title: Some(title.into()),
484 prompt: Some(prompt.into()),
485 soft_keys: Vec::new(),
486 key_items: Vec::new(),
487 url: url.into(),
488 items,
489 };
490 document.validate()?;
491 Ok(document)
492 }
493
494 pub fn validate(&self) -> Result<(), PhoneXmlError> {
496 validate_displayable(
497 self.title.as_deref(),
498 self.prompt.as_deref(),
499 self.application_id.as_deref(),
500 [
501 self.on_focus_lost.as_deref(),
502 self.on_focus_gained.as_deref(),
503 self.on_minimized.as_deref(),
504 self.on_closed.as_deref(),
505 ],
506 &self.soft_keys,
507 &self.key_items,
508 )?;
509 validate_optional_text(
510 "phone input submission URL",
511 Some(&self.url),
512 1,
513 PHONE_XML_URL_MAX_CHARS,
514 )?;
515 validate_count(
516 "phone input fields",
517 self.items.len(),
518 PHONE_INPUT_MAX_ITEMS,
519 )?;
520 for item in &self.items {
521 validate_optional_text(
522 "phone input display name",
523 item.display_name.as_deref(),
524 0,
525 32,
526 )?;
527 validate_optional_text(
528 "phone input default value",
529 item.default_value.as_deref(),
530 0,
531 32,
532 )?;
533 }
534 Ok(())
535 }
536}
537
538#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
540pub struct PhoneExecutePriority(u8);
541
542impl PhoneExecutePriority {
543 pub const LOW: Self = Self(0);
544 pub const NORMAL: Self = Self(1);
545 pub const HIGH: Self = Self(2);
546
547 pub fn new(value: u8) -> Result<Self, PhoneXmlError> {
549 if value > Self::HIGH.0 {
550 return Err(PhoneXmlError::InvalidField {
551 field: "phone execute priority",
552 expected: "between 0 and 2",
553 });
554 }
555 Ok(Self(value))
556 }
557
558 pub const fn wire(self) -> u8 {
559 self.0
560 }
561}
562
563impl Serialize for PhoneExecutePriority {
564 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
565 where
566 S: serde::Serializer,
567 {
568 serializer.serialize_u8(self.0)
569 }
570}
571
572impl<'de> serde::Deserialize<'de> for PhoneExecutePriority {
573 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
574 where
575 D: serde::Deserializer<'de>,
576 {
577 let value = <u8 as serde::Deserialize>::deserialize(deserializer)?;
578 Self::new(value).map_err(serde::de::Error::custom)
579 }
580}
581
582#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
584pub enum PhoneActionKind {
585 Http,
587 Internal,
589}
590
591#[derive(Clone, Debug, Eq, Hash, PartialEq)]
593pub struct PhoneExecuteUrl {
594 value: String,
595 kind: PhoneActionKind,
596}
597
598impl PhoneExecuteUrl {
599 pub fn new(value: impl Into<String>) -> Result<Self, PhoneXmlError> {
601 let value = value.into();
602 validate_optional_text(
603 "phone execute URL",
604 Some(&value),
605 1,
606 PHONE_XML_URL_MAX_CHARS,
607 )?;
608 let kind = action_kind(&value);
609 Ok(Self { value, kind })
610 }
611
612 pub fn as_str(&self) -> &str {
613 &self.value
614 }
615
616 pub const fn kind(&self) -> PhoneActionKind {
617 self.kind
618 }
619}
620
621impl Serialize for PhoneExecuteUrl {
622 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
623 where
624 S: serde::Serializer,
625 {
626 serializer.serialize_str(&self.value)
627 }
628}
629
630impl<'de> serde::Deserialize<'de> for PhoneExecuteUrl {
631 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
632 where
633 D: serde::Deserializer<'de>,
634 {
635 let value = <String as serde::Deserialize>::deserialize(deserializer)?;
636 Self::new(value).map_err(serde::de::Error::custom)
637 }
638}
639
640#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, Serialize)]
642#[serde(deny_unknown_fields)]
643pub struct CiscoIpPhoneExecuteItem {
644 #[serde(rename = "@Priority", default, skip_serializing_if = "Option::is_none")]
645 pub priority: Option<PhoneExecutePriority>,
646 #[serde(rename = "@URL")]
647 pub url: PhoneExecuteUrl,
648}
649
650impl CiscoIpPhoneExecuteItem {
651 pub fn new(url: impl Into<String>) -> Result<Self, PhoneXmlError> {
653 Ok(Self {
654 priority: None,
655 url: PhoneExecuteUrl::new(url)?,
656 })
657 }
658
659 pub fn with_priority(
661 url: impl Into<String>,
662 priority: PhoneExecutePriority,
663 ) -> Result<Self, PhoneXmlError> {
664 Ok(Self {
665 priority: Some(priority),
666 url: PhoneExecuteUrl::new(url)?,
667 })
668 }
669}
670
671#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, Serialize)]
673#[serde(rename = "CiscoIPPhoneExecute", deny_unknown_fields)]
674pub struct CiscoIpPhoneExecute {
675 #[serde(rename = "ExecuteItem", default)]
676 pub items: Vec<CiscoIpPhoneExecuteItem>,
677}
678
679impl CiscoIpPhoneExecute {
680 pub fn new(items: Vec<CiscoIpPhoneExecuteItem>) -> Result<Self, PhoneXmlError> {
682 let document = Self { items };
683 document.validate()?;
684 Ok(document)
685 }
686
687 pub fn validate(&self) -> Result<(), PhoneXmlError> {
689 if self.items.is_empty() {
690 return Err(PhoneXmlError::InvalidField {
691 field: "phone execute actions",
692 expected: "between 1 and 3 entries",
693 });
694 }
695 validate_count(
696 "phone execute actions",
697 self.items.len(),
698 PHONE_EXECUTE_MAX_ITEMS,
699 )?;
700 if self
701 .items
702 .iter()
703 .filter(|item| item.url.kind() == PhoneActionKind::Http)
704 .count()
705 > 1
706 {
707 return Err(PhoneXmlError::InvalidField {
708 field: "phone execute HTTP actions",
709 expected: "at most one HTTP or HTTPS action",
710 });
711 }
712 Ok(())
713 }
714}