Skip to content

Commit

Permalink
wip error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
oscartbeaumont committed Nov 22, 2024
1 parent 8d0a819 commit 63574fe
Showing 6 changed files with 302 additions and 108 deletions.
77 changes: 54 additions & 23 deletions crates/core/examples/basic.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
use std::pin::pin;

use futures::{stream::poll_fn, StreamExt};
use rspc_core::{Procedure, ProcedureStream};
use rspc_core::{Procedure, ProcedureStream, ResolverError};

#[derive(Debug)]
struct File;

fn main() {
// // /* Serialize */
// let y = Procedure::new(|_ctx, input| {
// let input: String = input.deserialize().unwrap();
// println!("GOT {}", input);
// });
// let result = y.exec_with_deserializer((), serde_json::Value::String("hello".to_string()));
futures::executor::block_on(main2());
}

async fn main2() {
// /* Serialize */
// TODO

// /* Serialize + Stream */
let y = Procedure::new(|_ctx, input| {
let input = input.deserialize::<String>();
// println!("GOT {}", input);

ProcedureStream::from_stream(futures::stream::iter(vec![
input.map(|x| x.len()).map_err(Into::into),
Ok(1),
Ok(2),
Ok(3),
Err(ResolverError::new(500, "Not found", None::<std::io::Error>)),
]))
});
// let mut result = y.exec_with_deserializer((), serde_json::Value::String("hello".to_string()));
// while let Some(value) = result.next(serde_json::value::Serializer).await {
// println!("{value:?}");
// }

let mut result = y.exec_with_deserializer((), serde_json::Value::Null);
while let Some(value) = result.next(serde_json::value::Serializer).await {
println!("{value:?}");
}

// // /* Non-serialize */
// let y = Procedure::new(|_ctx, input| {
@@ -41,32 +64,40 @@ fn main() {
// .await;
// println!("{:?}", got);

futures::executor::block_on(todo());
// todo().await;
}

async fn todo() {
println!("A");

// Side-effect based serializer
let mut result: ProcedureStream =
ProcedureStream::from_stream(futures::stream::iter(vec![1, 2, 3]));
// let mut result: ProcedureStream = ProcedureStream::from_stream(futures::stream::iter(vec![
// Ok(1),
// Ok(2),
// Ok(3),
// Err(ResolverError::new(500, "Not found", None::<std::io::Error>)),
// ]));

// TODO: Clean this up + `Stream` adapter.
loop {
let mut buf = Vec::new();
let Some(result) = result
.next(&mut serde_json::Serializer::new(&mut buf))
.await
else {
break;
};
let _result: () = result.unwrap();
println!("{:?}", String::from_utf8_lossy(&buf));
}
// loop {
// let mut buf = Vec::new();
// let Some(result) = result
// .next(&mut serde_json::Serializer::new(&mut buf))
// .await
// else {
// break;
// };
// let _result: () = result.unwrap(); // TODO
// println!("{:?}", String::from_utf8_lossy(&buf));
// }

// Result based serializer
let mut result: ProcedureStream =
ProcedureStream::from_stream(futures::stream::iter(vec![1, 2, 3]));
let mut result: ProcedureStream = ProcedureStream::from_stream(futures::stream::iter(vec![
Ok(1),
Ok(2),
Ok(3),
Err(ResolverError::new(500, "Not found", None::<std::io::Error>)),
]));

while let Some(value) = result.next(serde_json::value::Serializer).await {
println!("{value:?}");
25 changes: 18 additions & 7 deletions crates/core/src/dyn_input.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
use std::{any::Any, fmt};

use serde::Deserialize;

// TODO: Could we call this `DynValue` instead????
use serde::{de::Error, Deserialize};

/// TODO
pub struct DynInput<'a, 'de> {
// TODO: Or seal fields to this module and have constructors???
pub(crate) value: Option<&'a mut dyn Any>,
pub(crate) deserializer: Option<&'a mut dyn erased_serde::Deserializer<'de>>,
pub(crate) type_name: &'static str,
}

impl<'a, 'de> DynInput<'a, 'de> {
/// TODO
pub fn deserialize<T: Deserialize<'de>>(self) -> Result<T, ()> {
erased_serde::deserialize(self.deserializer.ok_or(())?).map_err(|_| ())
pub fn deserialize<T: Deserialize<'de>>(self) -> Result<T, DeserializeError> {
erased_serde::deserialize(self.deserializer.ok_or(DeserializeError(
erased_serde::Error::custom(format!(
"attempted to deserialize from value '{}' but expected deserializer",
self.type_name
)),
))?)
.map_err(|err| DeserializeError(err))
}

/// TODO
@@ -23,7 +27,7 @@ impl<'a, 'de> DynInput<'a, 'de> {
self.value?
.downcast_mut::<Option<T>>()?
.take()
// This takes method takes `self` and it's not `Clone` so it's not possible to previously take the value.
// This takes method takes `self` and it's not `Clone` so it's not possible to double take the value.
.expect("unreachable"),
)
}
@@ -34,3 +38,10 @@ impl<'a, 'de> fmt::Debug for DynInput<'a, 'de> {
todo!();
}
}

// TODO: Here or `error.rs`???
// TODO: impl Debug, Display, Error
#[derive(Debug)] // TODO: Remove
pub struct DeserializeError(pub(crate) erased_serde::Error);

// TODO: This should be convertable to a `ResolverError`.
139 changes: 139 additions & 0 deletions crates/core/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use std::{error, fmt};

use serde::{Serialize, Serializer};

use crate::DeserializeError;

/// TODO
pub enum ProcedureError<S: Serializer> {
/// Attempted to deserialize a value but failed.
Deserialize(DeserializeError),
// /// Attempting to downcast a value failed.
// Downcast {
// // If `None`, the procedure was got a deserializer but expected a value.
// // else the name of the type that was provided by the caller.
// from: Option<&'static str>,
// // The type the procedure expected.
// to: &'static str,
// }, // TODO: Is this going to be possible. Maybe `DowncastError` type?
/// An error occurred while serializing the value returned by the procedure.
Serializer(S::Error),
/// An error occurred while running the procedure.
Resolver(ResolverError),
}

impl<S: Serializer> From<ResolverError> for ProcedureError<S> {
fn from(err: ResolverError) -> Self {
match err.0 {
Repr::Custom { .. } => ProcedureError::Resolver(err),
Repr::Deserialize(err) => ProcedureError::Deserialize(err),
// Repr::Downcast { from, to } => todo!(),
}
}
}

impl<S: Serializer> fmt::Debug for ProcedureError<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Proper format
match self {
Self::Deserialize(err) => write!(f, "Deserialize({:?})", err),
// Self::Downcast { from, to } => write!(f, "Downcast({:?} -> {:?})", from, to),
Self::Serializer(err) => write!(f, "Serializer({:?})", err),
Self::Resolver(err) => write!(f, "Resolver({:?})", err),
}
}
}

impl<S: Serializer> fmt::Display for ProcedureError<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}

impl<S: Serializer> error::Error for ProcedureError<S> {}

#[derive(Debug)] // TODO: Remove
enum Repr {
// An actual resolver error.
Custom {
status: u16,
// source: erased_serde::Serialize

// type_name: &'static str,
// type_id: TypeId,
// inner: Box<dyn ErasedError>,
// #[cfg(debug_assertions)]
// source_type_name
},
// We hide these in here for DX (being able to do `?`) and then convert them to proper `ProcedureError` variants.
Deserialize(DeserializeError),
// Downcast {
// from: Option<&'static str>,
// to: &'static str,
// },
}

impl From<DeserializeError> for ResolverError {
fn from(err: DeserializeError) -> Self {
Self(Repr::Deserialize(err))
}
}

#[derive(Debug)] // TODO: Custom Debug & std::error::Error
pub struct ResolverError(Repr);

impl ResolverError {
pub fn new<T: Serialize + 'static, E: error::Error + 'static>(
status: u16,
value: T,
source: Option<E>,
) -> Self {
Self(Repr::Custom {
status,
// TODO: Avoid allocing `E` & `T` separately.
// type_name: type_name::<T>(),
// type_id: TypeId::of::<T>(),
// inner: Box::new(value),
})
}

pub(crate) fn _erased_serde(source: erased_serde::Error) -> Self {
Self(Repr::Deserialize(DeserializeError(source)))
}

// pub(crate) fn _downcast(from: Option<&'static str>, to: &'static str) -> Self {
// Self(Repr::Downcast { from, to })
// }
}

impl fmt::Display for ResolverError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}

impl error::Error for ResolverError {}

trait ErasedError {
// fn to_box_any(self: Box<Self>) -> Box<dyn Any>;

// fn to_value(&self) -> Option<Result<serde_value::Value, serde_value::SerializerError>>;
}

struct ResolverErrorInternal<T: Serialize, E: error::Error + 'static> {
status: u16,
value: T,
source: Option<E>,
}

// impl<T: error::Error + erased_serde::Serialize + 'static> ErasedError for T {
// // fn to_box_any(self: Box<Self>) -> Box<dyn Any> {
// // self
// // }

// // fn to_value(&self) -> Option<Result<serde_value::Value, serde_value::SerializerError>> {
// // Some(serde_value::to_value(self))
// // }
// }

// pub struct Serialize(Box<dyn Error>)
35 changes: 16 additions & 19 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -2,39 +2,34 @@
//!
//! TODO: Describe all the types and why the split?
//! TODO: This is kinda like `tower::Service`
//! TODO: Why this crate doesn't depend on Specta.
//! TODO: Discuss the traits that need to be layered on for this to be useful.
//! TODO: Discuss how middleware don't exist here.
// TODO: Crate icon and stuff

// TODO: Solve layer:
// TODO: Returning non-Serialize types (Eg. `File`)
// TODO: Should downcast have custom error (for more info Eg. type_name)?
// - `ProcedureStream::from_value`
// - `ProcedureStream::from_future`
// - Crate documentation
// - Finish `Debug` impls
// - Rename `DynInput` to `DynValue` maybe???
// - `ProcedureStream` to `impl futures::Stream` adapter.
// - `ProcedureStream::poll_next` - Keep or remove???
// - `Send` + `Sync` and the issues with single-threaded async runtimes
// - `DynInput<'a, 'de>` should really be &'a Input<'de>` but that's hard.
// - `DynInput` errors:
// - store `type_name` for better errors.
// - Deserializer error

mod dyn_input;
mod error;
mod procedure;
mod stream;

pub use dyn_input::DynInput;
pub use dyn_input::{DeserializeError, DynInput};
pub use error::{ProcedureError, ResolverError};
pub use procedure::Procedure;
pub use stream::ProcedureStream;

// TODO: Should `Procedure` hold types? It prevents them from being removed at runtime.

// TODO: Async or sync procedures??
// TODO: Single-threaded async support

// TODO: Result types
// TODO: Typesafe error handling

// TODO: non-'static TypeId would prevent the need for `Argument` vs `Input` because you could parse down `&dyn erased_serde::Deserializer`.

// TODO: The two `exec` methods is survivable by the problem is that we have `Input` and
// need to go from it to `TInput` within the erased procedure, either via Deserialize or downcast.
//
// We can avoid doing this in `rspc_core` but it's still a problem `rspc` needs to deal with.

// TODO: The naming is horid.
// Low-level concerns:
// - `Procedure` - Holds the handler (and probably type information)
@@ -82,3 +77,5 @@ pub use stream::ProcedureStream;
//

// A decent cause of the bloat is because `T` (Eg. `File`), `Deserializer` and `Deserialize` are all different. You end up with a `ResolverInput` trait which is `Deserialize` + `T` , a `ProcedureInput` trait which is `Deserializer` + `T` and then `ExecInput` which is the dyn-safe output of `ProcedureInput` and is given into `ResolverInput` so it can decode it back to the value the user expects. Then you basically copy the same thing for the output value. I think it might be worth replacing `ProcedureInput` with `Procedure::exec_with_deserializer` and `Procedure::exec_with_value` but i'm not sure we could get away with doing the same thing for `ResolverInput` because that would mean requiring two forms of queries/mutations/subscriptions in the high-level API. That being said `ResolverInput` could probably be broken out of the procedure primitive.

// TODO: The new system doesn't allocate Serde related `Error`'s and Serde return values, pog.
7 changes: 6 additions & 1 deletion crates/core/src/procedure.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::{any::Any, fmt};
use std::{
any::{type_name, Any},
fmt,
};

use serde::Deserializer;

@@ -27,6 +30,7 @@ impl<TCtx> Procedure<TCtx> {
let value = DynInput {
value: None,
deserializer: Some(&mut deserializer),
type_name: type_name::<D>(),
};

(self.handler)(ctx, value)
@@ -37,6 +41,7 @@ impl<TCtx> Procedure<TCtx> {
let value = DynInput {
value: Some(&mut input),
deserializer: None,
type_name: type_name::<T>(),
};

(self.handler)(ctx, value)
Loading

0 comments on commit 63574fe

Please sign in to comment.