1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
//! `[[crates]]` entries — the raw per-crate config as written in `alef.toml`.
//!
//! A `RawCrateConfig` is what the user types in their TOML; a
//! [`crate::config::resolved::ResolvedCrateConfig`] is what every backend
//! consumes after workspace defaults have been merged in.
//!
//! Each entry produces an independent set of polyglot binding packages.
//! Crates may share workspace defaults (tooling, DTO style, default
//! pipelines), but they do not share crate-shaped state (sources,
//! per-language module names, publish settings, e2e fixtures).
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use super::SourceCrate;
use super::e2e::E2eConfig;
use super::extras::{AdapterConfig, Language};
use super::languages::{
CSharpConfig, CustomModulesConfig, CustomRegistrationsConfig, DartConfig, ElixirConfig, FfiConfig, GleamConfig,
GoConfig, JavaConfig, JniConfig, KotlinAndroidConfig, KotlinConfig, NodeConfig, PhpConfig, PythonConfig, RConfig,
RubyConfig, SwiftConfig, WasmConfig, ZigConfig,
};
use super::output::{
BuildCommandConfig, CleanConfig, ExcludeConfig, IncludeConfig, LintConfig, OutputConfig, ReadmeConfig,
ScaffoldConfig, SetupConfig, TestConfig, UpdateConfig,
};
use super::publish::PublishConfig;
use super::trait_bridge::TraitBridgeConfig;
/// One `[[crates]]` entry — an independently published Rust facade plus its
/// per-crate language settings, pipelines, and packaging metadata.
///
/// Every field except `name` is optional. Fields left unset inherit from
/// the workspace defaults during resolution; required fields with no
/// workspace default fall back to Alef's built-in defaults or trigger a
/// validation error.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RawCrateConfig {
// -----------------------------------------------------------------
// Identity and source crate
// -----------------------------------------------------------------
/// Crate name (e.g. `"spikard"`). Must be unique within the workspace.
pub name: String,
/// Rust source files this crate's bindings extract from.
/// May be empty when [`source_crates`](Self::source_crates) is non-empty.
#[serde(default)]
pub sources: Vec<PathBuf>,
/// File whose `[package] version` is the source of truth for this crate.
/// Defaults to `"Cargo.toml"` (resolved relative to the alef.toml).
#[serde(default)]
pub version_from: Option<String>,
/// Rust import path for the core crate referenced from generated bindings.
/// Defaults to the crate name with hyphens replaced by underscores.
#[serde(default)]
pub core_import: Option<String>,
/// Optional workspace root path for resolving `pub use` re-exports from
/// sibling crates.
#[serde(default)]
pub workspace_root: Option<PathBuf>,
/// When true, skip adding `use {core_import};` to generated bindings.
#[serde(default)]
pub skip_core_import: bool,
/// Crate error type name (e.g. `"KreuzbergError"`). Used by trait-bridge
/// generation. Defaults to `"Error"`.
#[serde(default)]
pub error_type: Option<String>,
/// Pattern for constructing error values from a String in trait bridges.
/// `{msg}` is replaced with the format!(...) expression.
#[serde(default)]
pub error_constructor: Option<String>,
/// Cargo features enabled in binding crates. Fields gated by
/// `#[cfg(feature = "...")]` matching these features are treated as
/// always present (cfg stripped from the IR).
#[serde(default)]
pub features: Vec<String>,
/// Maps extracted rust_path prefixes to actual import paths in binding crates.
#[serde(default)]
pub path_mappings: HashMap<String, String>,
/// Additional Cargo dependencies merged into every binding crate Cargo.toml
/// for this crate (per-language extra_dependencies still override these).
#[serde(default)]
pub extra_dependencies: HashMap<String, toml::Value>,
/// Automatically derive path mappings from source file locations.
/// Default: `true`.
#[serde(default)]
pub auto_path_mappings: Option<bool>,
/// Multi-crate source groups for workspaces with types spread across
/// sibling crates. When non-empty, the top-level `sources` field is ignored.
#[serde(default)]
pub source_crates: Vec<SourceCrate>,
// -----------------------------------------------------------------
// Language selection
// -----------------------------------------------------------------
/// Override of the workspace `languages` list for this crate.
/// When `None`, this crate inherits the workspace default.
#[serde(default)]
pub languages: Option<Vec<Language>>,
// -----------------------------------------------------------------
// Per-language settings (was top-level [python], [node], …)
// -----------------------------------------------------------------
#[serde(default)]
pub python: Option<PythonConfig>,
#[serde(default)]
pub node: Option<NodeConfig>,
#[serde(default)]
pub ruby: Option<RubyConfig>,
#[serde(default)]
pub php: Option<PhpConfig>,
#[serde(default)]
pub elixir: Option<ElixirConfig>,
#[serde(default)]
pub wasm: Option<WasmConfig>,
#[serde(default)]
pub ffi: Option<FfiConfig>,
#[serde(default)]
pub go: Option<GoConfig>,
#[serde(default)]
pub java: Option<JavaConfig>,
#[serde(default)]
pub dart: Option<DartConfig>,
#[serde(default)]
pub kotlin: Option<KotlinConfig>,
#[serde(default)]
pub kotlin_android: Option<KotlinAndroidConfig>,
#[serde(default)]
pub jni: Option<JniConfig>,
#[serde(default)]
pub swift: Option<SwiftConfig>,
#[serde(default)]
pub gleam: Option<GleamConfig>,
#[serde(default)]
pub csharp: Option<CSharpConfig>,
#[serde(default)]
pub r: Option<RConfig>,
#[serde(default)]
pub zig: Option<ZigConfig>,
// -----------------------------------------------------------------
// Filters and output paths
// -----------------------------------------------------------------
#[serde(default)]
pub exclude: ExcludeConfig,
#[serde(default)]
pub include: IncludeConfig,
/// Per-crate explicit output paths. Wins over the workspace
/// `output_template` for any language with an entry here.
#[serde(default)]
pub output: OutputConfig,
// -----------------------------------------------------------------
// Per-crate generation, formatting, and DTO overrides.
// -----------------------------------------------------------------
/// Override the workspace default `generate` flags. When `Some`, replaces the
/// workspace value wholesale. When `None`, the crate inherits `workspace.generate`.
#[serde(default)]
pub generate: Option<super::GenerateConfig>,
/// Override the workspace default `format` flags. When `Some`, replaces the
/// workspace value wholesale.
#[serde(default)]
pub format: Option<super::FormatConfig>,
/// Override the workspace default DTO styles. When `Some`, replaces the
/// workspace value wholesale (no field-level merge — a partial DTO override
/// would silently drop unspecified language entries).
#[serde(default)]
pub dto: Option<super::DtoConfig>,
/// Per-language per-crate formatting overrides keyed by language code.
/// Merged with workspace `format_overrides`: per-crate keys win wholesale,
/// missing keys fall through to the workspace map.
#[serde(default)]
pub format_overrides: HashMap<String, super::FormatConfig>,
/// Per-language per-crate generation flag overrides keyed by language code.
/// Merged with workspace `generate_overrides`: per-crate keys win wholesale,
/// missing keys fall through to the workspace map.
#[serde(default)]
pub generate_overrides: HashMap<String, super::GenerateConfig>,
// -----------------------------------------------------------------
// Per-crate pipeline overrides — merged field-wise with workspace defaults.
// -----------------------------------------------------------------
#[serde(default)]
pub lint: HashMap<String, LintConfig>,
#[serde(default)]
pub test: HashMap<String, TestConfig>,
#[serde(default)]
pub setup: HashMap<String, SetupConfig>,
#[serde(default)]
pub update: HashMap<String, UpdateConfig>,
#[serde(default)]
pub clean: HashMap<String, CleanConfig>,
#[serde(default)]
pub build_commands: HashMap<String, BuildCommandConfig>,
// -----------------------------------------------------------------
// Packaging, e2e, extensibility
// -----------------------------------------------------------------
#[serde(default)]
pub publish: Option<PublishConfig>,
#[serde(default)]
pub e2e: Option<E2eConfig>,
#[serde(default)]
pub adapters: Vec<AdapterConfig>,
#[serde(default)]
pub trait_bridges: Vec<TraitBridgeConfig>,
#[serde(default)]
pub scaffold: Option<ScaffoldConfig>,
#[serde(default)]
pub readme: Option<ReadmeConfig>,
#[serde(default)]
pub custom_files: HashMap<String, Vec<PathBuf>>,
#[serde(default)]
pub custom_modules: CustomModulesConfig,
#[serde(default)]
pub custom_registrations: CustomRegistrationsConfig,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_crate_config_minimal_deserializes() {
let toml_str = r#"
name = "spikard"
sources = ["src/lib.rs"]
"#;
let cfg: RawCrateConfig = toml::from_str(toml_str).unwrap();
assert_eq!(cfg.name, "spikard");
assert_eq!(cfg.sources, vec![PathBuf::from("src/lib.rs")]);
assert!(cfg.python.is_none());
assert!(cfg.publish.is_none());
assert!(cfg.e2e.is_none());
}
#[test]
fn raw_crate_config_with_source_crates() {
let toml_str = r#"
name = "spikard"
sources = []
features = ["di"]
core_import = "spikard"
[[source_crates]]
name = "spikard-core"
sources = ["crates/spikard-core/src/http.rs"]
[[source_crates]]
name = "spikard-http"
sources = ["crates/spikard-http/src/lib.rs"]
"#;
let cfg: RawCrateConfig = toml::from_str(toml_str).unwrap();
assert_eq!(cfg.source_crates.len(), 2);
assert_eq!(cfg.source_crates[0].name, "spikard-core");
assert_eq!(cfg.features, vec!["di"]);
}
#[test]
fn raw_crate_config_with_per_crate_python_and_lint_override() {
let toml_str = r#"
name = "spikard"
sources = []
[python]
module_name = "_spikard"
pip_name = "spikard"
release_gil = true
[lint.python]
check = "ruff check crates/spikard-py/"
"#;
let cfg: RawCrateConfig = toml::from_str(toml_str).unwrap();
let py = cfg.python.expect("python section present");
assert_eq!(py.module_name.as_deref(), Some("_spikard"));
assert_eq!(py.pip_name.as_deref(), Some("spikard"));
assert!(py.release_gil);
let lint_py = cfg.lint.get("python").expect("lint.python override present");
assert_eq!(
lint_py.check.as_ref().unwrap().commands(),
vec!["ruff check crates/spikard-py/"]
);
}
}