use std::string::ToString;
use derive_more::Display;
use medea_control_api_proto::grpc::api as proto;
use crate::{
api::control::{
callback::url::CallbackUrlParseError,
grpc::server::GrpcControlApiError,
refs::{
fid::ParseFidError, local_uri::LocalUriParseError,
src_uri::SrcParseError,
},
TryFromElementError, TryFromProtobufError,
},
signalling::{
elements::{member::MemberError, MembersLoadError},
participants::ParticipantServiceErr,
room::RoomError,
room_service::RoomServiceError,
},
};
pub struct ErrorResponse {
error_code: ErrorCode,
element_id: Option<String>,
explanation: Option<String>,
}
impl ErrorResponse {
pub fn new<T: ToString>(error_code: ErrorCode, element_id: &T) -> Self {
Self {
error_code,
element_id: Some(element_id.to_string()),
explanation: None,
}
}
#[inline]
#[must_use]
pub fn without_id(error_code: ErrorCode) -> Self {
Self {
error_code,
element_id: None,
explanation: None,
}
}
pub fn unexpected<B: ToString>(unknown_error: &B) -> Self {
Self {
error_code: ErrorCode::UnexpectedError,
explanation: Some(unknown_error.to_string()),
element_id: None,
}
}
#[inline]
#[must_use]
pub fn with_explanation(
error_code: ErrorCode,
explanation: String,
id: Option<String>,
) -> Self {
Self {
error_code,
explanation: Some(explanation),
element_id: id,
}
}
}
impl From<ErrorResponse> for proto::Error {
fn from(resp: ErrorResponse) -> Self {
let text = if let Some(additional_text) = &resp.explanation {
format!("{} {}", resp.error_code.to_string(), additional_text)
} else {
resp.error_code.to_string()
};
Self {
doc: String::new(),
text,
element: resp.element_id.unwrap_or_default(),
code: resp.error_code as u32,
}
}
}
#[derive(Debug, Display)]
pub enum ErrorCode {
#[display(fmt = "Unimplemented API call.")]
UnimplementedCall = 1000,
#[display(fmt = "Request doesn't contain any elements")]
NoElement = 1001,
#[display(fmt = "Provided fid can't point to provided element")]
ElementIdMismatch = 1002,
#[display(fmt = "Room not found.")]
RoomNotFound = 1003,
#[display(fmt = "Member not found.")]
MemberNotFound = 1004,
#[display(fmt = "Endpoint not found.")]
EndpointNotFound = 1005,
#[display(fmt = "Expecting Room element but it's not.")]
NotRoomInSpec = 1006,
#[display(fmt = "Expected Member element but it's not.")]
NotMemberInSpec = 1007,
#[display(fmt = "Invalid source URI in 'WebRtcPlayEndpoint'.")]
InvalidSrcUri = 1008,
#[display(fmt = "Provided not source URI in 'WebRtcPlayEndpoint'.")]
NotSourceUri = 1009,
#[display(fmt = "Element's URI don't have 'local://' prefix.")]
ElementIdIsNotLocal = 1010,
#[display(fmt = "You provided element's FID/URI with too many paths.")]
ElementIdIsTooLong = 1011,
#[display(
fmt = "Missing some fields in source URI of WebRtcPublishEndpoint."
)]
MissingFieldsInSrcUri = 1012,
#[display(fmt = "Provided empty element ID.")]
EmptyElementId = 1013,
#[display(fmt = "Provided empty elements FIDs list.")]
EmptyElementsList = 1014,
#[display(fmt = "Provided not the same Room IDs in elements IDs. \
Probably you try use 'Delete' method for elements with \
different Room IDs")]
ProvidedNotSameRoomIds = 1015,
#[display(fmt = "Room with provided FID already exists.")]
RoomAlreadyExists = 1016,
#[display(fmt = "Member with provided FID already exists.")]
MemberAlreadyExists = 1017,
#[display(fmt = "Endpoint with provided FID already exists.")]
EndpointAlreadyExists = 1018,
#[display(fmt = "Missing path in some reference to the Medea element.")]
MissingPath = 1019,
#[display(fmt = "Missing host in callback URL.")]
MissingHostInCallbackUrl = 1020,
#[display(fmt = "Unsupported callback URL protocol.")]
UnsupportedCallbackUrlProtocol = 1021,
#[display(fmt = "Invalid callback URL.")]
InvalidCallbackUrl = 1022,
#[display(fmt = "Encountered negative duration")]
NegativeDuration = 1023,
#[display(fmt = "Unexpected error happened.")]
UnexpectedError = 2000,
}
impl From<ParticipantServiceErr> for ErrorResponse {
fn from(err: ParticipantServiceErr) -> Self {
use ParticipantServiceErr::{
EndpointNotFound, MemberError, ParticipantNotFound,
};
match err {
EndpointNotFound(id) => Self::new(ErrorCode::EndpointNotFound, &id),
ParticipantNotFound(id) => {
Self::new(ErrorCode::MemberNotFound, &id)
}
MemberError(_) => Self::unexpected(&err),
}
}
}
impl From<TryFromProtobufError> for ErrorResponse {
fn from(err: TryFromProtobufError) -> Self {
use TryFromProtobufError as E;
match err {
E::SrcUriError(e) => e.into(),
E::CallbackUrlParseErr(e) => e.into(),
E::NotMemberElementInRoomElement(id) => Self::with_explanation(
ErrorCode::UnimplementedCall,
String::from(
"Not Member elements in Room element currently is \
unimplemented.",
),
Some(id),
),
E::UnimplementedEndpoint(id) => Self::with_explanation(
ErrorCode::UnimplementedCall,
String::from("Endpoint is not implemented."),
Some(id),
),
E::ExpectedOtherElement(element, id) => Self::with_explanation(
ErrorCode::ElementIdMismatch,
format!(
"Provided fid can not point to element of type [{}]",
element
),
Some(id),
),
E::EmptyElement(id) => Self::with_explanation(
ErrorCode::NoElement,
String::from("No element was provided"),
Some(id),
),
E::NegativeDuration(id, field) => Self::with_explanation(
ErrorCode::NegativeDuration,
format!(
"Element [id = {}] contains negative duration field `{}`",
id, field
),
Some(id),
),
}
}
}
impl From<LocalUriParseError> for ErrorResponse {
fn from(err: LocalUriParseError) -> Self {
use LocalUriParseError as E;
match err {
E::NotLocal(text) => {
Self::new(ErrorCode::ElementIdIsNotLocal, &text)
}
E::TooManyPaths(text) => {
Self::new(ErrorCode::ElementIdIsTooLong, &text)
}
E::Empty => Self::without_id(ErrorCode::EmptyElementId),
E::MissingPaths(text) => {
Self::new(ErrorCode::MissingFieldsInSrcUri, &text)
}
E::UrlParseErr(id, _) => Self::new(ErrorCode::InvalidSrcUri, &id),
}
}
}
impl From<CallbackUrlParseError> for ErrorResponse {
fn from(err: CallbackUrlParseError) -> Self {
use CallbackUrlParseError::{
MissingHost, UnsupportedScheme, UrlParseErr,
};
match err {
MissingHost => {
Self::without_id(ErrorCode::MissingHostInCallbackUrl)
}
UnsupportedScheme => {
Self::without_id(ErrorCode::UnsupportedCallbackUrlProtocol)
}
UrlParseErr(_) => Self::without_id(ErrorCode::InvalidCallbackUrl),
}
}
}
impl From<ParseFidError> for ErrorResponse {
fn from(err: ParseFidError) -> Self {
use ParseFidError::{Empty, MissingPath, TooManyPaths};
match err {
TooManyPaths(text) => {
Self::new(ErrorCode::ElementIdIsTooLong, &text)
}
Empty => Self::without_id(ErrorCode::EmptyElementId),
MissingPath(text) => Self::new(ErrorCode::MissingPath, &text),
}
}
}
impl From<RoomError> for ErrorResponse {
fn from(err: RoomError) -> Self {
use RoomError as E;
match err {
E::MemberError(e) => e.into(),
E::MembersLoadError(e) => e.into(),
E::ParticipantServiceErr(e) => e.into(),
E::MemberAlreadyExists(id) => {
Self::new(ErrorCode::MemberAlreadyExists, &id)
}
E::EndpointAlreadyExists(id) => {
Self::new(ErrorCode::EndpointAlreadyExists, &id)
}
E::TryFromElement(id) => Self::new(ErrorCode::NotMemberInSpec, &id),
E::WrongRoomId(_, _)
| E::PeerNotFound(_)
| E::CallbackClientError(_)
| E::NoTurnCredentials(_)
| E::PeerError(_)
| E::BadRoomSpec(_)
| E::PeerTrafficWatcherMailbox(_)
| E::AuthorizationError
| E::TurnServiceErr(_) => Self::unexpected(&err),
}
}
}
impl From<MembersLoadError> for ErrorResponse {
fn from(err: MembersLoadError) -> Self {
use MembersLoadError::{
EndpointNotFound, MemberNotFound, TryFromError,
};
use TryFromElementError::{NotMember, NotRoom};
match err {
TryFromError(e, id) => match e {
NotMember => Self::new(ErrorCode::NotMemberInSpec, &id),
NotRoom => Self::new(ErrorCode::NotRoomInSpec, &id),
},
MemberNotFound(id) => Self::new(ErrorCode::MemberNotFound, &id),
EndpointNotFound(id) => Self::new(ErrorCode::EndpointNotFound, &id),
}
}
}
impl From<MemberError> for ErrorResponse {
fn from(err: MemberError) -> Self {
match err {
MemberError::EndpointNotFound(id) => {
Self::new(ErrorCode::EndpointNotFound, &id)
}
}
}
}
impl From<SrcParseError> for ErrorResponse {
fn from(err: SrcParseError) -> Self {
use SrcParseError::{LocalUriParseError, NotSrcUri};
match err {
NotSrcUri(text) => Self::new(ErrorCode::NotSourceUri, &text),
LocalUriParseError(err) => err.into(),
}
}
}
impl From<RoomServiceError> for ErrorResponse {
fn from(err: RoomServiceError) -> Self {
use RoomServiceError as E;
match err {
E::RoomNotFound(id) => Self::new(ErrorCode::RoomNotFound, &id),
E::RoomAlreadyExists(id) => {
Self::new(ErrorCode::RoomAlreadyExists, &id)
}
E::RoomError(e) => e.into(),
E::EmptyUrisList => Self::without_id(ErrorCode::EmptyElementsList),
E::NotSameRoomIds(id1, id2) => Self::with_explanation(
ErrorCode::ProvidedNotSameRoomIds,
format!(
"All FID's must have equal room_id. Provided Id's are \
different: [{}] != [{}]",
id1, id2
),
None,
),
E::RoomMailboxErr(_)
| E::FailedToLoadStaticSpecs(_)
| E::TryFromElement(_) => Self::unexpected(&err),
}
}
}
impl From<GrpcControlApiError> for ErrorResponse {
fn from(err: GrpcControlApiError) -> Self {
use GrpcControlApiError as E;
match err {
E::Fid(e) => e.into(),
E::TryFromProtobuf(e) => e.into(),
E::RoomServiceError(e) => e.into(),
E::RoomServiceMailboxError(_) => Self::unexpected(&err),
}
}
}