wip: metadata

This commit is contained in:
Aiden McClelland
2025-12-17 15:44:02 -07:00
parent c25bad4857
commit caad518581
4 changed files with 150 additions and 26 deletions

8
Cargo.lock generated
View File

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

View File

@@ -2,7 +2,7 @@
authors = ["Aiden McClelland <me@drbonez.dev>"]
description = "A toolkit for creating JSON-RPC 2.0 servers with automatic cli bindings"
documentation = "https://docs.rs/rpc-toolkit"
edition = "2018"
edition = "2024"
keywords = ["cli", "json", "rpc"]
license = "MIT"
name = "rpc-toolkit"
@@ -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.8", optional = true }
visit-rs = { version = "0.1.9", optional = true }
yajrc = "0.1"

View File

@@ -50,11 +50,11 @@ impl<Context: crate::Context> Server<Context> {
&self,
method: &str,
params: Value,
) -> impl Future<Output = Result<Value, RpcError>> + Send + 'static {
) -> impl Future<Output = Result<Value, RpcError>> + Send + 'static + use<Context> {
let (make_ctx, root_handler, method) = (
self.make_ctx.clone(),
self.root_handler.clone(),
self.root_handler.method_from_dots(method),
self.root_handler.method_from_dots(method.as_ref()),
);
async move {
@@ -74,14 +74,11 @@ impl<Context: crate::Context> Server<Context> {
&self,
RpcRequest { id, method, params }: RpcRequest,
) -> impl Future<Output = RpcResponse> + Send + 'static {
let handle = (|| Ok::<_, RpcError>(self.handle_command(method.as_str(), params)))();
let handle = self.handle_command(method.as_str(), params);
async move {
RpcResponse {
id,
result: match handle {
Ok(handle) => handle.await,
Err(e) => Err(e),
},
result: handle.await,
}
}
}

143
src/ts.rs
View File

@@ -6,7 +6,11 @@ use std::sync::Arc;
use imbl_value::imbl::{OrdMap, Vector};
use imbl_value::InternedString;
use visit_rs::{Named, Static, Visit, VisitFieldsStaticNamed, Visitor};
use visit_rs::{
Named, Static, StructInfo, StructInfoData, Variant, Visit, VisitFieldsStatic,
VisitFieldsStaticNamed, VisitVariantFieldsStatic, VisitVariantFieldsStaticNamed,
VisitVariantsStatic, Visitor,
};
use crate::{Adapter, FromFn, FromFnAsync, FromFnAsyncLocal, HandlerTypes, ParentHandler};
@@ -315,6 +319,96 @@ where
}
}
#[derive(Default)]
pub struct SerdeTag {
pub tag: Option<syn::LitStr>,
pub contents: Option<syn::LitStr>,
}
impl SerdeTag {
pub fn apply_meta(this: &mut Option<Self>, meta: &syn::Meta) {
if meta.path().is_ident("untagged") {
*this = None;
} else if meta.path().is_ident("tag") {
if let Some(tag) = meta.require_name_value().ok() {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(tag),
..
}) = &tag.value
{
this.get_or_insert_default().tag = Some(tag.clone());
}
}
} else if meta.path().is_ident("contents") {
if let Some(tag) = meta.require_name_value().ok() {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(tag),
..
}) = &tag.value
{
this.get_or_insert_default().contents = Some(tag.clone());
}
}
}
}
pub fn from_metas(metas: &[syn::Meta]) -> Option<Self> {
let mut res = Some(SerdeTag::default());
for meta in metas.into_iter().filter_map(|m| m.require_list().ok()) {
if meta.path.is_ident("serde") {
syn::parse2::<syn::Meta>(meta.tokens.clone())
.ok()
.as_ref()
.map(|meta| Self::apply_meta(&mut res, meta));
}
}
for meta in metas.into_iter().filter_map(|m| m.require_list().ok()) {
if meta.path.is_ident("visit") {
if let Some(meta) = syn::parse2::<syn::Meta>(meta.tokens.clone())
.ok()
.as_ref()
.and_then(|m| m.require_list().ok())
{
if meta.path.is_ident("ts") {
syn::parse2::<syn::Meta>(meta.tokens.clone())
.ok()
.as_ref()
.map(|meta| Self::apply_meta(&mut res, meta));
}
}
}
}
None
}
}
impl<'a, T> Visit<TSVisitor> for Variant<'a, Static<T>>
where
T: TS,
T: VisitVariantFieldsStaticNamed<TSVisitor> + VisitVariantFieldsStatic<TSVisitor>,
{
fn visit(&self, visitor: &mut TSVisitor) -> <TSVisitor as Visitor>::Result {
// TODO: handle serde tagging
let tag = Some(SerdeTag {
tag: None,
contents: None,
});
if T::DATA.variant_count > 1 {
visitor.ts.push('|');
}
visit_struct_impl(
&self.info,
|name, visitor| {
if name {
T::visit_variant_fields_static_named(&self.info, visitor).for_each(drop);
} else {
T::visit_variant_fields_static(&self.info, visitor).for_each(drop);
}
},
visitor,
);
}
}
pub struct LiteralTS(Cow<'static, str>);
impl Visit<TSVisitor> for LiteralTS {
fn visit(&self, visitor: &mut TSVisitor) -> <TSVisitor as Visitor>::Result {
@@ -347,22 +441,44 @@ 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 {
fn visit_struct_impl(
info: &StructInfoData,
fields: impl FnOnce(bool, &mut TSVisitor),
visitor: &mut TSVisitor,
) {
if !info.named_fields && info.field_count == 1 {
fields(false, visitor)
} else {
if info.named_fields {
visitor.ts.push_str("{");
} else {
visitor.ts.push_str("[");
}
T::visit_fields_static_named(visitor).for_each(drop);
if T::NAMED_FIELDS {
fields(true, visitor);
if info.named_fields {
visitor.ts.push_str("}");
} else {
visitor.ts.push_str("]");
}
}
}
pub fn visit_struct<T>(visitor: &mut TSVisitor)
where
T: VisitFieldsStaticNamed<TSVisitor> + VisitFieldsStatic<TSVisitor>,
{
visit_struct_impl(
&T::DATA,
|named, visitor| {
if named {
T::visit_fields_static_named(visitor).for_each(drop);
} else {
T::visit_fields_static(visitor).for_each(drop);
}
},
visitor,
);
}
#[macro_export]
macro_rules! impl_ts_struct {
@@ -376,6 +492,17 @@ macro_rules! impl_ts_struct {
};
}
pub fn visit_enum<T>(visitor: &mut TSVisitor)
where
T: VisitVariantsStatic<TSVisitor>,
{
if T::DATA.variant_count == 0 {
visitor.append_type::<Never>()
} else {
T::visit_variants_static(visitor).for_each(drop)
}
}
pub fn visit_map<K, V>(visitor: &mut TSVisitor)
where
K: TS,