pub(crate) mod data;
mod patch;
mod query;
pub(crate) use data::{InputData, InputSource};
pub(crate) use patch::InputPatch;
pub(crate) use query::InputQuery;
pub(crate) const INPUT_SOURCE_LONG_HELP: &str = "
If the value is '-', the value will be read from STDIN.
If the value begins with '|', the rest is a command to execute.
If the value begins with '$', the rest is an environment variable.
If the value begins with '@', the rest is a filename.
If the value begins with '!', the rest is a shell script to execute.
Otherwise, the value will be read as inline data.
";
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[error(transparent)]
#[remain::sorted]
pub(crate) enum Error {
#[error("FATAL CONFLICT: --input-data and --input-patch cannot both read from STDIN.")]
ConflictingStdinUsage,
Data(#[from] data::Error),
Patch(#[from] crate::patch::Error),
Query(#[from] crate::query::Error),
}
#[derive(Debug, Clone, clap::Args)]
#[group(id = "input")]
#[remain::sorted]
#[rustfmt::skip]
pub(crate) struct Input {
#[command(flatten)]
data: InputData,
#[command(flatten)]
patch: InputPatch,
#[command(flatten)]
query: InputQuery,
}
impl Input {
pub(crate) fn consume<T: Clone + Send + Sync + serde::de::DeserializeOwned>(self) -> Result<T, Error> {
let Self {
data,
patch,
query: InputQuery { data: query },
..
} = self;
if data.source.is_stdin() && patch.data.source.is_stdin() {
return Err(Error::ConflictingStdinUsage);
}
let value = {
let mut value = query.evaluate(&data.consume()?)?.to_owned();
let patch: crate::patch::Patch = patch.data.consume()?;
patch.apply(&mut value)?;
serde_json::from_value(value.to_owned()).map_err(data::Error::from)?
};
Ok(value)
}
}
#[derive(Debug, Clone, clap::Args)]
#[group(id = "input")]
#[remain::sorted]
#[rustfmt::skip]
pub(crate) struct InputOrOptions<T> where T: Clone + Send + Sync + clap::Args {
#[arg(
env = "INPUT_DATA",
long = "input-data",
id = "input.data.source",
value_name = "SOURCE",
help_heading = "Input Options",
help = data::HELP,
long_help = data::LONG_HELP,
)]
data: Option<InputSource>,
#[command(flatten)]
inline: T,
#[command(flatten)]
patch: InputPatch,
#[command(flatten)]
query: InputQuery,
}
impl<T> InputOrOptions<T>
where
T: Clone + Send + Sync + clap::Args + serde::de::DeserializeOwned,
{
pub(crate) fn consume(self) -> Result<T, Error> {
let Self {
data,
inline,
patch,
query,
..
} = self;
let value = match data {
None => inline,
Some(source) => Input {
data: InputData { source },
patch,
query,
}
.consume()?,
};
Ok(value)
}
}