use crate::{Error, Result};
use serde_json::Value;
use value_ext::JsonValueExt;
#[derive(Debug, strum::AsRefStr)]
pub enum AipackCustom {
Skip { reason: Option<String> },
DataResponse(DataResponse),
BeforeAllResponse(BeforeAllResponse),
}
#[derive(Debug, Default)]
pub struct DataResponse {
pub input: Option<Value>,
pub data: Option<Value>,
pub options: Option<Value>,
}
#[derive(Debug, Default)]
pub struct BeforeAllResponse {
pub inputs: Option<Vec<Value>>,
pub before_all: Option<Value>,
pub options: Option<Value>,
}
#[derive(Debug)]
pub enum FromValue {
AipackCustom(AipackCustom),
OriginalValue(Value),
}
impl AipackCustom {
pub fn from_value(value: Value) -> Result<FromValue> {
let Some(kind) = value.x_get::<String>("/_aipack_/kind").ok() else {
return Ok(FromValue::OriginalValue(value));
};
if kind == "Skip" {
let reason: Option<String> = value.x_get("/_aipack_/data/reason").ok();
Ok(FromValue::AipackCustom(Self::Skip { reason }))
} else if kind == "DataResponse" {
let custom_data: Option<Value> = value.x_get("/_aipack_/data").ok();
let data_response = parse_data_response(custom_data)?;
Ok(FromValue::AipackCustom(AipackCustom::DataResponse(data_response)))
} else if kind == "BeforeAllResponse" {
let custom_data: Option<Value> = value.x_get("/_aipack_/data").ok();
let before_all_response = parse_before_all_response(custom_data)?;
Ok(FromValue::AipackCustom(AipackCustom::BeforeAllResponse(
before_all_response,
)))
} else {
Err(format!("_aipack_ kind '{kind}' is not known.").into())
}
}
}
fn parse_before_all_response(custom_data: Option<Value>) -> Result<BeforeAllResponse> {
let Some(custom_data) = custom_data else {
return Ok(BeforeAllResponse::default());
};
const ERROR_CAUSE: &str =
"aip.flow.before_all_response(arg) - 'arg' can only have `.inputs`, `.options`, `.before_all`)";
let before_all_response = match custom_data {
Value::Object(mut obj) => {
let all_inputs = obj.remove("inputs");
let before_all = obj.remove("before_all");
let options = obj.remove("options");
let inputs = match all_inputs {
Some(Value::Array(new_inputs)) => Some(new_inputs),
Some(Value::Null) => None,
Some(_) => {
return Err(Error::BeforeAllFailWrongReturn {
cause: "aip.flow.before_all_response(arg) - 'arg.inputs` must be an nil or an array"
.to_string(),
});
}
None => None,
};
let keys: Vec<String> = obj.keys().map(|k| k.to_string()).collect();
if !keys.is_empty() {
return Err(Error::BeforeAllFailWrongReturn {
cause: format!("{ERROR_CAUSE}. But also contained: {}", keys.join(", ")),
});
}
BeforeAllResponse {
inputs,
before_all,
options,
}
}
_ => BeforeAllResponse::default(),
};
Ok(before_all_response)
}
fn parse_data_response(custom_data: Option<Value>) -> Result<DataResponse> {
let Some(custom_data) = custom_data else {
return Ok(DataResponse::default());
};
const ERROR_CAUSE: &str = "aip.flow.data_response(arg) argumen can can only have `.input`, `.options`)";
let before_all_response = match custom_data {
Value::Object(mut obj) => {
let input = obj.remove("input");
let data = obj.remove("data");
let options = obj.remove("options");
let keys: Vec<String> = obj.keys().map(|k| k.to_string()).collect();
if !keys.is_empty() {
return Err(Error::BeforeAllFailWrongReturn {
cause: format!("{ERROR_CAUSE}. But also contained: {}", keys.join(", ")),
});
}
DataResponse { input, data, options }
}
_ => DataResponse::default(),
};
Ok(before_all_response)
}
#[cfg(test)]
mod tests {
type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
use super::*;
use crate::_test_support::assert_contains;
use serde_json::json;
#[test]
fn test_aipack_custom_before_all_inputs() -> Result<()> {
let fx_custom = json!({
"_aipack_": {
"kind": "BeforeAllResponse",
"data": {
"inputs": ["A", "B", 123],
"before_all": "Some before all data"
}
}
});
let custom = AipackCustom::from_value(fx_custom)?;
let FromValue::AipackCustom(custom) = custom else {
return Err("Should be a aipack custom".into());
};
let debug_string = format!("{:?}", custom);
assert_contains(&debug_string, r#"inputs: Some([String("A"), String("B"), Number(123)]"#);
assert_contains(&debug_string, r#"before_all: Some(String("Some before all data"))"#);
Ok(())
}
}