waddling_errors_hash/
config_loader.rs1#[cfg(not(feature = "std"))]
24extern crate alloc;
25
26#[cfg(feature = "std")]
27use std::string::String;
28
29#[cfg(not(feature = "std"))]
30use alloc::string::String;
31
32use crate::algorithm::HashConfig;
33
34#[cfg(feature = "std")]
35use std::vec::Vec;
36
37#[cfg(not(feature = "std"))]
38use alloc::vec::Vec;
39
40#[derive(Debug, Clone)]
44pub struct DocGenConfig {
45 pub formats: Vec<String>,
47 pub output_dir: String,
49 pub namespace: Option<String>,
60}
61
62impl Default for DocGenConfig {
63 fn default() -> Self {
64 Self {
65 formats: Vec::new(),
66 output_dir: String::from("target/doc"),
67 namespace: None,
68 }
69 }
70}
71
72pub fn load_global_config() -> HashConfig {
91 if let Some(config) = load_from_env() {
93 return config;
94 }
95
96 #[cfg(feature = "std")]
98 if let Some(config) = load_from_cargo_toml() {
99 return config;
100 }
101
102 HashConfig::wdp_compliant()
104}
105
106fn load_from_env() -> Option<HashConfig> {
117 let seed_str = option_env!("WADDLING_HASH_SEED")?;
119
120 let seed = parse_seed(seed_str)?;
122
123 Some(HashConfig::with_seed(seed))
124}
125
126fn parse_seed(s: &str) -> Option<u64> {
132 let s = s.trim();
133 if s.starts_with("0x") || s.starts_with("0X") {
134 u64::from_str_radix(&s[2..], 16).ok()
135 } else {
136 s.parse().ok()
137 }
138}
139
140#[cfg(feature = "std")]
148fn load_from_cargo_toml() -> Option<HashConfig> {
149 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").ok()?;
151 let manifest_path = std::path::Path::new(&manifest_dir).join("Cargo.toml");
152
153 let cargo_toml_content = std::fs::read_to_string(manifest_path).ok()?;
155
156 let seed_str = parse_toml_value(&cargo_toml_content, "hash_seed")?;
158 let seed = parse_seed(&seed_str)?;
159
160 Some(HashConfig::with_seed(seed))
161}
162
163#[cfg(feature = "std")]
172fn parse_toml_value(toml_content: &str, key: &str) -> Option<String> {
173 let mut in_section = false;
174
175 for line in toml_content.lines() {
176 let line = line.trim();
177
178 if line == "[package.metadata.waddling-errors]" {
180 in_section = true;
181 continue;
182 }
183
184 if in_section && line.starts_with('[') {
186 break;
187 }
188
189 if in_section && line.contains('=') {
191 let parts: Vec<&str> = line.splitn(2, '=').collect();
192 if parts.len() == 2 {
193 let found_key = parts[0].trim();
194 let value = parts[1].trim();
195
196 if found_key == key {
197 let value = value.trim_matches('"').trim_matches('\'');
199 return Some(value.to_string());
200 }
201 }
202 }
203 }
204
205 None
206}
207
208pub fn apply_overrides(global: &HashConfig, seed: Option<u64>) -> HashConfig {
226 match seed {
227 Some(s) => HashConfig::with_seed(s),
228 None => global.clone(),
229 }
230}
231
232#[cfg(feature = "std")]
253pub fn load_namespace() -> Option<String> {
254 if let Ok(ns) = std::env::var("WADDLING_NAMESPACE")
256 && !ns.is_empty()
257 {
258 return Some(ns);
259 }
260
261 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").ok()?;
263 let manifest_path = std::path::Path::new(&manifest_dir).join("Cargo.toml");
264 let cargo_toml_content = std::fs::read_to_string(manifest_path).ok()?;
265
266 parse_toml_value(&cargo_toml_content, "namespace")
267}
268
269pub fn load_doc_gen_config() -> DocGenConfig {
285 #[cfg(feature = "std")]
287 {
288 if let Ok(env_formats) = std::env::var("WADDLING_DOC_FORMATS") {
289 let formats: Vec<String> = env_formats
290 .split(',')
291 .map(|s| s.trim().to_string())
292 .filter(|s| !s.is_empty())
293 .collect();
294
295 let output_dir = std::env::var("WADDLING_DOC_OUTPUT_DIR")
296 .unwrap_or_else(|_| "target/doc".to_string());
297
298 let namespace = std::env::var("WADDLING_NAMESPACE").ok();
299
300 return DocGenConfig {
301 formats,
302 output_dir,
303 namespace,
304 };
305 }
306 }
307
308 #[cfg(feature = "std")]
310 if let Some(config) = load_doc_config_from_cargo_toml() {
311 return config;
312 }
313
314 DocGenConfig::default()
316}
317
318#[cfg(feature = "std")]
320fn load_doc_config_from_cargo_toml() -> Option<DocGenConfig> {
321 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").ok()?;
323 let manifest_path = std::path::Path::new(&manifest_dir).join("Cargo.toml");
324
325 let cargo_toml_content = std::fs::read_to_string(manifest_path).ok()?;
327
328 let formats = parse_toml_array(&cargo_toml_content, "doc_formats").unwrap_or_default();
330
331 let output_dir = parse_toml_value(&cargo_toml_content, "doc_output_dir")
333 .unwrap_or_else(|| "target/doc".to_string());
334
335 let namespace = parse_toml_value(&cargo_toml_content, "namespace");
337
338 if formats.is_empty() && namespace.is_none() {
340 None
341 } else {
342 Some(DocGenConfig {
343 formats,
344 output_dir,
345 namespace,
346 })
347 }
348}
349
350#[cfg(feature = "std")]
359fn parse_toml_array(toml_content: &str, key: &str) -> Option<Vec<String>> {
360 let mut in_section = false;
361
362 for line in toml_content.lines() {
363 let line = line.trim();
364
365 if line == "[package.metadata.waddling-errors]" {
367 in_section = true;
368 continue;
369 }
370
371 if in_section && line.starts_with('[') {
373 break;
374 }
375
376 if in_section && line.contains('=') {
378 let parts: Vec<&str> = line.splitn(2, '=').collect();
379 if parts.len() == 2 {
380 let found_key = parts[0].trim();
381 let value = parts[1].trim();
382
383 if found_key == key {
384 if value.starts_with('[') && value.ends_with(']') {
386 let array_content =
387 value.trim_start_matches('[').trim_end_matches(']').trim();
388
389 let items: Vec<String> = array_content
390 .split(',')
391 .map(|s| s.trim().trim_matches('"').trim_matches('\'').to_string())
392 .filter(|s| !s.is_empty())
393 .collect();
394
395 return Some(items);
396 }
397 }
398 }
399 }
400 }
401
402 None
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use crate::algorithm::{HashAlgorithm, WDP_SEED};
409
410 #[test]
411 fn test_load_global_config_default() {
412 let config = load_global_config();
414 assert_eq!(config.algorithm, HashAlgorithm::XxHash3);
415 assert_eq!(config.seed, WDP_SEED);
416 }
417
418 #[test]
419 fn test_apply_overrides_with_seed() {
420 let global = HashConfig::default();
421 let config = apply_overrides(&global, Some(0x12345678));
422
423 assert_eq!(config.algorithm, HashAlgorithm::XxHash3);
424 assert_eq!(config.seed, 0x12345678);
425 }
426
427 #[test]
428 fn test_apply_overrides_none() {
429 let global = HashConfig::with_seed(0x87654321);
430 let config = apply_overrides(&global, None);
431
432 assert_eq!(config.algorithm, HashAlgorithm::XxHash3);
433 assert_eq!(config.seed, 0x87654321);
434 }
435
436 #[test]
437 fn test_parse_seed_hex() {
438 assert_eq!(parse_seed("0x12345678"), Some(0x12345678));
439 assert_eq!(parse_seed("0X12345678"), Some(0x12345678));
440 assert_eq!(parse_seed("0x000031762D706477"), Some(WDP_SEED));
441 }
442
443 #[test]
444 fn test_parse_seed_decimal() {
445 assert_eq!(parse_seed("12345678"), Some(12345678));
446 assert_eq!(parse_seed("0"), Some(0));
447 }
448
449 #[cfg(feature = "std")]
450 #[test]
451 fn test_parse_toml_value() {
452 let toml = r#"
453[package]
454name = "test"
455
456[package.metadata.waddling-errors]
457hash_seed = "0x12345678"
458namespace = "auth_service"
459
460[dependencies]
461some_dep = "1.0"
462"#;
463
464 assert_eq!(
465 parse_toml_value(toml, "hash_seed"),
466 Some("0x12345678".to_string())
467 );
468 assert_eq!(
469 parse_toml_value(toml, "namespace"),
470 Some("auth_service".to_string())
471 );
472 assert_eq!(parse_toml_value(toml, "nonexistent"), None);
473 }
474
475 #[cfg(feature = "std")]
476 #[test]
477 fn test_parse_toml_value_single_quotes() {
478 let toml = r#"
479[package.metadata.waddling-errors]
480hash_seed = '0x87654321'
481"#;
482
483 assert_eq!(
484 parse_toml_value(toml, "hash_seed"),
485 Some("0x87654321".to_string())
486 );
487 }
488
489 #[cfg(feature = "std")]
490 #[test]
491 fn test_parse_toml_value_section_boundary() {
492 let toml = r#"
493[package.metadata.waddling-errors]
494hash_seed = "0x12345678"
495
496[package.metadata.other]
497hash_seed = "ShouldNotMatch"
498"#;
499
500 assert_eq!(
502 parse_toml_value(toml, "hash_seed"),
503 Some("0x12345678".to_string())
504 );
505 }
506}