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> {
31 let key = key.into();
32 if !is_custom_guc_name(&key) {
33 return Err(ScopeError::InvalidKey { key });
34 }
35 Ok(Self {
36 key,
37 value: value.into(),
38 })
39 }
40
41 #[must_use]
42 pub fn key(&self) -> &str {
43 &self.key
44 }
45
46 #[must_use]
47 pub fn value(&self) -> &str {
48 &self.value
49 }
50}
51
52fn is_custom_guc_name(key: &str) -> bool {
53 let mut segments = 0;
54 for segment in key.split('.') {
55 let mut chars = segment.chars();
56 let Some(first) = chars.next() else {
57 return false;
58 };
59 if !(first.is_ascii_alphabetic() || first == '_') {
60 return false;
61 }
62 if !chars.all(|c| c.is_ascii_alphanumeric() || c == '_') {
63 return false;
64 }
65 segments += 1;
66 }
67 segments >= 2
68}
69
70#[async_trait::async_trait]
75pub trait ConnectionScopeProvider: Send + Sync {
76 async fn scope_settings(&self, scope: &RequestScope) -> Result<Vec<ScopeSetting>, ScopeError>;
77}
78
79pub type SharedScopeProvider = Arc<dyn ConnectionScopeProvider>;