use std::{borrow::Cow, error::Error as StdError, fmt, sync::Arc};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorKind {
Authentication,
Permission,
Input,
ResourceLimit,
Unavailable,
Timeout,
Cancelled,
AudioFormat,
Stream,
}
#[derive(Clone, Debug)]
pub struct Error {
kind: ErrorKind,
message: Cow<'static, str>,
request_id: Option<String>,
source: Option<Arc<dyn StdError + Send + Sync>>,
}
#[derive(Debug)]
struct RealtimeClosed;
impl fmt::Display for RealtimeClosed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("realtime session closed")
}
}
impl StdError for RealtimeClosed {}
impl Error {
pub fn cancelled() -> Self {
Self::new(ErrorKind::Cancelled, "operation cancelled")
}
pub(crate) fn realtime_closed() -> Self {
Self::new(ErrorKind::Cancelled, "realtime session closed").caused_by(RealtimeClosed)
}
pub(crate) fn is_realtime_closed(&self) -> bool {
self.source
.as_ref()
.is_some_and(|s| s.is::<RealtimeClosed>())
}
pub fn input(source: impl StdError + Send + Sync + 'static) -> Self {
Self::new(ErrorKind::Input, "input source failed").caused_by(source)
}
pub(crate) fn new(kind: ErrorKind, message: impl Into<Cow<'static, str>>) -> Self {
Self {
kind,
message: message.into(),
request_id: None,
source: None,
}
}
pub(crate) fn caused_by(mut self, source: impl StdError + Send + Sync + 'static) -> Self {
self.source = Some(Arc::new(source));
self
}
pub(crate) fn with_request_id(mut self, id: Option<&str>) -> Self {
if self.request_id.is_none() {
self.request_id = id.map(str::to_owned);
}
self
}
pub fn realtime_fault(&self) -> Option<&crate::realtime::RealtimeFault> {
self.source.as_ref()?.downcast_ref()
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn message(&self) -> &str {
&self.message
}
pub fn request_id(&self) -> Option<&str> {
self.request_id.as_deref()
}
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}", self.message)?;
if let Some(id) = &self.request_id {
write!(formatter, " (request {id})")?;
}
Ok(())
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.source
.as_deref()
.map(|error| error as &(dyn StdError + 'static))
}
}
impl From<tonic::Status> for Error {
fn from(status: tonic::Status) -> Self {
use tonic::Code;
let kind = match status.code() {
Code::Unauthenticated => ErrorKind::Authentication,
Code::PermissionDenied => ErrorKind::Permission,
Code::InvalidArgument => ErrorKind::Input,
Code::ResourceExhausted => ErrorKind::ResourceLimit,
Code::Unavailable => ErrorKind::Unavailable,
Code::DeadlineExceeded => ErrorKind::Timeout,
Code::Cancelled => ErrorKind::Cancelled,
_ => ErrorKind::Stream,
};
let message = if status.message().trim().is_empty() {
Cow::Borrowed("speech service request failed")
} else {
Cow::Owned(status.message().to_owned())
};
Self::new(kind, message)
.with_request_id(request_id(status.metadata()))
.caused_by(status)
}
}
pub(crate) fn request_id(metadata: &tonic::metadata::MetadataMap) -> Option<&str> {
metadata
.get("x-request-id")
.and_then(|value| value.to_str().ok())
}