use super::OrchestrateError;
use super::http_inject::HttpNodeEntry;
use serde_json::Value;
use std::collections::HashMap;
use std::path::PathBuf;
pub struct PreparedCards {
tmp: tempfile::TempDir,
pub cards_dir: PathBuf,
pub out_dir: PathBuf,
pub flow_name: String,
pub http_entries: Vec<HttpNodeEntry>,
}
impl PreparedCards {
pub fn persist(self) {
let _ = self.tmp.keep();
}
}
pub fn prepare_cards(
name: &str,
cards: &[(String, Value)],
) -> Result<PreparedCards, OrchestrateError> {
if cards.is_empty() {
return Err(OrchestrateError::Cards2Pack(
"no cards provided".to_string(),
));
}
let tmp = tempfile::tempdir()?;
let cards_dir = tmp.path().join("cards");
std::fs::create_dir_all(&cards_dir)?;
let flow_name = sanitize_filename(name);
let (card_only, http_entries) = super::http_inject::extract_http_entries(cards);
let mut id_map: HashMap<String, String> = HashMap::new();
let first_id = &card_only[0].0;
let has_welcome = card_only.iter().any(|(id, _)| id == "welcome");
if first_id != "welcome" && !has_welcome {
id_map.insert(first_id.clone(), "welcome".to_string());
}
for (original_id, card) in &card_only {
let mut card = card.clone();
let card_id = id_map
.get(original_id)
.cloned()
.unwrap_or_else(|| sanitize_filename(original_id));
if let Some(obj) = card.as_object_mut() {
let greentic = obj
.entry("greentic")
.or_insert_with(|| serde_json::json!({}));
if let Some(g) = greentic.as_object_mut() {
g.insert("cardId".to_string(), Value::String(card_id.clone()));
g.insert("flow".to_string(), Value::String(flow_name.clone()));
}
}
rewrite_next_card_ids(&mut card, &id_map);
let filename = format!("{card_id}.json");
let path = cards_dir.join(&filename);
let json = serde_json::to_string_pretty(&card).map_err(|e| {
OrchestrateError::Cards2Pack(format!("serialize card {original_id}: {e}"))
})?;
std::fs::write(&path, json)?;
}
let out_dir = tmp.path().join("workspace");
std::fs::create_dir_all(&out_dir)?;
Ok(PreparedCards {
tmp,
cards_dir,
out_dir,
flow_name,
http_entries,
})
}
fn rewrite_next_card_ids(value: &mut Value, id_map: &HashMap<String, String>) {
match value {
Value::Object(obj) => {
let next = obj
.get("data")
.and_then(|d| d.get("nextCardId"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
if let Some(next) = next {
let target = id_map
.get(&next)
.cloned()
.unwrap_or_else(|| sanitize_filename(&next));
let action_id = format!("goto_{target}");
if let Some(d) = obj.get_mut("data").and_then(|d| d.as_object_mut()) {
d.remove("nextCardId");
d.insert("routeToCardId".to_string(), Value::String(target));
d.insert("action_id".to_string(), Value::String(action_id));
}
}
for v in obj.values_mut() {
rewrite_next_card_ids(v, id_map);
}
}
Value::Array(arr) => {
for v in arr.iter_mut() {
rewrite_next_card_ids(v, id_map);
}
}
_ => {}
}
}
fn sanitize_filename(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_alphanumeric() || c == '-' || c == '_' {
c
} else {
'-'
}
})
.collect()
}