Skip to content

Commit

Permalink
wip: new rspc core
Browse files Browse the repository at this point in the history
  • Loading branch information
oscartbeaumont committed Mar 17, 2024
1 parent 69e89b5 commit 398c957
Show file tree
Hide file tree
Showing 6 changed files with 371 additions and 0 deletions.
21 changes: 21 additions & 0 deletions 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"] }
66 changes: 66 additions & 0 deletions 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>(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<T>
{
type Deserializer = T::Deserializer;

fn into_deserializer(self) -> Self::Deserializer {
self.0.into_deserializer()
}
}

#[tokio::main]
async fn main() {
let p = <Procedure>::new::<Infallible>()
.error::<String>()
.query(|ctx, input: i32| async move { Ok(()) });

let router = <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::<JsonValue, TokioRuntime>("a", (), value.into_deserializer())
// .await
// .unwrap();
// println!("{:?}", result);

// let result = router
// .exec::<JsonValue, TokioRuntime>(
// "a",
// (),
// &mut rspc::serde_json::Deserializer::new(rspc::serde_json::de::StrRead::new("44")),
// )
// .await
// .unwrap();
// println!("{:?}", result);
}
60 changes: 60 additions & 0 deletions 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<T: Serialize>(&mut self, v: T);

// fn take(self) -> Self::Output;
// }

pub(crate) trait DynFormat {
fn serialize(&mut self, v: &dyn erased_serde::Serialize);
}

// impl<T: Format> DynFormat for T {
// fn serialize(&mut self, v: &dyn erased_serde::Serialize) {
// <T as Format>::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<T: 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<u8> buffer
// // TODO: FormData
19 changes: 19 additions & 0 deletions 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 <https://rspc.dev>
//!

// 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;
117 changes: 117 additions & 0 deletions crates/rspc2/src/procedure.rs
@@ -0,0 +1,117 @@
use std::{borrow::Cow, future::Future, marker::PhantomData};

use crate::Router;

/// TODO
pub struct Procedure<TCtx = ()> {
#[cfg(debug_assertions)]
pub(crate) location: std::panic::Location<'static>,
pub(crate) build: Box<dyn FnOnce(Cow<'static, str>, &mut Router<TCtx>)>,
}

impl<TCtx> Procedure<TCtx> {
pub fn new<TError>() -> ProcedureBuilder<Generics<TCtx, TError, ()>> {
ProcedureBuilder(Generics(PhantomData))
}
}

mod private {
use super::*;
pub struct Generics<TCtx, TError, TStream>(pub(super) PhantomData<(TCtx, TError, TStream)>);
}
use private::Generics;

/// TODO
pub struct ProcedureBuilder<G>(G);

impl<TCtx, TError, TStream> ProcedureBuilder<Generics<TCtx, TError, TStream>> {
/// Manually override the error type of this procedure.
/// By default the error type will be infered from the [Router](rspc::Router).
pub fn error<T>(self) -> ProcedureBuilder<Generics<TCtx, T, TStream>> {
ProcedureBuilder(Generics(PhantomData))
}

// TODO: Middleware working
pub fn with(self) -> Self {
todo!();
}

#[track_caller]
pub fn query<F, I>(self, handler: F) -> Procedure<TCtx>
where
F: ProcedureFunc<TCtx, TError, I>,
{
let p: InternalProcedure<TCtx, TError, F, I> = 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<F, I>(self, handler: F) -> Procedure<TCtx>
where
F: ProcedureFunc<TCtx, TError, I>,
{
todo!();
}
}

// TODO: Prevent downstream impls by inherinting from sealed trait.
/// TODO
// `TInput` mostly exists as a generic to contrain the impl.
pub trait ProcedureFunc<TCtx, TError, TInput> {
type Result;
type Future: Future<Output = Result<Self::Result, TError>>;

fn exec(&self, ctx: TCtx, input: TInput) -> Self::Future;
}
impl<
TCtx,
TInput,
TResult,
TError,
F: Fn(TCtx, TInput) -> Fu,
Fu: Future<Output = Result<TResult, TError>>,
> ProcedureFunc<TCtx, TError, TInput> 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<T: Serializer>(); // TODO: It being generic means we can't box it.
}

// TODO: Remove generics
pub(crate) struct InternalProcedure<TCtx, TError, F, I> {
pub(crate) handler: F,
pub(crate) phantom: PhantomData<(TCtx, TError, I)>,
}

impl<TCtx, TError, F, I> AnyProcedure for InternalProcedure<TCtx, TError, F, I> where
F: ProcedureFunc<TCtx, TError, I>
{
}
88 changes: 88 additions & 0 deletions 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<TCtx>(Vec<(Cow<'static, str>, Procedure<TCtx>)>);

impl<TCtx> RouterBuilder<TCtx> {
/// TODO
pub fn procedure(mut self, key: &'static str, procedure: Procedure<TCtx>) -> Self {
self.0.push((Cow::Borrowed(key), procedure));
self
}

/// TODO: Register custom types to export

/// TODO
pub fn build(self) -> Result<Router<TCtx>, ()> {
// 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<TCtx> =
Box<dyn Fn(&mut dyn DynFormat, TCtx, &mut dyn erased_serde::Deserializer)>;

/// TODO
pub struct Router<TCtx = ()>(pub(crate) HashMap<Cow<'static, str>, ExecutableProcedure<TCtx>>);

impl<TCtx> Router<TCtx> {
/// TODO
pub fn new() -> RouterBuilder<TCtx> {
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<S::Ok, ()> {
let procedure = self.0.get(key).unwrap(); // TODO: .ok_or(())?;

let y = serde_value::Value::String("testing".into());

// let mut serializer = serde_value::ValueDeserializer::<Infallible>::new(y);

// let y = y.serialize(serializer).unwrap(); // TODO: Error handling

// serializer.deserialize();

// procedure(
// &mut serializer,
// ctx,
// &mut <dyn erased_serde::Deserializer>::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???
}

0 comments on commit 398c957

Please sign in to comment.