mirror of
https://github.com/Start9Labs/rpc-toolkit.git
synced 2026-03-26 02:11:56 +00:00
un-workspace
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
target/
|
||||
rpc.sock
|
||||
44
Cargo.toml
44
Cargo.toml
@@ -1,2 +1,42 @@
|
||||
[workspace]
|
||||
members = ["rpc-toolkit", "rpc-toolkit-macro", "rpc-toolkit-macro-internals"]
|
||||
[package]
|
||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||
edition = "2018"
|
||||
name = "rpc-toolkit"
|
||||
version = "0.2.3"
|
||||
description = "A toolkit for creating JSON-RPC 2.0 servers with automatic cli bindings"
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/rpc-toolkit"
|
||||
keywords = ["json", "rpc", "cli"]
|
||||
repository = "https://github.com/Start9Labs/rpc-toolkit"
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
cbor = ["serde_cbor"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
axum = "0.7.3"
|
||||
async-stream = "0.3"
|
||||
async-trait = "0.1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
futures = "0.3"
|
||||
http = "1"
|
||||
http-body-util = "0.1"
|
||||
# hyper = { version = "1", features = ["server", "http1", "http2", "client"] }
|
||||
itertools = "0.12"
|
||||
imbl-value = { git = "https://github.com/Start9Labs/imbl-value.git" }
|
||||
lazy_format = "2"
|
||||
lazy_static = "1.4"
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
pin-project = "1"
|
||||
reqwest = { version = "0.11" }
|
||||
rpc-toolkit-macro = { version = "0.2.2", path = "../rpc-toolkit-macro" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_cbor = { version = "0.11", optional = true }
|
||||
serde_json = "1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-stream = { version = "0.1", features = ["io-util", "net"] }
|
||||
url = "2"
|
||||
yajrc = "0.1"
|
||||
|
||||
2
rpc-toolkit-macro-internals/.gitignore
vendored
2
rpc-toolkit-macro-internals/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||
edition = "2018"
|
||||
name = "rpc-toolkit-macro-internals"
|
||||
version = "0.2.2"
|
||||
description = "internals for macros for rpc-toolkit"
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full", "fold"] }
|
||||
itertools = "0.12"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,102 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use syn::*;
|
||||
|
||||
pub mod build;
|
||||
mod parse;
|
||||
|
||||
pub enum ExecutionContext {
|
||||
Standard,
|
||||
CliOnly(Path),
|
||||
RpcOnly(Path),
|
||||
Local(Path),
|
||||
CustomCli {
|
||||
custom: Path,
|
||||
cli: Path,
|
||||
context: Type,
|
||||
is_async: bool,
|
||||
},
|
||||
}
|
||||
impl Default for ExecutionContext {
|
||||
fn default() -> Self {
|
||||
ExecutionContext::Standard
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LeafOptions {
|
||||
macro_debug: Option<Path>,
|
||||
blocking: Option<Path>,
|
||||
is_async: bool,
|
||||
aliases: Vec<LitStr>,
|
||||
about: Option<LitStr>,
|
||||
rename: Option<LitStr>,
|
||||
exec_ctx: ExecutionContext,
|
||||
display: Option<Path>,
|
||||
metadata: HashMap<Ident, Lit>,
|
||||
clap_attr: Vec<NestedMeta>,
|
||||
}
|
||||
|
||||
pub struct SelfImplInfo {
|
||||
path: Path,
|
||||
context: Type,
|
||||
is_async: bool,
|
||||
blocking: bool,
|
||||
}
|
||||
pub struct ParentOptions {
|
||||
common: LeafOptions,
|
||||
subcommands: Vec<Path>,
|
||||
self_impl: Option<SelfImplInfo>,
|
||||
}
|
||||
impl From<LeafOptions> for ParentOptions {
|
||||
fn from(opt: LeafOptions) -> Self {
|
||||
ParentOptions {
|
||||
common: opt,
|
||||
subcommands: Default::default(),
|
||||
self_impl: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Options {
|
||||
Leaf(LeafOptions),
|
||||
Parent(ParentOptions),
|
||||
}
|
||||
impl Options {
|
||||
fn to_parent(&mut self) -> Result<&mut ParentOptions> {
|
||||
if let Options::Leaf(opt) = self {
|
||||
*self = Options::Parent(std::mem::replace(opt, Default::default()).into());
|
||||
}
|
||||
Ok(match self {
|
||||
Options::Parent(a) => a,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
fn common(&mut self) -> &mut LeafOptions {
|
||||
match self {
|
||||
Options::Leaf(ref mut opt) => opt,
|
||||
Options::Parent(opt) => &mut opt.common,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ArgOptions {
|
||||
ty: Type,
|
||||
name: Option<Ident>,
|
||||
rename: Option<LitStr>,
|
||||
parse: Option<Path>,
|
||||
stdin: Option<Path>,
|
||||
default: Option<Path>,
|
||||
clap_attr: Vec<NestedMeta>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ParamType {
|
||||
None,
|
||||
Arg(ArgOptions),
|
||||
Context(Type),
|
||||
ParentData(Type),
|
||||
Request,
|
||||
Response,
|
||||
}
|
||||
@@ -1,851 +0,0 @@
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn parse_command_attr(args: AttributeArgs) -> Result<Options> {
|
||||
let mut opt = Options::Leaf(Default::default());
|
||||
for arg in args {
|
||||
match arg {
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("macro_debug") => {
|
||||
opt.common().macro_debug = Some(p);
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("subcommands") => {
|
||||
let inner = opt.to_parent()?;
|
||||
if !inner.subcommands.is_empty() {
|
||||
return Err(Error::new(list.span(), "duplicate argument `subcommands`"));
|
||||
}
|
||||
for subcmd in list.nested {
|
||||
match subcmd {
|
||||
NestedMeta::Meta(Meta::Path(subcmd)) => inner.subcommands.push(subcmd),
|
||||
NestedMeta::Lit(Lit::Str(s)) => {
|
||||
inner.subcommands.push(syn::parse_str(&s.value())?)
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(mut self_impl))
|
||||
if self_impl.path.is_ident("self") =>
|
||||
{
|
||||
if self_impl.nested.len() == 1 {
|
||||
match self_impl.nested.pop().unwrap().into_value() {
|
||||
NestedMeta::Meta(Meta::Path(self_impl)) => {
|
||||
if inner.self_impl.is_some() {
|
||||
return Err(Error::new(
|
||||
self_impl.span(),
|
||||
"duplicate argument `self`",
|
||||
));
|
||||
}
|
||||
inner.self_impl = Some(SelfImplInfo {
|
||||
path: self_impl,
|
||||
context: parse2(quote::quote! { () }).unwrap(),
|
||||
is_async: false,
|
||||
blocking: false,
|
||||
})
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(l)) => {
|
||||
if inner.self_impl.is_some() {
|
||||
return Err(Error::new(
|
||||
self_impl.span(),
|
||||
"duplicate argument `self`",
|
||||
));
|
||||
}
|
||||
let mut is_async = false;
|
||||
let mut blocking = false;
|
||||
let mut context: Type =
|
||||
parse2(quote::quote! { () }).unwrap();
|
||||
for meta in &l.nested {
|
||||
match meta {
|
||||
NestedMeta::Meta(Meta::Path(p))
|
||||
if p.is_ident("async") =>
|
||||
{
|
||||
is_async = true;
|
||||
if blocking {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`async` and `blocking` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p))
|
||||
if p.is_ident("blocking") =>
|
||||
{
|
||||
blocking = true;
|
||||
if is_async {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`async` and `blocking` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(p))
|
||||
if p.path.is_ident("context") =>
|
||||
{
|
||||
context = if let Some(NestedMeta::Meta(
|
||||
Meta::Path(context),
|
||||
)) = p.nested.first()
|
||||
{
|
||||
Type::Path(TypePath {
|
||||
path: context.clone(),
|
||||
qself: None,
|
||||
})
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`context` requires a path argument",
|
||||
));
|
||||
}
|
||||
}
|
||||
arg => {
|
||||
return Err(Error::new(
|
||||
arg.span(),
|
||||
"unknown argument",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
inner.self_impl = Some(SelfImplInfo {
|
||||
path: l.path,
|
||||
context,
|
||||
is_async,
|
||||
blocking,
|
||||
})
|
||||
}
|
||||
a => {
|
||||
return Err(Error::new(
|
||||
a.span(),
|
||||
"`self` implementation must be a path, or a list",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
self_impl.nested.span(),
|
||||
"`self` can only have one implementation",
|
||||
));
|
||||
}
|
||||
}
|
||||
arg => {
|
||||
return Err(Error::new(arg.span(), "unknown argument to `subcommands`"))
|
||||
}
|
||||
}
|
||||
}
|
||||
if inner.subcommands.is_empty() {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`subcommands` requires at least 1 argument",
|
||||
));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("subcommands") => {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`subcommands` requires at least 1 argument",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("subcommands") => {
|
||||
return Err(Error::new(
|
||||
nv.path.span(),
|
||||
"`subcommands` cannot be assigned to",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("display") => {
|
||||
if list.nested.len() == 1 {
|
||||
match &list.nested[0] {
|
||||
NestedMeta::Meta(Meta::Path(display_impl)) => {
|
||||
if opt.common().display.is_some() {
|
||||
return Err(Error::new(
|
||||
display_impl.span(),
|
||||
"duplicate argument `display`",
|
||||
));
|
||||
}
|
||||
opt.common().display = Some(display_impl.clone())
|
||||
}
|
||||
a => {
|
||||
return Err(Error::new(
|
||||
a.span(),
|
||||
"`display` implementation must be a path",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
list.nested.span(),
|
||||
"`display` can only have one implementation",
|
||||
));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("display") => {
|
||||
return Err(Error::new(p.span(), "`display` requires an argument"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("display") => {
|
||||
return Err(Error::new(
|
||||
nv.path.span(),
|
||||
"`display` cannot be assigned to",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("custom_cli") => {
|
||||
if list.nested.len() == 1 {
|
||||
match &opt.common().exec_ctx {
|
||||
ExecutionContext::Standard => match &list.nested[0] {
|
||||
NestedMeta::Meta(Meta::Path(custom_cli_impl)) => {
|
||||
opt.common().exec_ctx = ExecutionContext::CustomCli {
|
||||
custom: list.path,
|
||||
context: parse2(quote::quote!{ () }).unwrap(),
|
||||
cli: custom_cli_impl.clone(),
|
||||
is_async: false,
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(custom_cli_impl)) => {
|
||||
let mut is_async = false;
|
||||
let mut context: Type = parse2(quote::quote! { () }).unwrap();
|
||||
for meta in &custom_cli_impl.nested {
|
||||
match meta {
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("async") => is_async = true,
|
||||
NestedMeta::Meta(Meta::List(p)) if p.path.is_ident("context") => {
|
||||
context = if let Some(NestedMeta::Meta(Meta::Path(context))) = p.nested.first() {
|
||||
Type::Path(TypePath {
|
||||
path: context.clone(),
|
||||
qself: None,
|
||||
})
|
||||
} else {
|
||||
return Err(Error::new(p.span(), "`context` requires a path argument"));
|
||||
}
|
||||
}
|
||||
arg => return Err(Error::new(arg.span(), "unknown argument")),
|
||||
}
|
||||
}
|
||||
opt.common().exec_ctx = ExecutionContext::CustomCli {
|
||||
custom: list.path,
|
||||
context,
|
||||
cli: custom_cli_impl.path.clone(),
|
||||
is_async,
|
||||
};
|
||||
}
|
||||
a => {
|
||||
return Err(Error::new(
|
||||
a.span(),
|
||||
"`custom_cli` implementation must be a path, or a list with 1 argument",
|
||||
))
|
||||
}
|
||||
},
|
||||
ExecutionContext::CliOnly(_) => {
|
||||
return Err(Error::new(list.span(), "duplicate argument: `cli_only`"))
|
||||
}
|
||||
ExecutionContext::RpcOnly(_) => {
|
||||
return Err(Error::new(
|
||||
list.span(),
|
||||
"`cli_only` and `rpc_only` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
ExecutionContext::Local(_) => {
|
||||
return Err(Error::new(
|
||||
list.span(),
|
||||
"`cli_only` and `local` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
ExecutionContext::CustomCli { .. } => {
|
||||
return Err(Error::new(
|
||||
list.span(),
|
||||
"`cli_only` and `custom_cli` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
list.nested.span(),
|
||||
"`custom_cli` can only have one implementation",
|
||||
));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("custom_cli") => {
|
||||
return Err(Error::new(p.span(), "`custom_cli` requires an argument"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("custom_cli") => {
|
||||
return Err(Error::new(
|
||||
nv.path.span(),
|
||||
"`custom_cli` cannot be assigned to",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("aliases") => {
|
||||
if !opt.common().aliases.is_empty() {
|
||||
return Err(Error::new(list.span(), "duplicate argument `alias`"));
|
||||
}
|
||||
for nested in list.nested {
|
||||
match nested {
|
||||
NestedMeta::Lit(Lit::Str(alias)) => opt.common().aliases.push(alias),
|
||||
a => return Err(Error::new(a.span(), "`alias` must be a string")),
|
||||
}
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("alias") => {
|
||||
return Err(Error::new(p.span(), "`alias` requires an argument"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("alias") => {
|
||||
if !opt.common().aliases.is_empty() {
|
||||
return Err(Error::new(nv.path.span(), "duplicate argument `alias`"));
|
||||
}
|
||||
if let Lit::Str(alias) = nv.lit {
|
||||
opt.common().aliases.push(alias);
|
||||
} else {
|
||||
return Err(Error::new(nv.lit.span(), "`alias` must be a string"));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("cli_only") => {
|
||||
match &opt.common().exec_ctx {
|
||||
ExecutionContext::Standard => {
|
||||
opt.common().exec_ctx = ExecutionContext::CliOnly(p)
|
||||
}
|
||||
ExecutionContext::CliOnly(_) => {
|
||||
return Err(Error::new(p.span(), "duplicate argument: `cli_only`"))
|
||||
}
|
||||
ExecutionContext::RpcOnly(_) => {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`cli_only` and `rpc_only` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
ExecutionContext::Local(_) => {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`cli_only` and `local` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
ExecutionContext::CustomCli { .. } => {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`cli_only` and `custom_cli` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("cli_only") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`cli_only` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("cli_only") => {
|
||||
return Err(Error::new(
|
||||
nv.path.span(),
|
||||
"`cli_only` cannot be assigned to",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("rpc_only") => {
|
||||
match &opt.common().exec_ctx {
|
||||
ExecutionContext::Standard => {
|
||||
opt.common().exec_ctx = ExecutionContext::RpcOnly(p)
|
||||
}
|
||||
ExecutionContext::RpcOnly(_) => {
|
||||
return Err(Error::new(p.span(), "duplicate argument: `rpc_only`"))
|
||||
}
|
||||
ExecutionContext::CliOnly(_) => {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`rpc_only` and `cli_only` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
ExecutionContext::Local(_) => {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`rpc_only` and `local` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
ExecutionContext::CustomCli { .. } => {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`rpc_only` and `custom_cli` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("rpc_only") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`rpc_only` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("rpc_only") => {
|
||||
return Err(Error::new(
|
||||
nv.path.span(),
|
||||
"`rpc_only` cannot be assigned to",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("local") => {
|
||||
match &opt.common().exec_ctx {
|
||||
ExecutionContext::Standard => {
|
||||
opt.common().exec_ctx = ExecutionContext::Local(p)
|
||||
}
|
||||
ExecutionContext::Local(_) => {
|
||||
return Err(Error::new(p.span(), "duplicate argument: `local`"))
|
||||
}
|
||||
ExecutionContext::RpcOnly(_) => {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`local` and `rpc_only` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
ExecutionContext::CliOnly(_) => {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`local` and `cli_only` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
ExecutionContext::CustomCli { .. } => {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`local` and `custom_cli` are mutually exclusive",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("local") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`local` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("local") => {
|
||||
return Err(Error::new(nv.path.span(), "`local` cannot be assigned to"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("blocking") => {
|
||||
if opt.common().blocking.is_some() {
|
||||
return Err(Error::new(p.span(), "duplicate argument `blocking`"));
|
||||
}
|
||||
opt.common().blocking = Some(p);
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("blocking") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`blocking` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("blocking") => {
|
||||
return Err(Error::new(
|
||||
nv.path.span(),
|
||||
"`blocking` cannot be assigned to",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("about") => {
|
||||
if let Lit::Str(about) = nv.lit {
|
||||
if opt.common().about.is_some() {
|
||||
return Err(Error::new(about.span(), "duplicate argument `about`"));
|
||||
}
|
||||
opt.common().about = Some(about);
|
||||
} else {
|
||||
return Err(Error::new(nv.lit.span(), "about message must be a string"));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("about") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`about` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("about") => {
|
||||
return Err(Error::new(p.span(), "`about` must be assigned to"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("rename") => {
|
||||
if let Lit::Str(rename) = nv.lit {
|
||||
if opt.common().rename.is_some() {
|
||||
return Err(Error::new(rename.span(), "duplicate argument `rename`"));
|
||||
}
|
||||
opt.common().rename = Some(rename);
|
||||
} else {
|
||||
return Err(Error::new(nv.lit.span(), "`rename` must be a string"));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("rename") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`rename` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("rename") => {
|
||||
return Err(Error::new(p.span(), "`rename` must be assigned to"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("metadata") => {
|
||||
for meta in list.nested {
|
||||
match meta {
|
||||
NestedMeta::Meta(Meta::NameValue(metadata_pair)) => {
|
||||
let ident = metadata_pair.path.get_ident().ok_or(Error::new(
|
||||
metadata_pair.path.span(),
|
||||
"must be an identifier",
|
||||
))?;
|
||||
if opt
|
||||
.common()
|
||||
.metadata
|
||||
.insert(ident.clone(), metadata_pair.lit)
|
||||
.is_some()
|
||||
{
|
||||
return Err(Error::new(
|
||||
ident.span(),
|
||||
format!("duplicate metadata `{}`", ident),
|
||||
));
|
||||
}
|
||||
}
|
||||
a => {
|
||||
return Err(Error::new(
|
||||
a.span(),
|
||||
"`metadata` takes a list of identifiers to be assigned to",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("metadata") => {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`metadata` takes a list of identifiers to be assigned to",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("metadata") => {
|
||||
return Err(Error::new(
|
||||
nv.path.span(),
|
||||
"`metadata` cannot be assigned to",
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new(arg.span(), "unknown argument"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Options::Parent(opt) = &opt {
|
||||
if opt.self_impl.is_none() {
|
||||
if let Some(display) = &opt.common.display {
|
||||
return Err(Error::new(
|
||||
display.span(),
|
||||
"cannot define `display` for a command without an implementation",
|
||||
));
|
||||
}
|
||||
match &opt.common.exec_ctx {
|
||||
ExecutionContext::CliOnly(cli_only) => {
|
||||
return Err(Error::new(
|
||||
cli_only.span(),
|
||||
"cannot define `cli_only` for a command without an implementation",
|
||||
))
|
||||
}
|
||||
ExecutionContext::RpcOnly(rpc_only) => {
|
||||
return Err(Error::new(
|
||||
rpc_only.span(),
|
||||
"cannot define `rpc_only` for a command without an implementation",
|
||||
))
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(opt)
|
||||
}
|
||||
|
||||
pub fn parse_arg_attr(attr: Attribute, arg: PatType) -> Result<ArgOptions> {
|
||||
let arg_span = arg.span();
|
||||
let meta = attr.parse_meta()?;
|
||||
let mut opt = ArgOptions {
|
||||
ty: *arg.ty,
|
||||
name: match *arg.pat {
|
||||
Pat::Ident(i) => Some(i.ident),
|
||||
_ => None,
|
||||
},
|
||||
rename: None,
|
||||
parse: None,
|
||||
stdin: None,
|
||||
default: None,
|
||||
clap_attr: Vec::new(),
|
||||
};
|
||||
match meta {
|
||||
Meta::List(list) => {
|
||||
for arg in list.nested {
|
||||
match arg {
|
||||
NestedMeta::Meta(Meta::List(mut list)) if list.path.is_ident("parse") => {
|
||||
if list.nested.len() == 1 {
|
||||
match list.nested.pop().unwrap().into_value() {
|
||||
NestedMeta::Meta(Meta::Path(parse_impl)) => {
|
||||
if opt.parse.is_some() {
|
||||
return Err(Error::new(
|
||||
list.span(),
|
||||
"duplicate argument `parse`",
|
||||
));
|
||||
}
|
||||
opt.parse = Some(parse_impl)
|
||||
}
|
||||
a => {
|
||||
return Err(Error::new(
|
||||
a.span(),
|
||||
"`parse` implementation must be a path",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
list.nested.span(),
|
||||
"`parse` can only have one implementation",
|
||||
));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("parse") => {
|
||||
return Err(Error::new(p.span(), "`parse` requires an argument"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("parse") => {
|
||||
return Err(Error::new(nv.path.span(), "`parse` cannot be assigned to"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("stdin") => {
|
||||
if !opt.clap_attr.is_empty() {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`stdin` and clap parser attributes are mutually exclusive",
|
||||
));
|
||||
}
|
||||
opt.stdin = Some(p);
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("stdin") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`stdin` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("stdin") => {
|
||||
return Err(Error::new(nv.path.span(), "`stdin` cannot be assigned to"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("rename") => {
|
||||
if let Lit::Str(rename) = nv.lit {
|
||||
if opt.rename.is_some() {
|
||||
return Err(Error::new(
|
||||
rename.span(),
|
||||
"duplicate argument `rename`",
|
||||
));
|
||||
}
|
||||
opt.clap_attr
|
||||
.push(NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
||||
path: Path::from(Ident::new("name", nv.path.span())),
|
||||
eq_token: nv.eq_token,
|
||||
lit: Lit::Str(rename.clone()),
|
||||
})));
|
||||
opt.rename = Some(rename);
|
||||
} else {
|
||||
return Err(Error::new(nv.lit.span(), "`rename` must be a string"));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("rename") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`rename` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("rename") => {
|
||||
return Err(Error::new(p.span(), "`rename` must be assigned to"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("default") => {
|
||||
return Err(Error::new(nv.lit.span(), "`default` cannot be assigned to"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("default") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`default` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("default") => {
|
||||
opt.clap_attr
|
||||
.push(NestedMeta::Meta(Meta::Path(Path::from(Ident::new(
|
||||
"default_value_t",
|
||||
p.span(),
|
||||
)))));
|
||||
opt.default = Some(p);
|
||||
}
|
||||
unknown => {
|
||||
if opt.stdin.is_some() {
|
||||
return Err(Error::new(
|
||||
unknown.span(),
|
||||
"`stdin` and clap parser attributes are mutually exclusive",
|
||||
));
|
||||
}
|
||||
opt.clap_attr.push(unknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Meta::Path(_) => (),
|
||||
Meta::NameValue(nv) => return Err(Error::new(nv.span(), "`arg` cannot be assigned to")),
|
||||
}
|
||||
if opt.name.is_none() {
|
||||
return Err(Error::new(
|
||||
arg_span,
|
||||
"cannot infer name for pattern argument",
|
||||
));
|
||||
}
|
||||
Ok(opt)
|
||||
}
|
||||
|
||||
pub fn parse_param_attrs(item: &mut ItemFn) -> Result<Vec<ParamType>> {
|
||||
let mut params = Vec::new();
|
||||
for param in item.sig.inputs.iter_mut() {
|
||||
if let FnArg::Typed(param) = param {
|
||||
let mut ty = ParamType::None;
|
||||
let mut i = 0;
|
||||
while i != param.attrs.len() {
|
||||
if param.attrs[i].path.is_ident("arg") {
|
||||
let attr = param.attrs.remove(i);
|
||||
if matches!(ty, ParamType::None) {
|
||||
ty = ParamType::Arg(parse_arg_attr(attr, param.clone())?);
|
||||
} else if matches!(ty, ParamType::Arg(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`arg` attribute may only be specified once",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Context(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`arg` and `context` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::ParentData(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`arg` and `parent_data` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Request) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`arg` and `request` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Response) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`arg` and `response` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
} else if param.attrs[i].path.is_ident("context") {
|
||||
let attr = param.attrs.remove(i);
|
||||
if matches!(ty, ParamType::None) {
|
||||
ty = ParamType::Context(*param.ty.clone());
|
||||
} else if matches!(ty, ParamType::Context(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`context` attribute may only be specified once",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Arg(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`arg` and `context` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::ParentData(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`context` and `parent_data` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Request) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`context` and `request` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Response) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`context` and `response` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
} else if param.attrs[i].path.is_ident("parent_data") {
|
||||
let attr = param.attrs.remove(i);
|
||||
if matches!(ty, ParamType::None) {
|
||||
ty = ParamType::ParentData(*param.ty.clone());
|
||||
} else if matches!(ty, ParamType::ParentData(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`parent_data` attribute may only be specified once",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Arg(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`arg` and `parent_data` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Context(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`context` and `parent_data` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Request) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`parent_data` and `request` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Response) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`parent_data` and `response` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
} else if param.attrs[i].path.is_ident("request") {
|
||||
let attr = param.attrs.remove(i);
|
||||
if matches!(ty, ParamType::None) {
|
||||
ty = ParamType::Request;
|
||||
} else if matches!(ty, ParamType::Request) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`request` attribute may only be specified once",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Arg(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`arg` and `request` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Context(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`context` and `request` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::ParentData(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`parent_data` and `request` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Response) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`request` and `response` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
} else if param.attrs[i].path.is_ident("response") {
|
||||
let attr = param.attrs.remove(i);
|
||||
if matches!(ty, ParamType::None) {
|
||||
ty = ParamType::Response;
|
||||
} else if matches!(ty, ParamType::Response) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`response` attribute may only be specified once",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Arg(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`arg` and `response` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Context(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`context` and `response` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::ParentData(_)) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`parent_data` and `response` are mutually exclusive",
|
||||
));
|
||||
} else if matches!(ty, ParamType::Request) {
|
||||
return Err(Error::new(
|
||||
attr.span(),
|
||||
"`request` and `response` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if matches!(ty, ParamType::None) {
|
||||
return Err(Error::new(
|
||||
param.span(),
|
||||
"must specify either `arg`, `context`, `parent_data`, `request`, or `response` attributes",
|
||||
));
|
||||
}
|
||||
params.push(ty)
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
param.span(),
|
||||
"commands may not take `self` as an argument",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
macro_rules! macro_try {
|
||||
($x:expr) => {
|
||||
match $x {
|
||||
Ok(a) => a,
|
||||
Err(e) => return e.to_compile_error(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod command;
|
||||
|
||||
pub use command::build::build as build_command;
|
||||
2
rpc-toolkit-macro/.gitignore
vendored
2
rpc-toolkit-macro/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -1,17 +0,0 @@
|
||||
[package]
|
||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||
edition = "2018"
|
||||
name = "rpc-toolkit-macro"
|
||||
version = "0.2.2"
|
||||
description = "macros for rpc-toolkit"
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
rpc-toolkit-macro-internals = { version = "=0.2.2", path = "../rpc-toolkit-macro-internals" }
|
||||
syn = "1.0"
|
||||
@@ -1,44 +0,0 @@
|
||||
use proc_macro::{Span, TokenStream};
|
||||
use rpc_toolkit_macro_internals::*;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn command(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||
let item = syn::parse_macro_input!(item as syn::ItemFn);
|
||||
build_command(args, item).into()
|
||||
}
|
||||
|
||||
/// `#[arg(...)]` -> Take this argument as a parameter
|
||||
/// - `#[arg(help = "Help text")]` -> Set help text for the arg
|
||||
/// - `#[arg(alias = "ls")]` -> Set the alias `ls` in the CLI
|
||||
/// - `#[arg(aliases("show", "ls"))]` -> Set the aliases `ls` and `show` in the CLI
|
||||
/// - `#[arg(rename = "new_name")]` -> Set the name of the arg to `new_name` in the RPC and CLI
|
||||
/// - `#[arg(short = "a")]` -> Set the "short" representation of the arg to `-a` on the CLI
|
||||
/// - `#[arg(long = "arg")]` -> Set the "long" representation of the arg to `--arg` on the CLI
|
||||
/// - `#[arg(parse(custom_parse_fn))]` -> Use the function `custom_parse_fn` to parse the arg from the CLI
|
||||
/// - `custom_parse_fn :: Into<RpcError> err => (&str, &ArgMatches) -> Result<arg, err>`
|
||||
/// - note: `arg` is the type of the argument
|
||||
/// - `#[arg(stdin)]` -> Parse the argument from stdin when using the CLI
|
||||
/// - `custom_parse_fn :: Into<RpcError> err => (&[u8], &ArgMatches) -> Result<arg, err>`
|
||||
/// - `#[arg(count)]` -> Treat argument as flag, count occurrences
|
||||
/// - `#[arg(multiple)]` -> Allow the arg to be specified multiple times. Collect the args after parsing.
|
||||
#[proc_macro_attribute]
|
||||
pub fn arg(_: TokenStream, _: TokenStream) -> TokenStream {
|
||||
syn::Error::new(
|
||||
Span::call_site().into(),
|
||||
"`arg` is only allowed on arguments of a function with the `command` attribute",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// - `#[context]` -> Passes the application context into this parameter
|
||||
#[proc_macro_attribute]
|
||||
pub fn context(_: TokenStream, _: TokenStream) -> TokenStream {
|
||||
syn::Error::new(
|
||||
Span::call_site().into(),
|
||||
"`context` is only allowed on arguments of a function with the `command` attribute",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
2
rpc-toolkit/.gitignore
vendored
2
rpc-toolkit/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -1,42 +0,0 @@
|
||||
[package]
|
||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||
edition = "2018"
|
||||
name = "rpc-toolkit"
|
||||
version = "0.2.3"
|
||||
description = "A toolkit for creating JSON-RPC 2.0 servers with automatic cli bindings"
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/rpc-toolkit"
|
||||
keywords = ["json", "rpc", "cli"]
|
||||
repository = "https://github.com/Start9Labs/rpc-toolkit"
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
cbor = ["serde_cbor"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
axum = "0.7.3"
|
||||
async-stream = "0.3"
|
||||
async-trait = "0.1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
futures = "0.3"
|
||||
http = "1"
|
||||
http-body-util = "0.1"
|
||||
# hyper = { version = "1", features = ["server", "http1", "http2", "client"] }
|
||||
itertools = "0.12"
|
||||
imbl-value = { git = "https://github.com/Start9Labs/imbl-value.git" }
|
||||
lazy_format = "2"
|
||||
lazy_static = "1.4"
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
pin-project = "1"
|
||||
reqwest = { version = "0.11" }
|
||||
rpc-toolkit-macro = { version = "0.2.2", path = "../rpc-toolkit-macro" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_cbor = { version = "0.11", optional = true }
|
||||
serde_json = "1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-stream = { version = "0.1", features = ["io-util", "net"] }
|
||||
url = "2"
|
||||
yajrc = "0.1"
|
||||
Reference in New Issue
Block a user