use std::any::TypeId;
use crate::{
Error, Site,
callables::{self, ArgPart, Callable},
};
use super::args::{self, CommandArg};
use super::error::CommandError;
#[derive(Debug, Clone, serde::Serialize)]
pub struct CommandConf {
pub name: Option<String>,
pub description: Option<String>,
}
impl CommandConf {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: Some(name.into()),
description: None,
}
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
}
pub type CommandHandlerIn = Callable<CommandContext, Error>;
pub struct CommandContext {
pub(super) site: Site,
pub(super) payload: callables::DataBox,
}
impl CommandContext {
pub(super) fn new(site: Site, payload: callables::DataBox) -> Self {
Self { site, payload }
}
}
impl callables::IntoDataBox for CommandContext {
fn into_data_box(self) -> callables::DataBox {
self.payload
}
}
impl callables::HasSite for CommandContext {
fn site(&self) -> &Site {
&self.site
}
}
pub(crate) struct Command {
pub(crate) handler: CommandHandlerIn,
pub(crate) options: CommandConf,
pub(crate) args: Vec<CommandArg>,
pub(crate) parser: fn(&str, &[&str], &[CommandArg]) -> Result<callables::DataBox, CommandError>,
}
impl Command {
pub(crate) fn operation(&self) -> callables::Operation {
let spec = self.handler.inspect();
callables::Operation::from_specs(callables::OperationKind::Command, spec)
.with_conf(&self.options)
}
}
pub(crate) fn command<T, H, Args>(handler: H, options: CommandConf) -> Result<Command, CommandError>
where
T: callables::DataValue,
H: callables::Specable<Args, Output = Result<(), Error>> + Send + Sync + 'static,
Args: callables::FromContext<CommandContext>
+ callables::IntoArgSpecs
+ callables::HasData<T>
+ Send
+ 'static,
{
let mut callable: Callable<CommandContext, Error> = Callable::new(handler);
let schema = command_schema_from_args::<Args>()?;
let parsed_args = args::parse_schema_to_args(&schema)?;
let parser: fn(&str, &[&str], &[CommandArg]) -> Result<callables::DataBox, CommandError> =
|command_name, cli, arg_defs| {
let obj: T = args::parse_args(command_name, cli, arg_defs)?;
Ok(callables::DataBox::new(obj))
};
callable.type_id = TypeId::of::<T>();
Ok(Command {
handler: callable,
options,
args: parsed_args,
parser,
})
}
fn command_schema_from_args<Args>() -> Result<schemars::Schema, CommandError>
where
Args: callables::IntoArgSpecs,
{
let specs = Args::into_arg_specs();
let Some(spec) = specs
.iter()
.rev()
.find(|spec| matches!(spec.part, ArgPart::Body(_, _)))
else {
return Err(CommandError::UnsupportedSchema(
"command handlers must contain Data<T> or Valid<Data<T>>".into(),
));
};
let ArgPart::Body(schema, _) = &spec.part else {
unreachable!("body arg was selected above");
};
let mut settings = schemars::generate::SchemaSettings::default();
settings.inline_subschemas = true;
let mut generator = schemars::SchemaGenerator::new(settings);
Ok(schema.schema(&mut generator))
}