1use std::path::PathBuf;
8
9use serde::{Deserialize, Serialize};
10
11pub use sui_castore::BackendConfig;
13pub use sui_castore::WritePolicy;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct CacheConfig {
18 pub listen: String,
20 pub backend: BackendConfig,
22 pub signing_key: Option<PathBuf>,
28 pub priority: u32,
30 pub want_mass_query: bool,
32 pub store_dir: String,
34 #[serde(default)]
44 pub require_sigs: bool,
45}
46
47impl Default for CacheConfig {
48 fn default() -> Self {
49 Self {
50 listen: "0.0.0.0:5000".to_string(),
51 backend: BackendConfig::default(),
52 signing_key: None,
53 priority: 40,
54 want_mass_query: true,
55 store_dir: "/nix/store".to_string(),
56 require_sigs: false,
57 }
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use std::path::PathBuf;
65
66 #[test]
67 fn default_config_has_sane_values() {
68 let config = CacheConfig::default();
69 assert_eq!(config.listen, "0.0.0.0:5000");
70 assert_eq!(config.store_dir, "/nix/store");
71 assert_eq!(config.priority, 40);
72 assert!(config.want_mass_query);
73 assert!(config.signing_key.is_none());
74 }
75
76 #[test]
77 fn default_backend_is_local() {
78 let config = CacheConfig::default();
79 assert!(matches!(config.backend, BackendConfig::Local { .. }));
80 }
81
82 #[test]
83 fn config_serializes_to_json() {
84 let config = CacheConfig::default();
85 let json = serde_json::to_string(&config).unwrap();
86 assert!(json.contains("local"));
87 assert!(json.contains("5000"));
88 }
89
90 #[test]
91 fn config_roundtrips_through_json() {
92 let config = CacheConfig {
93 listen: "127.0.0.1:8080".to_string(),
94 backend: BackendConfig::S3 {
95 bucket: "my-cache".to_string(),
96 region: "us-east-1".to_string(),
97 endpoint: Some("http://localhost:9000".to_string()),
98 },
99 signing_key: Some(PathBuf::from("/tmp/key.sec")),
100 priority: 30,
101 want_mass_query: false,
102 store_dir: "/nix/store".to_string(),
103 require_sigs: true,
104 };
105 let json = serde_json::to_string_pretty(&config).unwrap();
106 let parsed: CacheConfig = serde_json::from_str(&json).unwrap();
107 assert_eq!(parsed.listen, "127.0.0.1:8080");
108 assert_eq!(parsed.priority, 30);
109 assert!(!parsed.want_mass_query);
110 assert!(parsed.require_sigs);
111 assert!(matches!(parsed.backend, BackendConfig::S3 { .. }));
112 }
113
114 #[test]
115 fn require_sigs_defaults_to_false_when_absent() {
116 let json = r#"{
119 "listen": "0.0.0.0:5000",
120 "backend": { "type": "local", "path": "/var/cache/sui" },
121 "signing_key": null,
122 "priority": 40,
123 "want_mass_query": true,
124 "store_dir": "/nix/store"
125 }"#;
126 let parsed: CacheConfig = serde_json::from_str(json).unwrap();
127 assert!(!parsed.require_sigs);
128 }
129
130 #[test]
131 fn tiered_backend_roundtrips_through_json() {
132 let backend = BackendConfig::Tiered {
133 l1: Box::new(BackendConfig::Redis {
134 url: "redis://redis:6379".to_string(),
135 ttl_secs: Some(3600),
136 }),
137 l2: Box::new(BackendConfig::Pg {
138 url: "postgres://pg:5432/sui".to_string(),
139 max_conns: 16,
140 }),
141 l3: Box::new(BackendConfig::S3 {
142 bucket: "sui-super-cache".to_string(),
143 region: "us-east-1".to_string(),
144 endpoint: None,
145 }),
146 write_policy: WritePolicy::WriteThrough,
147 };
148 let json = serde_json::to_string_pretty(&backend).unwrap();
149 assert!(json.contains("tiered"));
150 assert!(json.contains("redis"));
151 assert!(json.contains("write-through"));
152 let parsed: BackendConfig = serde_json::from_str(&json).unwrap();
153 match parsed {
154 BackendConfig::Tiered { l1, l2, l3, write_policy } => {
155 assert!(matches!(*l1, BackendConfig::Redis { .. }));
156 assert!(matches!(*l2, BackendConfig::Pg { .. }));
157 assert!(matches!(*l3, BackendConfig::S3 { .. }));
158 assert_eq!(write_policy, WritePolicy::WriteThrough);
159 }
160 other => panic!("expected tiered, got {other:?}"),
161 }
162 }
163
164 #[test]
165 fn tiered_write_policy_defaults_when_absent() {
166 let json = r#"{
168 "type": "tiered",
169 "l1": { "type": "redis", "url": "redis://r:6379" },
170 "l2": { "type": "pg", "url": "postgres://p:5432/s", "max_conns": 8 },
171 "l3": { "type": "local", "path": "/var/cache/sui" }
172 }"#;
173 let parsed: BackendConfig = serde_json::from_str(json).unwrap();
174 match parsed {
175 BackendConfig::Tiered { write_policy, .. } => {
176 assert_eq!(write_policy, WritePolicy::default());
177 assert_eq!(write_policy, WritePolicy::WriteThrough);
178 }
179 other => panic!("expected tiered, got {other:?}"),
180 }
181 }
182
183 #[test]
184 fn redis_ttl_defaults_to_none() {
185 let json = r#"{ "type": "redis", "url": "redis://r:6379" }"#;
186 let parsed: BackendConfig = serde_json::from_str(json).unwrap();
187 assert!(matches!(parsed, BackendConfig::Redis { ttl_secs: None, .. }));
188 }
189}