use crate::abi::{
parse_dust_rule, parse_must_rule, parse_trust_source, ConfigFormat, IntendDeclaration,
K9Contract, MustRule, SafetyTier,
};
use crate::codegen::parser::{ParsedEntry, ValueType};
pub fn generate_k9_contract(
name: &str,
safety_tier: SafetyTier,
must_rules: &[String],
trust_sources: &[String],
dust_rules: &[String],
intend_declarations: &[String],
parsed_entries: &[ParsedEntry],
) -> String {
let mut output = String::new();
output.push_str(&format!("# Auto-generated K9 contract for {}\n", name));
output.push_str(&format!("# Safety tier: {}\n", safety_tier));
output.push('\n');
output.push_str("[must]\n");
if must_rules.is_empty() {
output.push_str("# (no must rules defined)\n");
} else {
let parsed_rules: Vec<MustRule> = must_rules.iter().filter_map(|r| parse_must_rule(r)).collect();
let mut keys_seen: Vec<String> = Vec::new();
for rule in &parsed_rules {
if !keys_seen.contains(&rule.key) {
keys_seen.push(rule.key.clone());
}
}
for key in &keys_seen {
let key_rules: Vec<&MustRule> =
parsed_rules.iter().filter(|r| &r.key == key).collect();
let type_str = infer_type_for_key(key, parsed_entries);
let constraints: Vec<String> = key_rules
.iter()
.map(|r| format!("{} {}", r.operator, r.value))
.collect();
output.push_str(&format!(
"{} : {} {{ {} }}\n",
key,
type_str,
constraints.join(", ")
));
}
}
output.push('\n');
output.push_str("[trust]\n");
if trust_sources.is_empty() {
output.push_str("# (no trust sources defined)\n");
} else {
for ts in trust_sources {
if let Some(parsed) = parse_trust_source(ts) {
output.push_str(&format!("{} = \"{}\"\n", parsed.trust_type, parsed.source));
}
}
}
output.push('\n');
output.push_str("[dust]\n");
if dust_rules.is_empty() {
output.push_str("# (no dust rules defined)\n");
} else {
let parsed: Vec<_> = dust_rules.iter().filter_map(|d| parse_dust_rule(d)).collect();
let mut actions_seen: Vec<String> = Vec::new();
for d in &parsed {
if !actions_seen.contains(&d.action) {
actions_seen.push(d.action.clone());
}
}
for action in &actions_seen {
let targets: Vec<String> = parsed
.iter()
.filter(|d| &d.action == action)
.map(|d| format!("\"{}\"", d.target))
.collect();
output.push_str(&format!("{} = [{}]\n", action, targets.join(", ")));
}
}
output.push('\n');
output.push_str("[intend]\n");
if intend_declarations.is_empty() {
output.push_str("# (no intend declarations defined)\n");
} else {
for decl in intend_declarations {
output.push_str(&format!("{} = true\n", decl));
}
}
output
}
pub fn build_k9_contract(
name: &str,
source: &str,
format: ConfigFormat,
safety_tier: SafetyTier,
must_rules: &[String],
trust_sources: &[String],
dust_rules: &[String],
intend_declarations: &[String],
) -> K9Contract {
K9Contract {
name: name.to_string(),
source: source.to_string(),
format,
safety_tier,
must_rules: must_rules
.iter()
.filter_map(|r| parse_must_rule(r))
.collect(),
trust_sources: trust_sources
.iter()
.filter_map(|t| parse_trust_source(t))
.collect(),
dust_rules: dust_rules
.iter()
.filter_map(|d| parse_dust_rule(d))
.collect(),
intend_declarations: intend_declarations
.iter()
.map(|label| IntendDeclaration {
label: label.clone(),
})
.collect(),
}
}
fn infer_type_for_key(key: &str, parsed_entries: &[ParsedEntry]) -> &'static str {
let entry = parsed_entries.iter().find(|e| {
e.key == key || e.key.ends_with(&format!(".{}", key))
});
match entry {
Some(e) => match e.value_type {
ValueType::Int => "int",
ValueType::Float => "float",
ValueType::String => "string",
ValueType::Bool => "bool",
ValueType::Array => "array",
ValueType::Table => "table",
},
None => "string",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_k9_contract_basic() {
let content = generate_k9_contract(
"app-config",
SafetyTier::Kennel,
&["port > 0".into(), "port < 65536".into(), "host != ''".into()],
&["signed-by: ci-pipeline".into()],
&["remove: deprecated-keys".into()],
&["production-ready".into()],
&[
ParsedEntry {
key: "port".into(),
value: "8080".into(),
value_type: ValueType::Int,
},
ParsedEntry {
key: "host".into(),
value: "localhost".into(),
value_type: ValueType::String,
},
],
);
assert!(content.contains("# Auto-generated K9 contract for app-config"));
assert!(content.contains("# Safety tier: kennel"));
assert!(content.contains("[must]"));
assert!(content.contains("port : int { > 0, < 65536 }"));
assert!(content.contains("host : string { != '' }"));
assert!(content.contains("[trust]"));
assert!(content.contains("signed-by = \"ci-pipeline\""));
assert!(content.contains("[dust]"));
assert!(content.contains("remove = [\"deprecated-keys\"]"));
assert!(content.contains("[intend]"));
assert!(content.contains("production-ready = true"));
}
#[test]
fn test_build_k9_contract_struct() {
let contract = build_k9_contract(
"test",
"config/test.toml",
ConfigFormat::Toml,
SafetyTier::Yard,
&["port > 0".into()],
&["signed-by: ci".into()],
&["remove: old-keys".into()],
&["staging".into()],
);
assert_eq!(contract.name, "test");
assert_eq!(contract.safety_tier, SafetyTier::Yard);
assert_eq!(contract.must_rules.len(), 1);
assert_eq!(contract.trust_sources.len(), 1);
assert_eq!(contract.dust_rules.len(), 1);
assert_eq!(contract.intend_declarations.len(), 1);
}
}