alef/core/backend.rs
1use crate::core::config::{Language, ResolvedCrateConfig};
2use crate::core::ir::ApiSurface;
3use crate::core::validation::ValidatedApiSurface;
4use std::path::PathBuf;
5
6/// Build-time dependency for a language backend.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum BuildDependency {
9 /// Backend has no external build dependencies.
10 #[default]
11 None,
12 /// Backend depends on the C FFI base being built first (Go, Java, C#, Zig).
13 Ffi,
14 /// Backend depends on the Rustler NIF being built first (Gleam).
15 Rustler,
16}
17
18/// Build configuration for a language backend.
19#[derive(Debug, Clone)]
20pub struct BuildConfig {
21 /// Build tool name (e.g., "napi", "maturin", "wasm-pack", "cargo", "mvn", "dotnet", "mix").
22 pub tool: &'static str,
23 /// Crate suffix for Rust binding crate (e.g., "-node", "-py", "-wasm", "-ffi").
24 pub crate_suffix: &'static str,
25 /// Build-time dependency for this backend.
26 pub build_dep: BuildDependency,
27 /// Post-processing steps to run after build.
28 pub post_build: Vec<PostBuildStep>,
29}
30
31impl BuildConfig {
32 /// Returns whether this backend depends on the C FFI base (backwards compatibility).
33 pub fn depends_on_ffi(&self) -> bool {
34 matches!(self.build_dep, BuildDependency::Ffi)
35 }
36}
37
38/// In-process post-processor applied to a generated file after external build tools run.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum PostProcessor {
41 /// Rewrite frb-generated Dart sealed-class factory params from positional names (`field0`)
42 /// to payload-derived names (e.g. `metadata` for a `PdfMetadata` payload).
43 FrbDartSealedVariants,
44 /// Filter excluded function definitions from frb-generated Dart lib.dart.
45 /// Stores the set of function names to exclude.
46 FrbDartExcludeFunctions(Vec<String>),
47 /// Make struct constructor fields optional for types with Rust defaults.
48 /// This handles Dart types that have #[serde(default)] fields in Rust.
49 FrbDartOptionalFieldsWithDefaults,
50 /// Fix FRB-generated Dart code that incorrectly calls executeSync/executeNormal
51 /// on callback function parameters.
52 FrbDartFixHandlerExecutorCalls,
53}
54
55/// A post-build processing step.
56#[derive(Debug, Clone)]
57pub enum PostBuildStep {
58 /// Replace all occurrences of `find` with `replace` in `path` (relative to crate dir).
59 PatchFile {
60 /// File path relative to the binding crate directory.
61 path: &'static str,
62 /// Text to find.
63 find: &'static str,
64 /// Text to replace with.
65 replace: &'static str,
66 },
67 /// Run an external command (e.g., for generated code post-processing via flutter_rust_bridge).
68 RunCommand {
69 /// Command to execute.
70 cmd: &'static str,
71 /// Command arguments.
72 args: Vec<&'static str>,
73 },
74 /// Apply an in-process [`PostProcessor`] to the file at `path` (relative to crate dir).
75 PostProcessFile {
76 /// File path relative to the binding crate directory.
77 path: PathBuf,
78 /// In-process processor to apply.
79 processor: PostProcessor,
80 },
81 /// Stage Dart native libraries from build artifacts into the package directory.
82 /// Searches `{workspace}/target/{rust_target}/release/` for built libraries
83 /// and copies them to `{package_root}/lib/src/native/{rid}/`.
84 StageDartNatives {
85 /// The library stem (e.g., "sample_lib_dart" for libsample_lib_dart.dylib).
86 lib_stem: String,
87 },
88}
89
90/// A generated file to write to disk.
91#[derive(Debug, Clone)]
92pub struct GeneratedFile {
93 /// Path relative to the output root.
94 pub path: PathBuf,
95 /// File content.
96 pub content: String,
97 /// Whether to prepend a "DO NOT EDIT" header.
98 pub generated_header: bool,
99}
100
101/// Capabilities supported by a backend.
102#[derive(Debug, Clone, Default)]
103pub struct Capabilities {
104 pub supports_async: bool,
105 pub supports_classes: bool,
106 pub supports_enums: bool,
107 pub supports_option: bool,
108 pub supports_result: bool,
109 pub supports_callbacks: bool,
110 pub supports_streaming: bool,
111 /// Whether this backend implements [`Backend::generate_service_api`].
112 ///
113 /// Backends that support service API generation set this to `true` and
114 /// override `generate_service_api`. When `false` and a crate has non-empty
115 /// `services`, the generation pipeline emits a fatal readiness diagnostic.
116 pub supports_service_api: bool,
117}
118
119/// Trait that all language backends implement.
120pub trait Backend: Send + Sync {
121 /// Backend identifier (e.g., "pyo3", "napi", "ffi").
122 fn name(&self) -> &str;
123
124 /// Target language.
125 fn language(&self) -> Language;
126
127 /// What this backend supports.
128 fn capabilities(&self) -> Capabilities;
129
130 /// Generate binding source code.
131 fn generate_bindings(&self, api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>>;
132
133 /// Generate binding source code from a centrally validated API surface.
134 fn generate_bindings_checked(
135 &self,
136 api: ValidatedApiSurface<'_>,
137 config: &ResolvedCrateConfig,
138 ) -> anyhow::Result<Vec<GeneratedFile>> {
139 self.generate_bindings(api.api(), config)
140 }
141
142 /// Generate type stubs (.pyi, .rbs, .d.ts). Optional — default returns empty.
143 fn generate_type_stubs(
144 &self,
145 _api: &ApiSurface,
146 _config: &ResolvedCrateConfig,
147 ) -> anyhow::Result<Vec<GeneratedFile>> {
148 Ok(vec![])
149 }
150
151 /// Generate type stubs from a centrally validated API surface.
152 fn generate_type_stubs_checked(
153 &self,
154 api: ValidatedApiSurface<'_>,
155 config: &ResolvedCrateConfig,
156 ) -> anyhow::Result<Vec<GeneratedFile>> {
157 self.generate_type_stubs(api.api(), config)
158 }
159
160 /// Generate package scaffolding. Optional — default returns empty.
161 fn generate_scaffold(
162 &self,
163 _api: &ApiSurface,
164 _config: &ResolvedCrateConfig,
165 ) -> anyhow::Result<Vec<GeneratedFile>> {
166 Ok(vec![])
167 }
168
169 /// Generate language-native public API wrappers. Optional — default returns empty.
170 fn generate_public_api(
171 &self,
172 _api: &ApiSurface,
173 _config: &ResolvedCrateConfig,
174 ) -> anyhow::Result<Vec<GeneratedFile>> {
175 Ok(vec![])
176 }
177
178 /// Generate public API wrappers from a centrally validated API surface.
179 fn generate_public_api_checked(
180 &self,
181 api: ValidatedApiSurface<'_>,
182 config: &ResolvedCrateConfig,
183 ) -> anyhow::Result<Vec<GeneratedFile>> {
184 self.generate_public_api(api.api(), config)
185 }
186
187 /// Generate the idiomatic service/app object and async handler bridge for a
188 /// backend that supports service API generation.
189 ///
190 /// Called **after** `generate_bindings` and **before** `generate_public_api`
191 /// when `surface.services` is non-empty and `capabilities().supports_service_api`
192 /// is `true`. Backends that do not yet implement service API generation leave
193 /// the default no-op in place; the pipeline emits a warning for crates that
194 /// configure services against an unsupporting backend.
195 fn generate_service_api(
196 &self,
197 _api: &ApiSurface,
198 _config: &ResolvedCrateConfig,
199 ) -> anyhow::Result<Vec<GeneratedFile>> {
200 Ok(vec![])
201 }
202
203 /// Generate service API wrappers from a centrally validated API surface.
204 fn generate_service_api_checked(
205 &self,
206 api: ValidatedApiSurface<'_>,
207 config: &ResolvedCrateConfig,
208 ) -> anyhow::Result<Vec<GeneratedFile>> {
209 self.generate_service_api(api.api(), config)
210 }
211
212 /// Build configuration for this backend. Returns `None` if build is not supported.
213 fn build_config(&self) -> Option<BuildConfig> {
214 None
215 }
216
217 /// Build configuration for this backend with full access to the crate config.
218 /// This allows backends to customize build steps based on configuration (e.g., exclude functions, styles).
219 ///
220 /// Default implementation calls `build_config()` (no config dependency).
221 /// Backends that need config access (like Dart) can override this method.
222 fn build_config_with_config(&self, _config: &ResolvedCrateConfig) -> Option<BuildConfig> {
223 self.build_config()
224 }
225}