icepick/crates/icepick-module/src/lib.rs

152 lines
4.9 KiB
Rust
Raw Normal View History

/// The types used to build the `help` documentation for a module, as well as determine the
/// arguments that can be automatically detected by Icepick.
pub mod help {
use serde::{Deserialize, Serialize};
/// An operation that can be exposed to a frontend.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Operation {
/// The name of the argument, in the format expected by the deserializer.
pub name: String,
/// A description of what the operation does.
pub description: String,
/// The arguments to be provided to the operation's deserializer.
pub arguments: Vec<Argument>,
}
2025-01-21 08:02:00 +00:00
#[bon::bon]
impl Operation {
#[builder]
pub fn new(name: &'static str, description: &'static str) -> Self {
Operation {
name: name.into(),
description: description.into(),
arguments: vec![],
}
}
}
impl Operation {
pub fn argument(mut self, arg: &Argument) -> Self {
self.arguments.push(arg.clone());
self
}
}
/*
/// The context of whether a signature is signed, needs to be signed, or has been signed.
#[derive(Serialize, Deserialize, Clone)]
pub enum SigningContext {
/// This operation accepts a signed blob.
Signed,
/// This operation accepts an unsigned blob and will return a signed blob.
Signer,
/// This operation will return an unsigned blob.
Unsigned,
}
*/
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ArgumentType {
Required,
Optional,
}
/// An argument to an operation.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Argument {
/// The name of the argument, in the format expected by the deserializer.
pub name: String,
/// A description of the format and parameters of an argument.
pub description: String,
/// The type of argument - this may affect how it displays in the frontend.
pub r#type: ArgumentType,
}
2025-01-21 08:02:00 +00:00
#[bon::bon]
impl Argument {
#[builder]
pub fn new(name: &'static str, description: &'static str, r#type: ArgumentType) -> Self {
Argument {
name: name.into(),
description: description.into(),
r#type,
}
}
}
}
/// Implementation methods for Icepick Modules, performed over command I/O using JSON.
pub trait Module {
/// The error type returned by an operation.
type Error: std::error::Error + 'static;
/// The request type. See [`Module::handle_request`] for more information.
type Request: serde::de::DeserializeOwned + std::fmt::Debug + 'static;
/// Describe the operations interface of RPC calls. This information will be used to generate a
/// frontend for users to perform requests.
fn describe_operations() -> Vec<help::Operation>;
/// Handle an incoming request. Requests can often be formed using the following schema:
///
/// ```rust
/// #[derive(serde::Serialize, serde::Deserialize, Debug)]
/// #[serde(tag = "operation", rename_all="kebab-case")]
/// enum Request {
/// Transfer {
/// // This is the amount of the "primary" currency you'd be receiving, such as Ether,
/// // Bitcoin, or Sol.
/// amount: f64,
///
/// // This would be your native wallet address type. In this example, we'll just use a
/// // String.
/// to_address: String,
///
/// // This is the address of the derivation account.
/// from_account: u32,
/// }
///
/// Stake {
/// amount: f64,
/// from_account: u32,
/// }
///
/// Sign {
/// blob:
/// }
/// }
/// ```
fn handle_request(request: Self::Request) -> Result<serde_json::Value, Self::Error>;
fn run_responder() -> Result<(), Box<dyn std::error::Error>> {
let mut lines = std::io::stdin().lines();
while let Some(line) = lines.next().transpose()? {
let value: serde_json::Value = serde_json::from_str(&line)?;
if let Some(serde_json::Value::String(operation)) = value.get("operation") {
if operation == "help" {
println!("{}", serde_json::to_string(&Self::describe_operations())?);
continue;
}
if operation == "exit" {
break;
}
}
// TODO: error handling
let request: Self::Request = serde_json::from_value(value).expect("good value");
let response = Self::handle_request(request)?;
println!("{}", serde_json::to_string(&response).unwrap());
}
Ok(())
}
}