mirror of
https://github.com/Start9Labs/rpc-toolkit.git
synced 2026-03-26 02:11:56 +00:00
wip
This commit is contained in:
1172
Cargo.lock
generated
1172
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -16,24 +16,19 @@ cbor = ["serde_cbor"]
|
||||
default = ["cbor"]
|
||||
|
||||
[dependencies]
|
||||
clap = "3"
|
||||
async-trait = "0.1"
|
||||
clap = "4"
|
||||
futures = "0.3"
|
||||
hyper = { version = "0.14", features = [
|
||||
"server",
|
||||
"http1",
|
||||
"http2",
|
||||
"tcp",
|
||||
"stream",
|
||||
"client",
|
||||
] }
|
||||
hyper = { version = "1", features = ["server", "http1", "http2", "client"] }
|
||||
imbl-value = { git = "https://github.com/Start9Labs/imbl-value.git" }
|
||||
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" }
|
||||
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"
|
||||
url = "2"
|
||||
yajrc = "0.1"
|
||||
|
||||
336
rpc-toolkit/src/command.rs
Normal file
336
rpc-toolkit/src/command.rs
Normal file
@@ -0,0 +1,336 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::{ArgMatches, CommandFactory, FromArgMatches};
|
||||
use futures::future::BoxFuture;
|
||||
use futures::FutureExt;
|
||||
use imbl_value::Value;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::ser::Serialize;
|
||||
use tokio::runtime::Runtime;
|
||||
use yajrc::RpcError;
|
||||
|
||||
pub struct DynCommand<Context> {
|
||||
name: &'static str,
|
||||
implementation: Option<Implementation<Context>>,
|
||||
cli: Option<CliBindings>,
|
||||
subcommands: Vec<Self>,
|
||||
}
|
||||
impl<Context> DynCommand<Context> {
|
||||
fn cli_app(&self) -> Option<clap::Command> {
|
||||
if let Some(cli) = &self.cli {
|
||||
Some(
|
||||
cli.cmd
|
||||
.name(self.name)
|
||||
.subcommands(self.subcommands.iter().filter_map(|c| c.cli_app())),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn impl_from_cli_matches(
|
||||
&self,
|
||||
matches: &ArgMatches,
|
||||
parent: Value,
|
||||
) -> Result<Implementation<Context>, RpcError> {
|
||||
let args = combine(
|
||||
parent,
|
||||
(self
|
||||
.cli
|
||||
.as_ref()
|
||||
.ok_or(yajrc::METHOD_NOT_FOUND_ERROR)?
|
||||
.parser)(matches)?,
|
||||
)?;
|
||||
if let Some((cmd, matches)) = matches.subcommand() {
|
||||
self.subcommands
|
||||
.iter()
|
||||
.find(|c| c.name == cmd)
|
||||
.ok_or(yajrc::METHOD_NOT_FOUND_ERROR)?
|
||||
.impl_from_cli_matches(matches, args)
|
||||
} else if let Some(implementation) = self.implementation.clone() {
|
||||
Ok(implementation)
|
||||
} else {
|
||||
Err(yajrc::METHOD_NOT_FOUND_ERROR)
|
||||
}
|
||||
}
|
||||
pub fn run_cli(ctx: Context) {}
|
||||
}
|
||||
|
||||
struct Implementation<Context> {
|
||||
async_impl: Arc<dyn Fn(Context, Value) -> BoxFuture<'static, Result<Value, RpcError>>>,
|
||||
sync_impl: Arc<dyn Fn(Context, Value) -> Result<Value, RpcError>>,
|
||||
}
|
||||
impl<Context> Clone for Implementation<Context> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
async_impl: self.async_impl.clone(),
|
||||
sync_impl: self.sync_impl.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CliBindings {
|
||||
cmd: clap::Command,
|
||||
parser: Box<dyn for<'a> Fn(&'a ArgMatches) -> Result<Value, RpcError> + Send + Sync>,
|
||||
display: Option<Box<dyn Fn(Value) + Send + Sync>>,
|
||||
}
|
||||
impl CliBindings {
|
||||
fn from_parent<Cmd: FromArgMatches + CommandFactory + Serialize>() -> Self {
|
||||
Self {
|
||||
cmd: Cmd::command(),
|
||||
parser: Box::new(|matches| {
|
||||
imbl_value::to_value(&Cmd::from_arg_matches(matches).map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
})?)
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
})
|
||||
}),
|
||||
display: None,
|
||||
}
|
||||
}
|
||||
fn from_leaf<Cmd: FromArgMatches + CommandFactory + Serialize + LeafCommand>() -> Self {
|
||||
Self {
|
||||
display: Some(Box::new(|res| Cmd::display(todo!("{}", res)))),
|
||||
..Self::from_parent::<Cmd>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Command: DeserializeOwned + Sized {
|
||||
const NAME: &'static str;
|
||||
type Parent: Command;
|
||||
}
|
||||
|
||||
pub struct ParentChain<Cmd: Command>(PhantomData<Cmd>);
|
||||
pub struct Contains<T>(PhantomData<T>);
|
||||
impl<T, U> From<(Contains<T>, Contains<U>)> for Contains<(T, U)> {
|
||||
fn from(value: (Contains<T>, Contains<U>)) -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct Root {}
|
||||
impl Command for Root {
|
||||
const NAME: &'static str = "";
|
||||
type Parent = Root;
|
||||
}
|
||||
impl<Cmd> ParentChain<Cmd>
|
||||
where
|
||||
Cmd: Command,
|
||||
{
|
||||
pub fn unit(&self) -> Contains<()> {
|
||||
Contains(PhantomData)
|
||||
}
|
||||
pub fn child(&self) -> Contains<Cmd> {
|
||||
Contains(PhantomData)
|
||||
}
|
||||
pub fn parent(&self) -> ParentChain<Cmd::Parent> {
|
||||
ParentChain(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ParentCommand<Context>: Command {
|
||||
fn subcommands(chain: ParentChain<Self>) -> Vec<DynCommand<Context>>;
|
||||
}
|
||||
impl<Context> DynCommand<Context> {
|
||||
pub fn from_parent<
|
||||
Cmd: ParentCommand<Context> + FromArgMatches + CommandFactory + Serialize,
|
||||
>() -> Self {
|
||||
Self {
|
||||
name: Cmd::NAME,
|
||||
implementation: None,
|
||||
cli: Some(CliBindings::from_parent::<Cmd>()),
|
||||
subcommands: Cmd::subcommands(ParentChain::<Cmd>(PhantomData)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LeafCommand: Command {
|
||||
type Ok: Serialize;
|
||||
type Err: Into<RpcError>;
|
||||
fn display(res: Self::Ok);
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait AsyncCommand<Context>: LeafCommand {
|
||||
async fn implementation(
|
||||
self,
|
||||
ctx: Context,
|
||||
parent: Self::Parent,
|
||||
) -> Result<Self::Ok, Self::Err>;
|
||||
fn subcommands(chain: ParentChain<Self>) -> Vec<DynCommand<Context>> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
impl<Context: Send> Implementation<Context> {
|
||||
fn for_async<Cmd: AsyncCommand<Context>>(contains: Contains<Cmd::Parent>) -> Self {
|
||||
Self {
|
||||
async_impl: Arc::new(|ctx, params| {
|
||||
async move {
|
||||
let parent = extract::<Cmd::Parent>(¶ms)?;
|
||||
imbl_value::to_value(
|
||||
&extract::<Cmd>(¶ms)?
|
||||
.implementation(ctx, parent)
|
||||
.await
|
||||
.map_err(|e| e.into())?,
|
||||
)
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::PARSE_ERROR
|
||||
})
|
||||
}
|
||||
.boxed()
|
||||
}),
|
||||
sync_impl: Arc::new(|ctx, params| {
|
||||
let parent = extract::<Cmd::Parent>(¶ms)?;
|
||||
imbl_value::to_value(
|
||||
&Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(
|
||||
extract::<Cmd>(¶ms)
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
})?
|
||||
.implementation(ctx, parent),
|
||||
)
|
||||
.map_err(|e| e.into())?,
|
||||
)
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::PARSE_ERROR
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Context: Send> DynCommand<Context> {
|
||||
pub fn from_async<Cmd: AsyncCommand<Context> + FromArgMatches + CommandFactory + Serialize>(
|
||||
contains: Contains<Cmd::Parent>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: Cmd::NAME,
|
||||
implementation: Some(Implementation::for_async::<Cmd>(contains)),
|
||||
cli: Some(CliBindings::from_leaf::<Cmd>()),
|
||||
subcommands: Cmd::subcommands(ParentChain::<Cmd>(PhantomData)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SyncCommand<Context>: LeafCommand {
|
||||
const BLOCKING: bool;
|
||||
fn implementation(self, ctx: Context, parent: Self::Parent) -> Result<Self::Ok, Self::Err>;
|
||||
fn subcommands(chain: ParentChain<Self>) -> Vec<DynCommand<Context>> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
impl<Context: Send> Implementation<Context> {
|
||||
fn for_sync<Cmd: SyncCommand<Context>>(contains: Contains<Cmd::Parent>) -> Self {
|
||||
Self {
|
||||
async_impl: if Cmd::BLOCKING {
|
||||
Arc::new(|ctx, params| {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let parent = extract::<Cmd::Parent>(¶ms)?;
|
||||
imbl_value::to_value(
|
||||
&extract::<Cmd>(¶ms)
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
})?
|
||||
.implementation(ctx, parent)
|
||||
.map_err(|e| e.into())?,
|
||||
)
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::PARSE_ERROR
|
||||
})
|
||||
})
|
||||
.map(|f| {
|
||||
f.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INTERNAL_ERROR
|
||||
})?
|
||||
})
|
||||
.boxed()
|
||||
})
|
||||
} else {
|
||||
Arc::new(|ctx, params| {
|
||||
async move {
|
||||
let parent = extract::<Cmd::Parent>(¶ms)?;
|
||||
imbl_value::to_value(
|
||||
&extract::<Cmd>(¶ms)
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
})?
|
||||
.implementation(ctx, parent)
|
||||
.map_err(|e| e.into())?,
|
||||
)
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::PARSE_ERROR
|
||||
})
|
||||
}
|
||||
.boxed()
|
||||
})
|
||||
},
|
||||
sync_impl: Arc::new(|ctx, params| {
|
||||
let parent = extract::<Cmd::Parent>(¶ms)?;
|
||||
imbl_value::to_value(
|
||||
&extract::<Cmd>(¶ms)
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
})?
|
||||
.implementation(ctx, parent)
|
||||
.map_err(|e| e.into())?,
|
||||
)
|
||||
.map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::PARSE_ERROR
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<Context: Send> DynCommand<Context> {
|
||||
pub fn from_sync<Cmd: SyncCommand<Context> + FromArgMatches + CommandFactory + Serialize>(
|
||||
contains: Contains<Cmd::Parent>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: Cmd::NAME,
|
||||
implementation: Some(Implementation::for_sync::<Cmd>(contains)),
|
||||
cli: Some(CliBindings::from_leaf::<Cmd>()),
|
||||
subcommands: Cmd::subcommands(ParentChain::<Cmd>(PhantomData)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract<T: DeserializeOwned>(value: &Value) -> Result<T, RpcError> {
|
||||
imbl_value::from_value(value.clone()).map_err(|e| RpcError {
|
||||
data: Some(e.to_string().into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
})
|
||||
}
|
||||
|
||||
fn combine(v1: Value, v2: Value) -> Result<Value, RpcError> {
|
||||
let (Value::Object(mut v1), Value::Object(v2)) = (v1, v2) else {
|
||||
return Err(RpcError {
|
||||
data: Some("params must be object".into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
});
|
||||
};
|
||||
for (key, value) in v2 {
|
||||
if v1.insert(key.clone(), value).is_some() {
|
||||
return Err(RpcError {
|
||||
data: Some(format!("duplicate key: {key}").into()),
|
||||
..yajrc::INVALID_PARAMS_ERROR
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(Value::Object(v1))
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use reqwest::Client;
|
||||
use tokio::runtime::Runtime;
|
||||
use url::{Host, Url};
|
||||
|
||||
lazy_static! {
|
||||
static ref DEFAULT_CLIENT: Client = Client::new();
|
||||
}
|
||||
|
||||
pub trait Context {
|
||||
pub trait Context: Send {
|
||||
fn runtime(&self) -> Arc<Runtime> {
|
||||
Arc::new(Runtime::new().unwrap())
|
||||
}
|
||||
fn protocol(&self) -> &str {
|
||||
"http"
|
||||
}
|
||||
@@ -39,3 +45,13 @@ impl<'a, T: Context + 'a> From<T> for Box<dyn Context + 'a> {
|
||||
Box::new(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Context for (T, U)
|
||||
where
|
||||
T: Context,
|
||||
U: Send,
|
||||
{
|
||||
fn runtime(&self) -> Arc<Runtime> {
|
||||
self.0.runtime()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ pub use crate::context::Context;
|
||||
pub use crate::metadata::Metadata;
|
||||
pub use crate::rpc_server_helpers::RpcHandler;
|
||||
|
||||
mod command;
|
||||
pub mod command_helpers;
|
||||
mod context;
|
||||
mod metadata;
|
||||
|
||||
Reference in New Issue
Block a user