Use a custom error type in core

This commit is contained in:
Dustin J. Mitchell 2024-11-17 19:03:50 -05:00
parent 5a704729d7
commit dd6cc04d28
No known key found for this signature in database
10 changed files with 47 additions and 21 deletions

1
Cargo.lock generated
View file

@ -1424,6 +1424,7 @@ dependencies = [
"env_logger", "env_logger",
"log", "log",
"pretty_assertions", "pretty_assertions",
"thiserror",
"uuid", "uuid",
] ]

View file

@ -9,6 +9,7 @@ license = "MIT"
[dependencies] [dependencies]
uuid.workspace = true uuid.workspace = true
anyhow.workspace = true anyhow.workspace = true
thiserror.workspace = true
log.workspace = true log.workspace = true
env_logger.workspace = true env_logger.workspace = true
chrono.workspace = true chrono.workspace = true

9
core/src/error.rs Normal file
View file

@ -0,0 +1,9 @@
#[derive(Debug, thiserror::Error)]
pub enum ServerError {
/// There is no client with the given ClientId.
#[error("No such client")]
NoSuchClient,
#[error(transparent)]
Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error
}

View file

@ -23,10 +23,12 @@
//! * [`ServerConfig`] providing basic configuration for the server's behavior. //! * [`ServerConfig`] providing basic configuration for the server's behavior.
//! * `client_id` and a [`Client`] providing the client metadata. //! * `client_id` and a [`Client`] providing the client metadata.
mod error;
mod inmemory; mod inmemory;
mod server; mod server;
mod storage; mod storage;
pub use error::*;
pub use inmemory::*; pub use inmemory::*;
pub use server::*; pub use server::*;
pub use storage::*; pub use storage::*;

View file

@ -1,3 +1,4 @@
use crate::error::ServerError;
use crate::storage::{Client, Snapshot, Storage, StorageTxn}; use crate::storage::{Client, Snapshot, Storage, StorageTxn};
use chrono::Utc; use chrono::Utc;
use uuid::Uuid; use uuid::Uuid;
@ -110,7 +111,7 @@ impl Server {
client_id: ClientId, client_id: ClientId,
client: Client, client: Client,
parent_version_id: VersionId, parent_version_id: VersionId,
) -> anyhow::Result<GetVersionResult> { ) -> Result<GetVersionResult, ServerError> {
let mut txn = self.storage.txn()?; let mut txn = self.storage.txn()?;
// If a version with parentVersionId equal to the requested parentVersionId exists, it is // If a version with parentVersionId equal to the requested parentVersionId exists, it is
@ -147,7 +148,7 @@ impl Server {
client: Client, client: Client,
parent_version_id: VersionId, parent_version_id: VersionId,
history_segment: HistorySegment, history_segment: HistorySegment,
) -> anyhow::Result<(AddVersionResult, SnapshotUrgency)> { ) -> Result<(AddVersionResult, SnapshotUrgency), ServerError> {
let mut txn = self.storage.txn()?; let mut txn = self.storage.txn()?;
log::debug!( log::debug!(
@ -206,7 +207,7 @@ impl Server {
client: Client, client: Client,
version_id: VersionId, version_id: VersionId,
data: Vec<u8>, data: Vec<u8>,
) -> anyhow::Result<()> { ) -> Result<(), ServerError> {
let mut txn = self.storage.txn()?; let mut txn = self.storage.txn()?;
log::debug!( log::debug!(
@ -290,7 +291,7 @@ impl Server {
&self, &self,
client_id: ClientId, client_id: ClientId,
client: Client, client: Client,
) -> anyhow::Result<Option<(Uuid, Vec<u8>)>> { ) -> Result<Option<(Uuid, Vec<u8>)>, ServerError> {
let mut txn = self.storage.txn()?; let mut txn = self.storage.txn()?;
Ok(if let Some(snap) = client.snapshot { Ok(if let Some(snap) = client.snapshot {
@ -302,8 +303,8 @@ impl Server {
} }
/// Convenience method to get a transaction for the embedded storage. /// Convenience method to get a transaction for the embedded storage.
pub fn txn(&self) -> anyhow::Result<Box<dyn StorageTxn + '_>> { pub fn txn(&self) -> Result<Box<dyn StorageTxn + '_>, ServerError> {
self.storage.txn() Ok(self.storage.txn()?)
} }
} }

View file

@ -1,4 +1,6 @@
use crate::api::{client_id_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE}; use crate::api::{
client_id_header, failure_to_ise, server_error_to_actix, ServerState, SNAPSHOT_CONTENT_TYPE,
};
use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result};
use futures::StreamExt; use futures::StreamExt;
use std::sync::Arc; use std::sync::Arc;
@ -50,7 +52,7 @@ pub(crate) async fn service(
// completely, to avoid blocking other storage access while that data is // completely, to avoid blocking other storage access while that data is
// in transit. // in transit.
let client = { let client = {
let mut txn = server_state.server.txn().map_err(failure_to_ise)?; let mut txn = server_state.server.txn().map_err(server_error_to_actix)?;
// get, or create, the client // get, or create, the client
match txn.get_client(client_id).map_err(failure_to_ise)? { match txn.get_client(client_id).map_err(failure_to_ise)? {
@ -66,7 +68,7 @@ pub(crate) async fn service(
server_state server_state
.server .server
.add_snapshot(client_id, client, version_id, body.to_vec()) .add_snapshot(client_id, client, version_id, body.to_vec())
.map_err(failure_to_ise)?; .map_err(server_error_to_actix)?;
Ok(HttpResponse::Ok().body("")) Ok(HttpResponse::Ok().body(""))
} }

View file

@ -1,6 +1,7 @@
use crate::api::{ use crate::api::{
client_id_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, client_id_header, failure_to_ise, server_error_to_actix, ServerState,
PARENT_VERSION_ID_HEADER, SNAPSHOT_REQUEST_HEADER, VERSION_ID_HEADER, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, SNAPSHOT_REQUEST_HEADER,
VERSION_ID_HEADER,
}; };
use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result};
use futures::StreamExt; use futures::StreamExt;
@ -58,7 +59,7 @@ pub(crate) async fn service(
// completely, to avoid blocking other storage access while that data is // completely, to avoid blocking other storage access while that data is
// in transit. // in transit.
let client = { let client = {
let mut txn = server_state.server.txn().map_err(failure_to_ise)?; let mut txn = server_state.server.txn().map_err(server_error_to_actix)?;
match txn.get_client(client_id).map_err(failure_to_ise)? { match txn.get_client(client_id).map_err(failure_to_ise)? {
Some(client) => client, Some(client) => client,
@ -73,7 +74,7 @@ pub(crate) async fn service(
let (result, snap_urgency) = server_state let (result, snap_urgency) = server_state
.server .server
.add_version(client_id, client, parent_version_id, body.to_vec()) .add_version(client_id, client, parent_version_id, body.to_vec())
.map_err(failure_to_ise)?; .map_err(server_error_to_actix)?;
Ok(match result { Ok(match result {
AddVersionResult::Ok(version_id) => { AddVersionResult::Ok(version_id) => {

View file

@ -1,6 +1,6 @@
use crate::api::{ use crate::api::{
client_id_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, client_id_header, failure_to_ise, server_error_to_actix, ServerState,
PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER,
}; };
use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
use std::sync::Arc; use std::sync::Arc;
@ -23,7 +23,7 @@ pub(crate) async fn service(
let parent_version_id = path.into_inner(); let parent_version_id = path.into_inner();
let (client, client_id) = { let (client, client_id) = {
let mut txn = server_state.server.txn().map_err(failure_to_ise)?; let mut txn = server_state.server.txn().map_err(server_error_to_actix)?;
let client_id = client_id_header(&req)?; let client_id = client_id_header(&req)?;
@ -37,7 +37,7 @@ pub(crate) async fn service(
return match server_state return match server_state
.server .server
.get_child_version(client_id, client, parent_version_id) .get_child_version(client_id, client, parent_version_id)
.map_err(failure_to_ise)? .map_err(server_error_to_actix)?
{ {
GetVersionResult::Success { GetVersionResult::Success {
version_id, version_id,

View file

@ -1,5 +1,6 @@
use crate::api::{ use crate::api::{
client_id_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE, VERSION_ID_HEADER, client_id_header, failure_to_ise, server_error_to_actix, ServerState, SNAPSHOT_CONTENT_TYPE,
VERSION_ID_HEADER,
}; };
use actix_web::{error, get, web, HttpRequest, HttpResponse, Result}; use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
use std::sync::Arc; use std::sync::Arc;
@ -20,7 +21,7 @@ pub(crate) async fn service(
let client_id = client_id_header(&req)?; let client_id = client_id_header(&req)?;
let client = { let client = {
let mut txn = server_state.server.txn().map_err(failure_to_ise)?; let mut txn = server_state.server.txn().map_err(server_error_to_actix)?;
txn.get_client(client_id) txn.get_client(client_id)
.map_err(failure_to_ise)? .map_err(failure_to_ise)?
.ok_or_else(|| error::ErrorNotFound("no such client"))? .ok_or_else(|| error::ErrorNotFound("no such client"))?
@ -29,7 +30,7 @@ pub(crate) async fn service(
if let Some((version_id, data)) = server_state if let Some((version_id, data)) = server_state
.server .server
.get_snapshot(client_id, client) .get_snapshot(client_id, client)
.map_err(failure_to_ise)? .map_err(server_error_to_actix)?
{ {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type(SNAPSHOT_CONTENT_TYPE) .content_type(SNAPSHOT_CONTENT_TYPE)

View file

@ -1,5 +1,5 @@
use actix_web::{error, http::StatusCode, web, HttpRequest, Result, Scope}; use actix_web::{error, http::StatusCode, web, HttpRequest, Result, Scope};
use taskchampion_sync_server_core::{ClientId, Server}; use taskchampion_sync_server_core::{ClientId, Server, ServerError};
mod add_snapshot; mod add_snapshot;
mod add_version; mod add_version;
@ -43,6 +43,14 @@ fn failure_to_ise(err: anyhow::Error) -> impl actix_web::ResponseError {
error::InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR) error::InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR)
} }
/// Convert a ServerError to an Actix error
fn server_error_to_actix(err: ServerError) -> actix_web::Error {
match err {
ServerError::NoSuchClient => error::ErrorNotFound(err),
ServerError::Other(err) => error::ErrorInternalServerError(err),
}
}
/// Get the client id /// Get the client id
fn client_id_header(req: &HttpRequest) -> Result<ClientId> { fn client_id_header(req: &HttpRequest) -> Result<ClientId> {
fn badrequest() -> error::Error { fn badrequest() -> error::Error {