use std::collections::HashMap;
use crate::model::{Flow, Node, Prompt};
pub type NodeId = String;
impl Node {
pub fn kind(&self) -> &'static str {
match self {
Node::Greeting { .. } => "greeting",
Node::Hours { .. } => "hours",
Node::Menu { .. } => "menu",
Node::Ring { .. } => "ring",
Node::Message { .. } => "message",
Node::Transfer { .. } => "transfer",
Node::Hangup { .. } => "hangup",
}
}
pub fn is_terminal(&self) -> bool {
matches!(
self,
Node::Message { .. } | Node::Transfer { .. } | Node::Hangup { .. } | Node::Ring { .. }
)
}
pub fn required_exits(&self) -> Vec<String> {
match self {
Node::Greeting { .. } => vec!["next".into()],
Node::Hours { .. } => vec!["open".into(), "closed".into()],
Node::Menu { options, .. } => {
let mut names: Vec<String> = options.keys().cloned().collect();
names.push("no_input".into());
names.push("invalid".into());
names
}
Node::Ring { .. } => vec!["no_answer".into()],
Node::Message { .. } | Node::Transfer { .. } | Node::Hangup { .. } => Vec::new(),
}
}
pub fn exits(&self) -> Option<&HashMap<String, String>> {
let exits = match self {
Node::Greeting { exits, .. }
| Node::Hours { exits, .. }
| Node::Menu { exits, .. }
| Node::Ring { exits, .. }
| Node::Message { exits, .. }
| Node::Transfer { exits, .. }
| Node::Hangup { exits, .. } => exits,
};
exits.as_deref()
}
}
impl Prompt {
pub fn as_text(&self) -> Option<&str> {
match self {
Prompt::Text(t) => Some(t.as_str()),
Prompt::Audio { .. } => None,
}
}
pub fn transcript(&self) -> Option<&str> {
match self {
Prompt::Text(t) => Some(t.as_str()),
Prompt::Audio { transcript, .. } => transcript.as_deref(),
}
}
}
impl Flow {
pub fn from_yaml(src: &str) -> Result<Flow, serde_yaml_ng::Error> {
let value: serde_yaml_ng::Value = serde_yaml_ng::from_str(src)?;
serde_yaml_ng::from_value(value)
}
pub fn to_yaml(&self) -> Result<String, serde_yaml_ng::Error> {
serde_yaml_ng::to_string(self)
}
}
#[cfg(test)]
mod tests {
use crate::model::Prompt;
#[test]
fn as_text_is_synthesizable_text_only() {
let text = Prompt::Text("open eleven to ten".into());
assert_eq!(text.as_text(), Some("open eleven to ten"));
let audio = Prompt::Audio {
audio: "vprompt_ab12cd34".into(),
transcript: Some("open eleven to ten".into()),
};
assert_eq!(audio.as_text(), None);
}
#[test]
fn transcript_is_the_spoken_words_regardless_of_voicing() {
let text = Prompt::Text("open eleven to ten".into());
assert_eq!(text.transcript(), Some("open eleven to ten"));
let with_text = Prompt::Audio {
audio: "vprompt_ab12cd34".into(),
transcript: Some("open eleven to ten".into()),
};
assert_eq!(with_text.transcript(), Some("open eleven to ten"));
let without_text = Prompt::Audio {
audio: "vprompt_ff00ee11".into(),
transcript: None,
};
assert_eq!(without_text.transcript(), None);
}
#[test]
fn audio_transcript_round_trips_through_yaml() {
let with = "{ audio: vprompt_ab12cd34, transcript: open eleven to ten }";
let parsed: Prompt = serde_yaml_ng::from_str(with).unwrap();
assert_eq!(parsed.transcript(), Some("open eleven to ten"));
let yaml = serde_yaml_ng::to_string(&parsed).unwrap();
let reparsed: Prompt = serde_yaml_ng::from_str(&yaml).unwrap();
assert_eq!(reparsed.transcript(), Some("open eleven to ten"));
let bare: Prompt = serde_yaml_ng::from_str("{ audio: vprompt_ff00ee11 }").unwrap();
assert_eq!(bare.transcript(), None);
assert!(!serde_yaml_ng::to_string(&bare)
.unwrap()
.contains("transcript"));
}
}