Skip to main content

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}