use crate::{AsrVendor, ConnectState, FlowState, Region, VoiceServer};
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use cal_jambonz::dial::TranscribeDial;
use cal_jambonz::listen::Listen;
pub use crate::device::ani_router::ANIRouter;
pub use crate::device::app_flow::AppFlow;
pub use crate::device::client::Client;
pub use crate::device::date_range_router::DateRangeRouter;
pub use crate::device::day_of_week_router::DayOfWeekRouter;
pub use crate::device::digit_router::DigitRouter;
pub use crate::device::dnis_router::DNISRouter;
pub use crate::device::email::Email;
pub use crate::device::event::Event;
pub use crate::device::hunt_group::HuntGroup;
pub use crate::device::inbound_flow::InboundFlow;
pub use crate::device::message_ani_router::MessageAniRouter;
pub use crate::device::message_buttons::MessageButtons;
pub use crate::device::message_dnis_router::MessageDnisRouter;
pub use crate::device::message_plugin::MessagePlugin;
pub use crate::device::message_template::MessageTemplate;
pub use crate::device::message_text::MessageText;
pub use crate::device::outbound_flow::OutboundFlow;
pub use crate::device::play::Play;
pub use crate::device::plugin::Plugin;
pub use crate::device::queue::Queue;
pub use crate::device::remote::Remote;
pub use crate::device::say::Say;
pub use crate::device::service::Service;
pub use crate::device::shared::{RecordOptions, TranscribeOptions};
pub use crate::device::sip_extension::SipExtension;
pub use crate::device::sip_gateway::SipGateway;
pub use crate::device::sms::Sms;
pub use crate::device::tag_router::TagRouter;
pub use crate::device::teams::Teams;
pub use crate::device::time_range_router::TimeRangeRouter;
pub use crate::device::voicemail::VoiceMail;
pub use crate::device::whatsapp_flow::WhatsAppFlow;
pub use crate::zone_router::ZoneRouter;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
#[cfg(feature = "openapi")]
use utoipa::{ToSchema, IntoParams};
use crate::script::Script;
use crate::tag::Tag;
pub trait Connector {
fn get_connect_to(&mut self, state: &mut FlowState) -> ConnectState;
fn is_endpoint(&self) -> bool {
false
}
fn supports_routing(&self) -> bool {
true
}
}
pub trait BaseDevice {
fn get_id(&mut self) -> String;
fn get_name(&mut self) -> String;
fn get_extension(&mut self) -> u16;
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "openapi", schema(
title ="Enumeration of all available device types in the system",
example = "CLIENT"
))]
pub enum DeviceType {
#[serde(rename = "ROUTE_START")]
InboundFlow,
#[serde(rename = "MATCH_START")]
OutboundFlow,
#[serde(rename = "WHATSAPP_START")]
WhatsAppFlow,
#[serde(rename = "APP_START")]
AppFlow,
#[serde(rename = "ANI_ROUTER")]
AniRouter,
#[serde(rename = "DNIS_ROUTER")]
DnisRouter,
#[serde(rename = "ZONE_ROUTER")]
ZoneRouter,
#[serde(rename = "NUMBER_PLAN")]
DigitRouter,
#[serde(rename = "TIME_RANGE_ROUTER")]
TimeRangeRouter,
#[serde(rename = "DAY_OF_WEEK_ROUTER")]
DayOfWeekRouter,
#[serde(rename = "DATE_RANGE_ROUTER")]
DateRangeRouter,
#[serde(rename = "HUNT_GROUP")]
HuntGroup,
#[serde(rename = "CLIENT")]
Client,
#[serde(rename = "TEAMS")]
Teams,
#[serde(rename = "SIP")]
SipExtension,
#[serde(rename = "SIP_GATEWAY")]
SipGateway,
#[serde(rename = "REMOTE")]
Remote,
#[serde(rename = "PLUGIN")]
Plugin,
#[serde(rename = "PLAY")]
Play,
#[serde(rename = "SAY")]
Say,
#[serde(rename = "VOICEMAIL")]
Voicemail,
#[serde(rename = "SCRIPT")]
Script,
#[serde(rename = "QUEUE")]
Queue,
#[serde(rename = "SMS")]
Sms,
#[serde(rename = "EMAIL")]
Email,
#[serde(rename = "TAG")]
Tag,
#[serde(rename = "TAG_ROUTER")]
TagRouter,
#[serde(rename = "MESSAGE_PLUGIN")]
MessagePlugin,
#[serde(rename = "MESSAGE_TEXT")]
MessageText,
#[serde(rename = "MESSAGE_BUTTONS")]
MessageButtons,
#[serde(rename = "MESSAGE_TEMPLATE")]
MessageTemplate,
#[serde(rename = "MESSAGE_ANI_ROUTER")]
MessageAniRouter,
#[serde(rename = "SERVICE")]
Service,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum Device {
InboundFlow(InboundFlow),
OutboundFlow(OutboundFlow),
WhatsAppFlow(WhatsAppFlow),
AppFlow(AppFlow),
AniRouter(ANIRouter),
DnisRouter(DNISRouter),
DigitRouter(DigitRouter),
TimeRangeRouter(TimeRangeRouter),
DayOfWeekRouter(DayOfWeekRouter),
DateRangeRouter(DateRangeRouter),
ZoneRouter(ZoneRouter),
HuntGroup(HuntGroup),
Client(Client),
Teams(Teams),
SipExtension(SipExtension),
SipGateway(SipGateway),
Remote(Remote),
Plugin(Plugin),
Play(Play),
Say(Say),
Voicemail(VoiceMail),
Script(Script),
Queue(Queue),
Sms(Sms),
Email(Email),
Tag(Tag),
TagRouter(TagRouter),
MessagePlugin(MessagePlugin),
MessageButtons(MessageButtons),
MessageText(MessageText),
MessageTemplate(MessageTemplate),
MessageAniRouter(MessageAniRouter),
Service(Service),
}
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "openapi", schema(
title ="Device configuration containing all possible device types and their settings",
example = json!({
"_id": "dev_507f1f77bcf86cd799439011",
"type": "CLIENT",
"name": "John's Desk Phone",
"extension": 1001,
"tags": [
{
"type": "session",
"name": "department",
"value": "sales"
}
],
"client": {
"username": "john.doe",
"password": "encrypted_password",
"realm": "sip.example.com"
}
})
))]
#[serde(rename_all = "camelCase")]
pub struct DeviceStruct {
#[serde(rename = "_id")]
#[cfg_attr(feature = "openapi", schema(example = "dev_507f1f77bcf86cd799439011"))]
pub id: String,
#[serde(rename = "type")]
pub device_type: DeviceType,
#[cfg_attr(feature = "openapi", schema(example = "Reception Phone", min_length = 1))]
pub name: String,
#[serde(deserialize_with = "from_str")]
#[cfg_attr(feature = "openapi", schema(example = 1001, minimum = 1, maximum = 9999))]
pub extension: u16,
#[serde(default)]
pub tags: Vec<DeviceTag>,
#[serde(rename = "startRoute")]
#[serde(skip_serializing_if = "Option::is_none")]
pub start_route: Option<InboundFlow>,
#[serde(rename = "regexRoute")]
#[serde(skip_serializing_if = "Option::is_none")]
pub regex_route: Option<OutboundFlow>,
#[serde(rename = "appRoute")]
#[serde(skip_serializing_if = "Option::is_none")]
pub app_route: Option<AppFlow>,
#[serde(rename = "numberPlan")]
#[serde(skip_serializing_if = "Option::is_none")]
pub number_plan: Option<DigitRouter>,
#[serde(rename = "aniRouter")]
#[serde(skip_serializing_if = "Option::is_none")]
pub ani_router: Option<ANIRouter>,
#[serde(rename = "dnisRouter")]
#[serde(skip_serializing_if = "Option::is_none")]
pub dnis_router: Option<DNISRouter>,
#[serde(rename = "zoneRouter")]
#[serde(skip_serializing_if = "Option::is_none")]
pub zone_router: Option<ZoneRouter>,
#[serde(rename = "tagRouter")]
#[serde(skip_serializing_if = "Option::is_none")]
pub tag_router: Option<TagRouter>,
#[serde(rename = "timeRangeRoute")]
#[serde(skip_serializing_if = "Option::is_none")]
pub time_range_router: Option<TimeRangeRouter>,
#[serde(rename = "dayOfWeekRoute")]
#[serde(skip_serializing_if = "Option::is_none")]
pub day_of_week_router: Option<DayOfWeekRouter>,
#[serde(rename = "dateRangeRoute")]
#[serde(skip_serializing_if = "Option::is_none")]
pub date_range_router: Option<DateRangeRouter>,
#[serde(rename = "huntGroup")]
#[serde(skip_serializing_if = "Option::is_none")]
pub hunt_group: Option<HuntGroup>,
#[serde(rename = "client")]
#[serde(skip_serializing_if = "Option::is_none")]
pub client: Option<Client>,
#[serde(rename = "teams")]
#[serde(skip_serializing_if = "Option::is_none")]
pub teams: Option<Teams>,
#[serde(rename = "sipExtension")]
#[serde(skip_serializing_if = "Option::is_none")]
pub sip_extension: Option<SipExtension>,
#[serde(rename = "sipGateway")]
#[serde(skip_serializing_if = "Option::is_none")]
pub sip_gateway: Option<SipGateway>,
#[serde(rename = "remote")]
#[serde(skip_serializing_if = "Option::is_none")]
pub remote: Option<Remote>,
#[serde(rename = "voicemail")]
#[serde(skip_serializing_if = "Option::is_none")]
pub voice_mail: Option<VoiceMail>,
#[serde(rename = "queue")]
#[serde(skip_serializing_if = "Option::is_none")]
pub queue: Option<Queue>,
#[serde(rename = "plugin")]
#[serde(skip_serializing_if = "Option::is_none")]
pub plugin: Option<Plugin>,
#[serde(rename = "service")]
#[serde(skip_serializing_if = "Option::is_none")]
pub service: Option<Service>,
#[serde(rename = "play")]
#[serde(skip_serializing_if = "Option::is_none")]
pub play: Option<Play>,
#[serde(rename = "say")]
#[serde(skip_serializing_if = "Option::is_none")]
pub say: Option<Say>,
#[serde(rename = "script")]
#[serde(skip_serializing_if = "Option::is_none")]
pub script: Option<Script>,
#[serde(rename = "email")]
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<Email>,
#[serde(rename = "sms")]
#[serde(skip_serializing_if = "Option::is_none")]
pub sms: Option<Sms>,
#[serde(rename = "tag")]
#[serde(skip_serializing_if = "Option::is_none")]
pub tag: Option<Tag>,
#[serde(rename = "event")]
#[serde(skip_serializing_if = "Option::is_none")]
pub event: Option<Event>,
#[serde(rename = "whatsAppRoute")]
#[serde(skip_serializing_if = "Option::is_none")]
pub whats_app_flow: Option<WhatsAppFlow>,
#[serde(rename = "messagePlugin")]
#[serde(skip_serializing_if = "Option::is_none")]
pub message_plugin: Option<MessagePlugin>,
#[serde(rename = "messageText")]
#[serde(skip_serializing_if = "Option::is_none")]
pub message_text: Option<MessageText>,
#[serde(rename = "messageButtons")]
#[serde(skip_serializing_if = "Option::is_none")]
pub message_buttons: Option<MessageButtons>,
#[serde(rename = "messageTemplate")]
#[serde(skip_serializing_if = "Option::is_none")]
pub message_template: Option<MessageTemplate>,
#[serde(rename = "messageANIRouter")]
#[serde(skip_serializing_if = "Option::is_none")]
pub message_ani_router: Option<MessageAniRouter>,
#[serde(rename = "messageDNISRouter")]
#[serde(skip_serializing_if = "Option::is_none")]
pub message_dnis_router: Option<MessageDnisRouter>,
}
fn from_str<'de, D>(deserializer: D) -> Result<u16, D::Error>
where
D: Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
if value.is_string() {
Ok(value.as_str().unwrap().parse().unwrap())
} else if value.is_u64() {
Ok(value.as_u64().unwrap() as u16)
} else if value.is_i64() {
Ok(value.as_i64().unwrap() as u16)
} else {
Err(serde::de::Error::custom("Cannot map extension to u16"))
}
}
impl DeviceStruct {
pub fn record_options(&self) -> Option<RecordOptions> {
match &self.device_type {
DeviceType::HuntGroup => self.hunt_group.as_ref().map(|h| h.record_options.clone()),
DeviceType::Client => self.client.as_ref().map(|c| c.record_options.clone()),
DeviceType::Teams => self.teams.as_ref().map(|t| t.record_options.clone()),
DeviceType::SipExtension => self.sip_extension.as_ref().map(|s| s.record_options.clone()),
DeviceType::SipGateway => self.sip_gateway.as_ref().map(|s| s.record_options.clone()),
DeviceType::Remote => self.remote.as_ref().map(|r| r.record_options.clone()),
DeviceType::Plugin => self.plugin.as_ref().map(|p| p.record_options.clone()),
DeviceType::Voicemail => self.voice_mail.as_ref().map(|v| v.record_options.clone()),
_ => None,
}
}
pub fn transcribe_options(&self) -> Option<TranscribeOptions> {
match &self.device_type {
DeviceType::HuntGroup => self.hunt_group.as_ref().map(|h| h.transcribe_options.clone()),
DeviceType::Client => self.client.as_ref().map(|c| c.transcribe_options.clone()),
DeviceType::Teams => self.teams.as_ref().map(|t| t.transcribe_options.clone()),
DeviceType::SipExtension => self.sip_extension.as_ref().map(|s| s.transcribe_options.clone()),
DeviceType::SipGateway => self.sip_gateway.as_ref().map(|s| s.transcribe_options.clone()),
DeviceType::Remote => self.remote.as_ref().map(|r| r.transcribe_options.clone()),
DeviceType::Plugin => self.plugin.as_ref().map(|p| p.transcribe_options.clone()),
DeviceType::Voicemail => self.voice_mail.as_ref().map(|v| v.transcribe_options.clone()),
_ => None,
}
}
pub fn is_id(&self, _id: &str) -> bool {
self.id == _id
}
pub fn is_device_type(&self, device_type: DeviceType) -> bool {
self.device_type == device_type
}
pub fn match_device(&self, str: &str) -> bool {
let matched = match &self.device_type {
DeviceType::InboundFlow => {
self.start_route
.as_ref()
.map_or(false, |v| v.ddis.contains(&str.to_string()))
}
DeviceType::Client => {
self.client
.as_ref()
.map_or(false, |v| v.username.eq(str))
}
DeviceType::SipGateway => {
self.sip_gateway
.as_ref()
.map_or(false, |v| v.trunks.contains(&str.to_string()))
}
_ => false,
};
matched || self.id.eq(str) || self.extension.to_string().eq(str)
}
pub fn match_outbound_flow(&self) -> bool {
match &self.device_type {
DeviceType::OutboundFlow => true,
_ => false,
}
}
pub fn is_connector(&self) -> bool {
vec![
DeviceType::InboundFlow,
DeviceType::OutboundFlow,
DeviceType::AppFlow,
DeviceType::WhatsAppFlow,
DeviceType::AniRouter,
DeviceType::DnisRouter,
DeviceType::DigitRouter,
DeviceType::TimeRangeRouter,
DeviceType::DayOfWeekRouter,
DeviceType::DateRangeRouter,
DeviceType::Play,
DeviceType::Say,
DeviceType::Voicemail,
DeviceType::Script,
DeviceType::Queue,
DeviceType::Sms,
DeviceType::Email,
DeviceType::Tag,
DeviceType::TagRouter,
DeviceType::Service,
]
.contains(&self.device_type)
}
pub fn is_call_handler(&self) -> bool {
vec![
DeviceType::HuntGroup,
DeviceType::Client,
DeviceType::Teams,
DeviceType::SipExtension,
DeviceType::SipGateway,
DeviceType::Remote,
DeviceType::Plugin,
]
.contains(&self.device_type)
}
}
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "openapi", schema(
title ="Tag for categorizing and routing devices",
example = json!({
"type": "session",
"name": "department",
"value": "sales"
})
))]
#[serde(rename_all = "camelCase")]
pub struct DeviceTag {
#[serde(rename = "type")]
pub tag_type: TagType,
#[cfg_attr(feature = "openapi", schema(example = "department"))]
pub name: String,
#[cfg_attr(feature = "openapi", schema(example = "sales"))]
pub value: String,
}
#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[cfg_attr(feature = "openapi", schema(
title ="Scope of the tag - session-specific or globally available"
))]
#[serde(rename_all = "camelCase")]
pub enum TagType {
Session,
Global,
}
pub trait Endpoint {
fn get_caller_id(&self, state: &FlowState) -> String {
state.initial_request.from.clone()
}
fn get_called_id(&self, state: &FlowState) -> String {
state.initial_request.to.clone()
}
fn get_listen(&self, state: &FlowState) -> Option<Listen>;
fn get_proxy(&self, _state: &FlowState, _regions:Vec<Arc<Region>>) -> Option<VoiceServer> {
None
}
fn get_transcribe(&self, _state: &FlowState) -> Option<TranscribeDial> {
None
}
}
pub trait EndpointDevice: Connector {
fn is_endpoint(&self) -> bool {
true
}
fn device_type_name(&self) -> &'static str;
fn supports_routing(&self) -> bool {
false
}
}
impl Debug for DeviceType {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
DeviceType::InboundFlow => write!(f, "InboundFlow"),
DeviceType::OutboundFlow => write!(f, "OutboundFlow"),
DeviceType::WhatsAppFlow => write!(f, "WhatsAppFlow"),
DeviceType::AppFlow => write!(f, "AppFlow"),
DeviceType::AniRouter => write!(f, "AniRouter"),
DeviceType::DnisRouter => write!(f, "DnisRouter"),
DeviceType::DigitRouter => write!(f, "DigitRouter"),
DeviceType::TimeRangeRouter => write!(f, "TimeRangeRouter"),
DeviceType::DayOfWeekRouter => write!(f, "DayOfWeekRouter"),
DeviceType::DateRangeRouter => write!(f, "DateRangeRouter"),
DeviceType::ZoneRouter => write!(f, "ZoneRouter"),
DeviceType::HuntGroup => write!(f, "HuntGroup"),
DeviceType::Client => write!(f, "Client"),
DeviceType::Teams => write!(f, "Teams"),
DeviceType::SipExtension => write!(f, "SipExtension"),
DeviceType::SipGateway => write!(f, "SipGateway"),
DeviceType::Remote => write!(f, "Remote"),
DeviceType::Plugin => write!(f, "Plugin"),
DeviceType::Play => write!(f, "Play"),
DeviceType::Say => write!(f, "Say"),
DeviceType::Voicemail => write!(f, "Voicemail"),
DeviceType::Script => write!(f, "Script"),
DeviceType::Queue => write!(f, "Queue"),
DeviceType::Sms => write!(f, "Sms"),
DeviceType::Email => write!(f, "Email"),
DeviceType::Tag => write!(f, "Tag"),
DeviceType::TagRouter => write!(f, "TagRouter"),
DeviceType::MessagePlugin => write!(f, "MessagePlugin"),
DeviceType::MessageText => write!(f, "MessageText"),
DeviceType::MessageButtons => write!(f, "MessageButtons"),
DeviceType::MessageTemplate => write!(f, "MessageTemplate"),
DeviceType::MessageAniRouter => write!(f, "MessageAniRouter"),
DeviceType::Service => write!(f, "Service"),
}
}
}
impl Debug for Device {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Device::InboundFlow(_) => write!(f, "Device::InboundFlow"),
Device::OutboundFlow(_) => write!(f, "Device::OutboundFlow"),
Device::WhatsAppFlow(_) => write!(f, "Device::WhatsAppFlow"),
Device::AppFlow(_) => write!(f, "Device::AppFlow"),
Device::AniRouter(_) => write!(f, "Device::AniRouter"),
Device::DnisRouter(_) => write!(f, "Device::DnisRouter"),
Device::DigitRouter(_) => write!(f, "Device::DigitRouter"),
Device::TimeRangeRouter(_) => write!(f, "Device::TimeRangeRouter"),
Device::DayOfWeekRouter(_) => write!(f, "Device::DayOfWeekRouter"),
Device::DateRangeRouter(_) => write!(f, "Device::DateRangeRouter"),
Device::ZoneRouter(_) => write!(f, "Device::ZoneRouter"),
Device::HuntGroup(_) => write!(f, "Device::HuntGroup"),
Device::Client(_) => write!(f, "Device::Client"),
Device::Teams(_) => write!(f, "Device::Teams"),
Device::SipExtension(_) => write!(f, "Device::SipExtension"),
Device::SipGateway(_) => write!(f, "Device::SipGateway"),
Device::Remote(_) => write!(f, "Device::Remote"),
Device::Plugin(_) => write!(f, "Device::Plugin"),
Device::Play(_) => write!(f, "Device::Play"),
Device::Say(_) => write!(f, "Device::Say"),
Device::Voicemail(_) => write!(f, "Device::Voicemail"),
Device::Script(_) => write!(f, "Device::Script"),
Device::Queue(_) => write!(f, "Device::Queue"),
Device::Sms(_) => write!(f, "Device::Sms"),
Device::Email(_) => write!(f, "Device::Email"),
Device::Tag(_) => write!(f, "Device::Tag"),
Device::TagRouter(_) => write!(f, "Device::TagRouter"),
Device::MessagePlugin(_) => write!(f, "Device::MessagePlugin"),
Device::MessageButtons(_) => write!(f, "Device::MessageButtons"),
Device::MessageText(_) => write!(f, "Device::MessageText"),
Device::MessageTemplate(_) => write!(f, "Device::MessageTemplate"),
Device::MessageAniRouter(_) => write!(f, "Device::MessageAniRouter"),
Device::Service(_) => write!(f, "Device::Service"),
}
}
}
impl Debug for DeviceStruct {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("DeviceStruct")
.field("id", &self.id)
.field("device_type", &self.device_type)
.field("name", &self.name)
.field("extension", &self.extension)
.finish_non_exhaustive()
}
}
impl Debug for TagType {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
TagType::Session => write!(f, "Session"),
TagType::Global => write!(f, "Global"),
}
}
}
impl Debug for DeviceTag {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("DeviceTag")
.field("tag_type", &self.tag_type)
.field("name", &self.name)
.field("value", &self.value)
.finish()
}
}
impl Debug for RecordOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("RecordOptions")
.field("enabled", &self.enabled)
.field("retention", &self.retention)
.field("mix_type", &self.mix_type)
.field("sample_rate", &self.sample_rate)
.finish()
}
}
impl Debug for TranscribeOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("TranscribeOptions")
.field("enabled", &self.enabled)
.field("language", &self.language)
.finish()
}
}
impl Debug for AsrVendor {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
AsrVendor::Google => write!(f, "Google"),
AsrVendor::Deepgram => write!(f, "DeepGram"),
AsrVendor::Microsoft => write!(f, "Microsoft"),
AsrVendor::Aws => write!(f, "Aws"),
AsrVendor::Ibm => write!(f, "Ibm"),
AsrVendor::Nuance => write!(f, "Nuance"),
AsrVendor::Nvidia =>write!(f, "Nvidia"),
AsrVendor::Soniox => write!(f, "Soniox"),
}
}
}