Skip to main content

alef/core/config/resolved/
identifiers.rs

1//! Go/Java/Kotlin/C# identifier methods and reverse-DNS derivations.
2
3use super::ResolvedCrateConfig;
4use crate::core::config::derive::{derive_go_module_from_repo, derive_reverse_dns_package};
5
6impl ResolvedCrateConfig {
7    /// Get the GitHub repository URL, returning an error when no source has it set.
8    ///
9    /// Resolution order:
10    /// 1. `[e2e.registry] github_repo`
11    /// 2. `[package_metadata] repository`
12    /// 3. `[scaffold] repository`
13    pub fn try_github_repo(&self) -> Result<String, String> {
14        if let Some(e2e) = &self.e2e {
15            if let Some(url) = &e2e.registry.github_repo {
16                return Ok(url.clone());
17            }
18        }
19        if let Some(url) = self.package_metadata.as_ref().and_then(|p| p.repository.as_ref()) {
20            return Ok(url.clone());
21        }
22        if let Some(url) = self.scaffold.as_ref().and_then(|s| s.repository.as_ref()) {
23            return Ok(url.clone());
24        }
25        Err(format!(
26            "no repository URL configured — set `[scaffold] repository = \"...\"` (or `[e2e.registry] github_repo`) for crate `{}`",
27            self.name
28        ))
29    }
30
31    /// Get the GitHub repository URL with a vendor-neutral placeholder fallback.
32    pub fn github_repo(&self) -> String {
33        self.try_github_repo()
34            .unwrap_or_else(|_| format!("https://example.invalid/{}", self.name))
35    }
36
37    /// Get the Go module path, returning an error when neither `[go].module`
38    /// nor a derivable repository URL is configured.
39    pub(crate) fn try_go_module(&self) -> Result<String, String> {
40        if let Some(module) = self.go.as_ref().and_then(|g| g.module.as_ref()) {
41            return Ok(module.clone());
42        }
43        if let Ok(repo) = self.try_github_repo() {
44            if let Some(module) = derive_go_module_from_repo(&repo) {
45                return Ok(module);
46            }
47        }
48        Err(format!(
49            "no Go module configured — set `[go] module = \"...\"` or `[scaffold] repository = \"https://<host>/<org>/...\"` for crate `{}`",
50            self.name
51        ))
52    }
53
54    /// Get the Go module path with a vendor-neutral placeholder fallback.
55    pub fn go_module(&self) -> String {
56        self.try_go_module()
57            .unwrap_or_else(|_| format!("example.invalid/{}", self.name))
58    }
59
60    /// Get the Java package name, returning an error when neither `[java].package`
61    /// nor a derivable repository URL is configured.
62    pub(crate) fn try_java_package(&self) -> Result<String, String> {
63        if let Some(pkg) = self.java.as_ref().and_then(|j| j.package.as_ref()) {
64            return Ok(pkg.clone());
65        }
66        if let Ok(repo) = self.try_github_repo() {
67            if let Some(pkg) = derive_reverse_dns_package(&repo) {
68                return Ok(pkg);
69            }
70        }
71        Err(format!(
72            "no Java package configured — set `[java] package = \"...\"` or `[scaffold] repository = \"https://<host>/<org>/...\"` for crate `{}`",
73            self.name
74        ))
75    }
76
77    /// Get the Java package name with a vendor-neutral placeholder fallback.
78    pub fn java_package(&self) -> String {
79        self.try_java_package()
80            .unwrap_or_else(|_| "unconfigured.alef".to_string())
81    }
82
83    /// Get the Java Maven groupId.
84    ///
85    /// Prefers the explicit `[java] group_id` override when set; otherwise falls back to
86    /// the Java package name (most projects publish under `groupId == java package`).
87    pub fn java_group_id(&self) -> String {
88        if let Some(gid) = self.java.as_ref().and_then(|j| j.group_id.as_ref()) {
89            return gid.clone();
90        }
91        self.java_package()
92    }
93
94    /// Get the Java Maven artifactId.
95    ///
96    /// Prefers the explicit `[java] artifact_id` override; otherwise falls back to the
97    /// crate name (`[[crates]] name`).
98    pub fn java_artifact_id(&self) -> String {
99        self.java
100            .as_ref()
101            .and_then(|j| j.artifact_id.as_ref())
102            .cloned()
103            .unwrap_or_else(|| self.name.clone())
104    }
105
106    /// Get the Kotlin package name, returning an error when neither
107    /// `[kotlin].package` nor a derivable repository URL is configured.
108    pub(crate) fn try_kotlin_package(&self) -> Result<String, String> {
109        if let Some(pkg) = self.kotlin.as_ref().and_then(|k| k.package.as_ref()) {
110            return Ok(pkg.clone());
111        }
112        if let Ok(repo) = self.try_github_repo() {
113            if let Some(pkg) = derive_reverse_dns_package(&repo) {
114                return Ok(pkg);
115            }
116        }
117        Err(format!(
118            "no Kotlin package configured — set `[kotlin] package = \"...\"` or `[scaffold] repository = \"https://<host>/<org>/...\"` for crate `{}`",
119            self.name
120        ))
121    }
122
123    /// Get the Kotlin package name with a vendor-neutral placeholder fallback.
124    pub fn kotlin_package(&self) -> String {
125        self.try_kotlin_package()
126            .unwrap_or_else(|_| "unconfigured.alef".to_string())
127    }
128
129    /// Get the C# namespace.
130    pub fn csharp_namespace(&self) -> String {
131        self.csharp
132            .as_ref()
133            .and_then(|c| c.namespace.as_ref())
134            .cloned()
135            .unwrap_or_else(|| {
136                use heck::ToPascalCase;
137                self.name.to_pascal_case()
138            })
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use crate::core::config::new_config::NewAlefConfig;
145
146    fn resolved_one(toml: &str) -> super::super::ResolvedCrateConfig {
147        let cfg: NewAlefConfig = toml::from_str(toml).unwrap();
148        cfg.resolve().unwrap().remove(0)
149    }
150
151    fn with_repo(name: &str, repo: &str) -> super::super::ResolvedCrateConfig {
152        resolved_one(&format!(
153            r#"
154[workspace]
155languages = ["python"]
156
157[[crates]]
158name = "{name}"
159sources = ["src/lib.rs"]
160
161[crates.scaffold]
162repository = "{repo}"
163"#
164        ))
165    }
166
167    #[test]
168    fn go_module_derives_from_repo() {
169        let r = with_repo("my-lib", "https://github.com/foo/my-lib");
170        assert_eq!(r.go_module(), "github.com/foo/my-lib");
171    }
172
173    #[test]
174    fn go_module_explicit_wins_over_repo() {
175        let r = resolved_one(
176            r#"
177[workspace]
178languages = ["python"]
179
180[[crates]]
181name = "my-lib"
182sources = ["src/lib.rs"]
183
184[crates.scaffold]
185repository = "https://github.com/foo/my-lib"
186
187[crates.go]
188module = "custom.example.com/my-lib"
189"#,
190        );
191        assert_eq!(r.go_module(), "custom.example.com/my-lib");
192    }
193
194    #[test]
195    fn github_repo_uses_package_metadata_repository() {
196        let r = resolved_one(
197            r#"
198[workspace]
199languages = ["python"]
200
201[[crates]]
202name = "my-lib"
203sources = ["src/lib.rs"]
204
205[crates.package_metadata]
206repository = "https://gitlab.example.invalid/acme/my-lib"
207"#,
208        );
209        assert_eq!(
210            r.try_github_repo().as_deref(),
211            Ok("https://gitlab.example.invalid/acme/my-lib")
212        );
213    }
214
215    #[test]
216    fn java_package_derives_from_repo() {
217        let r = with_repo("my-lib", "https://github.com/foo-org/my-lib");
218        assert_eq!(r.java_package(), "com.github.foo_org");
219    }
220
221    #[test]
222    fn java_package_explicit_wins() {
223        let r = resolved_one(
224            r#"
225[workspace]
226languages = ["python"]
227
228[[crates]]
229name = "my-lib"
230sources = ["src/lib.rs"]
231
232[crates.java]
233package = "dev.sample_crate"
234"#,
235        );
236        assert_eq!(r.java_package(), "dev.sample_crate");
237    }
238
239    #[test]
240    fn kotlin_package_falls_back_to_placeholder() {
241        let r = resolved_one(
242            r#"
243[workspace]
244languages = ["python"]
245
246[[crates]]
247name = "my-lib"
248sources = ["src/lib.rs"]
249"#,
250        );
251        assert_eq!(r.kotlin_package(), "unconfigured.alef");
252    }
253
254    #[test]
255    fn csharp_namespace_derives_pascal_case() {
256        let r = resolved_one(
257            r#"
258[workspace]
259languages = ["python"]
260
261[[crates]]
262name = "my-lib"
263sources = ["src/lib.rs"]
264"#,
265        );
266        assert_eq!(r.csharp_namespace(), "MyLib");
267    }
268
269    #[test]
270    fn java_group_id_equals_package() {
271        let r = with_repo("my-lib", "https://github.com/foo-org/my-lib");
272        assert_eq!(r.java_group_id(), r.java_package());
273    }
274}