1use std::time::Duration;
2
3pub const DEFAULT_POSTGRES_MAX_CONNECTIONS: u32 = 10;
4pub const DEFAULT_POSTGRES_ACQUIRE_TIMEOUT_SECONDS: u64 = 30;
5pub const DEFAULT_POSTGRES_IDLE_TIMEOUT_SECONDS: u64 = 600;
6pub const DEFAULT_POSTGRES_LOCK_TIMEOUT_SECONDS: u64 = 5;
7pub const DEFAULT_POSTGRES_MAX_LIFETIME_SECONDS: u64 = 1_800;
8pub const DEFAULT_POSTGRES_MIGRATION_TIMEOUT_SECONDS: u64 = 300;
9pub const DEFAULT_POSTGRES_STATEMENT_TIMEOUT_SECONDS: u64 = 30;
10pub const DEFAULT_SQLITE_MAX_CONNECTIONS: u32 = 4;
11pub const DEFAULT_SQLITE_JOURNAL_SIZE_LIMIT_BYTES: u64 = 6_144_000;
12pub const DEFAULT_SQLITE_MMAP_SIZE_BYTES: u64 = 268_435_456;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct PostgresConfig {
16 pub max_connections: u32,
17 pub acquire_timeout: Duration,
18 pub lock_timeout: Duration,
19 pub migration_timeout: Duration,
20 pub statement_timeout: Duration,
21 pub idle_timeout: Option<Duration>,
22 pub max_lifetime: Option<Duration>,
23}
24
25impl Default for PostgresConfig {
26 fn default() -> Self {
27 Self {
28 max_connections: DEFAULT_POSTGRES_MAX_CONNECTIONS,
29 acquire_timeout: Duration::from_secs(DEFAULT_POSTGRES_ACQUIRE_TIMEOUT_SECONDS),
30 lock_timeout: Duration::from_secs(DEFAULT_POSTGRES_LOCK_TIMEOUT_SECONDS),
31 migration_timeout: Duration::from_secs(DEFAULT_POSTGRES_MIGRATION_TIMEOUT_SECONDS),
32 statement_timeout: Duration::from_secs(DEFAULT_POSTGRES_STATEMENT_TIMEOUT_SECONDS),
33 idle_timeout: Some(Duration::from_secs(DEFAULT_POSTGRES_IDLE_TIMEOUT_SECONDS)),
34 max_lifetime: Some(Duration::from_secs(DEFAULT_POSTGRES_MAX_LIFETIME_SECONDS)),
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum SqliteTempStore {
41 Default,
42 File,
43 #[default]
44 Memory,
45}
46
47impl SqliteTempStore {
48 #[must_use]
49 pub fn as_pragma_value(self) -> &'static str {
50 match self {
51 Self::Default => "DEFAULT",
52 Self::File => "FILE",
53 Self::Memory => "MEMORY",
54 }
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct SqliteConfig {
60 pub max_connections: u32,
61 pub journal_size_limit_bytes: u64,
62 pub temp_store: SqliteTempStore,
63 pub mmap_size_bytes: u64,
64}
65
66impl Default for SqliteConfig {
67 fn default() -> Self {
68 Self {
69 max_connections: DEFAULT_SQLITE_MAX_CONNECTIONS,
70 journal_size_limit_bytes: DEFAULT_SQLITE_JOURNAL_SIZE_LIMIT_BYTES,
71 temp_store: SqliteTempStore::default(),
72 mmap_size_bytes: DEFAULT_SQLITE_MMAP_SIZE_BYTES,
73 }
74 }
75}
76
77#[derive(Debug, Clone)]
78pub struct Config {
79 pub llm_api_base: String,
80 pub openai_api_key: Option<String>,
81 pub llm_ready_timeout_s: f64,
82 pub llm_ready_interval_s: f64,
83 pub skip_llm_ready_check: bool,
84 pub db_url: Option<String>,
87 pub postgres: PostgresConfig,
88 pub sqlite: SqliteConfig,
89}
90
91#[must_use]
92pub fn normalize_base_url(url: &str) -> String {
93 let mut s = url.trim_end_matches('/').to_owned();
94 if s.ends_with("/v1") {
95 s.truncate(s.len() - 3);
96 s = s.trim_end_matches('/').to_owned();
97 }
98 s
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn strip_trailing_v1() {
107 assert_eq!(normalize_base_url("http://host:8000/v1"), "http://host:8000");
108 assert_eq!(normalize_base_url("http://host:8000/v1/"), "http://host:8000");
109 }
110
111 #[test]
112 fn no_v1_unchanged() {
113 assert_eq!(normalize_base_url("http://host:8000"), "http://host:8000");
114 assert_eq!(normalize_base_url("http://host:8000/"), "http://host:8000");
115 }
116}