1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::path::PathBuf;
4
5pub mod dto;
6pub mod e2e;
7pub mod extras;
8pub mod languages;
9pub mod output;
10
11pub use dto::{
13 CsharpDtoStyle, DtoConfig, ElixirDtoStyle, GoDtoStyle, JavaDtoStyle, NodeDtoStyle, PhpDtoStyle, PythonDtoStyle,
14 RDtoStyle, RubyDtoStyle,
15};
16pub use e2e::E2eConfig;
17pub use extras::{AdapterConfig, AdapterParam, AdapterPattern, Language};
18pub use languages::{
19 CSharpConfig, CustomModulesConfig, CustomRegistration, CustomRegistrationsConfig, ElixirConfig, FfiConfig,
20 GoConfig, JavaConfig, NodeConfig, PhpConfig, PythonConfig, RConfig, RubyConfig, StubsConfig, WasmConfig,
21};
22pub use output::{
23 ExcludeConfig, IncludeConfig, LintConfig, OutputConfig, ReadmeConfig, ScaffoldConfig, SyncConfig, TestConfig,
24 TextReplacement,
25};
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct AlefConfig {
30 #[serde(rename = "crate")]
31 pub crate_config: CrateConfig,
32 pub languages: Vec<Language>,
33 #[serde(default)]
34 pub exclude: ExcludeConfig,
35 #[serde(default)]
36 pub include: IncludeConfig,
37 #[serde(default)]
38 pub output: OutputConfig,
39 #[serde(default)]
40 pub python: Option<PythonConfig>,
41 #[serde(default)]
42 pub node: Option<NodeConfig>,
43 #[serde(default)]
44 pub ruby: Option<RubyConfig>,
45 #[serde(default)]
46 pub php: Option<PhpConfig>,
47 #[serde(default)]
48 pub elixir: Option<ElixirConfig>,
49 #[serde(default)]
50 pub wasm: Option<WasmConfig>,
51 #[serde(default)]
52 pub ffi: Option<FfiConfig>,
53 #[serde(default)]
54 pub go: Option<GoConfig>,
55 #[serde(default)]
56 pub java: Option<JavaConfig>,
57 #[serde(default)]
58 pub csharp: Option<CSharpConfig>,
59 #[serde(default)]
60 pub r: Option<RConfig>,
61 #[serde(default)]
62 pub scaffold: Option<ScaffoldConfig>,
63 #[serde(default)]
64 pub readme: Option<ReadmeConfig>,
65 #[serde(default)]
66 pub lint: Option<HashMap<String, LintConfig>>,
67 #[serde(default)]
68 pub test: Option<HashMap<String, TestConfig>>,
69 #[serde(default)]
70 pub custom_files: Option<HashMap<String, Vec<PathBuf>>>,
71 #[serde(default)]
72 pub adapters: Vec<AdapterConfig>,
73 #[serde(default)]
74 pub custom_modules: CustomModulesConfig,
75 #[serde(default)]
76 pub custom_registrations: CustomRegistrationsConfig,
77 #[serde(default)]
78 pub sync: Option<SyncConfig>,
79 #[serde(default)]
83 pub opaque_types: HashMap<String, String>,
84 #[serde(default)]
86 pub generate: GenerateConfig,
87 #[serde(default)]
89 pub generate_overrides: HashMap<String, GenerateConfig>,
90 #[serde(default)]
92 pub dto: DtoConfig,
93 #[serde(default)]
95 pub e2e: Option<E2eConfig>,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct CrateConfig {
100 pub name: String,
101 pub sources: Vec<PathBuf>,
102 #[serde(default = "default_version_from")]
103 pub version_from: String,
104 #[serde(default)]
105 pub core_import: Option<String>,
106 #[serde(default)]
108 pub workspace_root: Option<PathBuf>,
109 #[serde(default)]
111 pub skip_core_import: bool,
112 #[serde(default)]
115 pub path_mappings: HashMap<String, String>,
116}
117
118fn default_version_from() -> String {
119 "Cargo.toml".to_string()
120}
121
122fn default_true() -> bool {
123 true
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct GenerateConfig {
131 #[serde(default = "default_true")]
133 pub bindings: bool,
134 #[serde(default = "default_true")]
136 pub errors: bool,
137 #[serde(default = "default_true")]
139 pub configs: bool,
140 #[serde(default = "default_true")]
142 pub async_wrappers: bool,
143 #[serde(default = "default_true")]
145 pub type_conversions: bool,
146 #[serde(default = "default_true")]
148 pub package_metadata: bool,
149 #[serde(default = "default_true")]
151 pub public_api: bool,
152}
153
154impl Default for GenerateConfig {
155 fn default() -> Self {
156 Self {
157 bindings: true,
158 errors: true,
159 configs: true,
160 async_wrappers: true,
161 type_conversions: true,
162 package_metadata: true,
163 public_api: true,
164 }
165 }
166}
167
168impl AlefConfig {
173 pub fn core_import(&self) -> String {
175 self.crate_config
176 .core_import
177 .clone()
178 .unwrap_or_else(|| self.crate_config.name.replace('-', "_"))
179 }
180
181 pub fn ffi_prefix(&self) -> String {
183 self.ffi
184 .as_ref()
185 .and_then(|f| f.prefix.as_ref())
186 .cloned()
187 .unwrap_or_else(|| self.crate_config.name.replace('-', "_"))
188 }
189
190 pub fn ffi_lib_name(&self) -> String {
198 if let Some(name) = self.ffi.as_ref().and_then(|f| f.lib_name.as_ref()) {
200 return name.clone();
201 }
202
203 if let Some(ffi_path) = self.output.ffi.as_ref() {
206 let path = std::path::Path::new(ffi_path);
207 let components: Vec<_> = path
210 .components()
211 .filter_map(|c| {
212 if let std::path::Component::Normal(s) = c {
213 s.to_str()
214 } else {
215 None
216 }
217 })
218 .collect();
219 let crate_dir = components
222 .iter()
223 .rev()
224 .find(|&&s| s != "src" && s != "lib" && s != "include")
225 .copied();
226 if let Some(dir) = crate_dir {
227 return dir.replace('-', "_");
228 }
229 }
230
231 format!("{}_ffi", self.ffi_prefix())
233 }
234
235 pub fn ffi_header_name(&self) -> String {
237 self.ffi
238 .as_ref()
239 .and_then(|f| f.header_name.as_ref())
240 .cloned()
241 .unwrap_or_else(|| format!("{}.h", self.ffi_prefix()))
242 }
243
244 pub fn python_module_name(&self) -> String {
246 self.python
247 .as_ref()
248 .and_then(|p| p.module_name.as_ref())
249 .cloned()
250 .unwrap_or_else(|| format!("_{}", self.crate_config.name.replace('-', "_")))
251 }
252
253 pub fn node_package_name(&self) -> String {
255 self.node
256 .as_ref()
257 .and_then(|n| n.package_name.as_ref())
258 .cloned()
259 .unwrap_or_else(|| self.crate_config.name.clone())
260 }
261
262 pub fn ruby_gem_name(&self) -> String {
264 self.ruby
265 .as_ref()
266 .and_then(|r| r.gem_name.as_ref())
267 .cloned()
268 .unwrap_or_else(|| self.crate_config.name.replace('-', "_"))
269 }
270
271 pub fn php_extension_name(&self) -> String {
273 self.php
274 .as_ref()
275 .and_then(|p| p.extension_name.as_ref())
276 .cloned()
277 .unwrap_or_else(|| self.crate_config.name.replace('-', "_"))
278 }
279
280 pub fn elixir_app_name(&self) -> String {
282 self.elixir
283 .as_ref()
284 .and_then(|e| e.app_name.as_ref())
285 .cloned()
286 .unwrap_or_else(|| self.crate_config.name.replace('-', "_"))
287 }
288
289 pub fn go_module(&self) -> String {
291 self.go
292 .as_ref()
293 .and_then(|g| g.module.as_ref())
294 .cloned()
295 .unwrap_or_else(|| format!("github.com/kreuzberg-dev/{}", self.crate_config.name))
296 }
297
298 pub fn java_package(&self) -> String {
300 self.java
301 .as_ref()
302 .and_then(|j| j.package.as_ref())
303 .cloned()
304 .unwrap_or_else(|| "dev.kreuzberg".to_string())
305 }
306
307 pub fn csharp_namespace(&self) -> String {
309 self.csharp
310 .as_ref()
311 .and_then(|c| c.namespace.as_ref())
312 .cloned()
313 .unwrap_or_else(|| {
314 use heck::ToPascalCase;
315 self.crate_config.name.to_pascal_case()
316 })
317 }
318
319 pub fn r_package_name(&self) -> String {
321 self.r
322 .as_ref()
323 .and_then(|r| r.package_name.as_ref())
324 .cloned()
325 .unwrap_or_else(|| self.crate_config.name.clone())
326 }
327
328 pub fn rewrite_path(&self, rust_path: &str) -> String {
331 let mut mappings: Vec<_> = self.crate_config.path_mappings.iter().collect();
333 mappings.sort_by(|a, b| b.0.len().cmp(&a.0.len()));
334
335 for (from, to) in &mappings {
336 if rust_path.starts_with(from.as_str()) {
337 return format!("{}{}", to, &rust_path[from.len()..]);
338 }
339 }
340 rust_path.to_string()
341 }
342}
343
344pub fn resolve_output_dir(config_path: Option<&PathBuf>, crate_name: &str, default: &str) -> String {
347 config_path
348 .map(|p| p.to_string_lossy().replace("{name}", crate_name))
349 .unwrap_or_else(|| default.replace("{name}", crate_name))
350}
351
352pub fn detect_serde_available(output_dir: &str) -> bool {
358 let src_path = std::path::Path::new(output_dir);
359 let mut dir = src_path;
361 loop {
362 let cargo_toml = dir.join("Cargo.toml");
363 if cargo_toml.exists() {
364 return cargo_toml_has_serde(&cargo_toml);
365 }
366 match dir.parent() {
367 Some(parent) if !parent.as_os_str().is_empty() => dir = parent,
368 _ => break,
369 }
370 }
371 false
372}
373
374fn cargo_toml_has_serde(path: &std::path::Path) -> bool {
380 let content = match std::fs::read_to_string(path) {
381 Ok(c) => c,
382 Err(_) => return false,
383 };
384
385 let has_serde_json = content.contains("serde_json");
386 let has_serde_dep = content.lines().any(|line| {
390 let trimmed = line.trim();
391 trimmed.starts_with("serde ")
393 || trimmed.starts_with("serde=")
394 || trimmed.starts_with("serde.")
395 || trimmed == "[dependencies.serde]"
396 });
397
398 has_serde_json && has_serde_dep
399}