Skip to content
This repository has been archived by the owner on Aug 16, 2023. It is now read-only.

Commit

Permalink
Add typed errors (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
feymartynov committed Oct 9, 2020
1 parent 1e565d2 commit 0c6946e
Show file tree
Hide file tree
Showing 19 changed files with 532 additions and 264 deletions.
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@
- [List](api/rtc_stream/list.md)
- [Agent](api/agent.md)
- [List](api/agent/list.md)
- [Errors](api/errors.md)
49 changes: 49 additions & 0 deletions docs/src/api/errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Errors

In case of an error, the response payload is an RFC7807 Problem Details object:

Name | Type | Default | Description
------ | ------ | ---------- | ---------------------------------
type | string | _required_ | Error type.
title | string | _required_ | Human-readable short description.
detail | string | _optional_ | Detailed error description.
status | int | _required_ | HTTP-compatible status code. The same code is in response properties.

## Troubleshooting by status code

- **400 Bad Request** – Failed to parse JSON payload of the message or endpoint-specific validation failed.
- **403 Forbidden** – Authorization failed. Check out Authorization section of the endpoint.
- **404 Not Found** – The entity doesn't exist in the DB or expired.
- **405 Method Not Allowed** – Unknown `method` property value in the request.
- **422 Unprocessable Entity** – DB query error or some logic error.
- **424 Failed Dependency** – The backend responded with an error.
- **500 Internal Server Error** – A low-level problem occurred on the server.
- **503 Service Unavailable** – The service is unable to complete the request due to lack of backend capacity.

## Error types

One must rely on the `type` field of the error for error identification, not the `title` nor `status`.
The following types are a part of the service's API and are guaranteed to maintain compatibility.

- `access_denied` – The action was forbidden by [authorization](authz.md#Authorization).
- `agent_not_entered_the_room` – The agent must preliminary make [room.enter](room/enter.md#room.enter) request.
- `authorization_failed` – Authorization request failed due to a network error or another reason.
- `backend_recording_missing` – The backend responded that it doesn't have the recording for the RTC.
- `backend_request_failed` – The backend responded with an error code.
- `backend_request_timed_out` – The backend request didn't finished in a reasonable time.
- `backend_not_found` – The backend that hosted the RTC went offline.
- `capacity_exceeded` – There's no free capacity left on the backend to connect to.
- `database_connection_acquisition_failed` – The service couldn't obtain a DB connection from the pool.
- `database_query_failed` – The database returned an error while executing a query.
- `invalid_subscription_object` – An object for dynamic subscription is not of format `["rooms", UUID, "events"]`.
- `message_building_failed` – An error occurred while building a message to another service.
- `message_handling_failed` – An incoming message is likely to have non-valid JSON payload or missing required properties.
- `message_parsing_failed` – Failed to parse a message from another service.
- `no_available_backends` – No backends found to host the RTC.
- `not_implemented` – The requested feature is not supported.
- `resubscription_failed` – The services has failed to resubscribe to topics after reconnect.
- `stats_collection_failed` – Couldn't collect metrics from one of the sources.
- `publish_failed` – Failed to publish an MQTT message.
- `room_not_found` – A [room](room.md#Room) is missing or closed.
- `rtc_not_found` – An [RTC](rtc.md#Real-time_Connection) is missing or closed.
- `unknown_method` – An unsupported value in `method` property of the request message.
10 changes: 10 additions & 0 deletions src/app/context.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::sync::Arc;

use chrono::{DateTime, Utc};
use diesel::pg::PgConnection;
use diesel::r2d2::{ConnectionManager, PooledConnection};
use slog::{Logger, OwnedKV, SendSyncRefUnwindSafeKV};
use svc_agent::{queue_counter::QueueCounterHandle, AgentId};
use svc_authz::cache::ConnectionPool as RedisConnectionPool;
use svc_authz::ClientMap as Authz;

use crate::app::error::{Error as AppError, ErrorExt, ErrorKind as AppErrorKind};
use crate::app::metrics::{DbPoolStatsCollector, DynamicStatsCollector};
use crate::backend::janus::Client as JanusClient;
use crate::config::Config;
Expand All @@ -26,6 +29,13 @@ pub(crate) trait GlobalContext: Sync {
fn redis_pool(&self) -> &Option<RedisConnectionPool>;
fn db_pool_stats(&self) -> &Option<DbPoolStatsCollector>;
fn dynamic_stats(&self) -> Option<&DynamicStatsCollector>;

fn get_conn(&self) -> Result<PooledConnection<ConnectionManager<PgConnection>>, AppError> {
self.db()
.get()
.map_err(|err| anyhow::Error::from(err).context("Failed to acquire DB connection"))
.error(AppErrorKind::DbConnAcquisitionFailed)
}
}

pub(crate) trait MessageContext: Send {
Expand Down
6 changes: 3 additions & 3 deletions src/app/endpoint/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ impl RequestHandler for ListHandler {
) -> Result {
// Check whether the room exists and open.
let room = {
let conn = context.db().get()?;
let conn = context.get_conn()?;

db::room::FindQuery::new()
.id(payload.room_id)
.time(db::room::now())
.execute(&conn)?
.ok_or_else(|| anyhow!("Room not found or closed"))
.status(ResponseStatus::NOT_FOUND)?
.error(AppErrorKind::RoomNotFound)?
};

shared::add_room_logger_tags(context, &room);
Expand All @@ -56,7 +56,7 @@ impl RequestHandler for ListHandler {

// Get agents list in the room.
let agents = {
let conn = context.db().get()?;
let conn = context.get_conn()?;

db::agent::ListQuery::new()
.room_id(payload.room_id)
Expand Down
19 changes: 9 additions & 10 deletions src/app/endpoint/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use svc_agent::mqtt::{
SubscriptionTopic,
};
use svc_agent::{Addressable, AgentId, Subscription};
use svc_error::Error as SvcError;
use uuid::Uuid;

use crate::app::context::Context;
Expand Down Expand Up @@ -44,7 +43,7 @@ impl RequestHandler for UnicastHandler {
context.add_logger_tags(o!("room_id" => payload.room_id.to_string()));

{
let conn = context.db().get()?;
let conn = context.get_conn()?;
let room = find_room(payload.room_id, &conn)?;
shared::add_room_logger_tags(context, &room);
check_room_presence(&room, reqp.as_agent_id(), &conn)?;
Expand All @@ -55,11 +54,11 @@ impl RequestHandler for UnicastHandler {
Subscription::multicast_requests_from(&payload.agent_id, Some(API_VERSION))
.subscription_topic(context.agent_id(), API_VERSION)
.map_err(|err| anyhow!("Error building responses subscription topic: {}", err))
.status(ResponseStatus::UNPROCESSABLE_ENTITY)?;
.error(AppErrorKind::MessageBuildingFailed)?;

let correlation_data = to_base64(reqp)
.map_err(|err| err.context("Error encoding incoming request properties"))
.status(ResponseStatus::UNPROCESSABLE_ENTITY)?;
.error(AppErrorKind::MessageBuildingFailed)?;

let props = reqp.to_request(
reqp.method(),
Expand Down Expand Up @@ -104,7 +103,7 @@ impl RequestHandler for BroadcastHandler {
context.add_logger_tags(o!("room_id" => payload.room_id.to_string()));

let room = {
let conn = context.db().get()?;
let conn = context.get_conn()?;
let room = find_room(payload.room_id, &conn)?;
shared::add_room_logger_tags(context, &room);
check_room_presence(&room, &reqp.as_agent_id(), &conn)?;
Expand Down Expand Up @@ -152,7 +151,7 @@ impl ResponseHandler for CallbackHandler {
respp: &IncomingResponseProperties,
) -> Result {
let reqp = from_base64::<IncomingRequestProperties>(respp.correlation_data())
.status(ResponseStatus::BAD_REQUEST)?;
.error(AppErrorKind::MessageParsingFailed)?;

let short_term_timing = ShortTermTimingProperties::until_now(context.start_timestamp());

Expand All @@ -178,27 +177,27 @@ impl ResponseHandler for CallbackHandler {

///////////////////////////////////////////////////////////////////////////////

fn find_room(id: Uuid, conn: &PgConnection) -> StdResult<Room, SvcError> {
fn find_room(id: Uuid, conn: &PgConnection) -> StdResult<Room, AppError> {
db::room::FindQuery::new()
.time(db::room::now())
.id(id)
.execute(&conn)?
.ok_or_else(|| anyhow!("Room not found or closed"))
.status(ResponseStatus::NOT_FOUND)
.error(AppErrorKind::RoomNotFound)
}

fn check_room_presence(
room: &Room,
agent_id: &AgentId,
conn: &PgConnection,
) -> StdResult<(), SvcError> {
) -> StdResult<(), AppError> {
let results = db::agent::ListQuery::new()
.room_id(room.id())
.agent_id(agent_id)
.execute(conn)?;

if results.is_empty() {
Err(anyhow!("Agent is not online in the room")).status(ResponseStatus::NOT_FOUND)
Err(anyhow!("Agent is not online in the room")).error(AppErrorKind::AgentNotEnteredTheRoom)
} else {
Ok(())
}
Expand Down
22 changes: 12 additions & 10 deletions src/app/endpoint/metric.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use async_std::stream;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use diesel::pg::PgConnection;
use serde_derive::Deserialize;
use svc_agent::mqtt::{
IncomingEventProperties, IntoPublishableMessage, OutgoingEvent, ResponseStatus,
ShortTermTimingProperties,
IncomingEventProperties, IntoPublishableMessage, OutgoingEvent, ShortTermTimingProperties,
};

use crate::app::context::Context;
Expand Down Expand Up @@ -44,7 +44,7 @@ impl EventHandler for PullHandler {
let stats = qc
.get_stats(payload.duration)
.map_err(|err| anyhow!("Failed to get stats: {}", err))
.status(ResponseStatus::BAD_REQUEST)?;
.error(AppErrorKind::StatsCollectionFailed)?;

vec![
Metric::IncomingQueueRequests(MetricValue::new(
Expand Down Expand Up @@ -97,10 +97,14 @@ impl EventHandler for PullHandler {
append_db_pool_stats(&mut metrics, context, now);

append_dynamic_stats(&mut metrics, context, now)
.status(ResponseStatus::UNPROCESSABLE_ENTITY)?;
.error(AppErrorKind::StatsCollectionFailed)?;

append_janus_stats(&mut metrics, context, now)
.status(ResponseStatus::UNPROCESSABLE_ENTITY)?;
{
let conn = context.get_conn()?;

append_janus_stats(&mut metrics, &conn, now)
.error(AppErrorKind::StatsCollectionFailed)?;
}

let metrics2 = metrics
.clone()
Expand Down Expand Up @@ -165,15 +169,13 @@ fn append_dynamic_stats<C: Context>(
Ok(())
}

fn append_janus_stats<C: Context>(
fn append_janus_stats(
metrics: &mut Vec<Metric>,
context: &C,
conn: &PgConnection,
now: DateTime<Utc>,
) -> anyhow::Result<()> {
use anyhow::Context;

let conn = context.db().get()?;

// The number of online janus backends.
let online_backends_count =
db::janus_backend::count(&conn).context("Failed to get janus backends count")?;
Expand Down
6 changes: 3 additions & 3 deletions src/app/endpoint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use svc_agent::mqtt::{
IncomingEvent, IncomingEventProperties, IncomingRequest, IncomingRequestProperties,
IncomingResponse, IncomingResponseProperties,
};
use svc_error::Error as SvcError;

use crate::app::context::Context;
use crate::app::error::Error as AppError;
pub(self) use crate::app::message_handler::MessageStream;
use crate::app::message_handler::{
EventEnvelopeHandler, RequestEnvelopeHandler, ResponseEnvelopeHandler,
Expand All @@ -17,7 +17,7 @@ use crate::backend::janus;

///////////////////////////////////////////////////////////////////////////////

pub(crate) type Result = StdResult<MessageStream, SvcError>;
pub(crate) type Result = StdResult<MessageStream, AppError>;

#[async_trait]
pub(crate) trait RequestHandler {
Expand Down Expand Up @@ -152,5 +152,5 @@ pub(crate) mod system;

pub(self) mod prelude {
pub(super) use super::{shared, EventHandler, RequestHandler, ResponseHandler, Result};
pub(super) use crate::app::message_handler::SvcErrorSugar;
pub(super) use crate::app::error::{Error as AppError, ErrorExt, ErrorKind as AppErrorKind};
}
Loading

0 comments on commit 0c6946e

Please sign in to comment.