From 398c9576f20c3d4266e29533163749c31f412458 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Sun, 17 Mar 2024 16:23:22 +0800 Subject: [PATCH] wip: new rspc core --- crates/rspc2/Cargo.toml | 21 ++++++ crates/rspc2/examples/basic.rs | 66 +++++++++++++++++++ crates/rspc2/src/format.rs | 60 +++++++++++++++++ crates/rspc2/src/lib.rs | 19 ++++++ crates/rspc2/src/procedure.rs | 117 +++++++++++++++++++++++++++++++++ crates/rspc2/src/router.rs | 88 +++++++++++++++++++++++++ 6 files changed, 371 insertions(+) create mode 100644 crates/rspc2/Cargo.toml create mode 100644 crates/rspc2/examples/basic.rs create mode 100644 crates/rspc2/src/format.rs create mode 100644 crates/rspc2/src/lib.rs create mode 100644 crates/rspc2/src/procedure.rs create mode 100644 crates/rspc2/src/router.rs diff --git a/crates/rspc2/Cargo.toml b/crates/rspc2/Cargo.toml new file mode 100644 index 00000000..ec5738eb --- /dev/null +++ b/crates/rspc2/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "rspc2" +version = "0.2.0" +edition = "2021" + +[dependencies] +# Public +serde = { version = "1", default-features = false } +specta = { version = "1", default-features = false } + +# Private +erased-serde = { version = "0.4.2", default-features = false, features = ["std"] } +serde-value = "0.7.0" + +# Temporary +serde_json = "1" +tokio = "1" +futures = "0.3.30" + +[dev-dependencies] +tokio = { version = "1", features = ["full"] } diff --git a/crates/rspc2/examples/basic.rs b/crates/rspc2/examples/basic.rs new file mode 100644 index 00000000..1cee2cc0 --- /dev/null +++ b/crates/rspc2/examples/basic.rs @@ -0,0 +1,66 @@ +use std::convert::Infallible; + +use rspc2::{serde, Procedure, Router}; + +// TODO +use rspc2::serde::de::IntoDeserializer; + +pub struct Primitive(T); + +// The error type here is just a placeholder, to contrain it. +impl<'de, T: IntoDeserializer<'de, serde::de::value::Error>> + IntoDeserializer<'de, serde::de::value::Error> for Primitive +{ + type Deserializer = T::Deserializer; + + fn into_deserializer(self) -> Self::Deserializer { + self.0.into_deserializer() + } +} + +#[tokio::main] +async fn main() { + let p = ::new::() + .error::() + .query(|ctx, input: i32| async move { Ok(()) }); + + let router = ::new().procedure("a", p).build().unwrap(); + + let result = router + .exec( + "a", + (), + Primitive(42).into_deserializer(), + rspc2::serde_json::value::Serializer, + ) + // .await // TODO + .unwrap(); + println!("{:?}", result); + + router + .exec( + "a", + (), + Primitive(42).into_deserializer(), + &mut rspc2::serde_json::Serializer::new(std::io::stdout()), + ) + // .await // TODO + .unwrap(); + + // let value = rspc::serde_json::json!(43); + // let result = router + // .exec::("a", (), value.into_deserializer()) + // .await + // .unwrap(); + // println!("{:?}", result); + + // let result = router + // .exec::( + // "a", + // (), + // &mut rspc::serde_json::Deserializer::new(rspc::serde_json::de::StrRead::new("44")), + // ) + // .await + // .unwrap(); + // println!("{:?}", result); +} diff --git a/crates/rspc2/src/format.rs b/crates/rspc2/src/format.rs new file mode 100644 index 00000000..caef939c --- /dev/null +++ b/crates/rspc2/src/format.rs @@ -0,0 +1,60 @@ +// use serde::Serialize; + +// // TODO: Drop this trait if still unused + +// /// TODO +// pub trait Format { +// type Output; + +// fn new() -> Self; + +// fn serialize(&mut self, v: T); + +// fn take(self) -> Self::Output; +// } + +pub(crate) trait DynFormat { + fn serialize(&mut self, v: &dyn erased_serde::Serialize); +} + +// impl DynFormat for T { +// fn serialize(&mut self, v: &dyn erased_serde::Serialize) { +// ::serialize(self, v) +// } +// } + +// // TODO: Move into another crate + +// /// TODO +// pub struct JsonValue { +// result: serde_json::Value, +// } + +// impl JsonValue { +// pub fn new() -> Self { +// Self { +// result: serde_json::Value::Null, +// } +// } +// } + +// impl Format for JsonValue { +// type Output = serde_json::Value; + +// fn new() -> Self { +// Self { +// result: serde_json::Value::Null, +// } +// } + +// fn serialize(&mut self, v: T) { +// self.result = serde_json::to_value(v).unwrap(); // TODO: Error handling +// } + +// fn take(self) -> Self::Output { +// self.result +// } +// } + +// // TODO: Json into Vec buffer +// // TODO: FormData diff --git a/crates/rspc2/src/lib.rs b/crates/rspc2/src/lib.rs new file mode 100644 index 00000000..7e2d4b92 --- /dev/null +++ b/crates/rspc2/src/lib.rs @@ -0,0 +1,19 @@ +//! rspc: A blazingly fast and easy to use tRPC-like server for Rust. +//! +//! Checkout the official docs +//! + +// TODO: Clippy lints + `Cargo.toml` + +mod format; +mod procedure; +mod router; + +// pub use format::Format; +pub use procedure::{Procedure, ProcedureBuilder, ProcedureFunc}; +pub use router::{Router, RouterBuilder}; + +// TODO: Remove this from public API +// pub use format::JsonValue; +pub use serde; +pub use serde_json; diff --git a/crates/rspc2/src/procedure.rs b/crates/rspc2/src/procedure.rs new file mode 100644 index 00000000..512c9ecc --- /dev/null +++ b/crates/rspc2/src/procedure.rs @@ -0,0 +1,117 @@ +use std::{borrow::Cow, future::Future, marker::PhantomData}; + +use crate::Router; + +/// TODO +pub struct Procedure { + #[cfg(debug_assertions)] + pub(crate) location: std::panic::Location<'static>, + pub(crate) build: Box, &mut Router)>, +} + +impl Procedure { + pub fn new() -> ProcedureBuilder> { + ProcedureBuilder(Generics(PhantomData)) + } +} + +mod private { + use super::*; + pub struct Generics(pub(super) PhantomData<(TCtx, TError, TStream)>); +} +use private::Generics; + +/// TODO +pub struct ProcedureBuilder(G); + +impl ProcedureBuilder> { + /// Manually override the error type of this procedure. + /// By default the error type will be infered from the [Router](rspc::Router). + pub fn error(self) -> ProcedureBuilder> { + ProcedureBuilder(Generics(PhantomData)) + } + + // TODO: Middleware working + pub fn with(self) -> Self { + todo!(); + } + + #[track_caller] + pub fn query(self, handler: F) -> Procedure + where + F: ProcedureFunc, + { + let p: InternalProcedure = InternalProcedure { + handler, // Box::new(|a, b| handler.exec(a, b)), + phantom: PhantomData, + }; + // Procedure { + // #[cfg(debug_assertions)] + // location: *std::panic::Location::caller(), + // build: Box::new(move |key, router| { + // router.0.insert( + // key, + // // TODO: Async procedures + // Box::new(move |serializer, ctx, input| { + // let input: i32 = erased_serde::deserialize(input).unwrap(); + + // serializer.serialize(&format!("Hello {:?}", input)); + // }), + // ); + // }), + // } + + todo!(); + } + + #[track_caller] + pub fn mutation(self, handler: F) -> Procedure + where + F: ProcedureFunc, + { + todo!(); + } +} + +// TODO: Prevent downstream impls by inherinting from sealed trait. +/// TODO +// `TInput` mostly exists as a generic to contrain the impl. +pub trait ProcedureFunc { + type Result; + type Future: Future>; + + fn exec(&self, ctx: TCtx, input: TInput) -> Self::Future; +} +impl< + TCtx, + TInput, + TResult, + TError, + F: Fn(TCtx, TInput) -> Fu, + Fu: Future>, + > ProcedureFunc for F +{ + type Result = TResult; + type Future = Fu; + + fn exec(&self, ctx: TCtx, input: TInput) -> Self::Future { + (self)(ctx, input) + } +} + +// TODO: Probs break out file + +pub(crate) trait AnyProcedure { + // fn exec(); // TODO: It being generic means we can't box it. +} + +// TODO: Remove generics +pub(crate) struct InternalProcedure { + pub(crate) handler: F, + pub(crate) phantom: PhantomData<(TCtx, TError, I)>, +} + +impl AnyProcedure for InternalProcedure where + F: ProcedureFunc +{ +} diff --git a/crates/rspc2/src/router.rs b/crates/rspc2/src/router.rs new file mode 100644 index 00000000..8fc8e9cc --- /dev/null +++ b/crates/rspc2/src/router.rs @@ -0,0 +1,88 @@ +use std::{borrow::Cow, collections::HashMap, convert::Infallible}; + +use serde::{ + de::{value::I32Deserializer, IntoDeserializer}, + Deserialize, Deserializer, Serialize, Serializer, +}; + +use crate::{format::DynFormat, Procedure}; + +/// TODO +pub struct RouterBuilder(Vec<(Cow<'static, str>, Procedure)>); + +impl RouterBuilder { + /// TODO + pub fn procedure(mut self, key: &'static str, procedure: Procedure) -> Self { + self.0.push((Cow::Borrowed(key), procedure)); + self + } + + /// TODO: Register custom types to export + + /// TODO + pub fn build(self) -> Result, ()> { + // TODO: Check for invalid or duplicate procedure names. + + let mut router = Router(Default::default()); + for (key, procedure) in self.0 { + (procedure.build)(key, &mut router); + } + Ok(router) + } +} + +pub(crate) type ExecutableProcedure = + Box; + +/// TODO +pub struct Router(pub(crate) HashMap, ExecutableProcedure>); + +impl Router { + /// TODO + pub fn new() -> RouterBuilder { + RouterBuilder(Vec::new()) + } + + // TODO: Specta exporting but support any Specta exporter. + + /// TODO: Dump the router for custom integrations + + /// TODO + pub fn exec<'de, S: Serializer>( + &self, + key: &str, + ctx: TCtx, + deserializer: impl Deserializer<'de>, + serializer: S, + // TODO: Custom error type + // TODO: Returning `Value or Future` so we can avoid spawning if not required???? + // // TODO: R::Output<> + ) -> Result { + let procedure = self.0.get(key).unwrap(); // TODO: .ok_or(())?; + + let y = serde_value::Value::String("testing".into()); + + // let mut serializer = serde_value::ValueDeserializer::::new(y); + + // let y = y.serialize(serializer).unwrap(); // TODO: Error handling + + // serializer.deserialize(); + + // procedure( + // &mut serializer, + // ctx, + // &mut ::erase(input), + // ); + + // TODO: With `async` in batches we need to ensure the `Serialize` is put somewhere. + + // TODO: Result before or after async runtime??? + + // Ok(serializer.take()) + // todo!(); + + Ok(y.serialize(serializer).unwrap()) // TODO: Error handling + } + + // TODO: Batching multiple exec's??? Can this just be done using the single `Exec` method and a shared runtime??? +}