From 35a253b2bb60dfb538a15802a2884fe2215a9479 Mon Sep 17 00:00:00 2001 From: Henrik Date: Wed, 3 Jun 2026 06:32:39 +0200 Subject: [PATCH] Seal FtlOutputOptions against additive breakage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mark FtlOutputOptions and both its variants `#[non_exhaustive]` so the enum can only be built through the `single_file`, `single_compressed_file` and `multi_file` constructors, never the `SingleFile { .. }` / `MultiFile { .. }` struct-literal syntax. New output modes (variants) and new fields on existing variants are then non-breaking minor releases. Enum variant fields can't carry their own visibility, so `#[non_exhaustive]` (not `pub(crate)`) is the mechanism here; in-crate construction — the constructors, `Default`, and the tests — is unaffected. Migrate the playground build script off the `MultiFile { .. }` literal to `FtlOutputOptions::multi_file(..)`, and document the constructors (the variant-field docs are no longer reachable from outside the crate). Second of the batched 0.7 breaking changes; version already at 0.7.0. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 5 +++++ playground/example1/build.rs | 4 +--- src/build/options/ftl_output_options.rs | 16 ++++++++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af7f84d..99eac97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ ## 0.7.0 (unreleased) ### Changed +- **Breaking:** `FtlOutputOptions` and its variants are now `#[non_exhaustive]`. + Construct it with `FtlOutputOptions::single_file()`, + `single_compressed_file()` or `multi_file()` instead of the + `FtlOutputOptions::SingleFile { .. }` / `MultiFile { .. }` struct-literal + syntax. This lets new output modes and fields be added in a minor release. - **Breaking:** `BuildOptions`' fields are now private. Build it from `BuildOptions::default()` and the `with_*` / `without_*` builder methods rather than struct-literal syntax or direct field assignment. Every field diff --git a/playground/example1/build.rs b/playground/example1/build.rs index 85209a9..fde90b4 100644 --- a/playground/example1/build.rs +++ b/playground/example1/build.rs @@ -15,9 +15,7 @@ fn main() -> ExitCode { fn try_main() -> Result<(), BuildError> { let multi_opts = BuildOptions::default() - .with_ftl_output(FtlOutputOptions::MultiFile { - output_ftl_folder: "gen/multi/".to_string(), - }) + .with_ftl_output(FtlOutputOptions::multi_file("gen/multi/")) .with_output_file_path("src/multi_l10n.rs"); try_build_from_locales_folder(multi_opts)?; diff --git a/src/build/options/ftl_output_options.rs b/src/build/options/ftl_output_options.rs index 6ef8570..11ad7a6 100644 --- a/src/build/options/ftl_output_options.rs +++ b/src/build/options/ftl_output_options.rs @@ -12,8 +12,11 @@ type CompressorFn = dyn Fn(Vec) -> Result, Box>; /// configure how the output ftl files are generated, and also what /// type of access code is generated. /// -/// Defaults to a SingleFileOptions with the output_ftl_folder set to "gen" -/// and gzip set to true. +/// Build it with [`FtlOutputOptions::single_file`], +/// [`FtlOutputOptions::single_compressed_file`] or +/// [`FtlOutputOptions::multi_file`] (the default is a single uncompressed file +/// at `gen/translations.ftl`). +#[non_exhaustive] pub enum FtlOutputOptions { /// Generates FTL files as one file per language which means /// that individual resource files are appended into one file. @@ -21,6 +24,7 @@ pub enum FtlOutputOptions { /// This is especially useful client-side when you /// don't want to embed the files in the binary or /// download them all together. + #[non_exhaustive] MultiFile { /// The path to the where the output ftl files will be written. /// For convenience fluent-typed joins all ftl resources for each language @@ -37,6 +41,7 @@ pub enum FtlOutputOptions { /// which typically is done server-side and also client-side when /// the files are small enough to either embed in the binary or /// download in a html request. + #[non_exhaustive] SingleFile { /// The path to the where the output ftl file will be written. /// For convenience fluent-typed joins all ftl resources for each language @@ -64,6 +69,8 @@ impl Default for FtlOutputOptions { } impl FtlOutputOptions { + /// All languages joined into one uncompressed `.ftl` file at `file`, + /// suitable for embedding in the binary (server-side) or a single download. pub fn single_file(file: &str) -> Self { Self::SingleFile { output_ftl_file: file.to_string(), @@ -71,6 +78,9 @@ impl FtlOutputOptions { } } + /// Like [`single_file`](Self::single_file), but the joined bytes are passed + /// through `compressor` before being written. You bring the compression + /// crate and must decompress the bytes the same way at load time. pub fn single_compressed_file(file: &str, compressor: F) -> Self where F: Fn(Vec) -> Result, Box> + 'static, @@ -81,6 +91,8 @@ impl FtlOutputOptions { } } + /// One `.ftl` file per language written into `folder`, for loading a single + /// language on demand rather than embedding them all. pub fn multi_file(folder: &str) -> Self { Self::MultiFile { output_ftl_folder: folder.to_string(),