better TS

This commit is contained in:
Aiden McClelland
2025-11-12 22:47:01 -07:00
parent c2bb290618
commit c25bad4857
10 changed files with 858 additions and 876 deletions

8
Cargo.lock generated
View File

@@ -1745,9 +1745,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "visit-rs"
version = "0.1.5"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ea0fdb05dd98029f7e226ccd11bd7184ad68566365547930bdde9ae79b5ce3d"
checksum = "fbd4baf031bf580bc7ba411f4625a304909c975bf3d3e8d9c43ed88d23be1b8e"
dependencies = [
"async-stream",
"futures",
@@ -1757,9 +1757,9 @@ dependencies = [
[[package]]
name = "visit-rs-derive"
version = "0.1.3"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dde3216fefaf10421d9509f88bc5a480b5fb3fe48779ca4f148e75896d3f5ad"
checksum = "2336cc680cfb205620939f0fa62348a4b6da37d1e727670f3d7d77aebc50b611"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -39,5 +39,5 @@ thiserror = "2.0"
tokio = { version = "1", features = ["full"] }
tokio-stream = { version = "0.1", features = ["io-util", "net"] }
url = "2"
visit-rs = { version = "0.1.5", optional = true }
visit-rs = { version = "0.1.8", optional = true }
yajrc = "0.1"

View File

@@ -13,9 +13,11 @@ use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader
use url::Url;
use yajrc::{Id, RpcError};
#[cfg(feature = "ts")]
use crate::ts::HandlerTSBindings;
use crate::util::{internal_error, invalid_params, parse_error, without, Flat, PhantomData};
use crate::{
AnyHandler, CliBindings, CliBindingsAny, Empty, HandleAny, HandleAnyArgs, HandlerArgs,
Adapter, AnyHandler, CliBindings, CliBindingsAny, Empty, HandleAny, HandleAnyArgs, HandlerArgs,
HandlerArgsFor, HandlerFor, HandlerTypes, Name, ParentHandler, PrintCliResult,
};
@@ -209,15 +211,22 @@ where
type Err = RemoteHandler::Err;
}
impl<Context, RemoteContext, RemoteHandler, Extra> Adapter
for CallRemoteHandler<Context, RemoteContext, RemoteHandler, Extra>
{
type Inner = RemoteHandler;
fn as_inner(&self) -> &Self::Inner {
&self.handler
}
}
#[cfg(feature = "ts")]
impl<Context, RemoteContext, RemoteHandler, Extra> crate::handler::HandlerTS
impl<Context, RemoteContext, RemoteHandler, Extra> HandlerTSBindings
for CallRemoteHandler<Context, RemoteContext, RemoteHandler, Extra>
where
RemoteHandler: crate::handler::HandlerTS,
Extra: Send + Sync + 'static,
RemoteHandler: HandlerTSBindings,
{
fn type_info(&self) -> Option<String> {
self.handler.type_info()
fn get_ts<'a>(&'a self) -> Option<crate::ts::HandlerTS<'a>> {
self.handler.get_ts()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,6 @@ use imbl_value::Value;
use serde::de::DeserializeOwned;
use serde::Serialize;
#[cfg(feature = "ts")]
use crate::ts::TSVisitor;
use crate::util::PhantomData;
use crate::{
CliBindings, Empty, HandlerArgs, HandlerArgsFor, HandlerFor, HandlerTypes, LeafHandler,
@@ -48,22 +46,6 @@ impl<F, T, E, Args> std::fmt::Debug for FromFn<F, T, E, Args> {
}
}
#[cfg(feature = "ts")]
impl<F, T, E, Args> crate::handler::HandlerTS for FromFn<F, T, E, Args>
where
Self: HandlerTypes,
visit_rs::Static<<Self as HandlerTypes>::Params>: visit_rs::Visit<TSVisitor>,
visit_rs::Static<<Self as HandlerTypes>::Ok>: visit_rs::Visit<TSVisitor>,
{
fn type_info(&self) -> Option<String> {
let mut visitor = TSVisitor::default();
Some(format!(
"{{_PARAMS:{},_RETURN:{}}}",
<Self as HandlerTypes>::Params::inline_flattened(),
<Self as HandlerTypes>::Ok::inline_flattened(),
))
}
}
impl<Context, F, T, E, Args> PrintCliResult<Context> for FromFn<F, T, E, Args>
where
Context: crate::Context,
@@ -174,21 +156,6 @@ impl<F, Fut, T, E, Args> std::fmt::Debug for FromFnAsync<F, Fut, T, E, Args> {
}
}
#[cfg(feature = "ts")]
impl<F, Fut, T, E, Args> crate::handler::HandlerTS for FromFnAsync<F, Fut, T, E, Args>
where
Self: HandlerTypes,
<Self as HandlerTypes>::Params: ts_rs::TS,
<Self as HandlerTypes>::Ok: ts_rs::TS,
{
fn type_info(&self) -> Option<String> {
Some(format!(
"{{_PARAMS:{},_RETURN:{}}}",
<Self as HandlerTypes>::Params::inline_flattened(),
<Self as HandlerTypes>::Ok::inline_flattened(),
))
}
}
impl<Context, F, Fut, T, E, Args> PrintCliResult<Context> for FromFnAsync<F, Fut, T, E, Args>
where
Context: crate::Context,
@@ -286,21 +253,6 @@ impl<F, Fut, T, E, Args> std::fmt::Debug for FromFnAsyncLocal<F, Fut, T, E, Args
}
}
#[cfg(feature = "ts")]
impl<F, Fut, T, E, Args> crate::handler::HandlerTS for FromFnAsyncLocal<F, Fut, T, E, Args>
where
Self: HandlerTypes,
<Self as HandlerTypes>::Params: ts_rs::TS,
<Self as HandlerTypes>::Ok: ts_rs::TS,
{
fn type_info(&self) -> Option<String> {
Some(format!(
"{{_PARAMS:{},_RETURN:{}}}",
<Self as HandlerTypes>::Params::inline_flattened(),
<Self as HandlerTypes>::Ok::inline_flattened(),
))
}
}
impl<Context, F, Fut, T, E, Args> PrintCliResult<Context> for FromFnAsyncLocal<F, Fut, T, E, Args>
where
Context: crate::Context,

View File

@@ -13,6 +13,8 @@ use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use yajrc::RpcError;
use crate::impl_ts_struct;
use crate::ts::HandlerTSBindings;
use crate::util::{internal_error, invalid_params, Flat};
pub mod adapters;
@@ -55,21 +57,8 @@ impl<Context: crate::Context, Inherited: Send + Sync> HandleAnyArgs<Context, Inh
}
}
pub(crate) trait HandleAnyTS {
#[allow(dead_code)]
fn type_info(&self) -> Option<String> {
None
}
}
impl<T: HandleAnyTS> HandleAnyTS for Arc<T> {
fn type_info(&self) -> Option<String> {
self.deref().type_info()
}
}
pub(crate) trait HandleAnyRequires: HandleAnyTS + Send + Sync {}
impl<T: HandleAnyTS + Send + Sync> HandleAnyRequires for T {}
pub(crate) trait HandleAnyRequires: HandlerTSBindings + Send + Sync {}
impl<T: HandlerTSBindings + Send + Sync> HandleAnyRequires for T {}
#[async_trait::async_trait]
pub(crate) trait HandleAny<Context>: HandleAnyRequires {
@@ -149,7 +138,9 @@ pub trait PrintCliResult<Context: crate::Context>: HandlerTypes {
}
#[allow(private_interfaces)]
pub struct DynHandler<Context, Inherited>(Arc<dyn HandleAny<Context, Inherited = Inherited>>);
pub struct DynHandler<Context, Inherited>(
pub(crate) Arc<dyn HandleAny<Context, Inherited = Inherited>>,
);
impl<Context: crate::Context, Inherited> DynHandler<Context, Inherited> {
pub fn new<C, H>(handler: H) -> Option<Self>
where
@@ -169,11 +160,6 @@ impl<Context, Inherited> Debug for DynHandler<Context, Inherited> {
f.debug_struct("DynHandler").finish()
}
}
impl<Context, Inherited> HandleAnyTS for DynHandler<Context, Inherited> {
fn type_info(&self) -> Option<String> {
self.0.type_info()
}
}
#[async_trait::async_trait]
impl<Context: crate::Context, Inherited: Send> HandleAny<Context>
for DynHandler<Context, Inherited>
@@ -201,6 +187,12 @@ impl<Context: crate::Context, Inherited: Send> HandleAny<Context>
self.0.cli()
}
}
#[cfg(feature = "ts")]
impl<Context, Inherited> HandlerTSBindings for DynHandler<Context, Inherited> {
fn get_ts<'a>(&'a self) -> Option<crate::ts::HandlerTS<'a>> {
self.0.get_ts()
}
}
#[allow(type_alias_bounds)]
pub type HandlerArgsFor<Context: crate::Context, H: HandlerTypes + ?Sized> =
@@ -227,19 +219,20 @@ pub trait HandlerTypes {
type Err: Send + Sync;
}
pub trait PassthroughHandlerTypes: Adapter {}
impl<T> HandlerTypes for T
where
T: PassthroughHandlerTypes,
T::Inner: HandlerTypes,
{
type Params = <T::Inner as HandlerTypes>::Params;
type InheritedParams = <T::Inner as HandlerTypes>::InheritedParams;
type Ok = <T::Inner as HandlerTypes>::Ok;
type Err = <T::Inner as HandlerTypes>::Err;
}
pub trait LeafHandler {}
pub trait HandlerTS {
fn type_info(&self) -> Option<String>;
}
#[cfg(not(feature = "ts"))]
impl<T: HandlerTypes> HandlerTS for T {
fn type_info(&self) -> Option<String> {
None
}
}
pub trait HandlerRequires: HandlerTypes + Clone + Send + Sync + 'static {}
impl<T: HandlerTypes + Clone + Send + Sync + 'static> HandlerRequires for T {}
@@ -293,6 +286,70 @@ pub trait HandlerFor<Context: crate::Context>: HandlerRequires {
}
}
}
pub trait PassthroughHandlerFor: PassthroughHandlerTypes {}
impl<T, Context> HandlerFor<Context> for T
where
T: PassthroughHandlerFor + Clone + Send + Sync + 'static,
T::Inner: HandlerFor<Context>,
Context: crate::Context,
{
fn handle_async(
&self,
handle_args: HandlerArgsFor<Context, Self>,
) -> impl Future<Output = Result<Self::Ok, Self::Err>> + Send {
self.as_inner().handle_async(handle_args)
}
fn handle_async_with_sync<'a>(
&'a self,
handle_args: HandlerArgsFor<Context, Self>,
) -> impl Future<Output = Result<Self::Ok, Self::Err>> + Send + 'a {
self.as_inner().handle_async_with_sync(handle_args)
}
fn handle_async_with_sync_blocking<'a>(
&'a self,
handle_args: HandlerArgsFor<Context, Self>,
) -> impl Future<Output = Result<Self::Ok, Self::Err>> + Send + 'a {
self.as_inner().handle_async_with_sync_blocking(handle_args)
}
fn handle_sync(
&self,
handle_args: HandlerArgsFor<Context, Self>,
) -> Result<Self::Ok, Self::Err> {
self.as_inner().handle_sync(handle_args)
}
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
self.as_inner().metadata(method)
}
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
self.as_inner().method_from_dots(method)
}
}
pub trait PassthroughCliBindings: PassthroughHandlerTypes {}
impl<T, Context> CliBindings<Context> for T
where
T: PassthroughCliBindings,
T::Inner: CliBindings<Context>,
Context: crate::Context,
{
const NO_CLI: bool = <T::Inner as CliBindings<Context>>::NO_CLI;
fn cli_command(&self) -> Command {
self.as_inner().cli_command()
}
fn cli_parse(
&self,
matches: &ArgMatches,
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
self.as_inner().cli_parse(matches)
}
fn cli_display(
&self,
handle_args: HandlerArgsFor<Context, Self>,
result: Self::Ok,
) -> Result<(), Self::Err> {
self.as_inner().cli_display(handle_args, result)
}
}
pub trait Handler<Inherited> {
type H: HandlerTypes;
@@ -315,7 +372,7 @@ impl<Context, H> WithContext<Context, H> {
impl<Context, Inherited, H> Handler<Inherited> for WithContext<Context, H>
where
Context: crate::Context,
H: HandlerFor<Context> + CliBindings<Context> + HandlerTS,
H: HandlerFor<Context> + CliBindings<Context> + HandlerTSBindings,
H::Ok: Serialize + DeserializeOwned,
H::Params: DeserializeOwned,
H::InheritedParams: OrEmpty<Inherited>,
@@ -356,12 +413,13 @@ impl<Context, Inherited, H: std::fmt::Debug> std::fmt::Debug for AnyHandler<Cont
}
}
impl<Context, Inherited, H> HandleAnyTS for AnyHandler<Context, Inherited, H>
#[cfg(feature = "ts")]
impl<Context, Inherited, H> HandlerTSBindings for AnyHandler<Context, Inherited, H>
where
H: HandlerTS,
H: HandlerTSBindings,
{
fn type_info(&self) -> Option<String> {
self.handler.type_info()
fn get_ts<'a>(&'a self) -> Option<crate::ts::HandlerTS<'a>> {
self.handler.get_ts()
}
}
@@ -369,7 +427,7 @@ where
impl<Context, Inherited, H> HandleAny<Context> for AnyHandler<Context, Inherited, H>
where
Context: crate::Context,
H: HandlerFor<Context> + CliBindings<Context> + HandlerTS,
H: HandlerFor<Context> + CliBindings<Context> + HandlerTSBindings,
H::Params: DeserializeOwned,
H::Ok: Serialize + DeserializeOwned,
H::InheritedParams: OrEmpty<Inherited>,
@@ -450,9 +508,9 @@ where
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, Parser)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "ts", ts(type = "{}"))]
#[cfg_attr(feature = "ts", derive(visit_rs::VisitFields))]
pub struct Empty {}
impl_ts_struct!(Empty);
pub trait OrEmpty<T> {
fn from_t(t: T) -> Self;

View File

@@ -7,15 +7,11 @@ use imbl_value::Value;
use serde::Serialize;
use yajrc::RpcError;
#[cfg(feature = "ts")]
use crate::handler::HandleAnyTS;
use crate::util::{combine, Flat, PhantomData};
use crate::{
CliBindings, DynHandler, Empty, HandleAny, HandleAnyArgs, Handler, HandlerArgs, HandlerArgsFor,
HandlerFor, HandlerRequires, HandlerTypes, WithContext,
};
#[cfg(feature = "ts")]
use crate::{CustomTS, UnknownTS};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Name(pub(crate) &'static str);
@@ -88,31 +84,6 @@ impl<Context, Params, InheritedParams> ParentHandler<Context, Params, InheritedP
self.metadata.insert(key, value);
self
}
#[cfg(feature = "ts")]
fn type_info_impl(&self, params_ty: &str) -> Option<String> {
use std::fmt::Write;
let mut res = "{".to_owned();
res.push_str("_CHILDREN:{");
for (name, handler) in &self.subcommands.1 {
let Some(ty) = handler.type_info() else {
continue;
};
write!(
&mut res,
"{}:{};",
serde_json::to_string(&name.0).unwrap(),
ty,
)
.ok()?;
}
res.push_str("};}");
if let Some(ty) = self.subcommands.0.as_ref().and_then(|h| h.type_info()) {
write!(&mut res, "&{}", ty).ok()?;
} else {
write!(&mut res, "&{{_PARAMS:{}}}", params_ty).ok()?;
}
Some(res)
}
}
impl<Context, Params, InheritedParams> Clone for ParentHandler<Context, Params, InheritedParams> {
fn clone(&self) -> Self {
@@ -169,40 +140,6 @@ where
type Err = RpcError;
}
#[cfg(feature = "ts")]
impl<Context, Params, InheritedParams> crate::handler::HandlerTS
for ParentHandler<Context, Params, InheritedParams>
where
Params: ts_rs::TS + Send + Sync + 'static,
InheritedParams: Send + Sync + 'static,
{
fn type_info(&self) -> Option<String> {
self.type_info_impl(&Params::inline_flattened())
}
}
#[cfg(feature = "ts")]
impl<Context, Params, InheritedParams> crate::handler::HandlerTS
for CustomTS<ParentHandler<Context, Params, InheritedParams>>
where
Params: Send + Sync + 'static,
InheritedParams: Send + Sync + 'static,
{
fn type_info(&self) -> Option<String> {
self.handler.type_info_impl(&self.params_ty)
}
}
#[cfg(feature = "ts")]
impl<Context, Params, InheritedParams> crate::handler::HandlerTS
for UnknownTS<ParentHandler<Context, Params, InheritedParams>>
where
Params: Send + Sync + 'static,
InheritedParams: Send + Sync + 'static,
{
fn type_info(&self) -> Option<String> {
self.0.type_info_impl("unknown")
}
}
impl<Context, Params, InheritedParams> HandlerFor<Context>
for ParentHandler<Context, Params, InheritedParams>
where

View File

@@ -14,4 +14,8 @@ mod server;
pub mod ts;
pub mod util;
#[cfg(feature = "ts")]
#[cfg(not(feature = "ts"))]
pub mod ts {
pub trait HandlerTSBindings {}
impl<T> HandlerTSBindings for T {}
}

471
src/ts.rs
View File

@@ -1,70 +1,338 @@
use visit_rs::{NamedStatic, Static, Visit, VisitFieldsStaticNamed, Visitor};
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::ops::Deref;
use std::rc::Rc;
use std::sync::Arc;
use imbl_value::imbl::{OrdMap, Vector};
use imbl_value::InternedString;
use visit_rs::{Named, Static, Visit, VisitFieldsStaticNamed, Visitor};
use crate::{Adapter, FromFn, FromFnAsync, FromFnAsyncLocal, HandlerTypes, ParentHandler};
pub fn type_helpers() -> &'static str {
include_str!("./type-helpers.ts")
}
#[derive(Default)]
pub trait TS {
const DEFINE: Option<&str> = None;
const IS_ENUMERABLE: bool = false;
fn visit_ts(visitor: &mut TSVisitor);
}
impl<T> Visit<TSVisitor> for Static<T>
where
T: TS,
{
fn visit(&self, visitor: &mut TSVisitor) -> <TSVisitor as Visitor>::Result {
T::visit_ts(visitor);
}
}
pub trait ParamsTS {
fn params_ts<'a>(&'a self) -> Box<dyn Fn(&mut TSVisitor) + Send + Sync + 'a>;
}
pub trait ReturnTS {
fn return_ts<'a>(&'a self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync + 'a>>;
}
pub trait ChildrenTS {
fn children_ts<'a>(&'a self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync + 'a>>;
}
pub trait HandlerTSBindings {
fn get_ts<'a>(&'a self) -> Option<HandlerTS<'a>>;
}
impl<T: ParamsTS + ReturnTS + ChildrenTS> HandlerTSBindings for T {
fn get_ts<'a>(&'a self) -> Option<HandlerTS<'a>> {
Some(HandlerTS::new(self))
}
}
impl<T: HandlerTSBindings> HandlerTSBindings for Arc<T> {
fn get_ts<'a>(&'a self) -> Option<HandlerTS<'a>> {
self.deref().get_ts()
}
}
pub struct HandlerTS<'a> {
params_ts: Box<dyn Fn(&mut TSVisitor) + Send + Sync + 'a>,
return_ts: Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync + 'a>>,
children: Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync + 'a>>,
}
impl<'a> HandlerTS<'a> {
pub fn new<H>(handler: &'a H) -> Self
where
H: ParamsTS + ReturnTS + ChildrenTS,
{
Self {
params_ts: handler.params_ts(),
return_ts: handler.return_ts(),
children: handler.children_ts(),
}
}
}
impl<'a> Visit<TSVisitor> for HandlerTS<'a> {
fn visit(&self, visitor: &mut TSVisitor) -> <TSVisitor as Visitor>::Result {
visitor.ts.push_str("{_PARAMS:");
(self.params_ts)(visitor);
if let Some(return_ty) = &self.return_ts {
visitor.ts.push_str(";_RETURN:");
return_ty(visitor);
}
if let Some(children) = &self.children {
visitor.ts.push_str(";_CHILDREN:");
children(visitor);
}
visitor.ts.push_str("}");
}
}
impl<F, T, E, Args> ParamsTS for FromFn<F, T, E, Args>
where
Self: HandlerTypes,
Static<<Self as HandlerTypes>::Params>: Visit<TSVisitor>,
{
fn params_ts(&self) -> Box<dyn Fn(&mut TSVisitor) + Send + Sync> {
Box::new(|visitor| Static::<<Self as HandlerTypes>::Params>::new().visit(visitor))
}
}
impl<F, Fut, T, E, Args> ParamsTS for FromFnAsync<F, Fut, T, E, Args>
where
Self: HandlerTypes,
Static<<Self as HandlerTypes>::Params>: Visit<TSVisitor>,
{
fn params_ts(&self) -> Box<dyn Fn(&mut TSVisitor) + Send + Sync> {
Box::new(|visitor| Static::<<Self as HandlerTypes>::Params>::new().visit(visitor))
}
}
impl<F, Fut, T, E, Args> ParamsTS for FromFnAsyncLocal<F, Fut, T, E, Args>
where
Self: HandlerTypes,
Static<<Self as HandlerTypes>::Params>: Visit<TSVisitor>,
{
fn params_ts(&self) -> Box<dyn Fn(&mut TSVisitor) + Send + Sync> {
Box::new(|visitor| Static::<<Self as HandlerTypes>::Params>::new().visit(visitor))
}
}
impl<Context, Params, InheritedParams> ParamsTS for ParentHandler<Context, Params, InheritedParams>
where
Self: HandlerTypes,
Static<<Self as HandlerTypes>::Params>: Visit<TSVisitor>,
{
fn params_ts(&self) -> Box<dyn Fn(&mut TSVisitor) + Send + Sync> {
Box::new(|visitor| Static::<<Self as HandlerTypes>::Params>::new().visit(visitor))
}
}
impl<F, T, E, Args> ReturnTS for FromFn<F, T, E, Args>
where
Self: HandlerTypes,
Static<<Self as HandlerTypes>::Ok>: Visit<TSVisitor>,
{
fn return_ts(&self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync>> {
Some(Box::new(|visitor| {
Static::<<Self as HandlerTypes>::Ok>::new().visit(visitor)
}))
}
}
impl<F, Fut, T, E, Args> ReturnTS for FromFnAsync<F, Fut, T, E, Args>
where
Self: HandlerTypes,
Static<<Self as HandlerTypes>::Ok>: Visit<TSVisitor>,
{
fn return_ts(&self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync>> {
Some(Box::new(|visitor| {
Static::<<Self as HandlerTypes>::Ok>::new().visit(visitor)
}))
}
}
impl<F, Fut, T, E, Args> ReturnTS for FromFnAsyncLocal<F, Fut, T, E, Args>
where
Self: HandlerTypes,
Static<<Self as HandlerTypes>::Ok>: Visit<TSVisitor>,
{
fn return_ts(&self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync>> {
Some(Box::new(|visitor| {
Static::<<Self as HandlerTypes>::Ok>::new().visit(visitor)
}))
}
}
impl<Context, Params, InheritedParams> ReturnTS
for ParentHandler<Context, Params, InheritedParams>
{
fn return_ts(&self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync>> {
None
}
}
impl<F, T, E, Args> ChildrenTS for FromFn<F, T, E, Args> {
fn children_ts(&self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync>> {
None
}
}
impl<F, Fut, T, E, Args> ChildrenTS for FromFnAsync<F, Fut, T, E, Args> {
fn children_ts(&self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync>> {
None
}
}
impl<F, Fut, T, E, Args> ChildrenTS for FromFnAsyncLocal<F, Fut, T, E, Args> {
fn children_ts(&self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync>> {
None
}
}
impl<Context, Params, InheritedParams> ChildrenTS
for ParentHandler<Context, Params, InheritedParams>
where
Context: crate::Context,
Params: Send + Sync + 'static,
InheritedParams: Send + Sync + 'static,
{
fn children_ts<'a>(&'a self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync + 'a>> {
use std::fmt::Write;
Some(Box::new(move |visitor| {
visitor.ts.push('{');
for (name, handler) in &self.subcommands.1 {
write!(
&mut visitor.ts,
"{}:",
serde_json::to_string(&name.0).unwrap()
)
.ok();
// Call get_ts on the handler
if let Some(ts) = handler.0.get_ts() {
ts.visit(visitor);
} else {
visitor.ts.push_str("unknown");
}
visitor.ts.push(';');
}
visitor.ts.push('}');
}))
}
}
pub trait PassthroughParamsTS: Adapter {}
impl<T: PassthroughParamsTS> ParamsTS for T
where
T::Inner: ParamsTS,
{
fn params_ts<'a>(&'a self) -> Box<dyn Fn(&mut TSVisitor) + Send + Sync + 'a> {
self.as_inner().params_ts()
}
}
pub trait PassthroughReturnTS: Adapter {}
impl<T: PassthroughReturnTS> ReturnTS for T
where
T::Inner: ReturnTS,
{
fn return_ts<'a>(&'a self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync + 'a>> {
self.as_inner().return_ts()
}
}
pub trait PassthroughChildrenTS: Adapter {}
impl<T: PassthroughChildrenTS> ChildrenTS for T
where
T::Inner: ChildrenTS,
{
fn children_ts<'a>(&'a self) -> Option<Box<dyn Fn(&mut TSVisitor) + Send + Sync + 'a>> {
self.as_inner().children_ts()
}
}
#[derive(Default, Debug, Clone)]
pub struct TSVisitor {
pub definitions: BTreeMap<&'static str, String>,
pub ts: String,
}
impl TSVisitor {
pub fn new() -> Self {
Self::default()
}
pub fn visit_ty<V: Visit<Self>>(&mut self, value: &V, define: Option<&'static str>) {
if let Some(name) = define {
self.ts.push_str(name);
let mut defn = Self::new();
value.visit(&mut defn);
self.load_definition(name, defn);
} else {
value.visit(self);
}
}
pub fn append_type<T>(&mut self)
where
T: TS,
{
self.visit_ty(&Static::<T>::new(), T::DEFINE);
}
pub fn insert_definition(&mut self, name: &'static str, definition: String) {
if let Some(def) = self.definitions.get(&name) {
assert_eq!(def, &definition, "Conflicting definitions for {name}");
}
debug_assert!(!definition.is_empty());
self.definitions.insert(name, definition);
}
pub fn load_definition(
&mut self,
name: &'static str,
TSVisitor { definitions, ts }: TSVisitor,
) {
for (name, definition) in definitions {
self.insert_definition(name, definition);
}
self.insert_definition(name, ts);
}
}
impl Visitor for TSVisitor {
type Result = ();
}
impl<T> Visit<TSVisitor> for Static<T>
where
T: VisitFieldsStaticNamed<TSVisitor>,
{
fn visit(&self, visitor: &mut TSVisitor) -> <TSVisitor as Visitor>::Result {
if Self::IS_NAMED {
visitor.ts.push_str("{");
} else {
visitor.ts.push_str("[");
}
self.visit_fields_static_named(visitor).collect::<()>();
if Self::IS_NAMED {
visitor.ts.push_str("}");
} else {
visitor.ts.push_str("]");
}
}
}
impl<T> Visit<TSVisitor> for NamedStatic<T>
impl<'a, T> Visit<TSVisitor> for Named<'a, Static<T>>
where
T: TS,
Static<T>: Visit<TSVisitor>,
{
fn visit(&self, visitor: &mut TSVisitor) -> <TSVisitor as Visitor>::Result {
use std::fmt::Write;
if let Some(name) = self.name {
if name.chars().all(|c| c.is_alphanumeric() || c == '_')
&& name.chars().next().map_or(false, |c| c.is_alphabetic())
{
visitor.ts.push_str(name);
} else {
write!(
&mut visitor.ts,
"[{}]",
serde_json::to_string(&name).unwrap()
)
.unwrap();
write!(&mut visitor.ts, "{}", serde_json::to_string(&name).unwrap()).unwrap();
}
visitor.ts.push_str(":");
}
Static::<T>::new().visit(visitor);
visitor.append_type::<T>();
if self.name.is_some() {
visitor.ts.push(";");
visitor.ts.push(';');
} else {
visitor.ts.push(",");
visitor.ts.push(',');
}
}
}
pub struct LiteralTS(Cow<'static, str>);
impl Visit<TSVisitor> for LiteralTS {
fn visit(&self, visitor: &mut TSVisitor) -> <TSVisitor as Visitor>::Result {
visitor.ts.push_str(&*self.0);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Unknown;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Never {}
#[macro_export]
macro_rules! impl_ts {
($($ty:ty),+ => $ts:expr) => {
$(
impl Visit<TSVisitor> for Static<$ty> {
fn visit(&self, visitor: &mut TSVisitor) -> <TSVisitor as Visitor>::Result {
impl $crate::ts::TS for $ty {
fn visit_ts(visitor: &mut $crate::ts::TSVisitor) {
visitor.ts.push_str($ts);
}
}
@@ -72,6 +340,141 @@ macro_rules! impl_ts {
};
}
impl_ts!(String => "string");
impl_ts!(bool => "boolean");
impl_ts!(String,str,InternedString => "string");
impl_ts!(usize,u8,u16,u32,isize,i8,i16,i32,f32,f64 => "number");
impl_ts!(u64,u128,i64,i128 => "bigint");
impl_ts!(Unknown,imbl_value::Value,serde_json::Value,serde_cbor::Value => "unknown");
impl_ts!(Never => "never");
pub fn visit_struct<T>(visitor: &mut TSVisitor)
where
T: VisitFieldsStaticNamed<TSVisitor>,
{
if T::NAMED_FIELDS {
visitor.ts.push_str("{");
} else {
visitor.ts.push_str("[");
}
T::visit_fields_static_named(visitor).for_each(drop);
if T::NAMED_FIELDS {
visitor.ts.push_str("}");
} else {
visitor.ts.push_str("]");
}
}
#[macro_export]
macro_rules! impl_ts_struct {
($ty:ty $({ define: $name:expr })?) => {
impl $crate::ts::TS for $ty {
$(const DEFINE: Option<&str> = Some($name);)?
fn visit_ts(visitor: &mut $crate::ts::TSVisitor) {
$crate::ts::visit_struct::<$ty>(visitor)
}
}
};
}
pub fn visit_map<K, V>(visitor: &mut TSVisitor)
where
K: TS,
V: TS,
{
visitor.ts.push_str("{[key");
if K::IS_ENUMERABLE {
visitor.ts.push_str(" in ");
} else {
visitor.ts.push(':');
}
visitor.append_type::<K>();
visitor.ts.push(']');
if K::IS_ENUMERABLE {
visitor.ts.push('?');
}
visitor.ts.push(':');
visitor.append_type::<V>();
visitor.ts.push('}');
}
#[macro_export]
macro_rules! impl_ts_map {
($(
$ty:ty
$(where [$($bounds:tt)*])?
),+ $(,)?) => {
$(
impl<K, V> $crate::ts::TS for $ty
where
K: $crate::ts::TS,
V: $crate::ts::TS,
$($($bounds)*,)?
{
fn visit_ts(visitor: &mut $crate::ts::TSVisitor) {
$crate::ts::visit_map::<K, V>(visitor)
}
}
)+
};
}
impl_ts_map!(
std::collections::HashMap<K,V>,
imbl_value::imbl::HashMap<K,V>,
imbl_value::InOMap<K,V> where [K: Eq + Clone, V: Clone],
BTreeMap<K,V>,
OrdMap<K,V>,
);
pub fn visit_array<T>(visitor: &mut TSVisitor)
where
T: TS,
{
visitor.ts.push('(');
visitor.append_type::<T>();
visitor.ts.push_str(")[]");
}
#[macro_export]
macro_rules! impl_ts_array {
($(
$ty:ty
$(where [$($bounds:tt)*])?
),+ $(,)?) => {
$(
impl<T> $crate::ts::TS for $ty
where
T: $crate::ts::TS,
$($($bounds)*,)?
{
fn visit_ts(visitor: &mut $crate::ts::TSVisitor) {
$crate::ts::visit_array::<T>(visitor)
}
}
)+
};
}
impl_ts_array!(Vec<T>, Vector<T>);
impl<T: TS> TS for Box<T> {
const DEFINE: Option<&str> = T::DEFINE;
const IS_ENUMERABLE: bool = T::IS_ENUMERABLE;
fn visit_ts(visitor: &mut TSVisitor) {
T::visit_ts(visitor);
}
}
impl<T: TS> TS for Arc<T> {
const DEFINE: Option<&str> = T::DEFINE;
const IS_ENUMERABLE: bool = T::IS_ENUMERABLE;
fn visit_ts(visitor: &mut TSVisitor) {
T::visit_ts(visitor);
}
}
impl<T: TS> TS for Rc<T> {
const DEFINE: Option<&str> = T::DEFINE;
const IS_ENUMERABLE: bool = T::IS_ENUMERABLE;
fn visit_ts(visitor: &mut TSVisitor) {
T::visit_ts(visitor);
}
}

View File

@@ -1,6 +1,9 @@
#![recursion_limit = "512"]
use clap::Parser;
use rpc_toolkit::ts::HandlerTSBindings;
use rpc_toolkit::{
from_fn, from_fn_async, Context, Empty, HandlerExt, HandlerTS, ParentHandler, Server,
from_fn, from_fn_async, impl_ts_struct, Context, Empty, HandlerExt, ParentHandler, Server,
};
use serde::{Deserialize, Serialize};
use yajrc::RpcError;
@@ -11,10 +14,12 @@ struct TestContext;
impl Context for TestContext {}
#[derive(Debug, Deserialize, Serialize, Parser)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "ts", derive(visit_rs::VisitFields))]
struct Thing1Params {
thing: String,
}
#[cfg(feature = "ts")]
impl_ts_struct!(Thing1Params);
#[derive(Debug, Deserialize, Serialize, Parser)]
struct NoTSParams {
@@ -30,11 +35,13 @@ fn no_ts_handler(_ctx: TestContext, params: NoTSParams) -> Result<String, RpcErr
}
#[derive(Debug, Deserialize, Serialize, Parser)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
#[cfg_attr(feature = "ts", derive(visit_rs::VisitFields))]
struct GroupParams {
#[arg(short, long)]
verbose: bool,
}
#[cfg(feature = "ts")]
impl_ts_struct!(GroupParams);
#[tokio::test]
async fn test_basic_server() {
@@ -53,7 +60,17 @@ async fn test_basic_server() {
.subcommand("no-ts", from_fn(no_ts_handler).no_ts()),
);
println!("{}", root_handler.type_info().unwrap_or_default());
println!(
"{:?}",
root_handler.get_ts().map(|t| {
use rpc_toolkit::ts::TSVisitor;
use visit_rs::Visit;
let mut ts = TSVisitor::new();
t.visit(&mut ts);
ts
})
);
let server = Server::new(|| async { Ok(TestContext) }, root_handler);