1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum PromptAuthority {
9 Platform,
11 Product,
13 Profile,
15 TrustedCapability,
17 Plugin,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct PromptSection {
24 pub id: String,
26 pub order: i32,
28 pub authority: PromptAuthority,
30 pub source: String,
32 pub version: String,
34 pub content: String,
36}
37
38#[derive(Debug, Clone, Default)]
40pub struct PromptRegistry {
41 sections: BTreeMap<(i32, String), PromptSection>,
42}
43
44impl PromptRegistry {
45 pub fn insert(&mut self, section: PromptSection) -> Result<(), String> {
47 if section.id.trim().is_empty() || section.content.trim().is_empty() {
48 return Err("prompt section id and content are required".into());
49 }
50 if self
51 .sections
52 .values()
53 .any(|current| current.id == section.id)
54 {
55 return Err(format!("duplicate prompt section '{}'", section.id));
56 }
57 self.sections
58 .insert((section.order, section.id.clone()), section);
59 Ok(())
60 }
61
62 pub fn render(&self) -> String {
64 self.sections
65 .values()
66 .map(|section| section.content.trim())
67 .collect::<Vec<_>>()
68 .join("\n\n")
69 }
70
71 pub fn sections(&self) -> Vec<PromptSection> {
73 self.sections.values().cloned().collect()
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn renders_stably_and_rejects_duplicate_ids() {
83 let mut prompts = PromptRegistry::default();
84 prompts
85 .insert(PromptSection {
86 id: "profile".into(),
87 order: 20,
88 authority: PromptAuthority::Profile,
89 source: "profile".into(),
90 version: "r1".into(),
91 content: "profile".into(),
92 })
93 .unwrap();
94 prompts
95 .insert(PromptSection {
96 id: "base".into(),
97 order: 10,
98 authority: PromptAuthority::Product,
99 source: "product".into(),
100 version: "1".into(),
101 content: "base".into(),
102 })
103 .unwrap();
104 assert_eq!(prompts.render(), "base\n\nprofile");
105 assert!(prompts
106 .insert(PromptSection {
107 id: "base".into(),
108 order: 30,
109 authority: PromptAuthority::Plugin,
110 source: "plugin".into(),
111 version: "1".into(),
112 content: "duplicate".into(),
113 })
114 .is_err());
115 }
116}