mirror of
https://github.com/Start9Labs/rpc-toolkit.git
synced 2026-03-31 04:33:41 +00:00
Merge pull request #3 from Start9Labs/refactor/no-dyn-ctx
Refactor: Use traits instead of macros
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
target/
|
||||
rpc.sock
|
||||
1476
Cargo.lock
generated
1476
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
43
Cargo.toml
43
Cargo.toml
@@ -1,2 +1,41 @@
|
||||
[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"
|
||||
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.12" }
|
||||
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,13 +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"] }
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,105 +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: bool,
|
||||
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>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ArgOptions {
|
||||
ty: Type,
|
||||
optional: bool,
|
||||
check_is_present: bool,
|
||||
help: Option<LitStr>,
|
||||
name: Option<Ident>,
|
||||
rename: Option<LitStr>,
|
||||
short: Option<LitChar>,
|
||||
long: Option<LitStr>,
|
||||
parse: Option<Path>,
|
||||
default: Option<Option<LitStr>>,
|
||||
count: Option<Path>,
|
||||
multiple: Option<Path>,
|
||||
stdin: Option<Path>,
|
||||
}
|
||||
|
||||
pub enum ParamType {
|
||||
None,
|
||||
Arg(ArgOptions),
|
||||
Context(Type),
|
||||
ParentData(Type),
|
||||
Request,
|
||||
Response,
|
||||
}
|
||||
@@ -1,999 +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 = true;
|
||||
}
|
||||
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 mut opt = ArgOptions {
|
||||
ty: *arg.ty,
|
||||
optional: false,
|
||||
check_is_present: false,
|
||||
help: None,
|
||||
name: match *arg.pat {
|
||||
Pat::Ident(i) => Some(i.ident),
|
||||
_ => None,
|
||||
},
|
||||
rename: None,
|
||||
short: None,
|
||||
long: None,
|
||||
parse: None,
|
||||
default: None,
|
||||
count: None,
|
||||
multiple: None,
|
||||
stdin: None,
|
||||
};
|
||||
match attr.parse_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.short.is_some() {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`stdin` and `short` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
if opt.long.is_some() {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`stdin` and `long` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
if opt.help.is_some() {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`stdin` and `help` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
if opt.count.is_some() {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`stdin` and `count` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
if opt.multiple.is_some() {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`stdin` and `multiple` 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::Path(p)) if p.is_ident("count") => {
|
||||
if opt.stdin.is_some() {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`stdin` and `count` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
if opt.multiple.is_some() {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`count` and `multiple` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
opt.count = Some(p);
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("count") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`count` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("count") => {
|
||||
return Err(Error::new(nv.path.span(), "`count` cannot be assigned to"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("multiple") => {
|
||||
if opt.stdin.is_some() {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`stdin` and `multiple` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
if opt.count.is_some() {
|
||||
return Err(Error::new(
|
||||
p.span(),
|
||||
"`count` and `multiple` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
opt.multiple = Some(p);
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("multiple") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`multiple` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("count") => {
|
||||
return Err(Error::new(nv.path.span(), "`count` cannot be assigned to"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("help") => {
|
||||
if let Lit::Str(help) = nv.lit {
|
||||
if opt.help.is_some() {
|
||||
return Err(Error::new(help.span(), "duplicate argument `help`"));
|
||||
}
|
||||
if opt.stdin.is_some() {
|
||||
return Err(Error::new(
|
||||
help.span(),
|
||||
"`stdin` and `help` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
opt.help = Some(help);
|
||||
} else {
|
||||
return Err(Error::new(nv.lit.span(), "help message must be a string"));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("help") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`help` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("help") => {
|
||||
return Err(Error::new(p.span(), "`help` must 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.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("short") => {
|
||||
if let Lit::Char(short) = nv.lit {
|
||||
if opt.short.is_some() {
|
||||
return Err(Error::new(short.span(), "duplicate argument `short`"));
|
||||
}
|
||||
if opt.stdin.is_some() {
|
||||
return Err(Error::new(
|
||||
short.span(),
|
||||
"`stdin` and `short` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
opt.short = Some(short);
|
||||
} else {
|
||||
return Err(Error::new(nv.lit.span(), "`short` must be a char"));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("short") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`short` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("short") => {
|
||||
return Err(Error::new(p.span(), "`short` must be assigned to"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("long") => {
|
||||
if let Lit::Str(long) = nv.lit {
|
||||
if opt.long.is_some() {
|
||||
return Err(Error::new(long.span(), "duplicate argument `long`"));
|
||||
}
|
||||
if opt.stdin.is_some() {
|
||||
return Err(Error::new(
|
||||
long.span(),
|
||||
"`stdin` and `long` are mutually exclusive",
|
||||
));
|
||||
}
|
||||
opt.long = Some(long);
|
||||
} else {
|
||||
return Err(Error::new(nv.lit.span(), "`long` must be a string"));
|
||||
}
|
||||
}
|
||||
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("long") => {
|
||||
return Err(Error::new(
|
||||
list.path.span(),
|
||||
"`long` does not take any arguments",
|
||||
));
|
||||
}
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("long") => {
|
||||
return Err(Error::new(p.span(), "`long` must be assigned to"));
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("default") => {
|
||||
if let Lit::Str(default) = nv.lit {
|
||||
if opt.default.is_some() {
|
||||
return Err(Error::new(
|
||||
default.span(),
|
||||
"duplicate argument `default`",
|
||||
));
|
||||
}
|
||||
opt.default = Some(Some(default));
|
||||
} else {
|
||||
return Err(Error::new(nv.lit.span(), "`default` must be a string"));
|
||||
}
|
||||
}
|
||||
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") => {
|
||||
if opt.default.is_some() {
|
||||
return Err(Error::new(p.span(), "duplicate argument `default`"));
|
||||
}
|
||||
opt.default = Some(None);
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new(arg.span(), "unknown argument"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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::Context(_)) {
|
||||
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,21 +0,0 @@
|
||||
macro_rules! macro_try {
|
||||
($x:expr) => {
|
||||
match $x {
|
||||
Ok(a) => a,
|
||||
Err(e) => return e.to_compile_error(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod command;
|
||||
mod rpc_handler;
|
||||
mod rpc_server;
|
||||
mod run_cli;
|
||||
|
||||
pub use command::build::build as build_command;
|
||||
pub use rpc_handler::build::build as build_rpc_handler;
|
||||
pub use rpc_handler::RpcHandlerArgs;
|
||||
pub use rpc_server::build::build as build_rpc_server;
|
||||
pub use rpc_server::RpcServerArgs;
|
||||
pub use run_cli::build::build as build_run_cli;
|
||||
pub use run_cli::RunCliArgs;
|
||||
@@ -1,129 +0,0 @@
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn build(args: RpcHandlerArgs) -> TokenStream {
|
||||
let mut command = args.command;
|
||||
let mut arguments = std::mem::replace(
|
||||
&mut command.segments.last_mut().unwrap().arguments,
|
||||
PathArguments::None,
|
||||
);
|
||||
let command_module = command.clone();
|
||||
if let PathArguments::AngleBracketed(a) = &mut arguments {
|
||||
a.args.push(syn::parse2(quote! { _ }).unwrap());
|
||||
}
|
||||
command.segments.push(PathSegment {
|
||||
ident: Ident::new("rpc_handler", command.span()),
|
||||
arguments,
|
||||
});
|
||||
let ctx = args.ctx;
|
||||
let parent_data = if let Some(data) = args.parent_data {
|
||||
quote! { #data }
|
||||
} else {
|
||||
quote! { () }
|
||||
};
|
||||
let status_fn = args.status_fn.unwrap_or_else(|| {
|
||||
syn::parse2(quote! { |_| ::rpc_toolkit::hyper::StatusCode::OK }).unwrap()
|
||||
});
|
||||
let middleware_name_clone = (0..)
|
||||
.map(|i| {
|
||||
Ident::new(
|
||||
&format!("__rpc_toolkit__rpc_handler__middleware_clone_{}", i),
|
||||
Span::call_site(),
|
||||
)
|
||||
})
|
||||
.take(args.middleware.len());
|
||||
let middleware_name_clone2 = middleware_name_clone.clone();
|
||||
let middleware_name_clone3 = middleware_name_clone.clone();
|
||||
let middleware_name_clone4 = middleware_name_clone.clone();
|
||||
let middleware_name_pre = (0..)
|
||||
.map(|i| Ident::new(&format!("middleware_pre_{}", i), Span::call_site()))
|
||||
.take(args.middleware.len());
|
||||
let middleware_name_pre2 = middleware_name_pre.clone();
|
||||
let middleware_name_post = (0..)
|
||||
.map(|i| Ident::new(&format!("middleware_post_{}", i), Span::call_site()))
|
||||
.take(args.middleware.len());
|
||||
let middleware_name_post_inv = middleware_name_post
|
||||
.clone()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.rev();
|
||||
let middleware_name = (0..)
|
||||
.map(|i| Ident::new(&format!("middleware_{}", i), Span::call_site()))
|
||||
.take(args.middleware.len());
|
||||
let middleware_name2 = middleware_name.clone();
|
||||
let middleware = args.middleware.iter();
|
||||
let res = quote! {
|
||||
{
|
||||
let __rpc_toolkit__rpc_handler__context = #ctx;
|
||||
let __rpc_toolkit__rpc_handler__parent_data = #parent_data;
|
||||
let __rpc_toolkit__rpc_handler__status_fn = #status_fn;
|
||||
#(
|
||||
let #middleware_name_clone = ::std::sync::Arc::new(#middleware);
|
||||
)*
|
||||
let res: ::rpc_toolkit::RpcHandler = ::std::sync::Arc::new(move |mut req| {
|
||||
let ctx = __rpc_toolkit__rpc_handler__context.clone();
|
||||
let parent_data = __rpc_toolkit__rpc_handler__parent_data.clone();
|
||||
let metadata = #command_module::Metadata::default();
|
||||
#(
|
||||
let #middleware_name_clone3 = #middleware_name_clone2.clone();
|
||||
)*
|
||||
::rpc_toolkit::futures::FutureExt::boxed(async move {
|
||||
#(
|
||||
let #middleware_name_pre = match ::rpc_toolkit::rpc_server_helpers::constrain_middleware(&*#middleware_name_clone4)(&mut req, metadata).await? {
|
||||
Ok(a) => a,
|
||||
Err(res) => return Ok(res),
|
||||
};
|
||||
)*
|
||||
let (mut req_parts, req_body) = req.into_parts();
|
||||
let (mut res_parts, _) = ::rpc_toolkit::hyper::Response::new(()).into_parts();
|
||||
let rpc_req = ::rpc_toolkit::rpc_server_helpers::make_request(&req_parts, req_body).await;
|
||||
match rpc_req {
|
||||
Ok(mut rpc_req) => {
|
||||
#(
|
||||
let #middleware_name_post = match #middleware_name_pre2(&mut req_parts, &mut rpc_req).await? {
|
||||
Ok(a) => a,
|
||||
Err(res) => return Ok(res),
|
||||
};
|
||||
)*
|
||||
let mut rpc_res = match ::rpc_toolkit::serde_json::from_value(::rpc_toolkit::serde_json::Value::Object(rpc_req.params)) {
|
||||
Ok(params) => #command(ctx, parent_data, &req_parts, &mut res_parts, ::rpc_toolkit::yajrc::RpcMethod::as_str(&rpc_req.method), params).await,
|
||||
Err(e) => Err(e.into())
|
||||
};
|
||||
#(
|
||||
let #middleware_name = match #middleware_name_post_inv(&mut res_parts, &mut rpc_res).await? {
|
||||
Ok(a) => a,
|
||||
Err(res) => return Ok(res),
|
||||
};
|
||||
)*
|
||||
let mut res = ::rpc_toolkit::rpc_server_helpers::to_response(
|
||||
&req_parts.headers,
|
||||
res_parts,
|
||||
Ok((
|
||||
rpc_req.id,
|
||||
rpc_res,
|
||||
)),
|
||||
__rpc_toolkit__rpc_handler__status_fn,
|
||||
)?;
|
||||
#(
|
||||
#middleware_name2(&mut res).await?;
|
||||
)*
|
||||
Ok::<_, ::rpc_toolkit::hyper::http::Error>(res)
|
||||
}
|
||||
Err(e) => ::rpc_toolkit::rpc_server_helpers::to_response(
|
||||
&req_parts.headers,
|
||||
res_parts,
|
||||
Err(e),
|
||||
__rpc_toolkit__rpc_handler__status_fn,
|
||||
),
|
||||
}
|
||||
})
|
||||
});
|
||||
res
|
||||
}
|
||||
};
|
||||
// panic!("{}", res);
|
||||
res
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
use syn::*;
|
||||
|
||||
pub struct RpcHandlerArgs {
|
||||
pub(crate) command: Path,
|
||||
pub(crate) ctx: Expr,
|
||||
pub(crate) parent_data: Option<Expr>,
|
||||
pub(crate) status_fn: Option<Expr>,
|
||||
pub(crate) middleware: punctuated::Punctuated<Expr, token::Comma>,
|
||||
}
|
||||
|
||||
pub mod build;
|
||||
mod parse;
|
||||
@@ -1,50 +0,0 @@
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
|
||||
use super::*;
|
||||
|
||||
impl Parse for RpcHandlerArgs {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let args;
|
||||
braced!(args in input);
|
||||
let mut command = None;
|
||||
let mut ctx = None;
|
||||
let mut parent_data = None;
|
||||
let mut status_fn = None;
|
||||
let mut middleware = Punctuated::new();
|
||||
while !args.is_empty() {
|
||||
let arg_name: syn::Ident = args.parse()?;
|
||||
let _: token::Colon = args.parse()?;
|
||||
match arg_name.to_string().as_str() {
|
||||
"command" => {
|
||||
command = Some(args.parse()?);
|
||||
}
|
||||
"context" => {
|
||||
ctx = Some(args.parse()?);
|
||||
}
|
||||
"parent_data" => {
|
||||
parent_data = Some(args.parse()?);
|
||||
}
|
||||
"status" => {
|
||||
status_fn = Some(args.parse()?);
|
||||
}
|
||||
"middleware" => {
|
||||
let middlewares;
|
||||
bracketed!(middlewares in args);
|
||||
middleware = middlewares.parse_terminated(Expr::parse)?;
|
||||
}
|
||||
_ => return Err(Error::new(arg_name.span(), "unknown argument")),
|
||||
}
|
||||
if !args.is_empty() {
|
||||
let _: token::Comma = args.parse()?;
|
||||
}
|
||||
}
|
||||
Ok(RpcHandlerArgs {
|
||||
command: command.expect("`command` is required"),
|
||||
ctx: ctx.expect("`context` is required"),
|
||||
parent_data,
|
||||
status_fn,
|
||||
middleware,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn build(mut args: RpcServerArgs) -> TokenStream {
|
||||
let ctx = std::mem::replace(
|
||||
&mut args.ctx,
|
||||
parse2(quote! { __rpc_toolkit__rpc_server__context }).unwrap(),
|
||||
);
|
||||
let handler = crate::rpc_handler::build::build(args);
|
||||
let res = quote! {
|
||||
{
|
||||
let __rpc_toolkit__rpc_server__context = #ctx;
|
||||
let __rpc_toolkit__rpc_server__builder = ::rpc_toolkit::rpc_server_helpers::make_builder(&__rpc_toolkit__rpc_server__context);
|
||||
let handler = #handler;
|
||||
__rpc_toolkit__rpc_server__builder.serve(::rpc_toolkit::hyper::service::make_service_fn(move |_| {
|
||||
let handler = handler.clone();
|
||||
async move { Ok::<_, ::std::convert::Infallible>(::rpc_toolkit::hyper::service::service_fn(move |req| handler(req))) }
|
||||
}))
|
||||
}
|
||||
};
|
||||
// panic!("{}", res);
|
||||
res
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
use syn::*;
|
||||
|
||||
pub type RpcServerArgs = crate::RpcHandlerArgs;
|
||||
|
||||
pub mod build;
|
||||
@@ -1,81 +0,0 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn build(args: RunCliArgs) -> TokenStream {
|
||||
let mut command_handler = args.command.clone();
|
||||
let mut arguments = std::mem::replace(
|
||||
&mut command_handler.segments.last_mut().unwrap().arguments,
|
||||
PathArguments::None,
|
||||
);
|
||||
let command = command_handler.clone();
|
||||
if let PathArguments::AngleBracketed(a) = &mut arguments {
|
||||
a.args.push(syn::parse2(quote! { () }).unwrap());
|
||||
a.args.push(syn::parse2(quote! { _ }).unwrap());
|
||||
}
|
||||
command_handler.segments.push(PathSegment {
|
||||
ident: Ident::new("cli_handler", command.span()),
|
||||
arguments,
|
||||
});
|
||||
let app = if let Some(mut_app) = args.mut_app {
|
||||
let ident = mut_app.app_ident;
|
||||
let body = mut_app.body;
|
||||
quote! {
|
||||
{
|
||||
let #ident = #command::build_app();
|
||||
#body
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! { #command::build_app() }
|
||||
};
|
||||
let make_ctx = if let Some(make_ctx) = args.make_ctx {
|
||||
let ident = make_ctx.matches_ident;
|
||||
let body = make_ctx.body;
|
||||
quote! {
|
||||
{
|
||||
let #ident = &rpc_toolkit_matches;
|
||||
#body
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! { &rpc_toolkit_matches }
|
||||
};
|
||||
let parent_data = if let Some(data) = args.parent_data {
|
||||
quote! { #data }
|
||||
} else {
|
||||
quote! { () }
|
||||
};
|
||||
let exit_fn = args.exit_fn.unwrap_or_else(|| {
|
||||
syn::parse2(quote! { |err: ::rpc_toolkit::yajrc::RpcError| {
|
||||
eprintln!("{}", err.message);
|
||||
if let Some(data) = err.data {
|
||||
eprintln!("{}", data);
|
||||
}
|
||||
std::process::exit(err.code);
|
||||
} })
|
||||
.unwrap()
|
||||
});
|
||||
quote! {
|
||||
{
|
||||
let rpc_toolkit_matches = #app.get_matches();
|
||||
let rpc_toolkit_ctx = #make_ctx;
|
||||
let rpc_toolkit_parent_data = #parent_data;
|
||||
if let Err(err) = #command_handler(
|
||||
rpc_toolkit_ctx,
|
||||
rpc_toolkit_parent_data,
|
||||
None,
|
||||
&rpc_toolkit_matches,
|
||||
"".into(),
|
||||
(),
|
||||
) {
|
||||
drop(rpc_toolkit_matches);
|
||||
(#exit_fn)(err);
|
||||
} else {
|
||||
drop(rpc_toolkit_matches);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
use syn::*;
|
||||
|
||||
pub struct MakeCtx {
|
||||
matches_ident: Ident,
|
||||
body: Expr,
|
||||
}
|
||||
|
||||
pub struct MutApp {
|
||||
app_ident: Ident,
|
||||
body: Expr,
|
||||
}
|
||||
|
||||
pub struct RunCliArgs {
|
||||
command: Path,
|
||||
mut_app: Option<MutApp>,
|
||||
make_ctx: Option<MakeCtx>,
|
||||
parent_data: Option<Expr>,
|
||||
exit_fn: Option<Expr>,
|
||||
}
|
||||
|
||||
pub mod build;
|
||||
mod parse;
|
||||
@@ -1,68 +0,0 @@
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl Parse for MakeCtx {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let matches_ident = input.parse()?;
|
||||
let _: token::FatArrow = input.parse()?;
|
||||
let body = input.parse()?;
|
||||
Ok(MakeCtx {
|
||||
matches_ident,
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for MutApp {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let app_ident = input.parse()?;
|
||||
let _: token::FatArrow = input.parse()?;
|
||||
let body = input.parse()?;
|
||||
Ok(MutApp { app_ident, body })
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for RunCliArgs {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let args;
|
||||
braced!(args in input);
|
||||
let mut command = None;
|
||||
let mut mut_app = None;
|
||||
let mut make_ctx = None;
|
||||
let mut parent_data = None;
|
||||
let mut exit_fn = None;
|
||||
while !args.is_empty() {
|
||||
let arg_name: syn::Ident = args.parse()?;
|
||||
let _: token::Colon = args.parse()?;
|
||||
match arg_name.to_string().as_str() {
|
||||
"command" => {
|
||||
command = Some(args.parse()?);
|
||||
}
|
||||
"app" => {
|
||||
mut_app = Some(args.parse()?);
|
||||
}
|
||||
"context" => {
|
||||
make_ctx = Some(args.parse()?);
|
||||
}
|
||||
"parent_data" => {
|
||||
parent_data = Some(args.parse()?);
|
||||
}
|
||||
"exit" => {
|
||||
exit_fn = Some(args.parse()?);
|
||||
}
|
||||
_ => return Err(Error::new(arg_name.span(), "unknown argument")),
|
||||
}
|
||||
if !args.is_empty() {
|
||||
let _: token::Comma = args.parse()?;
|
||||
}
|
||||
}
|
||||
Ok(RunCliArgs {
|
||||
command: command.expect("`command` is required"),
|
||||
mut_app,
|
||||
make_ctx,
|
||||
parent_data,
|
||||
exit_fn,
|
||||
})
|
||||
}
|
||||
}
|
||||
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,62 +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()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn rpc_handler(item: TokenStream) -> TokenStream {
|
||||
let item = syn::parse_macro_input!(item as RpcHandlerArgs);
|
||||
build_rpc_handler(item).into()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn rpc_server(item: TokenStream) -> TokenStream {
|
||||
let item = syn::parse_macro_input!(item as RpcServerArgs);
|
||||
build_rpc_server(item).into()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn run_cli(item: TokenStream) -> TokenStream {
|
||||
let item = syn::parse_macro_input!(item as RunCliArgs);
|
||||
build_run_cli(item).into()
|
||||
}
|
||||
2
rpc-toolkit/.gitignore
vendored
2
rpc-toolkit/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -1,39 +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 = ["cbor"]
|
||||
|
||||
[dependencies]
|
||||
clap = "3"
|
||||
futures = "0.3"
|
||||
hyper = { version = "0.14", features = [
|
||||
"server",
|
||||
"http1",
|
||||
"http2",
|
||||
"tcp",
|
||||
"stream",
|
||||
"client",
|
||||
] }
|
||||
lazy_static = "1.4"
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
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"] }
|
||||
url = "2.2.2"
|
||||
yajrc = "0.1.1"
|
||||
@@ -1,130 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
use std::io::Stdin;
|
||||
use std::marker::PhantomData;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use hyper::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use yajrc::{GenericRpcMethod, Id, RpcError, RpcRequest, RpcResponse};
|
||||
|
||||
use crate::Context;
|
||||
|
||||
pub mod prelude {
|
||||
pub use std::borrow::Cow;
|
||||
pub use std::marker::PhantomData;
|
||||
|
||||
pub use clap::{App, AppSettings, Arg, ArgMatches};
|
||||
pub use hyper::http::request::Parts as RequestParts;
|
||||
pub use hyper::http::response::Parts as ResponseParts;
|
||||
pub use serde::{Deserialize, Serialize};
|
||||
pub use serde_json::{from_value, to_value, Value};
|
||||
pub use tokio::runtime::Runtime;
|
||||
pub use tokio::task::spawn_blocking;
|
||||
pub use yajrc::{self, RpcError};
|
||||
|
||||
pub use super::{
|
||||
call_remote, default_arg_parser, default_display, default_stdin_parser, make_phantom,
|
||||
match_types,
|
||||
};
|
||||
pub use crate::Context;
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RequestError {
|
||||
#[error("JSON Error: {0}")]
|
||||
JSON(#[from] serde_json::Error),
|
||||
#[cfg(feature = "cbor")]
|
||||
#[error("CBOR Error: {0}")]
|
||||
CBOR(#[from] serde_cbor::Error),
|
||||
#[error("HTTP Error: {0}")]
|
||||
HTTP(#[from] reqwest::Error),
|
||||
#[error("Missing Content-Type")]
|
||||
MissingContentType,
|
||||
}
|
||||
|
||||
pub fn make_phantom<T>(_actual: T) -> PhantomData<T> {
|
||||
PhantomData
|
||||
}
|
||||
|
||||
pub fn match_types<T>(_: &T, _: &T) {}
|
||||
|
||||
pub async fn call_remote<Ctx: Context, Params: Serialize, Res: for<'de> Deserialize<'de>>(
|
||||
ctx: Ctx,
|
||||
method: &str,
|
||||
params: Params,
|
||||
_return_ty: PhantomData<Res>,
|
||||
) -> Result<RpcResponse<GenericRpcMethod<&str, Params, Res>>, RequestError> {
|
||||
let rpc_req: RpcRequest<GenericRpcMethod<&str, Params, Res>> = RpcRequest {
|
||||
id: Some(Id::Number(0.into())),
|
||||
method: GenericRpcMethod::new(method),
|
||||
params,
|
||||
};
|
||||
let mut req = ctx.client().request(Method::POST, ctx.url());
|
||||
let body;
|
||||
#[cfg(feature = "cbor")]
|
||||
{
|
||||
req = req.header("content-type", "application/cbor");
|
||||
req = req.header("accept", "application/cbor, application/json");
|
||||
body = serde_cbor::to_vec(&rpc_req)?;
|
||||
}
|
||||
#[cfg(not(feature = "cbor"))]
|
||||
{
|
||||
req = req.header("content-type", "application/json");
|
||||
req = req.header("accept", "application/json");
|
||||
body = serde_json::to_vec(&req)?;
|
||||
}
|
||||
let res = req
|
||||
.header("content-length", body.len())
|
||||
.body(body)
|
||||
.send()
|
||||
.await?;
|
||||
Ok(
|
||||
match res
|
||||
.headers()
|
||||
.get("content-type")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
{
|
||||
Some("application/json") => serde_json::from_slice(&*res.bytes().await?)?,
|
||||
#[cfg(feature = "cbor")]
|
||||
Some("application/cbor") => serde_cbor::from_slice(&*res.bytes().await?)?,
|
||||
_ => return Err(RequestError::MissingContentType),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn default_arg_parser<T: FromStr<Err = E>, E: Display>(
|
||||
arg: &str,
|
||||
_: &ArgMatches,
|
||||
) -> Result<T, RpcError> {
|
||||
arg.parse().map_err(|e| RpcError {
|
||||
data: Some(format!("{}", e).into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
})
|
||||
}
|
||||
|
||||
pub fn default_stdin_parser<T: FromStr<Err = E>, E: Display>(
|
||||
stdin: &mut Stdin,
|
||||
_: &ArgMatches,
|
||||
) -> Result<T, RpcError> {
|
||||
let mut s = String::new();
|
||||
stdin.read_line(&mut s).map_err(|e| RpcError {
|
||||
data: Some(format!("{}", e).into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
})?;
|
||||
if let Some(s) = s.strip_suffix("\n") {
|
||||
s
|
||||
} else {
|
||||
&s
|
||||
}
|
||||
.parse()
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(format!("{}", e).into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
})
|
||||
}
|
||||
|
||||
pub fn default_display<T: Display>(t: T, _: &ArgMatches) {
|
||||
println!("{}", t)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
use lazy_static::lazy_static;
|
||||
use reqwest::Client;
|
||||
use url::{Host, Url};
|
||||
|
||||
lazy_static! {
|
||||
static ref DEFAULT_CLIENT: Client = Client::new();
|
||||
}
|
||||
|
||||
pub trait Context {
|
||||
fn protocol(&self) -> &str {
|
||||
"http"
|
||||
}
|
||||
fn host(&self) -> Host<&str> {
|
||||
Host::Ipv4([127, 0, 0, 1].into())
|
||||
}
|
||||
fn port(&self) -> u16 {
|
||||
8080
|
||||
}
|
||||
fn path(&self) -> &str {
|
||||
"/"
|
||||
}
|
||||
fn url(&self) -> Url {
|
||||
let mut url: Url = "http://localhost".parse().unwrap();
|
||||
url.set_scheme(self.protocol()).expect("protocol");
|
||||
url.set_host(Some(&self.host().to_string())).expect("host");
|
||||
url.set_port(Some(self.port())).expect("port");
|
||||
url.set_path(self.path());
|
||||
url
|
||||
}
|
||||
fn client(&self) -> &Client {
|
||||
&*DEFAULT_CLIENT
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for () {}
|
||||
|
||||
impl<'a, T: Context + 'a> From<T> for Box<dyn Context + 'a> {
|
||||
fn from(ctx: T) -> Self {
|
||||
Box::new(ctx)
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/// `#[command(...)]`
|
||||
/// - `#[command(cli_only)]` -> executed by CLI instead of RPC server (leaf commands only)
|
||||
/// - `#[command(rpc_only)]` -> no CLI bindings (leaf commands only)
|
||||
/// - `#[command(local)]` -> executed wherever it was invoked. (By RPC when hit from RPC server, by cli when invoked from CLI)
|
||||
/// - `#[command(blocking)]` -> run with [spawn_blocking](tokio::task::spawn_blocking) if in an async context
|
||||
/// - `#[command(about = "About text")]` -> Set about text for the command
|
||||
/// - `#[command(rename = "new_name")]` -> Set the name of the command to `new_name` in the RPC and CLI
|
||||
/// - `#[command(subcommands(...))]` -> Set this as the parent command for the listed subcommands
|
||||
/// - note: the return type of the decorated function must be the [Context] required by its subcommands, and all args must implement [Clone](std::clone::Clone).
|
||||
/// - `#[command(subcommands(self(self_command_impl)))]` -> If no subcommand is provided, call this function
|
||||
/// - `self_command_impl :: Context ctx, Display res, Into<RpcError> err => ctx -> Result<res, err>`
|
||||
/// - note: [Display](std::fmt::Display) is not required for `res` if it has a custom display function that will take it as input
|
||||
/// - if `self_command_impl` is async, write `self(self_command_impl(async))`
|
||||
/// - if `self_command_impl` is blocking, write `self(self_command_impl(blocking))`
|
||||
/// - default: require a subcommand if subcommands are specified
|
||||
/// - `#[command(display(custom_display_fn))]` -> Use the function `custom_display_fn` to display the command's result (leaf commands only)
|
||||
/// - `custom_display_fn :: res -> ()`
|
||||
/// - note: `res` is the type of the decorated command's output
|
||||
/// - default: `default_display`
|
||||
///
|
||||
/// See also: [arg](rpc_toolkit_macro::arg), [context](rpc_toolkit_macro::context)
|
||||
pub use rpc_toolkit_macro::command;
|
||||
/// `rpc_handler!(command, context, status_fn)`
|
||||
/// - returns: [RpcHandler](rpc_toolkit::RpcHandler)
|
||||
/// - `command`: path to an rpc command (with the `#[command]` attribute)
|
||||
/// - `context`: The [Context] for `command`. Must implement [Clone](std::clone::Clone).
|
||||
/// - `status_fn` (optional): a function that takes a JSON RPC error code (`i32`) and returns a [StatusCode](hyper::StatusCode)
|
||||
/// - default: `|_| StatusCode::OK`
|
||||
pub use rpc_toolkit_macro::rpc_handler;
|
||||
/// `rpc_server!(command, context, status_fn)`
|
||||
/// - returns: [Server](hyper::Server)
|
||||
/// - `command`: path to an rpc command (with the `#[command]` attribute)
|
||||
/// - `context`: The [Context] for `command`. Must implement [Clone](std::clone::Clone).
|
||||
/// - `status_fn` (optional): a function that takes a JSON RPC error code (`i32`) and returns a [StatusCode](hyper::StatusCode)
|
||||
/// - default: `|_| StatusCode::OK`
|
||||
pub use rpc_toolkit_macro::rpc_server;
|
||||
/// `run_cli!(command, app_mutator, make_ctx, exit_fn)`
|
||||
/// - this function does not return
|
||||
/// - `command`: path to an rpc command (with the `#[command]` attribute)
|
||||
/// - `app_mutator` (optional): an expression that returns a mutated app.
|
||||
/// - example: `app => app.arg(Arg::with_name("port").long("port"))`
|
||||
/// - default: `app => app`
|
||||
/// - `make_ctx` (optional): an expression that takes [&ArgMatches](clap::ArgMatches) and returns the [Context] used by `command`.
|
||||
/// - example: `matches => matches.value_of("port")`
|
||||
/// - default: `matches => matches`
|
||||
/// - `exit_fn` (optional): a function that takes a JSON RPC error code (`i32`) and returns an Exit code (`i32`)
|
||||
/// - default: `|code| code`
|
||||
pub use rpc_toolkit_macro::run_cli;
|
||||
pub use {clap, futures, hyper, reqwest, serde, serde_json, tokio, url, yajrc};
|
||||
|
||||
pub use crate::context::Context;
|
||||
pub use crate::metadata::Metadata;
|
||||
pub use crate::rpc_server_helpers::RpcHandler;
|
||||
|
||||
pub mod command_helpers;
|
||||
mod context;
|
||||
mod metadata;
|
||||
pub mod rpc_server_helpers;
|
||||
@@ -1,68 +0,0 @@
|
||||
macro_rules! getter_for {
|
||||
($($name:ident => $t:ty,)*) => {
|
||||
$(
|
||||
#[allow(unused_variables)]
|
||||
fn $name(&self, command: &str, key: &str) -> Option<$t> {
|
||||
None
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
pub trait Metadata: Copy + Default + Send + Sync + 'static {
|
||||
fn get<Ty: Primitive>(&self, command: &str, key: &str) -> Option<Ty> {
|
||||
Ty::from_metadata(self, command, key)
|
||||
}
|
||||
getter_for!(
|
||||
get_bool => bool,
|
||||
get_u8 => u8,
|
||||
get_u16 => u16,
|
||||
get_u32 => u32,
|
||||
get_u64 => u64,
|
||||
get_usize => usize,
|
||||
get_i8 => i8,
|
||||
get_i16 => i16,
|
||||
get_i32 => i32,
|
||||
get_i64 => i64,
|
||||
get_isize => isize,
|
||||
get_f32 => f32,
|
||||
get_f64 => f64,
|
||||
get_char => char,
|
||||
get_str => &'static str,
|
||||
get_bstr => &'static [u8],
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! impl_primitive_for {
|
||||
($($name:ident => $t:ty,)*) => {
|
||||
$(
|
||||
impl Primitive for $t {
|
||||
fn from_metadata<M: Metadata + ?Sized>(m: &M, command: &str, key: &str) -> Option<Self> {
|
||||
m.$name(command, key)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
pub trait Primitive: Copy {
|
||||
fn from_metadata<M: Metadata + ?Sized>(m: &M, command: &str, key: &str) -> Option<Self>;
|
||||
}
|
||||
impl_primitive_for!(
|
||||
get_bool => bool,
|
||||
get_u8 => u8,
|
||||
get_u16 => u16,
|
||||
get_u32 => u32,
|
||||
get_u64 => u64,
|
||||
get_usize => usize,
|
||||
get_i8 => i8,
|
||||
get_i16 => i16,
|
||||
get_i32 => i32,
|
||||
get_i64 => i64,
|
||||
get_isize => isize,
|
||||
get_f32 => f32,
|
||||
get_f64 => f64,
|
||||
get_char => char,
|
||||
get_str => &'static str,
|
||||
get_bstr => &'static [u8],
|
||||
);
|
||||
@@ -1,186 +0,0 @@
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use futures::FutureExt;
|
||||
use hyper::body::Buf;
|
||||
use hyper::header::HeaderValue;
|
||||
use hyper::http::request::Parts as RequestParts;
|
||||
use hyper::http::response::Parts as ResponseParts;
|
||||
use hyper::http::Error as HttpError;
|
||||
use hyper::server::conn::AddrIncoming;
|
||||
use hyper::server::{Builder, Server};
|
||||
use hyper::{Body, HeaderMap, Request, Response, StatusCode};
|
||||
use lazy_static::lazy_static;
|
||||
use serde_json::{Map, Value};
|
||||
use url::Host;
|
||||
use yajrc::{AnyRpcMethod, GenericRpcMethod, Id, RpcError, RpcRequest, RpcResponse};
|
||||
|
||||
use crate::{Context, Metadata};
|
||||
|
||||
lazy_static! {
|
||||
#[cfg(feature = "cbor")]
|
||||
static ref CBOR_INTERNAL_ERROR: Vec<u8> =
|
||||
serde_cbor::to_vec(&RpcResponse::<AnyRpcMethod<'static>>::from(yajrc::INTERNAL_ERROR)).unwrap();
|
||||
static ref JSON_INTERNAL_ERROR: Vec<u8> =
|
||||
serde_json::to_vec(&RpcResponse::<AnyRpcMethod<'static>>::from(yajrc::INTERNAL_ERROR)).unwrap();
|
||||
}
|
||||
|
||||
pub fn make_builder<Ctx: Context>(ctx: &Ctx) -> Builder<AddrIncoming> {
|
||||
let addr = match ctx.host() {
|
||||
Host::Ipv4(ip) => (ip, ctx.port()).into(),
|
||||
Host::Ipv6(ip) => (ip, ctx.port()).into(),
|
||||
Host::Domain(localhost) if localhost == "localhost" => ([127, 0, 0, 1], ctx.port()).into(),
|
||||
_ => ([0, 0, 0, 0], ctx.port()).into(),
|
||||
};
|
||||
Server::bind(&addr)
|
||||
}
|
||||
|
||||
pub async fn make_request(
|
||||
req_parts: &RequestParts,
|
||||
req_body: Body,
|
||||
) -> Result<RpcRequest<GenericRpcMethod<String, Map<String, Value>>>, RpcError> {
|
||||
let body = hyper::body::aggregate(req_body).await?.reader();
|
||||
let rpc_req: RpcRequest<GenericRpcMethod<String, Map<String, Value>>>;
|
||||
#[cfg(feature = "cbor")]
|
||||
if req_parts
|
||||
.headers
|
||||
.get("content-type")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
== Some("application/cbor")
|
||||
{
|
||||
rpc_req = serde_cbor::from_reader(body)?;
|
||||
} else {
|
||||
rpc_req = serde_json::from_reader(body)?;
|
||||
}
|
||||
#[cfg(not(feature = "cbor"))]
|
||||
{
|
||||
rpc_req = serde_json::from_reader(body)?;
|
||||
}
|
||||
|
||||
Ok(rpc_req)
|
||||
}
|
||||
|
||||
pub fn to_response<F: Fn(i32) -> StatusCode>(
|
||||
req_headers: &HeaderMap<HeaderValue>,
|
||||
mut res_parts: ResponseParts,
|
||||
res: Result<(Option<Id>, Result<Value, RpcError>), RpcError>,
|
||||
status_code_fn: F,
|
||||
) -> Result<Response<Body>, HttpError> {
|
||||
let rpc_res: RpcResponse = match res {
|
||||
Ok((id, result)) => RpcResponse { id, result },
|
||||
Err(e) => e.into(),
|
||||
};
|
||||
let body;
|
||||
#[cfg(feature = "cbor")]
|
||||
if req_headers
|
||||
.get("accept")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.iter()
|
||||
.flat_map(|s| s.split(","))
|
||||
.map(|s| s.trim())
|
||||
.any(|s| s == "application/cbor")
|
||||
// prefer cbor if accepted
|
||||
{
|
||||
res_parts
|
||||
.headers
|
||||
.insert("content-type", HeaderValue::from_static("application/cbor"));
|
||||
body = serde_cbor::to_vec(&rpc_res).unwrap_or_else(|_| CBOR_INTERNAL_ERROR.clone());
|
||||
} else {
|
||||
res_parts
|
||||
.headers
|
||||
.insert("content-type", HeaderValue::from_static("application/json"));
|
||||
body = serde_json::to_vec(&rpc_res).unwrap_or_else(|_| JSON_INTERNAL_ERROR.clone());
|
||||
}
|
||||
#[cfg(not(feature = "cbor"))]
|
||||
{
|
||||
res_parts
|
||||
.headers
|
||||
.insert("content-type", HeaderValue::from_static("application/json"));
|
||||
body = serde_json::to_vec(&rpc_res).unwrap_or_else(|_| JSON_INTERNAL_ERROR.clone());
|
||||
}
|
||||
res_parts.headers.insert(
|
||||
"content-length",
|
||||
HeaderValue::from_str(&format!("{}", body.len()))?,
|
||||
);
|
||||
res_parts.status = match &rpc_res.result {
|
||||
Ok(_) => StatusCode::OK,
|
||||
Err(e) => status_code_fn(e.code),
|
||||
};
|
||||
Ok(Response::from_parts(res_parts, body.into()))
|
||||
}
|
||||
|
||||
pub type RpcHandler = Arc<
|
||||
dyn Fn(Request<Body>) -> BoxFuture<'static, Result<Response<Body>, HttpError>> + Send + Sync,
|
||||
>;
|
||||
|
||||
// &mut Request<Body> -> Result<Result<Future<&mut RpcRequest<...> -> Future<Result<Result<&mut Response<Body> -> Future<Result<(), HttpError>>, Response<Body>>, HttpError>>>, Response<Body>>, HttpError>
|
||||
pub type DynMiddleware<Metadata> = Box<
|
||||
dyn for<'a> Fn(
|
||||
&'a mut Request<Body>,
|
||||
Metadata,
|
||||
)
|
||||
-> BoxFuture<'a, Result<Result<DynMiddlewareStage2, Response<Body>>, HttpError>>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>;
|
||||
pub fn noop<M: Metadata>() -> DynMiddleware<M> {
|
||||
Box::new(|_, _| async { Ok(Ok(noop2())) }.boxed())
|
||||
}
|
||||
pub type DynMiddlewareStage2 = Box<
|
||||
dyn for<'a> FnOnce(
|
||||
&'a mut RequestParts,
|
||||
&'a mut RpcRequest<GenericRpcMethod<String, Map<String, Value>>>,
|
||||
) -> BoxFuture<
|
||||
'a,
|
||||
Result<Result<DynMiddlewareStage3, Response<Body>>, HttpError>,
|
||||
> + Send
|
||||
+ Sync,
|
||||
>;
|
||||
pub fn noop2() -> DynMiddlewareStage2 {
|
||||
Box::new(|_, _| async { Ok(Ok(noop3())) }.boxed())
|
||||
}
|
||||
pub type DynMiddlewareStage3 = Box<
|
||||
dyn for<'a> FnOnce(
|
||||
&'a mut ResponseParts,
|
||||
&'a mut Result<Value, RpcError>,
|
||||
) -> BoxFuture<
|
||||
'a,
|
||||
Result<Result<DynMiddlewareStage4, Response<Body>>, HttpError>,
|
||||
> + Send
|
||||
+ Sync,
|
||||
>;
|
||||
pub fn noop3() -> DynMiddlewareStage3 {
|
||||
Box::new(|_, _| async { Ok(Ok(noop4())) }.boxed())
|
||||
}
|
||||
pub type DynMiddlewareStage4 = Box<
|
||||
dyn for<'a> FnOnce(&'a mut Response<Body>) -> BoxFuture<'a, Result<(), HttpError>>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>;
|
||||
pub fn noop4() -> DynMiddlewareStage4 {
|
||||
Box::new(|_| async { Ok(()) }.boxed())
|
||||
}
|
||||
|
||||
pub fn constrain_middleware<
|
||||
'a,
|
||||
'b,
|
||||
'c,
|
||||
'd,
|
||||
M: Metadata,
|
||||
ReqFn: Fn(&'a mut Request<Body>, M) -> ReqFut + Clone,
|
||||
ReqFut: Future<Output = Result<Result<RpcReqFn, Response<Body>>, HttpError>> + 'a,
|
||||
RpcReqFn: FnOnce(
|
||||
&'b mut RequestParts,
|
||||
&'b mut RpcRequest<GenericRpcMethod<String, Map<String, Value>>>,
|
||||
) -> RpcReqFut,
|
||||
RpcReqFut: Future<Output = Result<Result<RpcResFn, Response<Body>>, HttpError>> + 'b,
|
||||
RpcResFn: FnOnce(&'c mut ResponseParts, &'c mut Result<Value, RpcError>) -> RpcResFut,
|
||||
RpcResFut: Future<Output = Result<Result<ResFn, Response<Body>>, HttpError>> + 'c,
|
||||
ResFn: FnOnce(&'d mut Response<Body>) -> ResFut,
|
||||
ResFut: Future<Output = Result<(), HttpError>> + 'd,
|
||||
>(
|
||||
f: ReqFn,
|
||||
) -> ReqFn {
|
||||
f
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures::FutureExt;
|
||||
use hyper::Request;
|
||||
use rpc_toolkit::clap::Arg;
|
||||
use rpc_toolkit::hyper::http::Error as HttpError;
|
||||
use rpc_toolkit::hyper::{Body, Response};
|
||||
use rpc_toolkit::rpc_server_helpers::{
|
||||
DynMiddlewareStage2, DynMiddlewareStage3, DynMiddlewareStage4,
|
||||
};
|
||||
use rpc_toolkit::serde::{Deserialize, Serialize};
|
||||
use rpc_toolkit::url::Host;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::{command, rpc_server, run_cli, Context, Metadata};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppState(Arc<ConfigSeed>);
|
||||
impl From<AppState> for () {
|
||||
fn from(_: AppState) -> Self {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConfigSeed {
|
||||
host: Host,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
impl Context for AppState {
|
||||
fn host(&self) -> Host<&str> {
|
||||
match &self.0.host {
|
||||
Host::Domain(s) => Host::Domain(s.as_str()),
|
||||
Host::Ipv4(i) => Host::Ipv4(*i),
|
||||
Host::Ipv6(i) => Host::Ipv6(*i),
|
||||
}
|
||||
}
|
||||
fn port(&self) -> u16 {
|
||||
self.0.port
|
||||
}
|
||||
}
|
||||
|
||||
fn test_string() -> String {
|
||||
"test".to_owned()
|
||||
}
|
||||
|
||||
#[command(
|
||||
about = "Does the thing",
|
||||
subcommands("dothething2::<U, E>", self(dothething_impl(async)))
|
||||
)]
|
||||
async fn dothething<
|
||||
U: Serialize + for<'a> Deserialize<'a> + FromStr<Err = E> + Clone + 'static,
|
||||
E: Display,
|
||||
>(
|
||||
#[context] _ctx: AppState,
|
||||
#[arg(short = 'a')] arg1: Option<String>,
|
||||
#[arg(short = 'b', default = "test_string")] val: String,
|
||||
#[arg(short = 'c', help = "I am the flag `c`!", default)] arg3: bool,
|
||||
#[arg(stdin)] structured: U,
|
||||
) -> Result<(Option<String>, String, bool, U), RpcError> {
|
||||
Ok((arg1, val, arg3, structured))
|
||||
}
|
||||
|
||||
async fn dothething_impl<U: Serialize>(
|
||||
ctx: AppState,
|
||||
parent_data: (Option<String>, String, bool, U),
|
||||
) -> Result<String, RpcError> {
|
||||
Ok(format!(
|
||||
"{:?}, {:?}, {}, {}, {}",
|
||||
ctx,
|
||||
parent_data.0,
|
||||
parent_data.1,
|
||||
parent_data.2,
|
||||
serde_json::to_string_pretty(&parent_data.3)?
|
||||
))
|
||||
}
|
||||
|
||||
#[command(about = "Does the thing")]
|
||||
fn dothething2<U: Serialize + for<'a> Deserialize<'a> + FromStr<Err = E>, E: Display>(
|
||||
#[parent_data] parent_data: (Option<String>, String, bool, U),
|
||||
#[arg(stdin)] structured2: U,
|
||||
) -> Result<String, RpcError> {
|
||||
Ok(format!(
|
||||
"{:?}, {}, {}, {}, {}",
|
||||
parent_data.0,
|
||||
parent_data.1,
|
||||
parent_data.2,
|
||||
serde_json::to_string_pretty(&parent_data.3)?,
|
||||
serde_json::to_string_pretty(&structured2)?,
|
||||
))
|
||||
}
|
||||
|
||||
async fn cors<M: Metadata + 'static>(
|
||||
req: &mut Request<Body>,
|
||||
_: M,
|
||||
) -> Result<Result<DynMiddlewareStage2, Response<Body>>, HttpError> {
|
||||
if req.method() == hyper::Method::OPTIONS {
|
||||
Ok(Err(Response::builder()
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.body(Body::empty())?))
|
||||
} else {
|
||||
Ok(Ok(Box::new(|_, _| {
|
||||
async move {
|
||||
let res: DynMiddlewareStage3 = Box::new(|_, _| {
|
||||
async move {
|
||||
let res: DynMiddlewareStage4 = Box::new(|res| {
|
||||
async move {
|
||||
res.headers_mut()
|
||||
.insert("Access-Control-Allow-Origin", "*".parse()?);
|
||||
Ok::<_, HttpError>(())
|
||||
}
|
||||
.boxed()
|
||||
});
|
||||
Ok::<_, HttpError>(Ok(res))
|
||||
}
|
||||
.boxed()
|
||||
});
|
||||
Ok::<_, HttpError>(Ok(res))
|
||||
}
|
||||
.boxed()
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_rpc() {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
let seed = Arc::new(ConfigSeed {
|
||||
host: Host::parse("localhost").unwrap(),
|
||||
port: 8000,
|
||||
});
|
||||
let server = rpc_server!({
|
||||
command: dothething::<String, _>,
|
||||
context: AppState(seed),
|
||||
middleware: [
|
||||
cors,
|
||||
],
|
||||
});
|
||||
let handle = tokio::spawn(server);
|
||||
let mut cmd = tokio::process::Command::new("cargo")
|
||||
.arg("test")
|
||||
.arg("--package")
|
||||
.arg("rpc-toolkit")
|
||||
.arg("--test")
|
||||
.arg("test")
|
||||
.arg("--")
|
||||
.arg("cli_test")
|
||||
.arg("--exact")
|
||||
.arg("--nocapture")
|
||||
.arg("--")
|
||||
// .arg("-b")
|
||||
// .arg("test")
|
||||
.arg("dothething2")
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
cmd.stdin
|
||||
.take()
|
||||
.unwrap()
|
||||
.write_all(b"TEST\nHAHA")
|
||||
.await
|
||||
.unwrap();
|
||||
let out = cmd.wait_with_output().await.unwrap();
|
||||
assert!(out.status.success());
|
||||
assert!(dbg!(std::str::from_utf8(&out.stdout).unwrap())
|
||||
.contains("\nNone, test, false, \"TEST\", \"HAHA\"\n"));
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_test() {
|
||||
let app = dothething::build_app();
|
||||
let mut skip = true;
|
||||
let args = std::iter::once(std::ffi::OsString::from("cli_test"))
|
||||
.chain(std::env::args_os().into_iter().skip_while(|a| {
|
||||
if a == "--" {
|
||||
skip = false;
|
||||
return true;
|
||||
}
|
||||
skip
|
||||
}))
|
||||
.collect::<Vec<_>>();
|
||||
if skip {
|
||||
return;
|
||||
}
|
||||
let matches = app.get_matches_from(args);
|
||||
let seed = Arc::new(ConfigSeed {
|
||||
host: Host::parse("localhost").unwrap(),
|
||||
port: 8000,
|
||||
});
|
||||
dothething::cli_handler::<String, _, _, _>(AppState(seed), (), None, &matches, "".into(), ())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn cli_example() {
|
||||
run_cli! ({
|
||||
command: dothething::<String, _>,
|
||||
app: app => app
|
||||
.arg(Arg::with_name("host").long("host").short('h').takes_value(true))
|
||||
.arg(Arg::with_name("port").long("port").short('p').takes_value(true)),
|
||||
context: matches => AppState(Arc::new(ConfigSeed {
|
||||
host: Host::parse(matches.value_of("host").unwrap_or("localhost")).unwrap(),
|
||||
port: matches.value_of("port").unwrap_or("8000").parse().unwrap(),
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
333
src/cli.rs
Normal file
333
src/cli.rs
Normal file
@@ -0,0 +1,333 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::OsString;
|
||||
|
||||
use clap::{CommandFactory, FromArgMatches};
|
||||
use futures::Future;
|
||||
use imbl_value::imbl::OrdMap;
|
||||
use imbl_value::Value;
|
||||
use reqwest::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE};
|
||||
use reqwest::{Client, Method};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader};
|
||||
use url::Url;
|
||||
use yajrc::{Id, RpcError};
|
||||
|
||||
use crate::util::{internal_error, invalid_params, parse_error, without, Flat, PhantomData};
|
||||
use crate::{
|
||||
AnyHandler, CliBindings, CliBindingsAny, Empty, HandleAny, HandleAnyArgs, HandlerArgs,
|
||||
HandlerArgsFor, HandlerFor, HandlerTypes, Name, ParentHandler, PrintCliResult,
|
||||
};
|
||||
|
||||
type GenericRpcMethod<'a> = yajrc::GenericRpcMethod<&'a str, Value, Value>;
|
||||
type RpcRequest<'a> = yajrc::RpcRequest<GenericRpcMethod<'a>>;
|
||||
type RpcResponse<'a> = yajrc::RpcResponse<GenericRpcMethod<'static>>;
|
||||
|
||||
pub struct CliApp<Context: crate::Context + Clone, Config: CommandFactory + FromArgMatches> {
|
||||
_phantom: PhantomData<(Context, Config)>,
|
||||
make_ctx: Box<dyn FnOnce(Config) -> Result<Context, RpcError> + Send + Sync>,
|
||||
root_handler: ParentHandler<Context>,
|
||||
}
|
||||
impl<Context: crate::Context + Clone, Config: CommandFactory + FromArgMatches>
|
||||
CliApp<Context, Config>
|
||||
{
|
||||
pub fn new<MakeCtx: FnOnce(Config) -> Result<Context, RpcError> + Send + Sync + 'static>(
|
||||
make_ctx: MakeCtx,
|
||||
root_handler: ParentHandler<Context>,
|
||||
) -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData::new(),
|
||||
make_ctx: Box::new(make_ctx),
|
||||
root_handler,
|
||||
}
|
||||
}
|
||||
pub fn run(self, args: impl IntoIterator<Item = OsString>) -> Result<(), RpcError> {
|
||||
let mut cmd = Config::command();
|
||||
for (name, handler) in &self.root_handler.subcommands.1 {
|
||||
if let (Name(name), Some(cli)) = (name, handler.cli()) {
|
||||
cmd = cmd.subcommand(cli.cli_command().name(name));
|
||||
}
|
||||
}
|
||||
let matches = cmd.get_matches_from(args);
|
||||
let config = Config::from_arg_matches(&matches)?;
|
||||
let ctx = (self.make_ctx)(config)?;
|
||||
let root_handler = AnyHandler::new(self.root_handler);
|
||||
let (method, params) = root_handler.cli_parse(&matches)?;
|
||||
let res = root_handler.handle_sync(HandleAnyArgs {
|
||||
context: ctx.clone(),
|
||||
parent_method: VecDeque::new(),
|
||||
method: method.clone(),
|
||||
params: params.clone(),
|
||||
inherited: crate::Empty {},
|
||||
})?;
|
||||
root_handler.cli_display(
|
||||
HandleAnyArgs {
|
||||
context: ctx,
|
||||
parent_method: VecDeque::new(),
|
||||
method,
|
||||
params,
|
||||
inherited: crate::Empty {},
|
||||
},
|
||||
res,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CallRemote<RemoteContext, Extra = Empty>: crate::Context {
|
||||
fn call_remote(
|
||||
&self,
|
||||
method: &str,
|
||||
params: Value,
|
||||
extra: Extra,
|
||||
) -> impl Future<Output = Result<Value, RpcError>> + Send;
|
||||
}
|
||||
|
||||
pub async fn call_remote_http(
|
||||
client: &Client,
|
||||
url: Url,
|
||||
method: &str,
|
||||
params: Value,
|
||||
) -> Result<Value, RpcError> {
|
||||
let rpc_req = RpcRequest {
|
||||
id: Some(Id::Number(0.into())),
|
||||
method: GenericRpcMethod::new(method),
|
||||
params,
|
||||
};
|
||||
let mut req = client.request(Method::POST, url);
|
||||
let body;
|
||||
#[cfg(feature = "cbor")]
|
||||
{
|
||||
req = req.header(CONTENT_TYPE, "application/cbor");
|
||||
req = req.header(ACCEPT, "application/cbor, application/json");
|
||||
body = serde_cbor::to_vec(&rpc_req)?;
|
||||
}
|
||||
#[cfg(not(feature = "cbor"))]
|
||||
{
|
||||
req = req.header(CONTENT_TYPE, "application/json");
|
||||
req = req.header(ACCEPT, "application/json");
|
||||
body = serde_json::to_vec(&rpc_req)?;
|
||||
}
|
||||
let res = req
|
||||
.header(CONTENT_LENGTH, body.len())
|
||||
.body(body)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
match res
|
||||
.headers()
|
||||
.get(CONTENT_TYPE)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
{
|
||||
Some("application/json") => {
|
||||
serde_json::from_slice::<RpcResponse>(&*res.bytes().await.map_err(internal_error)?)
|
||||
.map_err(parse_error)?
|
||||
.result
|
||||
}
|
||||
#[cfg(feature = "cbor")]
|
||||
Some("application/cbor") => {
|
||||
serde_cbor::from_slice::<RpcResponse>(&*res.bytes().await.map_err(internal_error)?)
|
||||
.map_err(parse_error)?
|
||||
.result
|
||||
}
|
||||
_ => Err(internal_error("missing content type")),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call_remote_socket(
|
||||
connection: impl AsyncRead + AsyncWrite,
|
||||
method: &str,
|
||||
params: Value,
|
||||
) -> Result<Value, RpcError> {
|
||||
let rpc_req = RpcRequest {
|
||||
id: Some(Id::Number(0.into())),
|
||||
method: GenericRpcMethod::new(method),
|
||||
params,
|
||||
};
|
||||
let conn = connection;
|
||||
tokio::pin!(conn);
|
||||
let mut buf = serde_json::to_vec(&rpc_req).map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INTERNAL_ERROR
|
||||
})?;
|
||||
buf.push(b'\n');
|
||||
conn.write_all(&buf).await.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INTERNAL_ERROR
|
||||
})?;
|
||||
let mut line = String::new();
|
||||
BufReader::new(conn).read_line(&mut line).await?;
|
||||
serde_json::from_str::<RpcResponse>(&line)
|
||||
.map_err(parse_error)?
|
||||
.result
|
||||
}
|
||||
|
||||
pub struct CallRemoteHandler<Context, RemoteContext, RemoteHandler, Extra = Empty> {
|
||||
_phantom: PhantomData<(Context, RemoteContext, Extra)>,
|
||||
handler: RemoteHandler,
|
||||
}
|
||||
impl<Context, RemoteContext, RemoteHandler, Extra>
|
||||
CallRemoteHandler<Context, RemoteContext, RemoteHandler, Extra>
|
||||
{
|
||||
pub fn new(handler: RemoteHandler) -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData::new(),
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Context, RemoteContext, RemoteHandler: Clone, Extra> Clone
|
||||
for CallRemoteHandler<Context, RemoteContext, RemoteHandler, Extra>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData::new(),
|
||||
handler: self.handler.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Context, RemoteHandler, Extra> std::fmt::Debug
|
||||
for CallRemoteHandler<Context, RemoteHandler, Extra>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("CallRemoteHandler").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context, RemoteContext, RemoteHandler, Extra> HandlerTypes
|
||||
for CallRemoteHandler<Context, RemoteContext, RemoteHandler, Extra>
|
||||
where
|
||||
RemoteHandler: HandlerTypes,
|
||||
RemoteHandler::Params: Serialize,
|
||||
RemoteHandler::Ok: DeserializeOwned,
|
||||
RemoteHandler::Err: From<RpcError>,
|
||||
Extra: Send + Sync + 'static,
|
||||
{
|
||||
type Params = Flat<RemoteHandler::Params, Extra>;
|
||||
type InheritedParams = RemoteHandler::InheritedParams;
|
||||
type Ok = RemoteHandler::Ok;
|
||||
type Err = RemoteHandler::Err;
|
||||
}
|
||||
|
||||
impl<Context, RemoteContext, RemoteHandler, Extra> HandlerFor<Context>
|
||||
for CallRemoteHandler<Context, RemoteContext, RemoteHandler, Extra>
|
||||
where
|
||||
Context: CallRemote<RemoteContext, Extra>,
|
||||
RemoteContext: crate::Context,
|
||||
RemoteHandler: HandlerFor<RemoteContext>,
|
||||
RemoteHandler::Params: Serialize,
|
||||
RemoteHandler::Ok: DeserializeOwned,
|
||||
RemoteHandler::Err: From<RpcError>,
|
||||
Extra: Serialize + Send + Sync + 'static,
|
||||
{
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
let full_method = handle_args
|
||||
.parent_method
|
||||
.into_iter()
|
||||
.chain(handle_args.method)
|
||||
.collect::<Vec<_>>();
|
||||
match handle_args
|
||||
.context
|
||||
.call_remote(
|
||||
&full_method.join("."),
|
||||
without(handle_args.raw_params.clone(), &handle_args.params.1)
|
||||
.map_err(invalid_params)?,
|
||||
handle_args.params.1,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(a) => imbl_value::from_value(a)
|
||||
.map_err(internal_error)
|
||||
.map_err(Self::Err::from),
|
||||
Err(e) => Err(Self::Err::from(e)),
|
||||
}
|
||||
}
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.handler.metadata(method)
|
||||
}
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
self.handler.method_from_dots(method)
|
||||
}
|
||||
}
|
||||
impl<Context, RemoteContext, RemoteHandler, Extra> PrintCliResult<Context>
|
||||
for CallRemoteHandler<Context, RemoteContext, RemoteHandler, Extra>
|
||||
where
|
||||
Context: CallRemote<RemoteContext>,
|
||||
RemoteHandler: PrintCliResult<Context>,
|
||||
RemoteHandler::Params: Serialize,
|
||||
RemoteHandler::Ok: DeserializeOwned,
|
||||
RemoteHandler::Err: From<RpcError>,
|
||||
Extra: Send + Sync + 'static,
|
||||
{
|
||||
fn print(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
self.handler.print(
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params: params.0,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
},
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<Context, RemoteContext, RemoteHandler, Extra> CliBindings<Context>
|
||||
for CallRemoteHandler<Context, RemoteContext, RemoteHandler, Extra>
|
||||
where
|
||||
Context: crate::Context,
|
||||
RemoteHandler: CliBindings<Context>,
|
||||
RemoteHandler::Params: Serialize,
|
||||
RemoteHandler::Ok: DeserializeOwned,
|
||||
RemoteHandler::Err: From<RpcError>,
|
||||
Extra: Send + Sync + 'static,
|
||||
{
|
||||
fn cli_command(&self) -> clap::Command {
|
||||
self.handler.cli_command()
|
||||
}
|
||||
fn cli_parse(
|
||||
&self,
|
||||
matches: &clap::ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
|
||||
self.handler.cli_parse(matches)
|
||||
}
|
||||
fn cli_display(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
self.handler.cli_display(
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params: params.0,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
},
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
33
src/command_helpers.rs
Normal file
33
src/command_helpers.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use std::fmt::Display;
|
||||
use std::io::Stdin;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::ArgMatches;
|
||||
pub use {clap, serde};
|
||||
|
||||
pub fn default_arg_parser<T>(arg: &str, _: &ArgMatches) -> Result<T, clap::Error>
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: Display,
|
||||
{
|
||||
arg.parse()
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e))
|
||||
}
|
||||
|
||||
pub fn default_stdin_parser<T>(stdin: &mut Stdin, _: &ArgMatches) -> Result<T, clap::Error>
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: Display,
|
||||
{
|
||||
let mut s = String::new();
|
||||
stdin
|
||||
.read_line(&mut s)
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::Io, e))?;
|
||||
if let Some(s) = s.strip_suffix("\n") {
|
||||
s
|
||||
} else {
|
||||
&s
|
||||
}
|
||||
.parse()
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e))
|
||||
}
|
||||
9
src/context.rs
Normal file
9
src/context.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
pub trait Context: Send + Sync + 'static {
|
||||
fn runtime(&self) -> Option<Arc<Runtime>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
866
src/handler/adapters.rs
Normal file
866
src/handler/adapters.rs
Normal file
@@ -0,0 +1,866 @@
|
||||
use std::any::TypeId;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use clap::builder::{IntoResettable, StyledStr};
|
||||
use clap::{CommandFactory, FromArgMatches};
|
||||
use imbl_value::imbl::OrdMap;
|
||||
use imbl_value::Value;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use yajrc::RpcError;
|
||||
|
||||
use crate::util::{Flat, PhantomData};
|
||||
use crate::{
|
||||
CallRemote, CallRemoteHandler, CliBindings, DynHandler, Handler, HandlerArgs, HandlerArgsFor,
|
||||
HandlerFor, HandlerTypes, OrEmpty, PrintCliResult, WithContext,
|
||||
};
|
||||
|
||||
pub trait HandlerExt<Context: crate::Context>: HandlerFor<Context> + Sized {
|
||||
fn no_cli(self) -> NoCli<Self>;
|
||||
fn no_display(self) -> NoDisplay<Self>;
|
||||
fn with_custom_display<C: crate::Context, P>(self, display: P) -> CustomDisplay<P, Self>
|
||||
where
|
||||
P: PrintCliResult<
|
||||
C,
|
||||
Params = Self::Params,
|
||||
InheritedParams = Self::InheritedParams,
|
||||
Ok = Self::Ok,
|
||||
Err = Self::Err,
|
||||
>;
|
||||
fn with_custom_display_fn<C: crate::Context, F>(
|
||||
self,
|
||||
display: F,
|
||||
) -> CustomDisplayFn<F, Self, C>
|
||||
where
|
||||
F: Fn(HandlerArgsFor<C, Self>, Self::Ok) -> Result<(), Self::Err>;
|
||||
fn with_inherited<Params, InheritedParams, F>(
|
||||
self,
|
||||
f: F,
|
||||
) -> InheritanceHandler<Params, InheritedParams, Self, F>
|
||||
where
|
||||
F: Fn(Params, InheritedParams) -> Self::InheritedParams;
|
||||
fn with_call_remote<C>(self) -> RemoteCaller<C, Context, Self>;
|
||||
fn with_about<M>(self, message: M) -> WithAbout<M, Self>
|
||||
where
|
||||
M: IntoResettable<StyledStr>;
|
||||
}
|
||||
|
||||
impl<Context: crate::Context, T: HandlerFor<Context> + Sized> HandlerExt<Context> for T {
|
||||
fn no_cli(self) -> NoCli<Self> {
|
||||
NoCli(self)
|
||||
}
|
||||
fn no_display(self) -> NoDisplay<Self> {
|
||||
NoDisplay(self)
|
||||
}
|
||||
fn with_custom_display<C: crate::Context, P>(self, display: P) -> CustomDisplay<P, Self>
|
||||
where
|
||||
P: PrintCliResult<
|
||||
C,
|
||||
Params = Self::Params,
|
||||
InheritedParams = Self::InheritedParams,
|
||||
Ok = Self::Ok,
|
||||
Err = Self::Err,
|
||||
>,
|
||||
{
|
||||
CustomDisplay {
|
||||
print: display,
|
||||
handler: self,
|
||||
}
|
||||
}
|
||||
fn with_custom_display_fn<C: crate::Context, F>(self, display: F) -> CustomDisplayFn<F, Self, C>
|
||||
where
|
||||
F: Fn(HandlerArgsFor<C, Self>, Self::Ok) -> Result<(), Self::Err>,
|
||||
{
|
||||
CustomDisplayFn {
|
||||
_phantom: PhantomData::new(),
|
||||
print: display,
|
||||
handler: self,
|
||||
}
|
||||
}
|
||||
fn with_inherited<Params, InheritedParams, F>(
|
||||
self,
|
||||
f: F,
|
||||
) -> InheritanceHandler<Params, InheritedParams, Self, F>
|
||||
where
|
||||
F: Fn(Params, InheritedParams) -> Self::InheritedParams,
|
||||
{
|
||||
InheritanceHandler {
|
||||
_phantom: PhantomData::new(),
|
||||
handler: self,
|
||||
inherit: f,
|
||||
}
|
||||
}
|
||||
fn with_call_remote<C>(self) -> RemoteCaller<C, Context, Self> {
|
||||
RemoteCaller {
|
||||
_phantom: PhantomData::new(),
|
||||
handler: self,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_about<M>(self, message: M) -> WithAbout<M, Self>
|
||||
where
|
||||
M: IntoResettable<StyledStr>,
|
||||
{
|
||||
WithAbout {
|
||||
handler: self,
|
||||
message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NoCli<H>(pub H);
|
||||
impl<H: HandlerTypes> HandlerTypes for NoCli<H> {
|
||||
type Params = H::Params;
|
||||
type InheritedParams = H::InheritedParams;
|
||||
type Ok = H::Ok;
|
||||
type Err = H::Err;
|
||||
}
|
||||
impl<Context, H> HandlerFor<Context> for NoCli<H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: HandlerFor<Context>,
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.0.handle_sync(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.0
|
||||
.handle_async(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
.await
|
||||
}
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.0.metadata(method)
|
||||
}
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
self.0.method_from_dots(method)
|
||||
}
|
||||
}
|
||||
impl<Context, H> CliBindings<Context> for NoCli<H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: HandlerTypes,
|
||||
{
|
||||
const NO_CLI: bool = true;
|
||||
fn cli_command(&self) -> clap::Command {
|
||||
unimplemented!()
|
||||
}
|
||||
fn cli_parse(
|
||||
&self,
|
||||
_: &clap::ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn cli_display(&self, _: HandlerArgsFor<Context, Self>, _: Self::Ok) -> Result<(), Self::Err> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NoDisplay<H>(pub H);
|
||||
impl<H: HandlerTypes> HandlerTypes for NoDisplay<H> {
|
||||
type Params = H::Params;
|
||||
type InheritedParams = H::InheritedParams;
|
||||
type Ok = H::Ok;
|
||||
type Err = H::Err;
|
||||
}
|
||||
|
||||
impl<Context, H> HandlerFor<Context> for NoDisplay<H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: HandlerFor<Context>,
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.0.handle_sync(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.0
|
||||
.handle_async(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
.await
|
||||
}
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.0.metadata(method)
|
||||
}
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
self.0.method_from_dots(method)
|
||||
}
|
||||
}
|
||||
impl<Context, H> PrintCliResult<Context> for NoDisplay<H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: HandlerTypes,
|
||||
H::Params: FromArgMatches + CommandFactory + Serialize,
|
||||
{
|
||||
fn print(&self, _: HandlerArgsFor<Context, Self>, _: Self::Ok) -> Result<(), Self::Err> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl<Context, H> CliBindings<Context> for NoDisplay<H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
Self: HandlerTypes,
|
||||
Self::Params: CommandFactory + FromArgMatches + Serialize,
|
||||
Self: PrintCliResult<Context>,
|
||||
{
|
||||
fn cli_command(&self) -> clap::Command {
|
||||
Self::Params::command()
|
||||
}
|
||||
fn cli_parse(
|
||||
&self,
|
||||
matches: &clap::ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
|
||||
Self::Params::from_arg_matches(matches).and_then(|a| {
|
||||
Ok((
|
||||
VecDeque::new(),
|
||||
imbl_value::to_value(&a)
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e))?,
|
||||
))
|
||||
})
|
||||
}
|
||||
fn cli_display(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
self.print(handle_args, result)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CustomDisplay<P, H> {
|
||||
print: P,
|
||||
handler: H,
|
||||
}
|
||||
impl<P, H> HandlerTypes for CustomDisplay<P, H>
|
||||
where
|
||||
H: HandlerTypes,
|
||||
{
|
||||
type Params = H::Params;
|
||||
type InheritedParams = H::InheritedParams;
|
||||
type Ok = H::Ok;
|
||||
type Err = H::Err;
|
||||
}
|
||||
|
||||
impl<Context, P, H> HandlerFor<Context> for CustomDisplay<P, H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: HandlerFor<Context>,
|
||||
P: Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.handler.handle_sync(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.handler
|
||||
.handle_async(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
.await
|
||||
}
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.handler.metadata(method)
|
||||
}
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
self.handler.method_from_dots(method)
|
||||
}
|
||||
}
|
||||
impl<Context, P, H> PrintCliResult<Context> for CustomDisplay<P, H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: HandlerTypes,
|
||||
P: PrintCliResult<
|
||||
Context,
|
||||
Params = H::Params,
|
||||
InheritedParams = H::InheritedParams,
|
||||
Ok = H::Ok,
|
||||
Err = H::Err,
|
||||
> + Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
+ 'static,
|
||||
{
|
||||
fn print(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
self.print.print(
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
},
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<Context, P, H> CliBindings<Context> for CustomDisplay<P, H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
Self: HandlerTypes,
|
||||
Self::Params: CommandFactory + FromArgMatches + Serialize,
|
||||
Self: PrintCliResult<Context>,
|
||||
{
|
||||
fn cli_command(&self) -> clap::Command {
|
||||
Self::Params::command()
|
||||
}
|
||||
fn cli_parse(
|
||||
&self,
|
||||
matches: &clap::ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
|
||||
Self::Params::from_arg_matches(matches).and_then(|a| {
|
||||
Ok((
|
||||
VecDeque::new(),
|
||||
imbl_value::to_value(&a)
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e))?,
|
||||
))
|
||||
})
|
||||
}
|
||||
fn cli_display(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
self.print(handle_args, result)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CustomDisplayFn<F, H, Context> {
|
||||
_phantom: PhantomData<Context>,
|
||||
print: F,
|
||||
handler: H,
|
||||
}
|
||||
impl<Context, F: Clone, H: Clone> Clone for CustomDisplayFn<F, H, Context> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData::new(),
|
||||
print: self.print.clone(),
|
||||
handler: self.handler.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Context, F: Debug, H: Debug> Debug for CustomDisplayFn<F, H, Context> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CustomDisplayFn")
|
||||
.field("print", &self.print)
|
||||
.field("handler", &self.handler)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl<F, H, Context> HandlerTypes for CustomDisplayFn<F, H, Context>
|
||||
where
|
||||
H: HandlerTypes,
|
||||
{
|
||||
type Params = H::Params;
|
||||
type InheritedParams = H::InheritedParams;
|
||||
type Ok = H::Ok;
|
||||
type Err = H::Err;
|
||||
}
|
||||
|
||||
impl<Context, F, H, C> HandlerFor<Context> for CustomDisplayFn<F, H, C>
|
||||
where
|
||||
Context: crate::Context,
|
||||
C: 'static,
|
||||
H: HandlerFor<Context>,
|
||||
F: Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.handler.handle_sync(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.handler
|
||||
.handle_async(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
.await
|
||||
}
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.handler.metadata(method)
|
||||
}
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
self.handler.method_from_dots(method)
|
||||
}
|
||||
}
|
||||
impl<F, H, Context> PrintCliResult<Context> for CustomDisplayFn<F, H, Context>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: HandlerTypes,
|
||||
F: Fn(HandlerArgsFor<Context, H>, H::Ok) -> Result<(), H::Err> + Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn print(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
(self.print)(
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
},
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<Context, F, H, C> CliBindings<Context> for CustomDisplayFn<F, H, C>
|
||||
where
|
||||
Context: crate::Context,
|
||||
Self: HandlerTypes,
|
||||
Self::Params: CommandFactory + FromArgMatches + Serialize,
|
||||
Self: PrintCliResult<Context>,
|
||||
{
|
||||
fn cli_command(&self) -> clap::Command {
|
||||
Self::Params::command()
|
||||
}
|
||||
fn cli_parse(
|
||||
&self,
|
||||
matches: &clap::ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
|
||||
Self::Params::from_arg_matches(matches).and_then(|a| {
|
||||
Ok((
|
||||
VecDeque::new(),
|
||||
imbl_value::to_value(&a)
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e))?,
|
||||
))
|
||||
})
|
||||
}
|
||||
fn cli_display(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
self.print(handle_args, result)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RemoteCaller<Context, RemoteContext, H> {
|
||||
_phantom: PhantomData<(Context, RemoteContext)>,
|
||||
handler: H,
|
||||
}
|
||||
impl<Context, RemoteContext, H: Clone> Clone for RemoteCaller<Context, RemoteContext, H> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData::new(),
|
||||
handler: self.handler.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Context, RemoteContext, H: Debug> Debug for RemoteCaller<Context, RemoteContext, H> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("RemoteCaller").field(&self.handler).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context, H, Inherited, RemoteContext> Handler<Inherited>
|
||||
for WithContext<Context, RemoteCaller<Context, RemoteContext, H>>
|
||||
where
|
||||
Context: crate::Context + CallRemote<RemoteContext>,
|
||||
RemoteContext: crate::Context,
|
||||
H: HandlerFor<RemoteContext> + CliBindings<Context>,
|
||||
H::Ok: Serialize + DeserializeOwned,
|
||||
H::Err: From<RpcError>,
|
||||
H::Params: Serialize + DeserializeOwned,
|
||||
H::InheritedParams: OrEmpty<Inherited>,
|
||||
RpcError: From<H::Err>,
|
||||
Inherited: Send + Sync + 'static,
|
||||
{
|
||||
type H = H;
|
||||
fn handler_for<C: crate::Context>(self) -> Option<DynHandler<C, Inherited>> {
|
||||
if TypeId::of::<C>() == TypeId::of::<RemoteContext>() {
|
||||
DynHandler::new(self.handler.handler.no_cli())
|
||||
} else if TypeId::of::<C>() == TypeId::of::<Context>() {
|
||||
DynHandler::new(CallRemoteHandler::<Context, RemoteContext, _>::new(
|
||||
self.handler.handler,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InheritanceHandler<Params, InheritedParams, H, F> {
|
||||
_phantom: PhantomData<(Params, InheritedParams)>,
|
||||
handler: H,
|
||||
inherit: F,
|
||||
}
|
||||
impl<Params, InheritedParams, H: Clone, F: Clone> Clone
|
||||
for InheritanceHandler<Params, InheritedParams, H, F>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData::new(),
|
||||
handler: self.handler.clone(),
|
||||
inherit: self.inherit.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Params, InheritedParams, H: std::fmt::Debug, F> std::fmt::Debug
|
||||
for InheritanceHandler<Params, InheritedParams, H, F>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("InheritanceHandler")
|
||||
.field(&self.handler)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl<Params, InheritedParams, H, F> HandlerTypes
|
||||
for InheritanceHandler<Params, InheritedParams, H, F>
|
||||
where
|
||||
H: HandlerTypes,
|
||||
Params: Send + Sync,
|
||||
InheritedParams: Send + Sync,
|
||||
{
|
||||
type Params = H::Params;
|
||||
type InheritedParams = Flat<Params, InheritedParams>;
|
||||
type Ok = H::Ok;
|
||||
type Err = H::Err;
|
||||
}
|
||||
|
||||
impl<Context, Params, InheritedParams, H, F> HandlerFor<Context>
|
||||
for InheritanceHandler<Params, InheritedParams, H, F>
|
||||
where
|
||||
Context: crate::Context,
|
||||
Params: Send + Sync + 'static,
|
||||
InheritedParams: Send + Sync + 'static,
|
||||
H: HandlerFor<Context>,
|
||||
F: Fn(Params, InheritedParams) -> H::InheritedParams + Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.handler.handle_sync(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params: (self.inherit)(inherited_params.0, inherited_params.1),
|
||||
raw_params,
|
||||
})
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.handler
|
||||
.handle_async(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params: (self.inherit)(inherited_params.0, inherited_params.1),
|
||||
raw_params,
|
||||
})
|
||||
.await
|
||||
}
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.handler.metadata(method)
|
||||
}
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
self.handler.method_from_dots(method)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context, Params, InheritedParams, H, F> CliBindings<Context>
|
||||
for InheritanceHandler<Params, InheritedParams, H, F>
|
||||
where
|
||||
Context: crate::Context,
|
||||
Params: Send + Sync + 'static,
|
||||
InheritedParams: Send + Sync + 'static,
|
||||
H: CliBindings<Context>,
|
||||
F: Fn(Params, InheritedParams) -> H::InheritedParams + Send + Sync + Clone + 'static,
|
||||
{
|
||||
fn cli_command(&self) -> clap::Command {
|
||||
self.handler.cli_command()
|
||||
}
|
||||
fn cli_parse(
|
||||
&self,
|
||||
matches: &clap::ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
|
||||
self.handler.cli_parse(matches)
|
||||
}
|
||||
fn cli_display(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
self.handler.cli_display(
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params: (self.inherit)(inherited_params.0, inherited_params.1),
|
||||
raw_params,
|
||||
},
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WithAbout<M, H> {
|
||||
handler: H,
|
||||
message: M,
|
||||
}
|
||||
impl<M, H> HandlerTypes for WithAbout<M, H>
|
||||
where
|
||||
H: HandlerTypes,
|
||||
{
|
||||
type Params = H::Params;
|
||||
type InheritedParams = H::InheritedParams;
|
||||
type Ok = H::Ok;
|
||||
type Err = H::Err;
|
||||
}
|
||||
impl<Context, M, H> HandlerFor<Context> for WithAbout<M, H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: HandlerFor<Context>,
|
||||
M: Clone + Send + Sync + 'static,
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.handler.handle_sync(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
self.handler
|
||||
.handle_async(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
})
|
||||
.await
|
||||
}
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.handler.metadata(method)
|
||||
}
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
self.handler.method_from_dots(method)
|
||||
}
|
||||
}
|
||||
impl<Context, M, H> CliBindings<Context> for WithAbout<M, H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: CliBindings<Context>,
|
||||
M: IntoResettable<StyledStr> + Clone,
|
||||
{
|
||||
fn cli_command(&self) -> clap::Command {
|
||||
self.handler.cli_command().about(self.message.clone())
|
||||
}
|
||||
fn cli_parse(
|
||||
&self,
|
||||
arg_matches: &clap::ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
|
||||
self.handler.cli_parse(arg_matches)
|
||||
}
|
||||
fn cli_display(
|
||||
&self,
|
||||
handler: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
self.handler.cli_display(handler, result)
|
||||
}
|
||||
}
|
||||
631
src/handler/from_fn.rs
Normal file
631
src/handler/from_fn.rs
Normal file
@@ -0,0 +1,631 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Display;
|
||||
|
||||
use clap::{CommandFactory, FromArgMatches};
|
||||
use futures::Future;
|
||||
use imbl_value::imbl::OrdMap;
|
||||
use imbl_value::Value;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::util::PhantomData;
|
||||
use crate::{
|
||||
CliBindings, Empty, HandlerArgs, HandlerArgsFor, HandlerFor, HandlerTypes, PrintCliResult,
|
||||
};
|
||||
|
||||
pub struct FromFn<F, T, E, Args> {
|
||||
_phantom: PhantomData<(T, E, Args)>,
|
||||
function: F,
|
||||
blocking: bool,
|
||||
metadata: OrdMap<&'static str, Value>,
|
||||
}
|
||||
impl<F, T, E, Args> FromFn<F, T, E, Args> {
|
||||
pub fn with_metadata(mut self, key: &'static str, value: Value) -> Self {
|
||||
self.metadata.insert(key, value);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<F: Clone, T, E, Args> Clone for FromFn<F, T, E, Args> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData::new(),
|
||||
function: self.function.clone(),
|
||||
blocking: self.blocking,
|
||||
metadata: self.metadata.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<F, T, E, Args> std::fmt::Debug for FromFn<F, T, E, Args> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FromFn")
|
||||
.field("blocking", &self.blocking)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl<Context, F, T, E, Args> PrintCliResult<Context> for FromFn<F, T, E, Args>
|
||||
where
|
||||
Context: crate::Context,
|
||||
Self: HandlerTypes,
|
||||
<Self as HandlerTypes>::Ok: Display,
|
||||
{
|
||||
fn print(&self, _: HandlerArgsFor<Context, Self>, result: Self::Ok) -> Result<(), Self::Err> {
|
||||
Ok(println!("{result}"))
|
||||
}
|
||||
}
|
||||
impl<Context, F, T, E, Args> CliBindings<Context> for FromFn<F, T, E, Args>
|
||||
where
|
||||
Context: crate::Context,
|
||||
Self: HandlerTypes,
|
||||
Self::Params: CommandFactory + FromArgMatches + Serialize,
|
||||
Self: PrintCliResult<Context>,
|
||||
{
|
||||
fn cli_command(&self) -> clap::Command {
|
||||
Self::Params::command()
|
||||
}
|
||||
fn cli_parse(
|
||||
&self,
|
||||
matches: &clap::ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
|
||||
Self::Params::from_arg_matches(matches).and_then(|a| {
|
||||
Ok((
|
||||
VecDeque::new(),
|
||||
imbl_value::to_value(&a)
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e))?,
|
||||
))
|
||||
})
|
||||
}
|
||||
fn cli_display(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
self.print(
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
},
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_fn<F, T, E, Args>(function: F) -> FromFn<F, T, E, Args>
|
||||
where
|
||||
FromFn<F, T, E, Args>: HandlerTypes,
|
||||
{
|
||||
FromFn {
|
||||
function,
|
||||
_phantom: PhantomData::new(),
|
||||
blocking: false,
|
||||
metadata: OrdMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_fn_blocking<F, T, E, Args>(function: F) -> FromFn<F, T, E, Args>
|
||||
where
|
||||
FromFn<F, T, E, Args>: HandlerTypes,
|
||||
{
|
||||
FromFn {
|
||||
function,
|
||||
_phantom: PhantomData::new(),
|
||||
blocking: true,
|
||||
metadata: OrdMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FromFnAsync<F, Fut, T, E, Args> {
|
||||
_phantom: PhantomData<(Fut, T, E, Args)>,
|
||||
function: F,
|
||||
metadata: OrdMap<&'static str, Value>,
|
||||
}
|
||||
impl<F, Fut, T, E, Args> FromFnAsync<F, Fut, T, E, Args> {
|
||||
pub fn with_metadata(mut self, key: &'static str, value: Value) -> Self {
|
||||
self.metadata.insert(key, value);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<F: Clone, Fut, T, E, Args> Clone for FromFnAsync<F, Fut, T, E, Args> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData::new(),
|
||||
function: self.function.clone(),
|
||||
metadata: self.metadata.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<F, Fut, T, E, Args> std::fmt::Debug for FromFnAsync<F, Fut, T, E, Args> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FromFnAsync").finish()
|
||||
}
|
||||
}
|
||||
impl<Context, F, Fut, T, E, Args> PrintCliResult<Context> for FromFnAsync<F, Fut, T, E, Args>
|
||||
where
|
||||
Context: crate::Context,
|
||||
Self: HandlerTypes,
|
||||
<Self as HandlerTypes>::Ok: Display,
|
||||
{
|
||||
fn print(&self, _: HandlerArgsFor<Context, Self>, result: Self::Ok) -> Result<(), Self::Err> {
|
||||
Ok(println!("{result}"))
|
||||
}
|
||||
}
|
||||
impl<Context, F, Fut, T, E, Args> CliBindings<Context> for FromFnAsync<F, Fut, T, E, Args>
|
||||
where
|
||||
Context: crate::Context,
|
||||
Self: HandlerTypes,
|
||||
Self::Params: CommandFactory + FromArgMatches + Serialize,
|
||||
Self: PrintCliResult<Context>,
|
||||
{
|
||||
fn cli_command(&self) -> clap::Command {
|
||||
Self::Params::command()
|
||||
}
|
||||
fn cli_parse(
|
||||
&self,
|
||||
matches: &clap::ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
|
||||
Self::Params::from_arg_matches(matches).and_then(|a| {
|
||||
Ok((
|
||||
VecDeque::new(),
|
||||
imbl_value::to_value(&a)
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e))?,
|
||||
))
|
||||
})
|
||||
}
|
||||
fn cli_display(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
self.print(
|
||||
HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
},
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_fn_async<F, Fut, T, E, Args>(function: F) -> FromFnAsync<F, Fut, T, E, Args>
|
||||
where
|
||||
FromFnAsync<F, Fut, T, E, Args>: HandlerTypes,
|
||||
{
|
||||
FromFnAsync {
|
||||
function,
|
||||
_phantom: PhantomData::new(),
|
||||
metadata: OrdMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, E, Context, Params, InheritedParams> HandlerTypes
|
||||
for FromFn<F, T, E, HandlerArgs<Context, Params, InheritedParams>>
|
||||
where
|
||||
F: Fn(HandlerArgs<Context, Params, InheritedParams>) -> Result<T, E>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
+ 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
Context: crate::Context,
|
||||
Params: Send + Sync,
|
||||
InheritedParams: Send + Sync,
|
||||
{
|
||||
type Params = Params;
|
||||
type InheritedParams = InheritedParams;
|
||||
type Ok = T;
|
||||
type Err = E;
|
||||
}
|
||||
|
||||
impl<F, T, E, Context, Params, InheritedParams> HandlerFor<Context>
|
||||
for FromFn<F, T, E, HandlerArgs<Context, Params, InheritedParams>>
|
||||
where
|
||||
F: Fn(HandlerArgs<Context, Params, InheritedParams>) -> Result<T, E>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
+ 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
Context: crate::Context,
|
||||
Params: Send + Sync + 'static,
|
||||
InheritedParams: Send + Sync + 'static,
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
(self.function)(handle_args)
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
if self.blocking {
|
||||
self.handle_async_with_sync_blocking(handle_args).await
|
||||
} else {
|
||||
self.handle_async_with_sync(handle_args).await
|
||||
}
|
||||
}
|
||||
fn metadata(&self, _: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.metadata.clone()
|
||||
}
|
||||
}
|
||||
impl<F, Fut, T, E, Context, Params, InheritedParams> HandlerTypes
|
||||
for FromFnAsync<F, Fut, T, E, HandlerArgs<Context, Params, InheritedParams>>
|
||||
where
|
||||
F: Fn(HandlerArgs<Context, Params, InheritedParams>) -> Fut + Send + Sync + Clone + 'static,
|
||||
Fut: Future<Output = Result<T, E>> + Send + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
Context: crate::Context,
|
||||
Params: Send + Sync,
|
||||
InheritedParams: Send + Sync,
|
||||
{
|
||||
type Params = Params;
|
||||
type InheritedParams = InheritedParams;
|
||||
type Ok = T;
|
||||
type Err = E;
|
||||
}
|
||||
|
||||
impl<F, Fut, T, E, Context, Params, InheritedParams> HandlerFor<Context>
|
||||
for FromFnAsync<F, Fut, T, E, HandlerArgs<Context, Params, InheritedParams>>
|
||||
where
|
||||
F: Fn(HandlerArgs<Context, Params, InheritedParams>) -> Fut + Send + Sync + Clone + 'static,
|
||||
Fut: Future<Output = Result<T, E>> + Send + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
Context: crate::Context,
|
||||
Params: Send + Sync + 'static,
|
||||
InheritedParams: Send + Sync + 'static,
|
||||
{
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
(self.function)(handle_args).await
|
||||
}
|
||||
fn metadata(&self, _: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.metadata.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, E> HandlerTypes for FromFn<F, T, E, ()>
|
||||
where
|
||||
F: Fn() -> Result<T, E> + Send + Sync + Clone + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
type Params = Empty;
|
||||
type InheritedParams = Empty;
|
||||
type Ok = T;
|
||||
type Err = E;
|
||||
}
|
||||
|
||||
impl<Context, F, T, E> HandlerFor<Context> for FromFn<F, T, E, ()>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn() -> Result<T, E> + Send + Sync + Clone + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
fn handle_sync(&self, _: HandlerArgsFor<Context, Self>) -> Result<Self::Ok, Self::Err> {
|
||||
(self.function)()
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
if self.blocking {
|
||||
self.handle_async_with_sync_blocking(handle_args).await
|
||||
} else {
|
||||
self.handle_async_with_sync(handle_args).await
|
||||
}
|
||||
}
|
||||
fn metadata(&self, _: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.metadata.clone()
|
||||
}
|
||||
}
|
||||
impl<F, Fut, T, E> HandlerTypes for FromFnAsync<F, Fut, T, E, ()>
|
||||
where
|
||||
F: Fn() -> Fut + Send + Sync + Clone + 'static,
|
||||
Fut: Future<Output = Result<T, E>> + Send + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
type Params = Empty;
|
||||
type InheritedParams = Empty;
|
||||
type Ok = T;
|
||||
type Err = E;
|
||||
}
|
||||
|
||||
impl<Context, F, Fut, T, E> HandlerFor<Context> for FromFnAsync<F, Fut, T, E, ()>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn() -> Fut + Send + Sync + Clone + 'static,
|
||||
Fut: Future<Output = Result<T, E>> + Send + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
async fn handle_async(&self, _: HandlerArgsFor<Context, Self>) -> Result<Self::Ok, Self::Err> {
|
||||
(self.function)().await
|
||||
}
|
||||
fn metadata(&self, _: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.metadata.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context, F, T, E> HandlerTypes for FromFn<F, T, E, (Context,)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context) -> Result<T, E> + Send + Sync + Clone + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
type Params = Empty;
|
||||
type InheritedParams = Empty;
|
||||
type Ok = T;
|
||||
type Err = E;
|
||||
}
|
||||
|
||||
impl<Context, F, T, E> HandlerFor<Context> for FromFn<F, T, E, (Context,)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context) -> Result<T, E> + Send + Sync + Clone + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
(self.function)(handle_args.context)
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
if self.blocking {
|
||||
self.handle_async_with_sync_blocking(handle_args).await
|
||||
} else {
|
||||
self.handle_async_with_sync(handle_args).await
|
||||
}
|
||||
}
|
||||
fn metadata(&self, _: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.metadata.clone()
|
||||
}
|
||||
}
|
||||
impl<Context, F, Fut, T, E> HandlerTypes for FromFnAsync<F, Fut, T, E, (Context,)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context) -> Fut + Send + Sync + Clone + 'static,
|
||||
Fut: Future<Output = Result<T, E>> + Send + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
type Params = Empty;
|
||||
type InheritedParams = Empty;
|
||||
type Ok = T;
|
||||
type Err = E;
|
||||
}
|
||||
|
||||
impl<Context, F, Fut, T, E> HandlerFor<Context> for FromFnAsync<F, Fut, T, E, (Context,)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context) -> Fut + Send + Sync + Clone + 'static,
|
||||
Fut: Future<Output = Result<T, E>> + Send + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
(self.function)(handle_args.context).await
|
||||
}
|
||||
fn metadata(&self, _: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.metadata.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context, F, T, E, Params> HandlerTypes for FromFn<F, T, E, (Context, Params)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context, Params) -> Result<T, E> + Send + Sync + Clone + 'static,
|
||||
Params: DeserializeOwned + Send + Sync + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
type Params = Params;
|
||||
type InheritedParams = Empty;
|
||||
type Ok = T;
|
||||
type Err = E;
|
||||
}
|
||||
|
||||
impl<Context, F, T, E, Params> HandlerFor<Context> for FromFn<F, T, E, (Context, Params)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context, Params) -> Result<T, E> + Send + Sync + Clone + 'static,
|
||||
Params: DeserializeOwned + Send + Sync + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
let HandlerArgs {
|
||||
context, params, ..
|
||||
} = handle_args;
|
||||
(self.function)(context, params)
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
if self.blocking {
|
||||
self.handle_async_with_sync_blocking(handle_args).await
|
||||
} else {
|
||||
self.handle_async_with_sync(handle_args).await
|
||||
}
|
||||
}
|
||||
fn metadata(&self, _: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.metadata.clone()
|
||||
}
|
||||
}
|
||||
impl<Context, F, Fut, T, E, Params> HandlerTypes for FromFnAsync<F, Fut, T, E, (Context, Params)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context, Params) -> Fut + Send + Sync + Clone + 'static,
|
||||
Fut: Future<Output = Result<T, E>> + Send + 'static,
|
||||
Params: DeserializeOwned + Send + Sync + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
type Params = Params;
|
||||
type InheritedParams = Empty;
|
||||
type Ok = T;
|
||||
type Err = E;
|
||||
}
|
||||
|
||||
impl<Context, F, Fut, T, E, Params> HandlerFor<Context>
|
||||
for FromFnAsync<F, Fut, T, E, (Context, Params)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context, Params) -> Fut + Send + Sync + Clone + 'static,
|
||||
Fut: Future<Output = Result<T, E>> + Send + 'static,
|
||||
Params: DeserializeOwned + Send + Sync + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
let HandlerArgs {
|
||||
context, params, ..
|
||||
} = handle_args;
|
||||
(self.function)(context, params).await
|
||||
}
|
||||
fn metadata(&self, _: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.metadata.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context, F, T, E, Params, InheritedParams> HandlerTypes
|
||||
for FromFn<F, T, E, (Context, Params, InheritedParams)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context, Params, InheritedParams) -> Result<T, E> + Send + Sync + Clone + 'static,
|
||||
Params: DeserializeOwned + Send + Sync + 'static,
|
||||
InheritedParams: Send + Sync + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
type Params = Params;
|
||||
type InheritedParams = InheritedParams;
|
||||
type Ok = T;
|
||||
type Err = E;
|
||||
}
|
||||
|
||||
impl<Context, F, T, E, Params, InheritedParams> HandlerFor<Context>
|
||||
for FromFn<F, T, E, (Context, Params, InheritedParams)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context, Params, InheritedParams) -> Result<T, E> + Send + Sync + Clone + 'static,
|
||||
Params: DeserializeOwned + Send + Sync + 'static,
|
||||
InheritedParams: Send + Sync + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
let HandlerArgs {
|
||||
context,
|
||||
params,
|
||||
inherited_params,
|
||||
..
|
||||
} = handle_args;
|
||||
(self.function)(context, params, inherited_params)
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
if self.blocking {
|
||||
self.handle_async_with_sync_blocking(handle_args).await
|
||||
} else {
|
||||
self.handle_async_with_sync(handle_args).await
|
||||
}
|
||||
}
|
||||
fn metadata(&self, _: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.metadata.clone()
|
||||
}
|
||||
}
|
||||
impl<Context, F, Fut, T, E, Params, InheritedParams> HandlerTypes
|
||||
for FromFnAsync<F, Fut, T, E, (Context, Params, InheritedParams)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context, Params, InheritedParams) -> Fut + Send + Sync + Clone + 'static,
|
||||
Fut: Future<Output = Result<T, E>> + Send + 'static,
|
||||
Params: DeserializeOwned + Send + Sync + 'static,
|
||||
InheritedParams: Send + Sync + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
type Params = Params;
|
||||
type InheritedParams = InheritedParams;
|
||||
type Ok = T;
|
||||
type Err = E;
|
||||
}
|
||||
|
||||
impl<Context, F, Fut, T, E, Params, InheritedParams> HandlerFor<Context>
|
||||
for FromFnAsync<F, Fut, T, E, (Context, Params, InheritedParams)>
|
||||
where
|
||||
Context: crate::Context,
|
||||
F: Fn(Context, Params, InheritedParams) -> Fut + Send + Sync + Clone + 'static,
|
||||
Fut: Future<Output = Result<T, E>> + Send + 'static,
|
||||
Params: DeserializeOwned + Send + Sync + 'static,
|
||||
InheritedParams: Send + Sync + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + 'static,
|
||||
{
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
let HandlerArgs {
|
||||
context,
|
||||
params,
|
||||
inherited_params,
|
||||
..
|
||||
} = handle_args;
|
||||
(self.function)(context, params, inherited_params).await
|
||||
}
|
||||
fn metadata(&self, _: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.metadata.clone()
|
||||
}
|
||||
}
|
||||
426
src/handler/mod.rs
Normal file
426
src/handler/mod.rs
Normal file
@@ -0,0 +1,426 @@
|
||||
use std::any::TypeId;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::{ArgMatches, Command, Parser};
|
||||
use futures::Future;
|
||||
use imbl_value::imbl::OrdMap;
|
||||
use imbl_value::Value;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yajrc::RpcError;
|
||||
|
||||
use crate::util::{internal_error, invalid_params, Flat};
|
||||
|
||||
pub mod adapters;
|
||||
pub mod from_fn;
|
||||
pub mod parent;
|
||||
|
||||
pub use adapters::*;
|
||||
pub use from_fn::*;
|
||||
pub use parent::*;
|
||||
|
||||
pub(crate) struct HandleAnyArgs<Context, Inherited> {
|
||||
pub(crate) context: Context,
|
||||
pub(crate) parent_method: VecDeque<&'static str>,
|
||||
pub(crate) method: VecDeque<&'static str>,
|
||||
pub(crate) params: Value,
|
||||
pub(crate) inherited: Inherited,
|
||||
}
|
||||
impl<Context: crate::Context, Inherited: Send + Sync> HandleAnyArgs<Context, Inherited> {
|
||||
fn downcast<H>(self) -> Result<HandlerArgsFor<Context, H>, imbl_value::Error>
|
||||
where
|
||||
H: HandlerTypes,
|
||||
H::InheritedParams: OrEmpty<Inherited>,
|
||||
H::Params: DeserializeOwned,
|
||||
{
|
||||
let Self {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params,
|
||||
inherited,
|
||||
} = self;
|
||||
Ok(HandlerArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params: imbl_value::from_value(params.clone())?,
|
||||
inherited_params: OrEmpty::from_t(inherited),
|
||||
raw_params: params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub(crate) trait HandleAny<Context>: Send + Sync {
|
||||
type Inherited: Send;
|
||||
fn handle_sync(
|
||||
&self,
|
||||
handle_args: HandleAnyArgs<Context, Self::Inherited>,
|
||||
) -> Result<Value, RpcError>;
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandleAnyArgs<Context, Self::Inherited>,
|
||||
) -> Result<Value, RpcError>;
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value>;
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>>;
|
||||
fn cli(&self) -> Option<&dyn CliBindingsAny<Context, Inherited = Self::Inherited>>;
|
||||
}
|
||||
#[async_trait::async_trait]
|
||||
impl<Context: crate::Context, T: HandleAny<Context>> HandleAny<Context> for Arc<T> {
|
||||
type Inherited = T::Inherited;
|
||||
fn handle_sync(
|
||||
&self,
|
||||
handle_args: HandleAnyArgs<Context, Self::Inherited>,
|
||||
) -> Result<Value, RpcError> {
|
||||
self.deref().handle_sync(handle_args)
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandleAnyArgs<Context, Self::Inherited>,
|
||||
) -> Result<Value, RpcError> {
|
||||
self.deref().handle_async(handle_args).await
|
||||
}
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.deref().metadata(method)
|
||||
}
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
self.deref().method_from_dots(method)
|
||||
}
|
||||
fn cli(&self) -> Option<&dyn CliBindingsAny<Context, Inherited = Self::Inherited>> {
|
||||
self.deref().cli()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait CliBindingsAny<Context> {
|
||||
type Inherited;
|
||||
fn cli_command(&self) -> Command;
|
||||
fn cli_parse(
|
||||
&self,
|
||||
matches: &ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error>;
|
||||
fn cli_display(
|
||||
&self,
|
||||
handle_args: HandleAnyArgs<Context, Self::Inherited>,
|
||||
result: Value,
|
||||
) -> Result<(), RpcError>;
|
||||
}
|
||||
|
||||
pub trait CliBindings<Context: crate::Context>: HandlerTypes {
|
||||
const NO_CLI: bool = false;
|
||||
fn cli_command(&self) -> Command;
|
||||
fn cli_parse(
|
||||
&self,
|
||||
matches: &ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error>;
|
||||
fn cli_display(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err>;
|
||||
}
|
||||
|
||||
pub trait PrintCliResult<Context: crate::Context>: HandlerTypes {
|
||||
fn print(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err>;
|
||||
}
|
||||
|
||||
#[allow(private_interfaces)]
|
||||
pub struct DynHandler<Context, Inherited>(Arc<dyn HandleAny<Context, Inherited = Inherited>>);
|
||||
impl<Context: crate::Context, Inherited> DynHandler<Context, Inherited> {
|
||||
pub fn new<C, H>(handler: H) -> Option<Self>
|
||||
where
|
||||
C: crate::Context,
|
||||
WithContext<C, H>: Handler<Inherited>,
|
||||
{
|
||||
WithContext::<C, _>::new(handler).handler_for::<Context>()
|
||||
}
|
||||
}
|
||||
impl<Context, Inherited> Clone for DynHandler<Context, Inherited> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
impl<Context, Inherited> Debug for DynHandler<Context, Inherited> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("DynHandler").finish()
|
||||
}
|
||||
}
|
||||
#[async_trait::async_trait]
|
||||
impl<Context: crate::Context, Inherited: Send> HandleAny<Context>
|
||||
for DynHandler<Context, Inherited>
|
||||
{
|
||||
type Inherited = Inherited;
|
||||
fn handle_sync(
|
||||
&self,
|
||||
handle_args: HandleAnyArgs<Context, Self::Inherited>,
|
||||
) -> Result<Value, RpcError> {
|
||||
self.0.handle_sync(handle_args)
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandleAnyArgs<Context, Self::Inherited>,
|
||||
) -> Result<Value, RpcError> {
|
||||
self.0.handle_async(handle_args).await
|
||||
}
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.0.metadata(method)
|
||||
}
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
self.0.method_from_dots(method)
|
||||
}
|
||||
fn cli(&self) -> Option<&dyn CliBindingsAny<Context, Inherited = Self::Inherited>> {
|
||||
self.0.cli()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(type_alias_bounds)]
|
||||
pub type HandlerArgsFor<Context: crate::Context, H: HandlerTypes + ?Sized> =
|
||||
HandlerArgs<Context, H::Params, H::InheritedParams>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HandlerArgs<
|
||||
Context: crate::Context,
|
||||
Params: Send + Sync = Empty,
|
||||
InheritedParams: Send + Sync = Empty,
|
||||
> {
|
||||
pub context: Context,
|
||||
pub parent_method: VecDeque<&'static str>,
|
||||
pub method: VecDeque<&'static str>,
|
||||
pub params: Params,
|
||||
pub inherited_params: InheritedParams,
|
||||
pub raw_params: Value,
|
||||
}
|
||||
|
||||
pub trait HandlerTypes {
|
||||
type Params: Send + Sync;
|
||||
type InheritedParams: Send + Sync;
|
||||
type Ok: Send + Sync;
|
||||
type Err: Send + Sync;
|
||||
}
|
||||
|
||||
pub trait HandlerFor<Context: crate::Context>:
|
||||
HandlerTypes + Clone + Send + Sync + 'static
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
if let Some(rt) = handle_args.context.runtime() {
|
||||
rt.block_on(self.handle_async(handle_args))
|
||||
} else {
|
||||
tokio::runtime::Handle::current().block_on(self.handle_async(handle_args))
|
||||
}
|
||||
}
|
||||
fn handle_async(
|
||||
&self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> impl Future<Output = Result<Self::Ok, Self::Err>> + Send;
|
||||
fn handle_async_with_sync<'a>(
|
||||
&'a self,
|
||||
handle_args: HandlerArgsFor<Context, Self>,
|
||||
) -> impl Future<Output = Result<Self::Ok, Self::Err>> + Send + 'a {
|
||||
async move { self.handle_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 {
|
||||
async move {
|
||||
let s = self.clone();
|
||||
if let Some(rt) = handle_args.context.runtime() {
|
||||
rt.spawn_blocking(move || s.handle_sync(handle_args)).await
|
||||
} else {
|
||||
tokio::runtime::Handle::current()
|
||||
.spawn_blocking(move || s.handle_sync(handle_args))
|
||||
.await
|
||||
}
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
OrdMap::new()
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
if method.is_empty() {
|
||||
Some(VecDeque::new())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Handler<Inherited> {
|
||||
type H: HandlerTypes;
|
||||
fn handler_for<C: crate::Context>(self) -> Option<DynHandler<C, Inherited>>;
|
||||
}
|
||||
|
||||
pub struct WithContext<Context, H> {
|
||||
_phantom: PhantomData<Context>,
|
||||
handler: H,
|
||||
}
|
||||
impl<Context, H> WithContext<Context, H> {
|
||||
pub fn new(handler: H) -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData,
|
||||
handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context, Inherited, H> Handler<Inherited> for WithContext<Context, H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: HandlerFor<Context> + CliBindings<Context>,
|
||||
H::Ok: Serialize + DeserializeOwned,
|
||||
H::Params: DeserializeOwned,
|
||||
H::InheritedParams: OrEmpty<Inherited>,
|
||||
RpcError: From<H::Err>,
|
||||
Inherited: Send + Sync + 'static,
|
||||
{
|
||||
type H = H;
|
||||
fn handler_for<C: crate::Context>(self) -> Option<DynHandler<C, Inherited>> {
|
||||
if TypeId::of::<Context>() == TypeId::of::<C>() {
|
||||
Some(unsafe {
|
||||
std::mem::transmute::<DynHandler<Context, Inherited>, DynHandler<C, Inherited>>(
|
||||
DynHandler(Arc::new(AnyHandler::new(self.handler))),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AnyHandler<Context, Inherited, H> {
|
||||
_phantom: PhantomData<(Context, Inherited)>,
|
||||
handler: H,
|
||||
}
|
||||
impl<Context, Inherited, H> AnyHandler<Context, Inherited, H> {
|
||||
pub(crate) fn new(handler: H) -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData,
|
||||
handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Context, Inherited, H: std::fmt::Debug> std::fmt::Debug for AnyHandler<Context, Inherited, H> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AnyHandler")
|
||||
.field("handler", &self.handler)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<Context, Inherited, H> HandleAny<Context> for AnyHandler<Context, Inherited, H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: HandlerFor<Context> + CliBindings<Context>,
|
||||
H::Params: DeserializeOwned,
|
||||
H::Ok: Serialize + DeserializeOwned,
|
||||
H::InheritedParams: OrEmpty<Inherited>,
|
||||
RpcError: From<H::Err>,
|
||||
Inherited: Send + Sync,
|
||||
{
|
||||
type Inherited = Inherited;
|
||||
fn handle_sync(
|
||||
&self,
|
||||
handle_args: HandleAnyArgs<Context, Self::Inherited>,
|
||||
) -> Result<Value, RpcError> {
|
||||
imbl_value::to_value(
|
||||
&self
|
||||
.handler
|
||||
.handle_sync(handle_args.downcast::<H>().map_err(invalid_params)?)?,
|
||||
)
|
||||
.map_err(internal_error)
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
handle_args: HandleAnyArgs<Context, Self::Inherited>,
|
||||
) -> Result<Value, RpcError> {
|
||||
imbl_value::to_value(
|
||||
&self
|
||||
.handler
|
||||
.handle_async(handle_args.downcast::<H>().map_err(invalid_params)?)
|
||||
.await?,
|
||||
)
|
||||
.map_err(internal_error)
|
||||
}
|
||||
fn metadata(&self, method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
self.handler.metadata(method)
|
||||
}
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
self.handler.method_from_dots(method)
|
||||
}
|
||||
fn cli(&self) -> Option<&dyn CliBindingsAny<Context, Inherited = Self::Inherited>> {
|
||||
if H::NO_CLI {
|
||||
None
|
||||
} else {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context, Inherited, H> CliBindingsAny<Context> for AnyHandler<Context, Inherited, H>
|
||||
where
|
||||
Context: crate::Context,
|
||||
H: CliBindings<Context>,
|
||||
H::Params: DeserializeOwned,
|
||||
H::Ok: Serialize + DeserializeOwned,
|
||||
RpcError: From<H::Err>,
|
||||
H::InheritedParams: OrEmpty<Inherited>,
|
||||
Inherited: Send + Sync,
|
||||
{
|
||||
type Inherited = Inherited;
|
||||
fn cli_command(&self) -> Command {
|
||||
self.handler.cli_command()
|
||||
}
|
||||
fn cli_parse(
|
||||
&self,
|
||||
matches: &ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
|
||||
self.handler.cli_parse(matches)
|
||||
}
|
||||
fn cli_display(
|
||||
&self,
|
||||
handle_args: HandleAnyArgs<Context, Self::Inherited>,
|
||||
result: Value,
|
||||
) -> Result<(), RpcError> {
|
||||
self.handler
|
||||
.cli_display(
|
||||
handle_args.downcast::<H>().map_err(invalid_params)?,
|
||||
imbl_value::from_value(result).map_err(internal_error)?,
|
||||
)
|
||||
.map_err(RpcError::from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, Parser)]
|
||||
pub struct Empty {}
|
||||
|
||||
pub(crate) trait OrEmpty<T> {
|
||||
fn from_t(t: T) -> Self;
|
||||
}
|
||||
impl<T> OrEmpty<T> for T {
|
||||
fn from_t(t: T) -> Self {
|
||||
t
|
||||
}
|
||||
}
|
||||
impl<A, B> OrEmpty<Flat<A, B>> for Empty {
|
||||
fn from_t(_: Flat<A, B>) -> Self {
|
||||
Empty {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, Parser)]
|
||||
pub enum Never {}
|
||||
389
src/handler/parent.rs
Normal file
389
src/handler/parent.rs
Normal file
@@ -0,0 +1,389 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use clap::{ArgMatches, Command, CommandFactory, FromArgMatches};
|
||||
use imbl_value::imbl::OrdMap;
|
||||
use imbl_value::Value;
|
||||
use serde::Serialize;
|
||||
use yajrc::RpcError;
|
||||
|
||||
use crate::util::{combine, Flat, PhantomData};
|
||||
use crate::{
|
||||
CliBindings, DynHandler, Empty, HandleAny, HandleAnyArgs, Handler, HandlerArgs, HandlerArgsFor,
|
||||
HandlerFor, HandlerTypes, WithContext,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub(crate) struct Name(pub(crate) &'static str);
|
||||
impl<'a> std::borrow::Borrow<&'a str> for Name {
|
||||
fn borrow(&self) -> &&'a str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SubcommandMap<Context, Params, InheritedParams>(
|
||||
pub(crate) Option<DynHandler<Context, InheritedParams>>,
|
||||
pub(crate) OrdMap<Name, DynHandler<Context, Flat<Params, InheritedParams>>>,
|
||||
);
|
||||
impl<Context, Params, InheritedParams> Clone for SubcommandMap<Context, Params, InheritedParams> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone(), self.1.clone())
|
||||
}
|
||||
}
|
||||
impl<Context, Params, InheritedParams> Debug for SubcommandMap<Context, Params, InheritedParams> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut map = f.debug_map();
|
||||
if let Some(root) = &self.0 {
|
||||
#[derive(Debug)]
|
||||
struct Root;
|
||||
map.entry(&Root, root);
|
||||
}
|
||||
map.entries(self.1.iter()).finish()
|
||||
}
|
||||
}
|
||||
impl<Context, Params, InheritedParams> SubcommandMap<Context, Params, InheritedParams> {
|
||||
fn set_root(&mut self, handler: DynHandler<Context, InheritedParams>) {
|
||||
self.0 = Some(handler);
|
||||
}
|
||||
fn get_root<'a>(&'a self) -> Option<&'a DynHandler<Context, InheritedParams>> {
|
||||
self.0.as_ref()
|
||||
}
|
||||
fn insert(
|
||||
&mut self,
|
||||
name: &'static str,
|
||||
handler: DynHandler<Context, Flat<Params, InheritedParams>>,
|
||||
) {
|
||||
self.1.insert(Name(name), handler);
|
||||
}
|
||||
fn get<'a>(
|
||||
&'a self,
|
||||
name: &str,
|
||||
) -> Option<(Name, &'a DynHandler<Context, Flat<Params, InheritedParams>>)> {
|
||||
if let Some((name, handler)) = self.1.get_key_value(&name) {
|
||||
Some((*name, handler))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ParentHandler<Context, Params = Empty, InheritedParams = Empty> {
|
||||
_phantom: PhantomData<Context>,
|
||||
pub(crate) subcommands: SubcommandMap<Context, Params, InheritedParams>,
|
||||
metadata: OrdMap<&'static str, Value>,
|
||||
}
|
||||
impl<Context, Params, InheritedParams> ParentHandler<Context, Params, InheritedParams> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData::new(),
|
||||
subcommands: SubcommandMap(None, OrdMap::new()),
|
||||
metadata: OrdMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn with_metadata(mut self, key: &'static str, value: Value) -> Self {
|
||||
self.metadata.insert(key, value);
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<Context, Params, InheritedParams> Clone for ParentHandler<Context, Params, InheritedParams> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData::new(),
|
||||
subcommands: self.subcommands.clone(),
|
||||
metadata: self.metadata.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Context, Params, InheritedParams> std::fmt::Debug
|
||||
for ParentHandler<Context, Params, InheritedParams>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("ParentHandler")
|
||||
.field(&self.subcommands)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context: crate::Context, Params, InheritedParams>
|
||||
ParentHandler<Context, Params, InheritedParams>
|
||||
{
|
||||
pub fn subcommand<C: crate::Context, H>(mut self, name: &'static str, handler: H) -> Self
|
||||
where
|
||||
WithContext<C, H>: Handler<Flat<Params, InheritedParams>>,
|
||||
{
|
||||
if let Some(h) = DynHandler::new(handler) {
|
||||
self.subcommands.insert(name.into(), h);
|
||||
}
|
||||
self
|
||||
}
|
||||
pub fn root_handler<C: crate::Context, H>(mut self, handler: H) -> Self
|
||||
where
|
||||
WithContext<C, H>: Handler<InheritedParams>,
|
||||
<WithContext<C, H> as Handler<InheritedParams>>::H: HandlerTypes<Params = Params>,
|
||||
{
|
||||
if let Some(h) = DynHandler::new(handler) {
|
||||
self.subcommands.set_root(h);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context, Params, InheritedParams> HandlerTypes
|
||||
for ParentHandler<Context, Params, InheritedParams>
|
||||
where
|
||||
Params: Send + Sync,
|
||||
InheritedParams: Send + Sync,
|
||||
{
|
||||
type Params = Params;
|
||||
type InheritedParams = InheritedParams;
|
||||
type Ok = Value;
|
||||
type Err = RpcError;
|
||||
}
|
||||
|
||||
impl<Context, Params, InheritedParams> HandlerFor<Context>
|
||||
for ParentHandler<Context, Params, InheritedParams>
|
||||
where
|
||||
Context: crate::Context,
|
||||
Params: Send + Sync + 'static,
|
||||
InheritedParams: Send + Sync + 'static,
|
||||
{
|
||||
fn handle_sync(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
mut parent_method,
|
||||
mut method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
let cmd = method.pop_front();
|
||||
if let Some(cmd) = cmd {
|
||||
parent_method.push_back(cmd);
|
||||
if let Some((_, sub_handler)) = &self.subcommands.get(cmd) {
|
||||
sub_handler.handle_sync(HandleAnyArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params: raw_params,
|
||||
inherited: Flat(params, inherited_params),
|
||||
})
|
||||
} else {
|
||||
Err(yajrc::METHOD_NOT_FOUND_ERROR)
|
||||
}
|
||||
} else {
|
||||
if let Some(sub_handler) = &self.subcommands.get_root() {
|
||||
sub_handler.handle_sync(HandleAnyArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params: raw_params,
|
||||
inherited: inherited_params,
|
||||
})
|
||||
} else {
|
||||
Err(yajrc::METHOD_NOT_FOUND_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn handle_async(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
mut parent_method,
|
||||
mut method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
) -> Result<Self::Ok, Self::Err> {
|
||||
let cmd = method.pop_front();
|
||||
if let Some(cmd) = cmd {
|
||||
parent_method.push_back(cmd);
|
||||
if let Some((_, sub_handler)) = &self.subcommands.get(cmd) {
|
||||
sub_handler
|
||||
.handle_async(HandleAnyArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params: raw_params,
|
||||
inherited: Flat(params, inherited_params),
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
Err(yajrc::METHOD_NOT_FOUND_ERROR)
|
||||
}
|
||||
} else {
|
||||
if let Some(sub_handler) = &self.subcommands.get_root() {
|
||||
sub_handler
|
||||
.handle_async(HandleAnyArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params: raw_params,
|
||||
inherited: inherited_params,
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
Err(yajrc::METHOD_NOT_FOUND_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn metadata(&self, mut method: VecDeque<&'static str>) -> OrdMap<&'static str, Value> {
|
||||
let metadata = self.metadata.clone();
|
||||
if let Some(cmd) = method.pop_front() {
|
||||
if let Some((_, handler)) = self.subcommands.get(cmd) {
|
||||
handler.metadata(method).union(metadata)
|
||||
} else {
|
||||
metadata
|
||||
}
|
||||
} else {
|
||||
if let Some(handler) = self.subcommands.get_root() {
|
||||
handler.metadata(method).union(metadata)
|
||||
} else {
|
||||
metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
fn method_from_dots(&self, method: &str) -> Option<VecDeque<&'static str>> {
|
||||
let (head, tail) = if method.is_empty() {
|
||||
(None, None)
|
||||
} else {
|
||||
method
|
||||
.split_once(".")
|
||||
.map(|(head, tail)| (Some(head), Some(tail)))
|
||||
.unwrap_or((Some(method), None))
|
||||
};
|
||||
if let Some(head) = head {
|
||||
let (Name(name), h) = self.subcommands.get(head)?;
|
||||
let mut res = VecDeque::new();
|
||||
res.push_back(name);
|
||||
if let Some(tail) = tail {
|
||||
res.append(&mut h.method_from_dots(tail)?);
|
||||
}
|
||||
Some(res)
|
||||
} else {
|
||||
let h = self.subcommands.get_root()?;
|
||||
let mut res = VecDeque::new();
|
||||
if let Some(tail) = tail {
|
||||
res.append(&mut h.method_from_dots(tail)?);
|
||||
}
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context, Params, InheritedParams> CliBindings<Context>
|
||||
for ParentHandler<Context, Params, InheritedParams>
|
||||
where
|
||||
Context: crate::Context,
|
||||
Params: FromArgMatches + CommandFactory + Serialize + Send + Sync + 'static,
|
||||
InheritedParams: Send + Sync + 'static,
|
||||
{
|
||||
fn cli_command(&self) -> Command {
|
||||
let mut base = if let Some(cli) = &self.subcommands.0.as_ref().and_then(|h| h.cli()) {
|
||||
cli.cli_command().subcommand_required(false)
|
||||
} else {
|
||||
Params::command().subcommand_required(true)
|
||||
};
|
||||
for (name, handler) in &self.subcommands.1 {
|
||||
match (name, handler.cli()) {
|
||||
(Name(name), Some(cli)) => {
|
||||
base = base.subcommand(cli.cli_command().name(name));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
base
|
||||
}
|
||||
fn cli_parse(
|
||||
&self,
|
||||
root_matches: &ArgMatches,
|
||||
) -> Result<(VecDeque<&'static str>, Value), clap::Error> {
|
||||
let (name, matches) = match root_matches.subcommand() {
|
||||
Some((name, matches)) => (Some(name), matches),
|
||||
None => (None, root_matches),
|
||||
};
|
||||
if let Some(name) = name {
|
||||
let root_params = imbl_value::to_value(&Params::from_arg_matches(root_matches)?)
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e))?;
|
||||
if let Some((Name(name), cli)) = self
|
||||
.subcommands
|
||||
.get(name)
|
||||
.and_then(|(n, h)| h.cli().map(|c| (n, c)))
|
||||
{
|
||||
let (mut method, params) = cli.cli_parse(matches)?;
|
||||
method.push_front(name);
|
||||
|
||||
Ok((
|
||||
method,
|
||||
combine(root_params, params).map_err(|e| {
|
||||
clap::Error::raw(clap::error::ErrorKind::ArgumentConflict, e)
|
||||
})?,
|
||||
))
|
||||
} else {
|
||||
Ok((VecDeque::new(), root_params))
|
||||
}
|
||||
} else {
|
||||
if let Some(cli) = self.subcommands.get_root().and_then(|h| h.cli()) {
|
||||
let (method, params) = cli.cli_parse(matches)?;
|
||||
|
||||
Ok((method, params))
|
||||
} else {
|
||||
let root_params = imbl_value::to_value(&Params::from_arg_matches(matches)?)
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::ValueValidation, e))?;
|
||||
Ok((VecDeque::new(), root_params))
|
||||
}
|
||||
}
|
||||
}
|
||||
fn cli_display(
|
||||
&self,
|
||||
HandlerArgs {
|
||||
context,
|
||||
mut parent_method,
|
||||
mut method,
|
||||
params,
|
||||
inherited_params,
|
||||
raw_params,
|
||||
}: HandlerArgsFor<Context, Self>,
|
||||
result: Self::Ok,
|
||||
) -> Result<(), Self::Err> {
|
||||
let cmd = method.pop_front();
|
||||
if let Some(cmd) = cmd {
|
||||
parent_method.push_back(cmd);
|
||||
if let Some((_, cli)) = self
|
||||
.subcommands
|
||||
.get(cmd)
|
||||
.and_then(|(n, h)| h.cli().map(|c| (n, c)))
|
||||
{
|
||||
cli.cli_display(
|
||||
HandleAnyArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params: raw_params,
|
||||
inherited: Flat(params, inherited_params),
|
||||
},
|
||||
result,
|
||||
)
|
||||
} else {
|
||||
Err(yajrc::METHOD_NOT_FOUND_ERROR)
|
||||
}
|
||||
} else {
|
||||
if let Some(cli) = self.subcommands.get_root().and_then(|h| h.cli()) {
|
||||
cli.cli_display(
|
||||
HandleAnyArgs {
|
||||
context,
|
||||
parent_method,
|
||||
method,
|
||||
params: raw_params,
|
||||
inherited: inherited_params,
|
||||
},
|
||||
result,
|
||||
)
|
||||
} else {
|
||||
Err(yajrc::METHOD_NOT_FOUND_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/lib.rs
Normal file
15
src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
#![cfg_attr(feature = "nightly", feature(const_trait_impl, const_type_id))]
|
||||
|
||||
pub use cli::*;
|
||||
// pub use command::*;
|
||||
pub use context::*;
|
||||
pub use handler::*;
|
||||
pub use server::*;
|
||||
pub use {clap, futures, reqwest, serde, serde_json, tokio, url, yajrc};
|
||||
|
||||
mod cli;
|
||||
pub mod command_helpers;
|
||||
mod context;
|
||||
mod handler;
|
||||
mod server;
|
||||
pub mod util;
|
||||
302
src/server/http.rs
Normal file
302
src/server/http.rs
Normal file
@@ -0,0 +1,302 @@
|
||||
use axum::body::Body;
|
||||
use axum::extract::Request;
|
||||
use axum::handler::Handler;
|
||||
use axum::response::Response;
|
||||
use futures::future::{join_all, BoxFuture};
|
||||
use futures::{Future, FutureExt};
|
||||
use http::header::{CONTENT_LENGTH, CONTENT_TYPE};
|
||||
use http_body_util::BodyExt;
|
||||
use imbl_value::imbl::Vector;
|
||||
use imbl_value::Value;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use yajrc::{RpcError, RpcMethod};
|
||||
|
||||
use crate::server::{RpcRequest, RpcResponse, SingleOrBatchRpcRequest};
|
||||
use crate::util::{internal_error, parse_error};
|
||||
use crate::{HandleAny, Server};
|
||||
|
||||
const FALLBACK_ERROR: &str = "{\"error\":{\"code\":-32603,\"message\":\"Internal error\",\"data\":\"Failed to serialize rpc response\"}}";
|
||||
|
||||
pub fn fallback_rpc_error_response() -> Response {
|
||||
Response::builder()
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.header(CONTENT_LENGTH, FALLBACK_ERROR.len())
|
||||
.body(Body::from(FALLBACK_ERROR.as_bytes()))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn json_http_response<T: Serialize>(t: &T) -> Response {
|
||||
let body = match serde_json::to_vec(t) {
|
||||
Ok(a) => a,
|
||||
Err(_) => return fallback_rpc_error_response(),
|
||||
};
|
||||
Response::builder()
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.header(CONTENT_LENGTH, body.len())
|
||||
.body(Body::from(body))
|
||||
.unwrap_or_else(|_| fallback_rpc_error_response())
|
||||
}
|
||||
|
||||
pub trait Middleware<Context: Send + 'static>: Clone + Send + Sync + 'static {
|
||||
type Metadata: DeserializeOwned + Send + 'static;
|
||||
#[allow(unused_variables)]
|
||||
fn process_http_request(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
request: &mut Request,
|
||||
) -> impl Future<Output = Result<(), Response>> + Send {
|
||||
async { Ok(()) }
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn process_rpc_request(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
metadata: Self::Metadata,
|
||||
request: &mut RpcRequest,
|
||||
) -> impl Future<Output = Result<(), RpcResponse>> + Send {
|
||||
async { Ok(()) }
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn process_rpc_response(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
response: &mut RpcResponse,
|
||||
) -> impl Future<Output = ()> + Send {
|
||||
async { () }
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
fn process_http_response(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
response: &mut Response,
|
||||
) -> impl Future<Output = ()> + Send {
|
||||
async { () }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(private_bounds)]
|
||||
trait _Middleware<Context>: Send + Sync {
|
||||
fn dyn_clone(&self) -> DynMiddleware<Context>;
|
||||
fn process_http_request<'a>(
|
||||
&'a mut self,
|
||||
context: &'a Context,
|
||||
request: &'a mut Request,
|
||||
) -> BoxFuture<'a, Result<(), Response>>;
|
||||
fn process_rpc_request<'a>(
|
||||
&'a mut self,
|
||||
context: &'a Context,
|
||||
metadata: Value,
|
||||
request: &'a mut RpcRequest,
|
||||
) -> BoxFuture<'a, Result<(), RpcResponse>>;
|
||||
fn process_rpc_response<'a>(
|
||||
&'a mut self,
|
||||
|
||||
context: &'a Context,
|
||||
response: &'a mut RpcResponse,
|
||||
) -> BoxFuture<'a, ()>;
|
||||
fn process_http_response<'a>(
|
||||
&'a mut self,
|
||||
context: &'a Context,
|
||||
response: &'a mut Response,
|
||||
) -> BoxFuture<'a, ()>;
|
||||
}
|
||||
impl<Context: Send + 'static, T: Middleware<Context> + Send + Sync> _Middleware<Context> for T {
|
||||
fn dyn_clone(&self) -> DynMiddleware<Context> {
|
||||
DynMiddleware(Box::new(<Self as Clone>::clone(&self)))
|
||||
}
|
||||
fn process_http_request<'a>(
|
||||
&'a mut self,
|
||||
context: &'a Context,
|
||||
request: &'a mut Request,
|
||||
) -> BoxFuture<'a, Result<(), Response>> {
|
||||
<Self as Middleware<Context>>::process_http_request(self, context, request).boxed()
|
||||
}
|
||||
fn process_rpc_request<'a>(
|
||||
&'a mut self,
|
||||
context: &'a Context,
|
||||
metadata: Value,
|
||||
request: &'a mut RpcRequest,
|
||||
) -> BoxFuture<'a, Result<(), RpcResponse>> {
|
||||
<Self as Middleware<Context>>::process_rpc_request(
|
||||
self,
|
||||
context,
|
||||
match imbl_value::from_value(metadata) {
|
||||
Ok(a) => a,
|
||||
Err(e) => return async { Err(internal_error(e).into()) }.boxed(),
|
||||
},
|
||||
request,
|
||||
)
|
||||
.boxed()
|
||||
}
|
||||
fn process_rpc_response<'a>(
|
||||
&'a mut self,
|
||||
context: &'a Context,
|
||||
response: &'a mut RpcResponse,
|
||||
) -> BoxFuture<'a, ()> {
|
||||
<Self as Middleware<Context>>::process_rpc_response(self, context, response).boxed()
|
||||
}
|
||||
fn process_http_response<'a>(
|
||||
&'a mut self,
|
||||
context: &'a Context,
|
||||
response: &'a mut Response,
|
||||
) -> BoxFuture<'a, ()> {
|
||||
<Self as Middleware<Context>>::process_http_response(self, context, response).boxed()
|
||||
}
|
||||
}
|
||||
|
||||
struct DynMiddleware<Context>(Box<dyn _Middleware<Context>>);
|
||||
impl<Context> Clone for DynMiddleware<Context> {
|
||||
fn clone(&self) -> Self {
|
||||
self.0.dyn_clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HttpServer<Context: crate::Context> {
|
||||
inner: Server<Context>,
|
||||
middleware: Vector<DynMiddleware<Context>>,
|
||||
}
|
||||
impl<Context: crate::Context> Clone for HttpServer<Context> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
middleware: self.middleware.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Context: crate::Context> Server<Context> {
|
||||
pub fn for_http(self) -> HttpServer<Context> {
|
||||
HttpServer {
|
||||
inner: self,
|
||||
middleware: Vector::new(),
|
||||
}
|
||||
}
|
||||
pub fn middleware<T: Middleware<Context>>(self, middleware: T) -> HttpServer<Context> {
|
||||
self.for_http().middleware(middleware)
|
||||
}
|
||||
}
|
||||
impl<Context: crate::Context> HttpServer<Context> {
|
||||
pub fn middleware<T: Middleware<Context>>(mut self, middleware: T) -> Self {
|
||||
self.middleware
|
||||
.push_back(DynMiddleware(Box::new(middleware)));
|
||||
self
|
||||
}
|
||||
async fn process_http_request(&self, mut req: Request) -> Response {
|
||||
let mut mid = self.middleware.clone();
|
||||
match async {
|
||||
let ctx = (self.inner.make_ctx)().await?;
|
||||
for middleware in mid.iter_mut().rev() {
|
||||
if let Err(e) = middleware.0.process_http_request(&ctx, &mut req).await {
|
||||
return Ok::<_, RpcError>(e);
|
||||
}
|
||||
}
|
||||
let (_, body) = req.into_parts();
|
||||
match serde_json::from_slice::<SingleOrBatchRpcRequest>(
|
||||
&*body.collect().await.map_err(internal_error)?.to_bytes(),
|
||||
)
|
||||
.map_err(parse_error)?
|
||||
{
|
||||
SingleOrBatchRpcRequest::Single(rpc_req) => {
|
||||
let mut res = json_http_response(
|
||||
&self.process_rpc_request(&ctx, &mut mid, rpc_req).await,
|
||||
);
|
||||
for middleware in mid.iter_mut() {
|
||||
middleware.0.process_http_response(&ctx, &mut res).await;
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
SingleOrBatchRpcRequest::Batch(rpc_reqs) => {
|
||||
let (mids, rpc_res): (Vec<_>, Vec<_>) =
|
||||
join_all(rpc_reqs.into_iter().map(|rpc_req| async {
|
||||
let mut mid = mid.clone();
|
||||
let res = self.process_rpc_request(&ctx, &mut mid, rpc_req).await;
|
||||
(mid, res)
|
||||
}))
|
||||
.await
|
||||
.into_iter()
|
||||
.unzip();
|
||||
let mut res = json_http_response(&rpc_res);
|
||||
for mut mid in mids.into_iter().fold(
|
||||
vec![Vec::with_capacity(rpc_res.len()); mid.len()],
|
||||
|mut acc, x| {
|
||||
for (idx, middleware) in x.into_iter().enumerate() {
|
||||
acc[idx].push(middleware);
|
||||
}
|
||||
acc
|
||||
},
|
||||
) {
|
||||
for middleware in mid.iter_mut() {
|
||||
middleware.0.process_http_response(&ctx, &mut res).await;
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
.await
|
||||
{
|
||||
Ok(a) => a,
|
||||
Err(e) => json_http_response(&RpcResponse {
|
||||
id: None,
|
||||
result: Err(e),
|
||||
}),
|
||||
}
|
||||
}
|
||||
async fn process_rpc_request(
|
||||
&self,
|
||||
ctx: &Context,
|
||||
mid: &mut Vector<DynMiddleware<Context>>,
|
||||
mut req: RpcRequest,
|
||||
) -> RpcResponse {
|
||||
let metadata = Value::Object(
|
||||
self.inner
|
||||
.root_handler
|
||||
.metadata(
|
||||
match self
|
||||
.inner
|
||||
.root_handler
|
||||
.method_from_dots(req.method.as_str())
|
||||
{
|
||||
Some(a) => a,
|
||||
None => {
|
||||
return RpcResponse {
|
||||
id: req.id,
|
||||
result: Err(yajrc::METHOD_NOT_FOUND_ERROR),
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key.into(), value))
|
||||
.collect(),
|
||||
);
|
||||
let mut res = async {
|
||||
for middleware in mid.iter_mut().rev() {
|
||||
if let Err(res) = middleware
|
||||
.0
|
||||
.process_rpc_request(ctx, metadata.clone(), &mut req)
|
||||
.await
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
self.inner.handle_single_request(req).await
|
||||
}
|
||||
.await;
|
||||
for middleware in mid.iter_mut() {
|
||||
middleware.0.process_rpc_response(ctx, &mut res).await;
|
||||
}
|
||||
res
|
||||
}
|
||||
pub fn handle(&self, req: Request) -> BoxFuture<'static, Response> {
|
||||
let server = self.clone();
|
||||
async move { server.process_http_request(req).await }.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context: crate::Context> Handler<(), ()> for HttpServer<Context> {
|
||||
type Future = BoxFuture<'static, Response>;
|
||||
fn call(self, req: Request, _: ()) -> Self::Future {
|
||||
self.handle(req)
|
||||
}
|
||||
}
|
||||
131
src/server/mod.rs
Normal file
131
src/server/mod.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures::future::{join_all, BoxFuture};
|
||||
use futures::{Future, FutureExt, Stream, StreamExt};
|
||||
use imbl_value::Value;
|
||||
use yajrc::{RpcError, RpcMethod};
|
||||
|
||||
use crate::util::{invalid_request, JobRunner};
|
||||
use crate::{AnyHandler, Empty, HandleAny, HandleAnyArgs, ParentHandler};
|
||||
|
||||
pub type GenericRpcMethod = yajrc::GenericRpcMethod<String, Value, Value>;
|
||||
pub type RpcRequest = yajrc::RpcRequest<GenericRpcMethod>;
|
||||
pub type RpcResponse = yajrc::RpcResponse<GenericRpcMethod>;
|
||||
pub type SingleOrBatchRpcRequest = yajrc::SingleOrBatchRpcRequest<GenericRpcMethod>;
|
||||
|
||||
pub mod http;
|
||||
pub mod socket;
|
||||
|
||||
pub use http::*;
|
||||
pub use socket::*;
|
||||
|
||||
pub struct Server<Context: crate::Context> {
|
||||
make_ctx: Arc<dyn Fn() -> BoxFuture<'static, Result<Context, RpcError>> + Send + Sync>,
|
||||
root_handler: Arc<AnyHandler<Context, Empty, ParentHandler<Context>>>,
|
||||
}
|
||||
impl<Context: crate::Context> Clone for Server<Context> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
make_ctx: self.make_ctx.clone(),
|
||||
root_handler: self.root_handler.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Context: crate::Context> Server<Context> {
|
||||
pub fn new<
|
||||
MakeCtx: Fn() -> Fut + Send + Sync + 'static,
|
||||
Fut: Future<Output = Result<Context, RpcError>> + Send + 'static,
|
||||
>(
|
||||
make_ctx: MakeCtx,
|
||||
root_handler: ParentHandler<Context>,
|
||||
) -> Self {
|
||||
Server {
|
||||
make_ctx: Arc::new(move || make_ctx().boxed()),
|
||||
root_handler: Arc::new(AnyHandler::new(root_handler)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_command(
|
||||
&self,
|
||||
method: &str,
|
||||
params: Value,
|
||||
) -> impl Future<Output = Result<Value, RpcError>> + Send + 'static {
|
||||
let (make_ctx, root_handler, method) = (
|
||||
self.make_ctx.clone(),
|
||||
self.root_handler.clone(),
|
||||
self.root_handler.method_from_dots(method),
|
||||
);
|
||||
|
||||
async move {
|
||||
root_handler
|
||||
.handle_async(HandleAnyArgs {
|
||||
context: make_ctx().await?,
|
||||
parent_method: VecDeque::new(),
|
||||
method: method.ok_or_else(|| yajrc::METHOD_NOT_FOUND_ERROR)?,
|
||||
params,
|
||||
inherited: crate::Empty {},
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_single_request(
|
||||
&self,
|
||||
RpcRequest { id, method, params }: RpcRequest,
|
||||
) -> impl Future<Output = RpcResponse> + Send + 'static {
|
||||
let handle = (|| Ok::<_, RpcError>(self.handle_command(method.as_str(), params)))();
|
||||
async move {
|
||||
RpcResponse {
|
||||
id,
|
||||
result: match handle {
|
||||
Ok(handle) => handle.await,
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle(
|
||||
&self,
|
||||
request: Result<Value, RpcError>,
|
||||
) -> BoxFuture<'static, Result<Value, imbl_value::Error>> {
|
||||
match request.and_then(|request| {
|
||||
imbl_value::from_value::<SingleOrBatchRpcRequest>(request).map_err(invalid_request)
|
||||
}) {
|
||||
Ok(SingleOrBatchRpcRequest::Single(req)) => {
|
||||
let fut = self.handle_single_request(req);
|
||||
async { imbl_value::to_value(&fut.await) }.boxed()
|
||||
}
|
||||
Ok(SingleOrBatchRpcRequest::Batch(reqs)) => {
|
||||
let futs: Vec<_> = reqs
|
||||
.into_iter()
|
||||
.map(|req| self.handle_single_request(req))
|
||||
.collect();
|
||||
async { imbl_value::to_value(&join_all(futs).await) }.boxed()
|
||||
}
|
||||
Err(e) => async {
|
||||
imbl_value::to_value(&RpcResponse {
|
||||
id: None,
|
||||
result: Err(e),
|
||||
})
|
||||
}
|
||||
.boxed(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stream<'a>(
|
||||
&'a self,
|
||||
requests: impl Stream<Item = Result<Value, RpcError>> + Send + 'a,
|
||||
) -> impl Stream<Item = Result<Value, imbl_value::Error>> + 'a {
|
||||
async_stream::try_stream! {
|
||||
let mut runner = JobRunner::new();
|
||||
let requests = requests.fuse().map(|req| self.handle(req));
|
||||
tokio::pin!(requests);
|
||||
|
||||
while let Some(res) = runner.next_result(&mut requests).await.transpose()? {
|
||||
yield res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/server/socket.rs
Normal file
95
src/server/socket.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures::{Future, Stream, StreamExt, TryStreamExt};
|
||||
use imbl_value::Value;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader};
|
||||
use tokio::net::{TcpListener, ToSocketAddrs, UnixListener};
|
||||
use tokio::sync::Notify;
|
||||
use yajrc::RpcError;
|
||||
|
||||
use crate::util::{parse_error, JobRunner, StreamUntil};
|
||||
use crate::Server;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ShutdownHandle(Arc<Notify>);
|
||||
impl ShutdownHandle {
|
||||
pub fn shutdown(self) {
|
||||
self.0.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context: crate::Context> Server<Context> {
|
||||
pub fn run_socket<'a, T: AsyncRead + AsyncWrite + Send>(
|
||||
&'a self,
|
||||
listener: impl Stream<Item = std::io::Result<T>> + 'a,
|
||||
error_handler: impl Fn(std::io::Error) + Sync + 'a,
|
||||
) -> (ShutdownHandle, impl Future<Output = ()> + 'a) {
|
||||
let shutdown = Arc::new(Notify::new());
|
||||
(ShutdownHandle(shutdown.clone()), async move {
|
||||
let mut runner = JobRunner::<std::io::Result<()>>::new();
|
||||
let jobs = StreamUntil::new(listener, shutdown.notified()).map(|pipe| async {
|
||||
let pipe = pipe?;
|
||||
let (r, mut w) = tokio::io::split(pipe);
|
||||
let stream = self.stream(
|
||||
tokio_stream::wrappers::LinesStream::new(BufReader::new(r).lines())
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INTERNAL_ERROR
|
||||
})
|
||||
.try_filter_map(|a| async move {
|
||||
Ok(if a.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(serde_json::from_str::<Value>(&a).map_err(parse_error)?)
|
||||
})
|
||||
}),
|
||||
);
|
||||
tokio::pin!(stream);
|
||||
while let Some(res) = stream.next().await {
|
||||
if let Err(e) = async {
|
||||
let mut buf = serde_json::to_vec(
|
||||
&res.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?,
|
||||
)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
||||
buf.push(b'\n');
|
||||
w.write_all(&buf).await
|
||||
}
|
||||
.await
|
||||
{
|
||||
error_handler(e)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
tokio::pin!(jobs);
|
||||
while let Some(res) = runner.next_result(&mut jobs).await {
|
||||
if let Err(e) = res {
|
||||
error_handler(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
pub fn run_unix<'a>(
|
||||
&'a self,
|
||||
path: impl AsRef<Path> + 'a,
|
||||
error_handler: impl Fn(std::io::Error) + Sync + 'a,
|
||||
) -> std::io::Result<(ShutdownHandle, impl Future<Output = ()> + 'a)> {
|
||||
let listener = UnixListener::bind(path)?;
|
||||
Ok(self.run_socket(
|
||||
tokio_stream::wrappers::UnixListenerStream::new(listener),
|
||||
error_handler,
|
||||
))
|
||||
}
|
||||
pub async fn run_tcp<'a>(
|
||||
&'a self,
|
||||
addr: impl ToSocketAddrs + 'a,
|
||||
error_handler: impl Fn(std::io::Error) + Sync + 'a,
|
||||
) -> std::io::Result<(ShutdownHandle, impl Future<Output = ()> + 'a)> {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
Ok(self.run_socket(
|
||||
tokio_stream::wrappers::TcpListenerStream::new(listener),
|
||||
error_handler,
|
||||
))
|
||||
}
|
||||
}
|
||||
315
src/util.rs
Normal file
315
src/util.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::task::Waker;
|
||||
|
||||
use futures::future::{BoxFuture, FusedFuture};
|
||||
use futures::stream::FusedStream;
|
||||
use futures::{Future, FutureExt, Stream, StreamExt};
|
||||
use imbl_value::Value;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::ser::Error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yajrc::RpcError;
|
||||
|
||||
pub fn extract<T: DeserializeOwned>(value: &Value) -> Result<T, RpcError> {
|
||||
imbl_value::from_value(value.clone()).map_err(invalid_params)
|
||||
}
|
||||
|
||||
pub fn without<T: Serialize>(value: Value, remove: &T) -> Result<Value, imbl_value::Error> {
|
||||
let to_remove = imbl_value::to_value(remove)?;
|
||||
let (Value::Object(mut value), Value::Object(to_remove)) = (value, to_remove) else {
|
||||
return Err(imbl_value::Error {
|
||||
kind: imbl_value::ErrorKind::Serialization,
|
||||
source: serde_json::Error::custom("params must be object"),
|
||||
});
|
||||
};
|
||||
for k in to_remove.keys() {
|
||||
value.remove(k);
|
||||
}
|
||||
Ok(Value::Object(value))
|
||||
}
|
||||
|
||||
pub fn combine(v1: Value, v2: Value) -> Result<Value, imbl_value::Error> {
|
||||
let (Value::Object(mut v1), Value::Object(v2)) = (v1, v2) else {
|
||||
return Err(imbl_value::Error {
|
||||
kind: imbl_value::ErrorKind::Serialization,
|
||||
source: serde_json::Error::custom("params must be object"),
|
||||
});
|
||||
};
|
||||
for (key, value) in v2 {
|
||||
if v1.insert(key.clone(), value).is_some() {
|
||||
return Err(imbl_value::Error {
|
||||
kind: imbl_value::ErrorKind::Serialization,
|
||||
source: serde_json::Error::custom(lazy_format::lazy_format!(
|
||||
"duplicate key: {key}"
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(Value::Object(v1))
|
||||
}
|
||||
|
||||
pub fn invalid_params(e: imbl_value::Error) -> RpcError {
|
||||
RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invalid_request(e: imbl_value::Error) -> RpcError {
|
||||
RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INVALID_REQUEST_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_error(e: impl Display) -> RpcError {
|
||||
RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::PARSE_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
pub fn internal_error(e: impl Display) -> RpcError {
|
||||
RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INTERNAL_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Flat<A, B>(pub A, pub B);
|
||||
impl<'de, A, B> Deserialize<'de> for Flat<A, B>
|
||||
where
|
||||
A: DeserializeOwned,
|
||||
B: DeserializeOwned,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v = Value::deserialize(deserializer)?;
|
||||
let a = imbl_value::from_value(v.clone()).map_err(serde::de::Error::custom)?;
|
||||
let b = imbl_value::from_value(v).map_err(serde::de::Error::custom)?;
|
||||
Ok(Flat(a, b))
|
||||
}
|
||||
}
|
||||
impl<A, B> Serialize for Flat<A, B>
|
||||
where
|
||||
A: Serialize,
|
||||
B: Serialize,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
#[derive(serde::Serialize)]
|
||||
struct FlatStruct<'a, A, B> {
|
||||
#[serde(flatten)]
|
||||
a: &'a A,
|
||||
#[serde(flatten)]
|
||||
b: &'a B,
|
||||
}
|
||||
FlatStruct {
|
||||
a: &self.0,
|
||||
b: &self.1,
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
impl<A, B> clap::CommandFactory for Flat<A, B>
|
||||
where
|
||||
A: clap::CommandFactory,
|
||||
B: clap::Args,
|
||||
{
|
||||
fn command() -> clap::Command {
|
||||
B::augment_args(A::command())
|
||||
}
|
||||
fn command_for_update() -> clap::Command {
|
||||
B::augment_args_for_update(A::command_for_update())
|
||||
}
|
||||
}
|
||||
impl<A, B> clap::FromArgMatches for Flat<A, B>
|
||||
where
|
||||
A: clap::FromArgMatches,
|
||||
B: clap::FromArgMatches,
|
||||
{
|
||||
fn from_arg_matches(matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
|
||||
Ok(Self(
|
||||
A::from_arg_matches(matches)?,
|
||||
B::from_arg_matches(matches)?,
|
||||
))
|
||||
}
|
||||
fn from_arg_matches_mut(matches: &mut clap::ArgMatches) -> Result<Self, clap::Error> {
|
||||
Ok(Self(
|
||||
A::from_arg_matches_mut(matches)?,
|
||||
B::from_arg_matches_mut(matches)?,
|
||||
))
|
||||
}
|
||||
fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> {
|
||||
self.0.update_from_arg_matches(matches)?;
|
||||
self.1.update_from_arg_matches(matches)?;
|
||||
Ok(())
|
||||
}
|
||||
fn update_from_arg_matches_mut(
|
||||
&mut self,
|
||||
matches: &mut clap::ArgMatches,
|
||||
) -> Result<(), clap::Error> {
|
||||
self.0.update_from_arg_matches_mut(matches)?;
|
||||
self.1.update_from_arg_matches_mut(matches)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_select_all<'a, T>(
|
||||
futs: &mut Vec<BoxFuture<'a, T>>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<T> {
|
||||
let item = futs
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find_map(|(i, f)| match f.poll_unpin(cx) {
|
||||
std::task::Poll::Pending => None,
|
||||
std::task::Poll::Ready(e) => Some((i, e)),
|
||||
});
|
||||
match item {
|
||||
Some((idx, res)) => {
|
||||
drop(futs.swap_remove(idx));
|
||||
std::task::Poll::Ready(res)
|
||||
}
|
||||
None => std::task::Poll::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JobRunner<'a, T> {
|
||||
wakers: Vec<Waker>,
|
||||
closed: bool,
|
||||
running: Vec<BoxFuture<'a, T>>,
|
||||
}
|
||||
impl<'a, T> JobRunner<'a, T> {
|
||||
pub fn new() -> Self {
|
||||
JobRunner {
|
||||
wakers: Vec::new(),
|
||||
closed: false,
|
||||
running: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub async fn next_result<
|
||||
Src: Stream<Item = Fut> + Unpin,
|
||||
Fut: Future<Output = T> + Send + 'a,
|
||||
>(
|
||||
&mut self,
|
||||
job_source: &mut Src,
|
||||
) -> Option<T> {
|
||||
let mut job_source = Some(job_source);
|
||||
loop {
|
||||
let next_job_fut = async {
|
||||
if let Some(job_source) = &mut job_source {
|
||||
job_source.next().await
|
||||
} else {
|
||||
futures::future::pending().await
|
||||
}
|
||||
};
|
||||
tokio::select! {
|
||||
job = next_job_fut => {
|
||||
if let Some(job) = job {
|
||||
self.running.push(job.boxed());
|
||||
while let Some(waker) = self.wakers.pop() {
|
||||
waker.wake();
|
||||
}
|
||||
} else {
|
||||
job_source.take();
|
||||
self.closed = true;
|
||||
if self.running.is_empty() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
res = self.next() => {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a, T> Stream for JobRunner<'a, T> {
|
||||
type Item = T;
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
if self.running.is_empty() {
|
||||
self.wakers.push(cx.waker().clone());
|
||||
return std::task::Poll::Pending;
|
||||
}
|
||||
match poll_select_all(&mut self.running, cx) {
|
||||
std::task::Poll::Pending if self.closed && self.running.is_empty() => {
|
||||
std::task::Poll::Ready(None)
|
||||
}
|
||||
a => a.map(Some),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct StreamUntil<S, F> {
|
||||
#[pin]
|
||||
stream: S,
|
||||
#[pin]
|
||||
until: F,
|
||||
done: bool,
|
||||
}
|
||||
impl<S, F> StreamUntil<S, F> {
|
||||
pub fn new(stream: S, until: F) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
until,
|
||||
done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<S, F> Stream for StreamUntil<S, F>
|
||||
where
|
||||
S: Stream,
|
||||
F: Future,
|
||||
{
|
||||
type Item = S::Item;
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
let this = self.project();
|
||||
*this.done = *this.done || this.until.poll(cx).is_ready();
|
||||
if *this.done {
|
||||
std::task::Poll::Ready(None)
|
||||
} else {
|
||||
this.stream.poll_next(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<S, F> FusedStream for StreamUntil<S, F>
|
||||
where
|
||||
S: FusedStream,
|
||||
F: FusedFuture,
|
||||
{
|
||||
fn is_terminated(&self) -> bool {
|
||||
self.done || self.stream.is_terminated() || self.until.is_terminated()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PhantomData<T>(std::marker::PhantomData<T>);
|
||||
impl<T> PhantomData<T> {
|
||||
pub fn new() -> Self {
|
||||
PhantomData(std::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
impl<T> Clone for PhantomData<T> {
|
||||
fn clone(&self) -> Self {
|
||||
PhantomData::new()
|
||||
}
|
||||
}
|
||||
impl<T> Debug for PhantomData<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
unsafe impl<T> Send for PhantomData<T> {}
|
||||
unsafe impl<T> Sync for PhantomData<T> {}
|
||||
251
tests/handler.rs
Normal file
251
tests/handler.rs
Normal file
@@ -0,0 +1,251 @@
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Display;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use futures::future::ready;
|
||||
use imbl_value::Value;
|
||||
use rpc_toolkit::{
|
||||
call_remote_socket, from_fn, from_fn_async, CallRemote, CliApp, Context, Empty, HandlerExt,
|
||||
ParentHandler, Server,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::sync::{Mutex, OnceCell};
|
||||
use yajrc::RpcError;
|
||||
|
||||
#[derive(Parser, Deserialize)]
|
||||
#[command(
|
||||
name = "test-cli",
|
||||
version,
|
||||
author,
|
||||
about = "This is a test cli application."
|
||||
)]
|
||||
struct CliConfig {
|
||||
#[arg(long = "host")]
|
||||
host: Option<PathBuf>,
|
||||
#[arg(short = 'c', long = "config")]
|
||||
config: Option<PathBuf>,
|
||||
}
|
||||
impl CliConfig {
|
||||
fn load_rec(&mut self) -> Result<(), RpcError> {
|
||||
if let Some(path) = self.config.as_ref() {
|
||||
let mut extra_cfg: Self =
|
||||
serde_json::from_str(&std::fs::read_to_string(path).map_err(internal_error)?)
|
||||
.map_err(internal_error)?;
|
||||
extra_cfg.load_rec()?;
|
||||
self.merge_with(extra_cfg);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn merge_with(&mut self, extra: Self) {
|
||||
if self.host.is_none() {
|
||||
self.host = extra.host;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CliContextSeed {
|
||||
host: PathBuf,
|
||||
rt: OnceCell<Arc<Runtime>>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
struct CliContext(Arc<CliContextSeed>);
|
||||
impl Context for CliContext {
|
||||
fn runtime(&self) -> Option<Arc<Runtime>> {
|
||||
if self.0.rt.get().is_none() {
|
||||
let rt = Arc::new(Runtime::new().unwrap());
|
||||
self.0.rt.set(rt.clone()).unwrap_or_default();
|
||||
Some(rt)
|
||||
} else {
|
||||
self.0.rt.get().cloned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CallRemote<ServerContext> for CliContext {
|
||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||
call_remote_socket(
|
||||
tokio::net::UnixStream::connect(&self.0.host).await.unwrap(),
|
||||
method,
|
||||
params,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn make_cli() -> CliApp<CliContext, CliConfig> {
|
||||
CliApp::new(
|
||||
|mut config: CliConfig| {
|
||||
config.load_rec()?;
|
||||
Ok(CliContext(Arc::new(CliContextSeed {
|
||||
host: config
|
||||
.host
|
||||
.unwrap_or_else(|| Path::new("./rpc.sock").to_owned()),
|
||||
rt: OnceCell::new(),
|
||||
})))
|
||||
},
|
||||
make_api(),
|
||||
)
|
||||
}
|
||||
|
||||
struct ServerContextSeed {
|
||||
state: Mutex<Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ServerContext(Arc<ServerContextSeed>);
|
||||
impl Context for ServerContext {}
|
||||
|
||||
fn make_server() -> Server<ServerContext> {
|
||||
let ctx = ServerContext(Arc::new(ServerContextSeed {
|
||||
state: Mutex::new(Value::Null),
|
||||
}));
|
||||
Server::new(move || ready(Ok(ctx.clone())), make_api())
|
||||
}
|
||||
|
||||
fn make_api<C: Context>() -> ParentHandler<C> {
|
||||
async fn a_hello(_: CliContext) -> Result<String, RpcError> {
|
||||
Ok::<_, RpcError>("Async Subcommand".to_string())
|
||||
}
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser)]
|
||||
struct EchoParams {
|
||||
next: String,
|
||||
}
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser)]
|
||||
struct HelloParams {
|
||||
whom: String,
|
||||
}
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Parser)]
|
||||
struct InheritParams {
|
||||
donde: String,
|
||||
}
|
||||
ParentHandler::<C>::new()
|
||||
.subcommand(
|
||||
"echo",
|
||||
from_fn_async(
|
||||
|c: ServerContext, EchoParams { next }: EchoParams| async move {
|
||||
Ok::<_, RpcError>(std::mem::replace(
|
||||
&mut *c.0.state.lock().await,
|
||||
Value::String(Arc::new(next)),
|
||||
))
|
||||
},
|
||||
)
|
||||
.with_custom_display_fn(|_, a| Ok(println!("{a}")))
|
||||
.with_about("Testing")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"hello",
|
||||
from_fn(|_: C, HelloParams { whom }: HelloParams| {
|
||||
Ok::<_, RpcError>(format!("Hello {whom}").to_string())
|
||||
}),
|
||||
)
|
||||
.subcommand("a_hello", from_fn_async(a_hello))
|
||||
.subcommand(
|
||||
"dondes",
|
||||
ParentHandler::<C, InheritParams>::new().subcommand(
|
||||
"donde",
|
||||
from_fn(|c: CliContext, _: (), donde| {
|
||||
Ok::<_, RpcError>(
|
||||
format!(
|
||||
"Subcommand No Cli: Host {host} Donde = {donde}",
|
||||
host = c.0.host.display()
|
||||
)
|
||||
.to_string(),
|
||||
)
|
||||
})
|
||||
.with_inherited(|InheritParams { donde }, _| donde)
|
||||
.no_cli(),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
"fizz",
|
||||
ParentHandler::<C, InheritParams>::new().root_handler(
|
||||
from_fn(|c: CliContext, _: Empty, InheritParams { donde }| {
|
||||
Ok::<_, RpcError>(
|
||||
format!(
|
||||
"Root Command: Host {host} Donde = {donde}",
|
||||
host = c.0.host.display(),
|
||||
)
|
||||
.to_string(),
|
||||
)
|
||||
})
|
||||
.with_inherited(|a, _| a),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
"error",
|
||||
ParentHandler::<C, InheritParams>::new().root_handler(
|
||||
from_fn(|_: CliContext, _: Empty, InheritParams { .. }| {
|
||||
Err::<String, _>(RpcError {
|
||||
code: 1,
|
||||
message: "This is an example message".into(),
|
||||
data: None,
|
||||
})
|
||||
})
|
||||
.with_inherited(|a, _| a)
|
||||
.no_cli(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn internal_error(e: impl Display) -> RpcError {
|
||||
RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INTERNAL_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli() {
|
||||
make_cli()
|
||||
.run(["test-cli", "hello", "me"].iter().map(OsString::from))
|
||||
.unwrap();
|
||||
make_cli()
|
||||
.run(["test-cli", "fizz", "buzz"].iter().map(OsString::from))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_server() {
|
||||
let path = Path::new(env!("CARGO_TARGET_TMPDIR")).join("rpc.sock");
|
||||
tokio::fs::remove_file(&path).await.unwrap_or_default();
|
||||
let server = make_server();
|
||||
let (shutdown, fut) = server
|
||||
.run_unix(path.clone(), |err| eprintln!("IO Error: {err}"))
|
||||
.unwrap();
|
||||
tokio::join!(
|
||||
tokio::task::spawn_blocking(move || {
|
||||
make_cli()
|
||||
.run(
|
||||
[
|
||||
"test-cli",
|
||||
&format!("--host={}", path.display()),
|
||||
"echo",
|
||||
"foo",
|
||||
]
|
||||
.iter()
|
||||
.map(OsString::from),
|
||||
)
|
||||
.unwrap();
|
||||
make_cli()
|
||||
.run(
|
||||
[
|
||||
"test-cli",
|
||||
&format!("--host={}", path.display()),
|
||||
"echo",
|
||||
"bar",
|
||||
]
|
||||
.iter()
|
||||
.map(OsString::from),
|
||||
)
|
||||
.unwrap();
|
||||
shutdown.shutdown()
|
||||
}),
|
||||
fut
|
||||
)
|
||||
.0
|
||||
.unwrap();
|
||||
}
|
||||
109
tests/test.rs
Normal file
109
tests/test.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
// use std::path::PathBuf;
|
||||
|
||||
// use clap::Parser;
|
||||
// use futures::Future;
|
||||
// use rpc_toolkit::{
|
||||
// AsyncCommand, CliContextSocket, Command, Contains, Context, DynCommand, LeafCommand, NoParent,
|
||||
// ParentCommand, ParentInfo, Server, ShutdownHandle,
|
||||
// };
|
||||
// use serde::{Deserialize, Serialize};
|
||||
// use tokio::net::UnixStream;
|
||||
// use yajrc::RpcError;
|
||||
|
||||
// struct ServerContext;
|
||||
// impl Context for ServerContext {
|
||||
// type Metadata = ();
|
||||
// }
|
||||
|
||||
// struct CliContext(PathBuf);
|
||||
// impl Context for CliContext {
|
||||
// type Metadata = ();
|
||||
// }
|
||||
|
||||
// impl CliContextSocket for CliContext {
|
||||
// type Stream = UnixStream;
|
||||
// async fn connect(&self) -> std::io::Result<Self::Stream> {
|
||||
// UnixStream::connect(&self.0).await
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl rpc_toolkit::CliContext for CliContext {
|
||||
// async fn call_remote(
|
||||
// &self,
|
||||
// method: &str,
|
||||
// params: imbl_value::Value,
|
||||
// ) -> Result<imbl_value::Value, RpcError> {
|
||||
// <Self as CliContextSocket>::call_remote(self, method, params).await
|
||||
// }
|
||||
// }
|
||||
|
||||
// async fn run_server() {
|
||||
// Server::new(
|
||||
// vec![
|
||||
// DynCommand::from_parent::<Group>(Contains::none()),
|
||||
// DynCommand::from_async::<Thing1>(Contains::none()),
|
||||
// // DynCommand::from_async::<Thing2>(Contains::none()),
|
||||
// // DynCommand::from_sync::<Thing3>(Contains::none()),
|
||||
// // DynCommand::from_sync::<Thing4>(Contains::none()),
|
||||
// ],
|
||||
// || async { Ok(ServerContext) },
|
||||
// )
|
||||
// .run_unix("./test.sock", |e| eprintln!("{e}"))
|
||||
// .unwrap()
|
||||
// .1
|
||||
// .await
|
||||
// }
|
||||
|
||||
// #[derive(Debug, Deserialize, Serialize, Parser)]
|
||||
// struct Group {
|
||||
// #[arg(short, long)]
|
||||
// verbose: bool,
|
||||
// }
|
||||
// impl Command for Group {
|
||||
// const NAME: &'static str = "group";
|
||||
// type Parent = NoParent;
|
||||
// }
|
||||
// impl<Ctx> ParentCommand<Ctx> for Group
|
||||
// where
|
||||
// Ctx: Context,
|
||||
// // SubThing: AsyncCommand<Ctx>,
|
||||
// Thing1: AsyncCommand<Ctx>,
|
||||
// {
|
||||
// fn subcommands(chain: rpc_toolkit::ParentChain<Self>) -> Vec<DynCommand<Ctx>> {
|
||||
// vec![
|
||||
// // DynCommand::from_async::<SubThing>(chain.child()),
|
||||
// DynCommand::from_async::<Thing1>(Contains::none()),
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[derive(Debug, Deserialize, Serialize, Parser)]
|
||||
// struct Thing1 {
|
||||
// thing: String,
|
||||
// }
|
||||
// impl Command for Thing1 {
|
||||
// const NAME: &'static str = "thing1";
|
||||
// type Parent = NoParent;
|
||||
// }
|
||||
// impl LeafCommand<ServerContext> for Thing1 {
|
||||
// type Ok = String;
|
||||
// type Err = RpcError;
|
||||
// fn display(self, _: ServerContext, _: rpc_toolkit::ParentInfo<Self::Parent>, res: Self::Ok) {
|
||||
// println!("{}", res);
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl AsyncCommand<ServerContext> for Thing1 {
|
||||
// async fn implementation(
|
||||
// self,
|
||||
// _: ServerContext,
|
||||
// _: ParentInfo<Self::Parent>,
|
||||
// ) -> Result<Self::Ok, Self::Err> {
|
||||
// Ok(format!("Thing1 is {}", self.thing))
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[tokio::test]
|
||||
// async fn test() {
|
||||
// let server = tokio::spawn(run_server());
|
||||
// }
|
||||
Reference in New Issue
Block a user