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#[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 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#[must_use]
75pub fn paths() -> CratePaths {
76 CratePaths::new()
77}
78
79#[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}