1use std::fmt;
9use std::ops::Range;
10use std::sync::Arc;
11
12use crate::channel::HangupCause;
13use crate::commands::endpoint::Endpoint;
14use crate::commands::originate::OriginateError;
15use crate::commands::variables::{DialStringTarget, Variables, VariablesType};
16use crate::switch_passes::brackets::{install, same_header, Block, Pair, PairEffect};
17use crate::switch_passes::escape::{escape_text, EscapedField};
18use crate::switch_passes::expansion::names_a_variable;
19use crate::switch_passes::originate_legs::{
20 channel_variables, DialList, Leg, PastLimit, Thread, ENTERPRISE_DELIM, MAX_PEERS,
21};
22use crate::switch_passes::{pipeline, PipelineError};
23use crate::variables::VariableName;
24
25#[cfg(test)]
26mod tests;
27
28#[cfg(all(test, feature = "serde"))]
29mod c_oracle;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct FlattenedDialString {
34 head: String,
35 blocks: Vec<Block>,
36 threads: Vec<FlattenedThread>,
37 tail: String,
38 warnings: Vec<ListWarning>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct FlattenedThread {
44 separator: String,
45 head: String,
46 blocks: Vec<Block>,
47 groups: Vec<FlattenedGroup>,
48 trailer: String,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct FlattenedGroup {
54 separator: String,
55 legs: Vec<FlattenedLeg>,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct FlattenedLeg {
61 separator: String,
62 raw: String,
63 leg: Leg,
64 inherited: Arc<[Block]>,
65 nested_vars: bool,
66 target: LegTarget,
67 warnings: Vec<LegWarning>,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72#[non_exhaustive]
73pub enum LegTarget {
74 Error(ErrorLeg),
76 Endpoint(Endpoint),
78 Unparsed(UnparsedLeg),
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct ErrorLeg {
85 as_written: String,
86 reading: CauseReading,
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct UnparsedLeg {
92 endpoint: String,
93 error: OriginateError,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98#[non_exhaustive]
99pub enum CauseReading {
100 Name(HangupCause),
102 Number(u32),
104 Unrecognized,
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
110#[non_exhaustive]
111pub enum LegWarning {
112 PairIgnored {
114 block: usize,
116 key: String,
118 },
119 PairCleared {
121 block: usize,
123 key: String,
125 },
126 NestedVarsRefused {
129 block: usize,
131 key: String,
133 },
134 BlockSeparatorUnreadable {
137 block: usize,
139 },
140 BlockRewritesFollowingText {
143 block: usize,
145 },
146 PairUnreadable {
149 block: usize,
151 },
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156#[non_exhaustive]
157pub enum ListWarning {
158 QuoteSpansLegs,
160 CarrierExpands,
163 BlockSeparatorUnreadable {
166 block: usize,
168 },
169 BlockRewritesFollowingText {
172 block: usize,
174 },
175 LegsPastLimit {
178 thread: Option<usize>,
181 group: Option<usize>,
184 excess: usize,
187 },
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192#[non_exhaustive]
193pub enum FlattenedDialStringError {
194 Empty,
196 ArgvSplit,
200 UnclosedBlock {
202 leg: usize,
204 },
205 TrailingNewline,
207 SplitSeparatorUnreadable,
210}
211
212#[derive(Debug, Clone, Copy)]
215pub struct FlattenedDialStringDisplay<'a> {
216 list: &'a FlattenedDialString,
217 render: Render,
218}
219
220#[derive(Debug, Clone, Copy)]
221enum Render {
222 Raw,
223 For(DialStringTarget),
224}
225
226impl FlattenedDialString {
227 pub fn parse_for(
229 input: &str,
230 target: impl Into<DialStringTarget>,
231 ) -> Result<Self, FlattenedDialStringError> {
232 if input.ends_with('\n') {
233 return Err(FlattenedDialStringError::TrailingNewline);
234 }
235 let list = pipeline::read(input, target.into())
236 .map_err(FlattenedDialStringError::from_pipeline)?;
237 let DialList {
238 blocks,
239 threads,
240 quote_spans_legs,
241 carrier_expands,
242 past_limit,
243 } = list;
244 let past_limit: Vec<ListWarning> = past_limit
245 .into_iter()
246 .map(|cut| ListWarning::past_limit(&threads, cut))
247 .collect();
248 let unreadable = blocks
249 .iter()
250 .chain(
251 threads
252 .iter()
253 .flat_map(|thread| &thread.blocks),
254 )
255 .enumerate()
256 .filter_map(|(block, parsed)| {
257 if parsed.separator_unreadable() {
258 Some(ListWarning::BlockSeparatorUnreadable { block })
259 } else {
260 parsed
261 .rewrites_following_text
262 .then_some(ListWarning::BlockRewritesFollowingText { block })
263 }
264 });
265 let warnings = [
266 (quote_spans_legs, ListWarning::QuoteSpansLegs),
267 (carrier_expands, ListWarning::CarrierExpands),
268 ]
269 .into_iter()
270 .filter_map(|(raised, warning)| raised.then_some(warning))
271 .chain(unreadable)
272 .chain(past_limit)
273 .collect();
274 let mut kept: Vec<(Range<usize>, FlattenedThread)> = Vec::new();
275 for thread in threads {
276 let raw = thread
277 .raw
278 .clone();
279 let separator_start = kept
280 .last()
281 .map_or(raw.start, |(before, _)| before.end);
282 if let Some(read) = FlattenedThread::read(input, thread, &blocks, separator_start) {
283 kept.push((raw, read));
284 }
285 }
286 let (Some((first, _)), Some((last, _))) = (kept.first(), kept.last()) else {
287 return Err(FlattenedDialStringError::Empty);
288 };
289 Ok(Self {
290 head: slice(input, 0..first.start),
291 tail: slice(input, last.end..input.len()),
292 blocks,
293 threads: kept
294 .into_iter()
295 .map(|(_, thread)| thread)
296 .collect(),
297 warnings,
298 })
299 }
300
301 pub fn threads(&self) -> impl Iterator<Item = &FlattenedThread> {
303 self.threads
304 .iter()
305 }
306
307 pub fn legs(&self) -> impl Iterator<Item = &FlattenedLeg> {
309 self.threads
310 .iter()
311 .flat_map(FlattenedThread::legs)
312 }
313
314 pub fn variables(&self) -> impl Iterator<Item = (&str, &str)> {
318 install(&self.blocks).into_iter()
319 }
320
321 pub fn warnings(&self) -> &[ListWarning] {
323 &self.warnings
324 }
325
326 pub fn retain(&mut self, mut keep: impl FnMut(&FlattenedLeg) -> bool) {
328 for thread in &mut self.threads {
329 for group in &mut thread.groups {
330 group
331 .legs
332 .retain(&mut keep);
333 }
334 thread
335 .groups
336 .retain(|group| {
337 !group
338 .legs
339 .is_empty()
340 });
341 }
342 self.threads
343 .retain(|thread| {
344 !thread
345 .groups
346 .is_empty()
347 });
348 }
349
350 pub fn is_empty(&self) -> bool {
352 self.threads
353 .is_empty()
354 }
355
356 fn write_raw(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 if self.is_empty() {
358 return Ok(());
359 }
360 f.write_str(&self.head)?;
361 for (t, thread) in self
362 .threads
363 .iter()
364 .enumerate()
365 {
366 if t > 0 {
367 f.write_str(&thread.separator)?;
368 }
369 f.write_str(&thread.head)?;
370 for (g, group) in thread
371 .groups
372 .iter()
373 .enumerate()
374 {
375 if g > 0 {
376 f.write_str(&group.separator)?;
377 }
378 for (l, leg) in group
379 .legs
380 .iter()
381 .enumerate()
382 {
383 if l > 0 {
384 f.write_str(&leg.separator)?;
385 }
386 f.write_str(&leg.raw)?;
387 }
388 }
389 f.write_str(&thread.trailer)?;
390 }
391 f.write_str(&self.tail)
392 }
393
394 fn write_for(&self, f: &mut fmt::Formatter<'_>, target: DialStringTarget) -> fmt::Result {
395 if self.is_empty() {
396 return Ok(());
397 }
398 write_blocks(f, &self.blocks, target)?;
399 for (t, thread) in self
400 .threads
401 .iter()
402 .enumerate()
403 {
404 if t > 0 {
405 f.write_str(ENTERPRISE_DELIM)?;
406 }
407 write_blocks(f, &thread.blocks, target)?;
408 for (g, group) in thread
409 .groups
410 .iter()
411 .enumerate()
412 {
413 if g > 0 {
414 f.write_str("|")?;
415 }
416 for (l, leg) in group
417 .legs
418 .iter()
419 .enumerate()
420 {
421 if l > 0 {
422 f.write_str(",")?;
423 }
424 write_blocks(
425 f,
426 &leg.leg
427 .blocks,
428 target,
429 )?;
430 f.write_str(&escape_text(
431 &leg.leg
432 .endpoint,
433 target,
434 EscapedField::Endpoint,
435 ))?;
436 }
437 }
438 }
439 Ok(())
440 }
441
442 pub fn display_raw(&self) -> FlattenedDialStringDisplay<'_> {
445 FlattenedDialStringDisplay {
446 list: self,
447 render: Render::Raw,
448 }
449 }
450
451 pub fn display_for(
454 &self,
455 target: impl Into<DialStringTarget>,
456 ) -> FlattenedDialStringDisplay<'_> {
457 FlattenedDialStringDisplay {
458 list: self,
459 render: Render::For(target.into()),
460 }
461 }
462}
463
464impl fmt::Display for FlattenedDialStringDisplay<'_> {
465 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
466 match self.render {
467 Render::Raw => self
468 .list
469 .write_raw(f),
470 Render::For(target) => target.write_argument(f, |f, target| {
471 self.list
472 .write_for(f, target)
473 }),
474 }
475 }
476}
477
478fn write_blocks(
480 f: &mut fmt::Formatter<'_>,
481 blocks: &[Block],
482 target: DialStringTarget,
483) -> fmt::Result {
484 for block in blocks {
485 let mut vars = Variables::new(VariablesType::of_block(block));
486 for pair in &block.pairs {
487 if let PairEffect::Set(value) = &pair.effect {
488 vars.insert(
489 pair.key
490 .as_str(),
491 value.as_str(),
492 );
493 }
494 }
495 if vars.is_empty() {
496 continue;
497 }
498 let vars = match block.separator {
501 ',' => vars,
502 sep => vars
503 .clone()
504 .with_separator(sep)
505 .unwrap_or(vars),
506 };
507 write!(f, "{}", vars.display_for(target))?;
508 }
509 Ok(())
510}
511
512fn slice(input: &str, range: Range<usize>) -> String {
514 debug_assert!(input
515 .get(range.clone())
516 .is_some());
517 input
518 .get(range)
519 .unwrap_or_default()
520 .to_owned()
521}
522
523impl FlattenedThread {
524 fn read(
525 input: &str,
526 thread: Thread,
527 list_blocks: &[Block],
528 separator_start: usize,
529 ) -> Option<Self> {
530 let Thread {
531 raw,
532 blocks,
533 groups: pipeline_groups,
534 nested_vars,
535 } = thread;
536 let inherited: Arc<[Block]> = list_blocks
537 .iter()
538 .chain(&blocks)
539 .cloned()
540 .collect();
541 let mut first_start = None;
542 let mut last_end = None;
543 let mut groups = Vec::new();
544 for group in pipeline_groups {
545 let mut separator = None;
546 let mut legs = Vec::with_capacity(group.len());
547 let mut leg_end = None;
548 for leg in group {
549 let start = leg
550 .raw
551 .start;
552 separator.get_or_insert_with(|| slice(input, last_end.unwrap_or(start)..start));
553 first_start.get_or_insert(start);
554 let leg_separator = slice(input, leg_end.unwrap_or(start)..start);
555 leg_end = Some(
556 leg.raw
557 .end,
558 );
559 legs.push(FlattenedLeg::read(
560 input,
561 leg,
562 leg_separator,
563 &inherited,
564 nested_vars,
565 ));
566 }
567 let Some(separator) = separator else {
568 continue;
569 };
570 last_end = leg_end;
571 groups.push(FlattenedGroup { separator, legs });
572 }
573 let (start, end) = first_start.zip(last_end)?;
574 Some(Self {
575 separator: slice(input, separator_start..raw.start),
576 head: slice(input, raw.start..start),
577 blocks,
578 groups,
579 trailer: slice(input, end..raw.end),
580 })
581 }
582
583 pub fn variables(&self) -> impl Iterator<Item = (&str, &str)> {
587 install(&self.blocks).into_iter()
588 }
589
590 pub fn groups(&self) -> impl Iterator<Item = &FlattenedGroup> {
592 self.groups
593 .iter()
594 }
595
596 pub fn legs(&self) -> impl Iterator<Item = &FlattenedLeg> {
598 self.groups
599 .iter()
600 .flat_map(FlattenedGroup::legs)
601 }
602}
603
604impl FlattenedGroup {
605 pub fn legs(&self) -> impl Iterator<Item = &FlattenedLeg> {
607 self.legs
608 .iter()
609 }
610}
611
612impl FlattenedLeg {
613 fn read(
614 input: &str,
615 leg: Leg,
616 separator: String,
617 inherited: &Arc<[Block]>,
618 nested_vars: bool,
619 ) -> Self {
620 let warnings = leg
621 .blocks
622 .iter()
623 .enumerate()
624 .flat_map(|(block, parsed)| {
625 parsed
626 .separator_unreadable()
627 .then_some(LegWarning::BlockSeparatorUnreadable { block })
628 .into_iter()
629 .chain(
630 parsed
631 .rewrites_following_text
632 .then_some(LegWarning::BlockRewritesFollowingText { block }),
633 )
634 .chain(
635 parsed
636 .pairs
637 .iter()
638 .filter_map(move |pair| LegWarning::of(block, pair, nested_vars)),
639 )
640 })
641 .collect();
642 Self {
643 separator,
644 raw: slice(
645 input,
646 leg.raw
647 .clone(),
648 ),
649 target: LegTarget::read(&leg.endpoint),
650 leg,
651 inherited: Arc::clone(inherited),
652 nested_vars,
653 warnings,
654 }
655 }
656
657 pub fn raw(&self) -> &str {
659 &self.raw
660 }
661
662 pub fn variable(&self, name: impl VariableName) -> Option<&str> {
670 self.variable_str(name.as_str())
671 }
672
673 pub fn variable_str(&self, name: &str) -> Option<&str> {
688 self.variables()
689 .find(|(held, _)| same_header(held, name))
690 .map(|(_, value)| value)
691 }
692
693 pub fn variables(&self) -> impl Iterator<Item = (&str, &str)> {
700 channel_variables(
701 self.inherited
702 .iter(),
703 &self.leg,
704 self.nested_vars,
705 )
706 .into_iter()
707 }
708
709 pub fn target(&self) -> &LegTarget {
711 &self.target
712 }
713
714 pub fn warnings(&self) -> &[LegWarning] {
716 &self.warnings
717 }
718}
719
720impl ErrorLeg {
721 pub fn as_written(&self) -> &str {
723 &self.as_written
724 }
725
726 pub fn reading(&self) -> CauseReading {
728 self.reading
729 }
730
731 pub fn cause(&self) -> Option<HangupCause> {
737 match self.reading {
738 CauseReading::Name(cause) => Some(cause),
739 CauseReading::Number(number) => u16::try_from(number)
740 .ok()
741 .and_then(HangupCause::from_number),
742 CauseReading::Unrecognized => None,
743 }
744 }
745}
746
747impl LegTarget {
748 fn read(endpoint: &str) -> Self {
749 if let Some(cause) = endpoint.strip_prefix("error/") {
750 return Self::Error(ErrorLeg {
751 as_written: cause.to_owned(),
752 reading: str2cause(cause),
753 });
754 }
755 match Endpoint::parse_bare(endpoint) {
756 Ok(parsed) => Self::Endpoint(parsed),
757 Err(error) => Self::Unparsed(UnparsedLeg {
758 endpoint: endpoint.to_owned(),
759 error,
760 }),
761 }
762 }
763}
764
765fn str2cause(text: &str) -> CauseReading {
767 if text.starts_with(|c: char| c.is_ascii_digit()) {
768 let long = text
770 .bytes()
771 .take_while(u8::is_ascii_digit)
772 .fold(0i128, |n, digit| {
773 (n * 10 + i128::from(digit - b'0')).min(i128::from(i64::MAX))
774 });
775 return CauseReading::Number(long as i64 as u32);
776 }
777 text.to_ascii_uppercase()
778 .parse::<HangupCause>()
779 .map_or(CauseReading::Unrecognized, CauseReading::Name)
780}
781
782impl LegWarning {
783 fn of(block: usize, pair: &Pair, nested_vars: bool) -> Option<Self> {
784 let key = || {
785 pair.key
786 .clone()
787 };
788 match &pair.effect {
789 PairEffect::Ignored | PairEffect::Valueless => {
790 Some(Self::PairIgnored { block, key: key() })
791 }
792 PairEffect::Unreadable => Some(Self::PairUnreadable { block }),
793 PairEffect::Cleared => Some(Self::PairCleared { block, key: key() }),
794 PairEffect::Set(value) if !nested_vars && names_a_variable(value) => {
795 Some(Self::NestedVarsRefused { block, key: key() })
796 }
797 PairEffect::Set(_) => None,
798 }
799 }
800}
801
802impl ListWarning {
803 fn past_limit(threads: &[Thread], cut: PastLimit) -> Self {
805 let kept_groups = |thread: &Thread, before: usize| {
806 thread
807 .groups
808 .iter()
809 .take(before)
810 .filter(|group| !group.is_empty())
811 .count()
812 };
813 Self::LegsPastLimit {
814 thread: cut
815 .thread
816 .map(|t| {
817 threads
818 .iter()
819 .take(t)
820 .filter(|thread| kept_groups(thread, usize::MAX) > 0)
821 .count()
822 }),
823 group: cut
824 .thread
825 .and_then(|t| threads.get(t))
826 .zip(cut.group)
827 .map(|(thread, g)| kept_groups(thread, g)),
828 excess: cut.excess,
829 }
830 }
831}
832
833impl FlattenedDialStringError {
834 fn from_pipeline(error: PipelineError) -> Self {
835 match error {
836 PipelineError::Empty => Self::Empty,
837 PipelineError::ArgvSplit => Self::ArgvSplit,
838 PipelineError::UnclosedBlock { leg } => Self::UnclosedBlock { leg },
839 PipelineError::SplitSeparatorUnreadable => Self::SplitSeparatorUnreadable,
840 }
841 }
842}
843
844impl UnparsedLeg {
845 pub fn endpoint(&self) -> &str {
847 &self.endpoint
848 }
849
850 pub fn error(&self) -> &OriginateError {
852 &self.error
853 }
854}
855
856impl fmt::Display for UnparsedLeg {
857 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
858 let kind = if self
859 .endpoint
860 .is_empty()
861 {
862 "empty leg"
863 } else {
864 "unrecognized endpoint"
865 };
866 write!(
867 f,
868 "{kind} ({} bytes)",
869 self.endpoint
870 .len()
871 )
872 }
873}
874
875impl fmt::Display for LegWarning {
876 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
877 match self {
878 Self::PairIgnored { block, key } => {
879 write!(
880 f,
881 "pair {key} in block {block} has no value and is not installed"
882 )
883 }
884 Self::PairCleared { block, key } => {
885 write!(
886 f,
887 "pair {key} in block {block} is empty and deletes earlier values"
888 )
889 }
890 Self::NestedVarsRefused { block, key } => write!(
891 f,
892 "pair {key} in block {block} names a variable while origination_nested_vars is off"
893 ),
894 Self::BlockSeparatorUnreadable { block } => write!(
895 f,
896 "block {block} has a non-ASCII separator and contributes no pairs"
897 ),
898 Self::BlockRewritesFollowingText { block } => {
899 write!(f, "parsing block {block} rewrites the leg's text after it")
900 }
901 Self::PairUnreadable { block } => write!(
902 f,
903 "a pair in block {block} opens a non-ASCII ^^ separator the switch splits by byte"
904 ),
905 }
906 }
907}
908
909impl fmt::Display for ListWarning {
910 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
911 match self {
912 Self::QuoteSpansLegs => f.write_str("a quote holds a leg or group separator"),
913 Self::CarrierExpands => {
914 f.write_str("the dialplan carrier expands a variable reference kept as written")
915 }
916 Self::BlockSeparatorUnreadable { block } => write!(
917 f,
918 "list block {block} has a non-ASCII separator and contributes no pairs"
919 ),
920 Self::BlockRewritesFollowingText { block } => {
921 write!(f, "parsing list block {block} rewrites the text after it")
922 }
923 Self::LegsPastLimit {
924 thread,
925 group,
926 excess,
927 } => {
928 match (thread, group) {
929 (Some(thread), Some(group)) => {
930 write!(f, "the leg split of group {group} in thread {thread}")?
931 }
932 (Some(thread), None) => write!(f, "the group split of thread {thread}")?,
933 (None, _) => f.write_str("the thread split")?,
934 }
935 write!(
936 f,
937 " stops at the switch's limit of {MAX_PEERS}; \
938 {excess} more are never split off"
939 )
940 }
941 }
942 }
943}
944
945impl fmt::Display for FlattenedDialStringError {
946 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
947 match self {
948 Self::Empty => f.write_str("dial string has no leg to dial"),
949 Self::ArgvSplit => f.write_str("originate's argument split cuts the dial string"),
950 Self::UnclosedBlock { leg } => write!(f, "a block on leg {leg} never closes"),
951 Self::TrailingNewline => f.write_str("dial string ends in a newline"),
952 Self::SplitSeparatorUnreadable => f.write_str(
953 "a split on a non-ASCII ^^ separator's first byte reaches past its text",
954 ),
955 }
956 }
957}
958
959impl std::error::Error for FlattenedDialStringError {}