systemprompt_models/profile/
mod.rs1mod cloud;
12mod database;
13mod error;
14mod from_env;
15mod gateway;
16mod governance;
17mod info;
18mod paths;
19mod providers;
20mod rate_limits;
21mod runtime;
22mod secrets;
23mod security;
24mod server;
25mod site;
26mod style;
27mod validation;
28
29pub use cloud::{CloudConfig, CloudValidationMode};
30pub use database::{DatabaseConfig, PoolConfig};
31pub use error::{ProfileError, ProfileResult};
32pub use gateway::{
33 GatewayConfig, GatewayConfigSpec, GatewayProfileError, GatewayResult, GatewayRoute,
34 GatewayState, OverrideRuleAction, ResponseFormatKind, RouteMatch, SystemPromptRule,
35 slugify_pattern, synthesize_route_id,
36};
37pub use governance::{
38 AuthzConfig, AuthzHookConfig, AuthzMode, GovernanceConfig, UNRESTRICTED_ACKNOWLEDGEMENT,
39};
40pub use info::ProfileInfo;
41pub use paths::{PathsConfig, expand_home, resolve_path, resolve_with_home};
42pub use providers::{
43 ApiSurface, ProviderEntry, ProviderModel, ProviderRegistry, ProviderRegistryError,
44 ProviderRegistryResult, WireProtocol,
45};
46pub use rate_limits::{
47 RateLimitsConfig, TierMultipliers, default_a2a_multiplier, default_admin_multiplier,
48 default_agent_registry, default_agents, default_anon_multiplier, default_artifacts,
49 default_burst, default_content, default_contexts, default_mcp, default_mcp_multiplier,
50 default_mcp_registry, default_oauth_auth, default_oauth_public, default_service_multiplier,
51 default_stream, default_tasks, default_user_multiplier,
52};
53pub use runtime::{Environment, LogLevel, OutputFormat, RuntimeConfig};
54pub use secrets::{SecretsConfig, SecretsSource, SecretsValidationMode};
55pub use security::{
56 DEFAULT_ID_JAG_TTL_SECS, GATEWAY_REQUIRED_RESOURCE_AUDIENCES, SecurityConfig, TrustedIssuer,
57 default_resource_audiences,
58};
59pub use server::{
60 ContentNegotiationConfig, FrameOptions, ReferrerPolicy, SecurityHeadersConfig, ServerConfig,
61};
62pub use site::SiteConfig;
63pub use style::ProfileStyle;
64
65use serde::{Deserialize, Serialize};
66use std::path::Path;
67
68use crate::env::{interpolate, read_env_optional};
69
70#[derive(Debug, Clone, Default, Serialize, Deserialize, schemars::JsonSchema)]
71#[serde(deny_unknown_fields)]
72pub struct ExtensionsConfig {
73 #[serde(default)]
74 pub disabled: Vec<String>,
75}
76
77impl ExtensionsConfig {
78 pub fn is_disabled(&self, extension_id: &str) -> bool {
79 self.disabled.iter().any(|id| id == extension_id)
80 }
81}
82
83#[derive(
84 Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, schemars::JsonSchema,
85)]
86#[serde(rename_all = "lowercase")]
87pub enum ProfileType {
88 #[default]
89 Local,
90 Cloud,
91}
92
93impl ProfileType {
94 pub const fn is_cloud(&self) -> bool {
95 matches!(self, Self::Cloud)
96 }
97
98 pub const fn is_local(&self) -> bool {
99 matches!(self, Self::Local)
100 }
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
104#[serde(deny_unknown_fields)]
105pub struct Profile {
106 pub name: String,
107
108 pub display_name: String,
109
110 #[serde(default)]
111 pub target: ProfileType,
112
113 pub site: SiteConfig,
114
115 pub database: DatabaseConfig,
116
117 pub server: ServerConfig,
118
119 pub paths: PathsConfig,
120
121 pub security: SecurityConfig,
122
123 pub rate_limits: RateLimitsConfig,
124
125 pub system_admin: crate::services::SystemAdminConfig,
126
127 #[serde(default)]
128 pub runtime: RuntimeConfig,
129
130 #[serde(default)]
131 pub cloud: Option<CloudConfig>,
132
133 #[serde(default)]
134 pub secrets: Option<SecretsConfig>,
135
136 #[serde(default)]
137 pub extensions: ExtensionsConfig,
138
139 #[serde(default)]
140 pub providers: ProviderRegistry,
141
142 #[serde(default)]
143 pub gateway: Option<GatewayState>,
144
145 #[serde(default)]
146 pub governance: Option<GovernanceConfig>,
147}
148
149impl Profile {
150 #[must_use]
151 pub fn is_local_trial(&self) -> bool {
152 self.cloud.as_ref().is_none_or(CloudConfig::is_local_trial)
153 }
154
155 #[must_use]
159 pub const fn path_resolution(&self) -> crate::paths::PathResolution {
160 if self.target.is_cloud() {
161 crate::paths::PathResolution::Lexical
162 } else {
163 crate::paths::PathResolution::Canonicalize
164 }
165 }
166
167 pub fn from_yaml(content: &str, profile_path: &Path) -> ProfileResult<Self> {
168 let content = interpolate(content, &|name| read_env_optional(name));
169
170 let mut profile: Self =
171 serde_yaml::from_str(&content).map_err(|source| ProfileError::ParseYaml {
172 path: profile_path.to_path_buf(),
173 source,
174 })?;
175
176 let profile_dir =
177 profile_path
178 .parent()
179 .ok_or_else(|| ProfileError::InvalidProfilePath {
180 path: profile_path.to_path_buf(),
181 })?;
182
183 profile.paths.resolve_relative_to(profile_dir);
184
185 Ok(profile)
186 }
187
188 pub fn to_yaml(&self) -> ProfileResult<String> {
189 serde_yaml::to_string(self).map_err(ProfileError::SerializeYaml)
190 }
191
192 pub fn profile_style(&self) -> ProfileStyle {
193 match self.name.to_lowercase().as_str() {
194 "dev" | "development" | "local" => ProfileStyle::Development,
195 "prod" | "production" => ProfileStyle::Production,
196 "staging" | "stage" => ProfileStyle::Staging,
197 "test" | "testing" => ProfileStyle::Test,
198 _ => ProfileStyle::Custom,
199 }
200 }
201
202 pub fn mask_secret(value: &str, visible_chars: usize) -> String {
203 if value.is_empty() {
204 return "(not set)".to_owned();
205 }
206 if value.len() <= visible_chars {
207 return "***".to_owned();
208 }
209 format!("{}...", &value[..visible_chars])
210 }
211
212 pub fn mask_database_url(url: &str) -> String {
213 if let Some(at_pos) = url.find('@')
214 && let Some(colon_pos) = url[..at_pos].rfind(':')
215 {
216 let prefix = &url[..=colon_pos];
217 let suffix = &url[at_pos..];
218 return format!("{}***{}", prefix, suffix);
219 }
220 url.to_owned()
221 }
222
223 pub fn is_masked_database_url(url: &str) -> bool {
224 url.contains(":***@") || url.contains(":********@")
225 }
226}