1use indexmap::IndexMap;
4use std::fmt;
5use std::str::FromStr;
6
7use super::originate::OriginateError;
8use crate::switch_passes::brackets::{check_separator, same_header, unbalanced, Block, PairEffect};
9use crate::switch_passes::escape::{escape_text, escape_value, EscapedField};
10use crate::switch_passes::originate_legs::{splits_into_threads, ENTERPRISE_DELIM};
11use crate::switch_passes::{pipeline, PipelineError};
12
13mod target;
14#[cfg(test)]
15mod tests;
16
17pub use target::{
18 BlockParse, DialStringCarrier, DialStringTarget, InvalidArgvSeparator, ParseBlockParseError,
19 UnvouchedVersion,
20};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
30#[non_exhaustive]
31pub enum VariablesType {
32 Enterprise,
34 Default,
36 Channel,
38}
39
40impl VariablesType {
41 pub(crate) fn delimiters(self) -> (char, char) {
42 match self {
43 Self::Enterprise => ('<', '>'),
44 Self::Default => ('{', '}'),
45 Self::Channel => ('[', ']'),
46 }
47 }
48
49 pub(crate) fn of_block(block: &Block) -> Self {
51 match block.open {
52 '<' => Self::Enterprise,
53 '{' => Self::Default,
54 _ => Self::Channel,
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct Variables {
86 vars_type: VariablesType,
87 inner: IndexMap<String, String>,
88 separator: Option<char>,
92}
93
94fn check_key(key: &str, vars_type: VariablesType) -> Result<(), OriginateError> {
97 let fault = if key.is_empty() {
98 Some("is empty, which the switch installs under no name".to_owned())
99 } else if key.contains('[') {
100 Some(
101 "carries '[', which the switch reads as an array index, installing the value \
102 under the text before it"
103 .to_owned(),
104 )
105 } else if splits_into_threads(key) {
106 Some(format!(
107 "carries the enterprise separator {ENTERPRISE_DELIM}, on which the switch splits \
108 the dial string into threads whatever quoting or escaping surrounds it"
109 ))
110 } else if vars_type == VariablesType::Channel && key.contains('\'') {
111 Some(
112 "carries a single quote in channel scope, which the switch pairs with the next \
113 quote in the dial string before it parses the block"
114 .to_owned(),
115 )
116 } else {
117 unbalanced(key, vars_type.delimiters())
118 };
119 fault.map_or(Ok(()), |fault| {
120 Err(OriginateError::ParseError(format!(
121 "a variable name {fault}"
122 )))
123 })
124}
125
126fn check_representable(
129 key: &str,
130 value: &str,
131 vars_type: VariablesType,
132) -> Result<(), OriginateError> {
133 check_key(key, vars_type)?;
134 if splits_into_threads(value) {
135 return Err(OriginateError::ParseError(format!(
136 "variable {key} carries the enterprise separator {ENTERPRISE_DELIM}, on which \
137 the switch splits the dial string into threads whatever quoting or escaping \
138 surrounds it"
139 )));
140 }
141 if vars_type == VariablesType::Channel && value.contains('\'') {
142 return Err(OriginateError::ParseError(format!(
143 "variable {key} carries a single quote in channel scope: the switch \
144 pairs it with the next quote in the dial string before it parses \
145 the block, whatever escaping precedes either. Use default scope, \
146 or keep the quote out of the value"
147 )));
148 }
149 if value.is_empty() {
150 return Err(OriginateError::ParseError(format!(
151 "variable {key} has an empty value: the switch discards such a pair \
152 without logging it, so it cannot be told from an absent variable on \
153 the wire. Give it a value or remove it -- and if its presence was \
154 itself the signal, that signal needs a home outside the dial string"
155 )));
156 }
157
158 unbalanced(value, vars_type.delimiters()).map_or(Ok(()), |fault| {
159 Err(OriginateError::ParseError(format!(
160 "variable {key} {fault}"
161 )))
162 })
163}
164
165fn check_case_collision<'a>(
168 earlier: impl IntoIterator<Item = &'a String>,
169 key: &str,
170 at: usize,
171) -> Result<(), OriginateError> {
172 let collides = earlier
173 .into_iter()
174 .any(|name| name != key && same_header(name, key));
175 match collides {
176 true => Err(OriginateError::ParseError(format!(
177 "variable {at} differs only in case from an earlier name, which the switch \
178 installs as the same variable"
179 ))),
180 false => Ok(()),
181 }
182}
183
184impl Variables {
185 pub fn new(vars_type: VariablesType) -> Self {
187 Self {
188 vars_type,
189 inner: IndexMap::new(),
190 separator: None,
191 }
192 }
193
194 pub fn with_vars(
196 vars_type: VariablesType,
197 vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
198 ) -> Self {
199 Self {
200 vars_type,
201 inner: vars
202 .into_iter()
203 .map(|(k, v)| (k.into(), v.into()))
204 .collect(),
205 separator: None,
206 }
207 }
208
209 pub fn with_separator(mut self, sep: char) -> Result<Self, OriginateError> {
227 check_separator(sep, self.vars_type)?;
228 if self
229 .inner
230 .keys()
231 .any(|k| k.contains(sep))
232 {
233 return Err(OriginateError::ParseError(format!(
234 "a variable name contains the chosen '{sep}' separator"
235 )));
236 }
237 if let Some((key, _)) = self
238 .inner
239 .iter()
240 .find(|(_, v)| v.contains(sep))
241 {
242 return Err(OriginateError::ParseError(format!(
243 "variable {key} contains the chosen '{sep}' separator"
244 )));
245 }
246 self.separator = Some(sep);
247 Ok(self)
248 }
249
250 pub fn separator(&self) -> Option<char> {
252 self.separator
253 }
254
255 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
257 self.inner
258 .insert(key.into(), value.into());
259 }
260
261 pub fn remove(&mut self, key: &str) -> Option<String> {
263 self.inner
264 .shift_remove(key)
265 }
266
267 pub fn get(&self, key: &str) -> Option<&str> {
269 self.inner
270 .get(key)
271 .map(|s| s.as_str())
272 }
273
274 pub fn is_empty(&self) -> bool {
276 self.inner
277 .is_empty()
278 }
279
280 pub fn len(&self) -> usize {
282 self.inner
283 .len()
284 }
285
286 pub fn scope(&self) -> VariablesType {
288 self.vars_type
289 }
290
291 pub fn set_scope(&mut self, scope: VariablesType) {
293 self.vars_type = scope;
294 }
295
296 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
298 self.inner
299 .iter()
300 .map(|(k, v)| (k.as_str(), v.as_str()))
301 }
302
303 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut String)> {
305 self.inner
306 .iter_mut()
307 .map(|(k, v)| (k.as_str(), v))
308 }
309
310 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut String> {
312 self.inner
313 .values_mut()
314 }
315}
316
317#[cfg(feature = "serde")]
318impl serde::Serialize for Variables {
319 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
320 if self.vars_type == VariablesType::Default
321 && self
322 .separator
323 .is_none()
324 {
325 self.inner
326 .serialize(serializer)
327 } else {
328 use serde::ser::SerializeStruct;
329 let fields = 2 + usize::from(
330 self.separator
331 .is_some(),
332 );
333 let mut s = serializer.serialize_struct("Variables", fields)?;
334 s.serialize_field("scope", &self.vars_type)?;
335 s.serialize_field("vars", &self.inner)?;
336 if let Some(sep) = self.separator {
337 s.serialize_field("separator", &sep)?;
338 }
339 s.end()
340 }
341 }
342}
343
344#[cfg(feature = "serde")]
345impl<'de> serde::Deserialize<'de> for Variables {
346 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
347 #[derive(serde::Deserialize)]
348 #[serde(untagged)]
349 enum VariablesRepr {
350 Scoped {
351 scope: VariablesType,
352 vars: IndexMap<String, String>,
353 #[serde(default)]
354 separator: Option<char>,
355 },
356 Flat(IndexMap<String, String>),
357 }
358
359 let (vars_type, inner, separator) = match VariablesRepr::deserialize(deserializer)? {
360 VariablesRepr::Scoped {
361 scope,
362 vars,
363 separator,
364 } => (scope, vars, separator),
365 VariablesRepr::Flat(map) => (VariablesType::Default, map, None),
366 };
367 for (at, (key, value)) in inner
370 .iter()
371 .enumerate()
372 {
373 check_representable(key, value, vars_type)
374 .and_then(|()| {
375 check_case_collision(
376 inner
377 .keys()
378 .take(at),
379 key,
380 at,
381 )
382 })
383 .map_err(serde::de::Error::custom)?;
384 }
385 let vars = Self {
386 vars_type,
387 inner,
388 separator: None,
389 };
390 match separator {
391 Some(sep) => vars
392 .with_separator(sep)
393 .map_err(serde::de::Error::custom),
394 None => Ok(vars),
395 }
396 }
397}
398
399#[derive(Debug, Clone, Copy)]
402pub struct VariablesDisplay<'a> {
403 vars: &'a Variables,
404 target: DialStringTarget,
405}
406
407impl fmt::Display for VariablesDisplay<'_> {
408 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409 self.target
410 .write_argument(f, |f, target| {
411 self.vars
412 .write_for(f, target)
413 })
414 }
415}
416
417impl Variables {
418 pub fn display_for(&self, target: impl Into<DialStringTarget>) -> VariablesDisplay<'_> {
421 VariablesDisplay {
422 vars: self,
423 target: target.into(),
424 }
425 }
426
427 pub(super) fn write_for(
428 &self,
429 f: &mut fmt::Formatter<'_>,
430 target: DialStringTarget,
431 ) -> fmt::Result {
432 let (open, close) = self
433 .vars_type
434 .delimiters();
435 f.write_fmt(format_args!("{}", open))?;
436 if let Some(sep) = self.separator {
437 write!(f, "^^{sep}")?;
438 }
439 let commas_separate = self
442 .separator
443 .is_none_or(|sep| sep == ',');
444 let sep = self
445 .separator
446 .unwrap_or(',');
447 for (i, (key, value)) in self
448 .inner
449 .iter()
450 .enumerate()
451 {
452 if i > 0 {
453 write!(f, "{sep}")?;
454 }
455 let key = escape_text(
456 key,
457 target,
458 EscapedField::Key {
459 scope: self.vars_type,
460 commas_separate,
461 },
462 );
463 let value = escape_value(value, target, commas_separate, self.vars_type);
464 write!(f, "{}={}", key, value)?;
465 }
466 f.write_fmt(format_args!("{}", close))
467 }
468}
469
470impl fmt::Display for Variables {
471 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472 self.write_for(f, DialStringCarrier::EslApi.into())
473 }
474}
475
476impl FromStr for Variables {
477 type Err = OriginateError;
478
479 fn from_str(s: &str) -> Result<Self, Self::Err> {
480 Self::parse_for(s, DialStringCarrier::EslApi)
481 }
482}
483
484const LONE_BLOCK_ENDPOINT: &str = "null/x";
486
487impl Variables {
488 pub fn parse_for(s: &str, target: impl Into<DialStringTarget>) -> Result<Self, OriginateError> {
493 let (argument, target) = target
494 .into()
495 .read_argument(s)?;
496 let s = argument.trim_matches(' ');
497 if s.len() < 2 {
498 return Err(OriginateError::ParseError(
499 "variable block too short".into(),
500 ));
501 }
502 match (s.as_bytes()[0], s.as_bytes()[s.len() - 1]) {
503 (b'{', b'}') | (b'<', b'>') | (b'[', b']') => {}
504 (open, close) => {
505 return Err(OriginateError::ParseError(format!(
506 "unknown variable delimiters: {:?}..{:?}",
507 open as char, close as char
508 )));
509 }
510 }
511 match read_leg(&format!("{s}{LONE_BLOCK_ENDPOINT}"), target)? {
512 (Some(block), endpoint) if endpoint == LONE_BLOCK_ENDPOINT => Self::from_block(&block),
513 _ => Err(OriginateError::ParseError(
514 "the switch ends the block before its last bracket".into(),
515 )),
516 }
517 }
518
519 pub(crate) fn from_block(block: &Block) -> Result<Self, OriginateError> {
521 let vars_type = VariablesType::of_block(block);
522 if block.separator != ',' {
523 check_separator(block.separator, vars_type)?;
524 }
525 if block.rewrites_following_text {
526 return Err(OriginateError::ParseError(
527 "the block's parse rewrites the text after it".into(),
528 ));
529 }
530 let mut inner = IndexMap::new();
531 for (i, pair) in block
532 .pairs
533 .iter()
534 .enumerate()
535 {
536 let value = match &pair.effect {
537 PairEffect::Set(value) => value.as_str(),
538 PairEffect::Cleared | PairEffect::Valueless => "",
539 PairEffect::Ignored => {
540 return Err(OriginateError::ParseError(format!(
541 "missing = in variable {i}"
542 )))
543 }
544 PairEffect::Unreadable => {
545 return Err(OriginateError::ParseError(format!(
546 "variable {i} opens a non-ASCII ^^ separator the switch splits by byte"
547 )))
548 }
549 };
550 let key = pair
551 .key
552 .as_str();
553 check_representable(key, value, vars_type)?;
554 check_case_collision(inner.keys(), key, i)?;
555 if block.separator != ','
556 && (key.contains(block.separator) || value.contains(block.separator))
557 {
558 return Err(OriginateError::ParseError(format!(
559 "variable {i} contains the block's ^^ separator"
560 )));
561 }
562 inner.insert(key.to_owned(), value.to_owned());
563 }
564 Ok(Self {
565 vars_type,
566 inner,
567 separator: None,
568 })
569 }
570}
571
572pub(crate) fn read_leg(
575 text: &str,
576 target: DialStringTarget,
577) -> Result<(Option<Block>, String), OriginateError> {
578 let list = pipeline::read(text, target).map_err(read_error)?;
579 let one_leg = || OriginateError::ParseError("the switch reads more than one leg".into());
580 let [thread] = &list.threads[..] else {
581 return Err(one_leg());
582 };
583 let [group] = &thread.groups[..] else {
584 return Err(one_leg());
585 };
586 let [leg] = &group[..] else {
587 return Err(one_leg());
588 };
589 let mut blocks = list
590 .blocks
591 .iter()
592 .chain(&thread.blocks)
593 .chain(&leg.blocks);
594 match (blocks.next(), blocks.next()) {
595 (Some(_), Some(_)) => Err(OriginateError::ParseError(
596 "an endpoint carries one variable block".into(),
597 )),
598 (block, _) => Ok((
599 block.cloned(),
600 leg.endpoint
601 .clone(),
602 )),
603 }
604}
605
606pub(crate) fn installed_variables(
608 block: Option<&Block>,
609) -> Result<Option<Variables>, OriginateError> {
610 Ok(block
611 .map(Variables::from_block)
612 .transpose()?
613 .filter(|vars| !vars.is_empty()))
614}
615
616pub(crate) fn read_error(error: PipelineError) -> OriginateError {
618 OriginateError::ParseError(
619 match error {
620 PipelineError::Empty => "no endpoint to dial",
621 PipelineError::ArgvSplit => "originate's argument split cuts the dial string",
622 PipelineError::UnclosedBlock { .. } => "a variable block never closes",
623 PipelineError::SplitSeparatorUnreadable => {
624 "a split on a non-ASCII ^^ separator's first byte reaches past its text"
625 }
626 }
627 .into(),
628 )
629}