use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PromptAuthority {
Platform,
Product,
Profile,
TrustedCapability,
Plugin,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PromptSection {
pub id: String,
pub order: i32,
pub authority: PromptAuthority,
pub source: String,
pub version: String,
pub content: String,
}
#[derive(Debug, Clone, Default)]
pub struct PromptRegistry {
sections: BTreeMap<(i32, String), PromptSection>,
}
impl PromptRegistry {
pub fn insert(&mut self, section: PromptSection) -> Result<(), String> {
if section.id.trim().is_empty() || section.content.trim().is_empty() {
return Err("prompt section id and content are required".into());
}
if self
.sections
.values()
.any(|current| current.id == section.id)
{
return Err(format!("duplicate prompt section '{}'", section.id));
}
self.sections
.insert((section.order, section.id.clone()), section);
Ok(())
}
pub fn render(&self) -> String {
self.sections
.values()
.map(|section| section.content.trim())
.collect::<Vec<_>>()
.join("\n\n")
}
pub fn sections(&self) -> Vec<PromptSection> {
self.sections.values().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn renders_stably_and_rejects_duplicate_ids() {
let mut prompts = PromptRegistry::default();
prompts
.insert(PromptSection {
id: "profile".into(),
order: 20,
authority: PromptAuthority::Profile,
source: "profile".into(),
version: "r1".into(),
content: "profile".into(),
})
.unwrap();
prompts
.insert(PromptSection {
id: "base".into(),
order: 10,
authority: PromptAuthority::Product,
source: "product".into(),
version: "1".into(),
content: "base".into(),
})
.unwrap();
assert_eq!(prompts.render(), "base\n\nprofile");
assert!(prompts
.insert(PromptSection {
id: "base".into(),
order: 30,
authority: PromptAuthority::Plugin,
source: "plugin".into(),
version: "1".into(),
content: "duplicate".into(),
})
.is_err());
}
}