use std::fs;
use std::path::Path;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::error::CliResult;
#[derive(Debug, Deserialize, Serialize)]
struct EntityEntry {
name: String,
label: String,
plural: String,
}
#[derive(Debug, Deserialize, Serialize)]
struct SfConfig {
title: String,
subtitle: String,
#[serde(default)]
constraints: Vec<String>,
#[serde(default)]
entities: Vec<EntityEntry>,
#[serde(default)]
facts: Vec<EntityEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
view: Option<Value>,
#[serde(flatten)]
extra: Map<String, Value>,
}
const CONFIG_PATH: &str = "static/sf-config.json";
fn load() -> Option<SfConfig> {
let path = Path::new(CONFIG_PATH);
if !path.exists() {
return None;
}
let content = fs::read_to_string(path).ok()?;
serde_json::from_str(&content).ok()
}
fn save(config: &SfConfig) -> CliResult {
let json = serde_json::to_string_pretty(config).map_err(|e| {
crate::error::CliError::general(format!("sf-config serialize error: {}", e))
})?;
fs::write(CONFIG_PATH, json).map_err(|e| crate::error::CliError::IoError {
context: "failed to write sf-config.json".to_string(),
source: e,
})?;
Ok(())
}
pub fn add_constraint(name: &str) -> CliResult {
let Some(mut config) = load() else {
return Ok(());
};
if !config.constraints.contains(&name.to_string()) {
config.constraints.push(name.to_string());
save(&config)?;
}
Ok(())
}
pub fn remove_constraint(name: &str) -> CliResult {
let Some(mut config) = load() else {
return Ok(());
};
config.constraints.retain(|c| c != name);
save(&config)
}
pub fn add_entity(name: &str, label: &str, plural: &str) -> CliResult {
let Some(mut config) = load() else {
return Ok(());
};
if !config.entities.iter().any(|e| e.name == name) {
config.entities.push(EntityEntry {
name: name.to_string(),
label: label.to_string(),
plural: plural.to_string(),
});
save(&config)?;
}
Ok(())
}
pub fn remove_entity(name: &str) -> CliResult {
let Some(mut config) = load() else {
return Ok(());
};
config.entities.retain(|e| e.name != name);
save(&config)
}
pub fn add_fact(name: &str, label: &str, plural: &str) -> CliResult {
let Some(mut config) = load() else {
return Ok(());
};
if !config.facts.iter().any(|f| f.name == name) {
config.facts.push(EntityEntry {
name: name.to_string(),
label: label.to_string(),
plural: plural.to_string(),
});
save(&config)?;
}
Ok(())
}
pub fn remove_fact(name: &str) -> CliResult {
let Some(mut config) = load() else {
return Ok(());
};
config.facts.retain(|f| f.name != name);
save(&config)
}
#[cfg(test)]
#[path = "sf_config_tests.rs"]
mod tests;