Skip to content

Commit

Permalink
Extend-errors (#15)
Browse files Browse the repository at this point in the history
This PR extends errors to allow users of the SDK to set the details json
object.

The PR also implements `IntoResponse` for all the error types, so they
can be returned directly from their respective routes.

This means the routes.rs file is removed. Note we add a `fetch_metrics`
function, this contains the logic that used to live in the respective
route (this was the only route with logic).

Before merging, I'll move all the error types into their own file, but
wanted to keep them as-is for now, so it's easier to see what
changed/stayed the same.

You may notice the `IntoResponse` implementation does _not_ use the
`to_string` method on the error types to add the prefix to the message.
This is intentional, that information is conveyed with the status code.
  • Loading branch information
BenoitRanque authored Jul 4, 2024
1 parent 29adcb5 commit f4f6502
Show file tree
Hide file tree
Showing 7 changed files with 585 additions and 537 deletions.
223 changes: 4 additions & 219 deletions crates/sdk/src/connector.rs
Original file line number Diff line number Diff line change
@@ -1,225 +1,10 @@
use std::error::Error;
use std::fmt::Display;
use std::path::{Path, PathBuf};

use crate::json_response::JsonResponse;
use async_trait::async_trait;
use ndc_models as models;
use serde::Serialize;
use thiserror::Error;

use crate::json_response::JsonResponse;

use std::path::Path;
pub mod error;
pub mod example;

/// Errors which occur when trying to validate connector
/// configuration.
///
/// See [`Connector::parse_configuration`].
#[derive(Debug, Error)]
pub enum ParseError {
#[error("error parsing configuration: {0}")]
ParseError(LocatedError),
#[error("error validating configuration: {0}")]
ValidateError(InvalidNodes),
#[error("could not find configuration file: {0}")]
CouldNotFindConfiguration(PathBuf),
#[error("error processing configuration: {0}")]
IoError(#[from] std::io::Error),
#[error("error processing configuration: {0}")]
Other(#[from] Box<dyn Error + Send + Sync>),
}

/// An error associated with the position of a single character in a text file.
#[derive(Debug, Clone)]
pub struct LocatedError {
pub file_path: PathBuf,
pub line: usize,
pub column: usize,
pub message: String,
}

impl Display for LocatedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{0}:{1}:{2}: {3}",
self.file_path.display(),
self.line,
self.column,
self.message
)
}
}

/// An error associated with a node in a graph structure.
#[derive(Debug, Clone)]
pub struct InvalidNode {
pub file_path: PathBuf,
pub node_path: Vec<KeyOrIndex>,
pub message: String,
}

impl Display for InvalidNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}, at ", self.file_path.display())?;
for segment in &self.node_path {
write!(f, ".{segment}")?;
}
write!(f, ": {}", self.message)?;
Ok(())
}
}

/// A set of invalid nodes.
#[derive(Debug, Clone)]
pub struct InvalidNodes(pub Vec<InvalidNode>);

impl Display for InvalidNodes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut iterator = self.0.iter();
if let Some(first) = iterator.next() {
first.fmt(f)?;
}
for next in iterator {
write!(f, ", {next}")?;
}
Ok(())
}
}

/// A segment in a node path, used with [InvalidNode].
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum KeyOrIndex {
Key(String),
Index(u32),
}

impl Display for KeyOrIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Key(key) => write!(f, "[{key:?}]"),
Self::Index(index) => write!(f, "[{index}]"),
}
}
}

/// Errors which occur when trying to initialize connector
/// state.
///
/// See [`Connector::try_init_state`].
#[derive(Debug, Error)]
pub enum InitializationError {
#[error("error initializing connector state: {0}")]
Other(#[from] Box<dyn Error + Send + Sync>),
}

/// Errors which occur when trying to update metrics.
///
/// See [`Connector::fetch_metrics`].
#[derive(Debug, Error)]
pub enum FetchMetricsError {
#[error("error fetching metrics: {0}")]
Other(#[from] Box<dyn Error + Send + Sync>),
}

/// Errors which occur when checking connector health.
///
/// See [`Connector::health_check`].
#[derive(Debug, Error)]
pub enum HealthError {
#[error("error checking health status: {0}")]
Other(#[from] Box<dyn Error + Send + Sync>),
}

/// Errors which occur when retrieving the connector schema.
///
/// See [`Connector::get_schema`].
#[derive(Debug, Error)]
pub enum SchemaError {
#[error("error retrieving the schema: {0}")]
Other(#[from] Box<dyn Error + Send + Sync>),
}

/// Errors which occur when executing a query.
///
/// See [`Connector::query`].
#[derive(Debug, Error)]
pub enum QueryError {
/// The request was invalid or did not match the
/// requirements of the specification. This indicates
/// an error with the client.
#[error("invalid request: {0}")]
InvalidRequest(String),
/// The request was well formed but was unable to be
/// followed due to semantic errors. This indicates
/// an error with the client.
#[error("unprocessable content: {0}")]
UnprocessableContent(String),
/// The request relies on an unsupported feature or
/// capability. This may indicate an error with the client,
/// or just an unimplemented feature.
#[error("unsupported operation: {0}")]
UnsupportedOperation(String),
#[error("error executing query: {0}")]
Other(#[from] Box<dyn Error + Send + Sync>),
}

/// Errors which occur when explaining a query.
///
/// See [`Connector::query_explain`, `Connector::mutation_explain`].
#[derive(Debug, Error)]
pub enum ExplainError {
/// The request was invalid or did not match the
/// requirements of the specification. This indicates
/// an error with the client.
#[error("invalid request: {0}")]
InvalidRequest(String),
/// The request was well formed but was unable to be
/// followed due to semantic errors. This indicates
/// an error with the client.
#[error("unprocessable content: {0}")]
UnprocessableContent(String),
/// The request relies on an unsupported feature or
/// capability. This may indicate an error with the client,
/// or just an unimplemented feature.
#[error("unsupported operation: {0}")]
UnsupportedOperation(String),
#[error("explain error: {0}")]
Other(#[from] Box<dyn Error + Send + Sync>),
}

/// Errors which occur when executing a mutation.
///
/// See [`Connector::mutation`].
#[derive(Debug, Error)]
pub enum MutationError {
/// The request was invalid or did not match the
/// requirements of the specification. This indicates
/// an error with the client.
#[error("invalid request: {0}")]
InvalidRequest(String),
/// The request was well formed but was unable to be
/// followed due to semantic errors. This indicates
/// an error with the client.
#[error("unprocessable content: {0}")]
UnprocessableContent(String),
/// The request relies on an unsupported feature or
/// capability. This may indicate an error with the client,
/// or just an unimplemented feature.
#[error("unsupported operation: {0}")]
UnsupportedOperation(String),
/// The request would result in a conflicting state
/// in the underlying data store.
#[error("mutation would result in a conflicting state: {0}")]
Conflict(String),
/// The request would violate a constraint in the
/// underlying data store.
#[error("mutation violates constraint: {0}")]
ConstraintNotMet(String),
#[error("error executing mutation: {0}")]
Other(#[from] Box<dyn Error + Send + Sync>),
}
pub use error::*;

/// Connectors using this library should implement this trait.
///
Expand Down
Loading

0 comments on commit f4f6502

Please sign in to comment.