systemprompt_runtime/
config_planes.rs1use std::collections::HashSet;
13use std::path::{Path, PathBuf};
14use std::sync::Arc;
15
16use serde::Serialize;
17use systemprompt_manifest::profile::{ConfigPlanesConfig, PlaneAuthority};
18use systemprompt_provider_contracts::{ConfigPlane, ConfigPlaneError, ConfigPlaneResult};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
21#[serde(rename_all = "snake_case")]
22pub enum EffectiveAuthority {
23 File,
24 Db,
25 Undeclared,
26}
27
28impl EffectiveAuthority {
29 #[must_use]
30 pub const fn as_str(self) -> &'static str {
31 match self {
32 Self::File => "file",
33 Self::Db => "db",
34 Self::Undeclared => "undeclared",
35 }
36 }
37
38 #[must_use]
39 pub const fn is_file_managed(self) -> bool {
40 matches!(self, Self::File)
41 }
42}
43
44#[derive(Clone)]
45pub struct ConfigPlaneSet {
46 planes: Vec<Arc<dyn ConfigPlane>>,
47 config: ConfigPlanesConfig,
48 services_root: PathBuf,
49}
50
51impl std::fmt::Debug for ConfigPlaneSet {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.debug_struct("ConfigPlaneSet")
54 .field("planes", &self.names())
55 .field("config", &self.config)
56 .field("services_root", &self.services_root)
57 .finish()
58 }
59}
60
61impl ConfigPlaneSet {
62 pub fn new(
63 planes: Vec<Arc<dyn ConfigPlane>>,
64 config: ConfigPlanesConfig,
65 services_root: PathBuf,
66 ) -> ConfigPlaneResult<Self> {
67 let mut seen = HashSet::new();
68 for plane in &planes {
69 if !seen.insert(plane.name()) {
70 return Err(ConfigPlaneError::DuplicatePlane(plane.name().to_owned()));
71 }
72 }
73 Ok(Self {
74 planes,
75 config,
76 services_root,
77 })
78 }
79
80 #[must_use]
81 pub fn planes(&self) -> &[Arc<dyn ConfigPlane>] {
82 &self.planes
83 }
84
85 #[must_use]
86 pub fn names(&self) -> Vec<&'static str> {
87 self.planes.iter().map(|p| p.name()).collect()
88 }
89
90 #[must_use]
91 pub fn services_root(&self) -> &Path {
92 &self.services_root
93 }
94
95 #[must_use]
96 pub const fn config(&self) -> &ConfigPlanesConfig {
97 &self.config
98 }
99
100 pub fn find(&self, name: &str) -> ConfigPlaneResult<&Arc<dyn ConfigPlane>> {
101 self.planes
102 .iter()
103 .find(|p| p.name() == name)
104 .ok_or_else(|| ConfigPlaneError::UnknownPlane(name.to_owned()))
105 }
106
107 #[must_use]
108 pub fn declared_exists(&self, plane: &dyn ConfigPlane) -> bool {
109 self.services_root.join(plane.declared_path()).exists()
110 }
111
112 #[must_use]
113 pub fn authority_of(&self, plane: &dyn ConfigPlane) -> EffectiveAuthority {
114 match self.config.authority_for(plane.name()) {
115 PlaneAuthority::Db => EffectiveAuthority::Db,
116 PlaneAuthority::File if self.declared_exists(plane) => EffectiveAuthority::File,
117 PlaneAuthority::File => EffectiveAuthority::Undeclared,
118 }
119 }
120
121 pub fn authority(&self, name: &str) -> ConfigPlaneResult<EffectiveAuthority> {
122 Ok(self.authority_of(self.find(name)?.as_ref()))
123 }
124
125 pub fn is_file_managed(&self, name: &str) -> ConfigPlaneResult<bool> {
126 Ok(self.authority(name)?.is_file_managed())
127 }
128
129 pub fn ensure_db_writable(&self, name: &str) -> ConfigPlaneResult<()> {
130 let plane = self.find(name)?;
131 if self.authority_of(plane.as_ref()).is_file_managed() {
132 return Err(ConfigPlaneError::FileManaged {
133 plane: name.to_owned(),
134 path: plane.declared_path().to_owned(),
135 });
136 }
137 Ok(())
138 }
139
140 #[must_use]
141 pub fn unregistered_authority_keys(&self) -> Vec<&str> {
142 self.config
143 .authority
144 .keys()
145 .map(String::as_str)
146 .filter(|key| !self.planes.iter().any(|p| p.name() == *key))
147 .collect()
148 }
149}