use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::error::RenderError;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum FillPiece {
Literal {
text: String,
},
Hole {
parameter: String,
},
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct FillTemplate {
pub pieces: Vec<FillPiece>,
}
impl FillTemplate {
#[must_use]
pub fn literal(text: impl Into<String>) -> Self {
Self {
pieces: vec![FillPiece::Literal { text: text.into() }],
}
}
#[must_use]
pub fn holes(&self) -> Vec<&str> {
self.pieces
.iter()
.filter_map(|piece| match piece {
FillPiece::Literal { .. } => None,
FillPiece::Hole { parameter } => Some(parameter.as_str()),
})
.collect()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ArgumentValue {
Scalar(String),
List(Vec<String>),
}
impl ArgumentValue {
pub fn scalar(value: impl Into<String>) -> Self {
Self::Scalar(value.into())
}
pub fn list<I, S>(values: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self::List(values.into_iter().map(Into::into).collect())
}
#[must_use]
pub fn describe(&self) -> String {
match self {
Self::Scalar(text) => text.clone(),
Self::List(items) => format!("[{}]", items.join(", ")),
}
}
pub fn from_json(parameter: &str, value: &Value) -> Result<Self, RenderError> {
if let Value::Array(items) = value {
let mut rendered = Vec::with_capacity(items.len());
for item in items {
rendered.push(scalar_text(parameter, item)?);
}
return Ok(Self::List(rendered));
}
Ok(Self::Scalar(scalar_text(parameter, value)?))
}
}
fn scalar_text(parameter: &str, value: &Value) -> Result<String, RenderError> {
let unrepresentable = |kind: &'static str| RenderError::UnrepresentableValue {
parameter: parameter.to_owned(),
kind,
};
match value {
Value::String(text) => {
if text.contains('\0') {
return Err(RenderError::InteriorNul {
parameter: parameter.to_owned(),
});
}
Ok(text.clone())
}
Value::Number(number) => Ok(number.to_string()),
Value::Bool(flag) => Ok(if *flag { "true" } else { "false" }.to_owned()),
Value::Null => Err(unrepresentable("null")),
Value::Array(_) => Err(unrepresentable("a nested array")),
Value::Object(_) => Err(unrepresentable("an object")),
}
}