Skip to main content

icydb_paths/
lib.rs

1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::Path;
4
5const INTERNAL_CRATES: &[&str] = &[
6    "icydb",
7    "icydb-base",
8    "icydb-build",
9    "icydb-core",
10    "icydb-error",
11    "icydb-macros",
12    "icydb-paths",
13    "icydb-schema",
14];
15
16fn env_path(name: &str) -> Option<TokenStream> {
17    std::env::var(name)
18        .ok()
19        .map(|value| value.trim().to_string())
20        .and_then(|value| syn::parse_str::<Path>(&value).ok())
21        .map(|path| quote!(#path))
22}
23
24///
25/// CratePaths
26///
27/// Resolves crate roots for generated code. Internal icydb crates default to
28/// direct crate names to avoid meta-crate cycles; other crates prefer the
29/// public `icydb::` facade. Env vars allow overrides:
30/// `ICYDB_CORE_CRATE`, `ICYDB_SCHEMA_CRATE`, `ICYDB_ERROR_CRATE`.
31///
32
33#[derive(Clone, Debug, Default)]
34pub struct CratePaths {
35    pub core: TokenStream,
36    pub schema: TokenStream,
37    pub error: TokenStream,
38}
39
40impl CratePaths {
41    #[must_use]
42    /// Resolve crate paths for generated code, honoring environment overrides.
43    pub fn new() -> Self {
44        let pkg = std::env::var("CARGO_PKG_NAME").unwrap_or_default();
45        let use_meta_paths = !INTERNAL_CRATES.contains(&pkg.as_str());
46
47        let core = if use_meta_paths {
48            quote!(icydb::core)
49        } else {
50            quote!(icydb_core)
51        };
52
53        let schema = if use_meta_paths {
54            quote!(icydb::schema)
55        } else {
56            quote!(icydb_schema)
57        };
58
59        let error = if use_meta_paths {
60            quote!(icydb::error)
61        } else {
62            quote!(icydb_error)
63        };
64
65        Self {
66            core: env_path("ICYDB_CORE_CRATE").unwrap_or(core),
67            schema: env_path("ICYDB_SCHEMA_CRATE").unwrap_or(schema),
68            error: env_path("ICYDB_ERROR_CRATE").unwrap_or(error),
69        }
70    }
71}
72
73/// Singleton accessor for proc-macro contexts.
74#[must_use]
75pub fn paths() -> CratePaths {
76    CratePaths::new()
77}
78
79///
80/// TESTS
81///
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use quote::quote;
87    use std::env;
88    use std::sync::Mutex;
89
90    static ENV_LOCK: Mutex<()> = Mutex::new(());
91
92    struct TempEnv {
93        key: &'static str,
94        prev: Option<String>,
95    }
96
97    impl TempEnv {
98        fn set(key: &'static str, value: Option<&str>) -> Self {
99            let prev = env::var(key).ok();
100            unsafe {
101                match value {
102                    Some(v) => env::set_var(key, v),
103                    None => env::remove_var(key),
104                }
105            }
106            Self { key, prev }
107        }
108    }
109
110    impl Drop for TempEnv {
111        fn drop(&mut self) {
112            unsafe {
113                match &self.prev {
114                    Some(value) => env::set_var(self.key, value),
115                    None => env::remove_var(self.key),
116                }
117            }
118        }
119    }
120
121    #[test]
122    fn uses_internal_crate_names_inside_workspace() {
123        let _lock = ENV_LOCK.lock().unwrap();
124        let _pkg = TempEnv::set("CARGO_PKG_NAME", Some("icydb-paths"));
125        let _core = TempEnv::set("ICYDB_CORE_CRATE", None);
126        let _schema = TempEnv::set("ICYDB_SCHEMA_CRATE", None);
127        let _error = TempEnv::set("ICYDB_ERROR_CRATE", None);
128
129        let paths = CratePaths::new();
130
131        assert_eq!(paths.core.to_string(), quote!(icydb_core).to_string());
132        assert_eq!(paths.schema.to_string(), quote!(icydb_schema).to_string());
133        assert_eq!(paths.error.to_string(), quote!(icydb_error).to_string());
134    }
135
136    #[test]
137    fn honors_env_overrides_for_external_consumers() {
138        let _lock = ENV_LOCK.lock().unwrap();
139        let _pkg = TempEnv::set("CARGO_PKG_NAME", Some("external-app"));
140        let _core = TempEnv::set("ICYDB_CORE_CRATE", Some("custom::core"));
141        let _schema = TempEnv::set("ICYDB_SCHEMA_CRATE", Some("custom::schema"));
142        let _error = TempEnv::set("ICYDB_ERROR_CRATE", Some("custom::error"));
143
144        let paths = CratePaths::new();
145
146        assert_eq!(paths.core.to_string(), quote!(custom::core).to_string());
147        assert_eq!(paths.schema.to_string(), quote!(custom::schema).to_string());
148        assert_eq!(paths.error.to_string(), quote!(custom::error).to_string());
149    }
150}