systemprompt_database/scope/
provider.rs1use std::sync::Arc;
7
8use systemprompt_models::RequestScope;
9
10#[derive(Debug, thiserror::Error)]
11pub enum ScopeError {
12 #[error("invalid scope setting key '{key}': must be a dotted custom-GUC name")]
13 InvalidKey { key: String },
14 #[error("scope provider failed: {0}")]
15 Provider(String),
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct ScopeSetting {
25 key: String,
26 value: String,
27}
28
29impl ScopeSetting {
30 pub fn new(key: impl Into<String>, value: impl Into<String>) -> Result<Self, ScopeError> {
34 let key = key.into();
35 if !is_custom_guc_name(&key) {
36 return Err(ScopeError::InvalidKey { key });
37 }
38 Ok(Self {
39 key,
40 value: value.into(),
41 })
42 }
43
44 #[must_use]
45 pub fn key(&self) -> &str {
46 &self.key
47 }
48
49 #[must_use]
50 pub fn value(&self) -> &str {
51 &self.value
52 }
53}
54
55fn is_custom_guc_name(key: &str) -> bool {
56 let mut segments = 0;
57 for segment in key.split('.') {
58 let mut chars = segment.chars();
59 let Some(first) = chars.next() else {
60 return false;
61 };
62 if !(first.is_ascii_alphabetic() || first == '_') {
63 return false;
64 }
65 if !chars.all(|c| c.is_ascii_alphanumeric() || c == '_') {
66 return false;
67 }
68 segments += 1;
69 }
70 segments >= 2
71}
72
73#[async_trait::async_trait]
78pub trait ConnectionScopeProvider: Send + Sync {
79 async fn scope_settings(&self, scope: &RequestScope) -> Result<Vec<ScopeSetting>, ScopeError>;
82}
83
84pub type SharedScopeProvider = Arc<dyn ConnectionScopeProvider>;