facet_generate 0.16.0

Generate Swift, Kotlin and TypeScript from types annotated with `#[derive(Facet)]`
Documentation
//! Generates idiomatic source code in Swift, Kotlin, TypeScript, and C# from Rust types
//! annotated with `#[derive(Facet)]`.
//!
//! When Rust is the source of truth for a data model shared across platforms (e.g. a mobile
//! app talking to a Rust core over FFI), every target language needs matching type definitions
//! — and, optionally, serialization code to move data across the boundary. Writing these by
//! hand is tedious and error-prone; this crate automates it.
//!
//! Optionally, when an [`Encoding`](generation::Encoding) such as Bincode or JSON is
//! configured, the generated types include `serialize` / `deserialize` methods and the
//! appropriate runtime library is installed alongside the generated code.
//!
//! # Modules
//!
//! - [`reflection`] — walks Rust type metadata (via the [`facet`] crate) and builds a
//!   language-neutral [`Registry`]: a flat map from qualified type names to their
//!   [`ContainerFormat`](reflection::format::ContainerFormat) descriptions.
//! - [`generation`] — transforms a registry into source code for a target language.
//!   Each language (`kotlin`, `csharp`, `swift`, `typescript`) lives behind a feature flag and
//!   follows a three-layer pipeline: **Installer** (project scaffolding and manifests) →
//!   **Generator** (file-level output with imports and namespaces) → **Emitter** (per-type
//!   code emission).
//!
//! # Example
//!
//! Define your shared data model in Rust:
//!
//! ```rust,ignore
//! use facet::Facet;
//!
//! #[derive(Facet)]
//! struct HttpResponse {
//!     status: u16,
//!     headers: Vec<HttpHeader>,
//!     #[facet(bytes)]
//!     body: Vec<u8>,
//! }
//!
//! #[derive(Facet)]
//! struct HttpHeader {
//!     name: String,
//!     value: String,
//! }
//! ```
//!
//! Build a registry from the types, then generate a complete project for one or more target
//! languages:
//!
//! ```rust,ignore
//! use facet_generate::{reflection::RegistryBuilder, generation::{Encoding, kotlin, swift}};
//!
//! let registry = RegistryBuilder::new()
//!     .add_type::<HttpResponse>()?
//!     .build()?;
//!
//! // Swift package with Bincode serialization
//! swift::Installer::new("MyPackage", &out_dir)
//!     .encoding(Encoding::Bincode)
//!     .generate(&registry)?;
//!
//! // Kotlin package (type definitions only, no serialization)
//! kotlin::Installer::new("com.example", &out_dir)
//!     .generate(&registry)?;
//! ```
//!
//! # Testing
//!
//! Tests are organised in four layers, from fast and narrow to slow and broad:
//!
//! ## Unit tests (snapshot)
//!
//! Each language has snapshot-based tests that assert on generated **text** without touching the
//! filesystem.
//!
//! | Layer | Location | What it covers |
//! |-------|----------|----------------|
//! | Emitter | `generation/<lang>/emitter/tests.rs` (+ `tests_bincode.rs`, `tests_json.rs`) | Output for individual types — no file headers, no imports. Uses the [`emit!`] macro. |
//! | Generator | `generation/<lang>/generator/tests.rs` | Full file output including package declarations, imports, and namespace-qualified names. |
//! | Installer | `generation/<lang>/installer/tests.rs` | Generated manifest strings (`.csproj`, `build.gradle.kts`, `package.json`, `Package.swift`). Still pure string assertions — no files written. |
//!
//! All three use the [`insta`](https://docs.rs/insta) crate for snapshot assertions.
//!
//! ## Cross-language expect-file tests ([`tests`] module, `src/tests/`)
//!
//! Each sub-module defines one or more Rust types and invokes the [`test!`] macro, which reflects
//! the types and runs the full [`CodeGen`](generation::CodeGen) pipeline for every listed language
//! (e.g. `for kotlin, swift`). The output is compared against checked-in expect files
//! (`output.kt`, `output.swift`, …) sitting alongside each `mod.rs`, using the
//! [`expect_test`](https://docs.rs/expect_test) crate. These tests are fast (no compiler
//! invocation) but exercise the complete generator path — including package declarations, imports,
//! and multi-type ordering — across multiple languages in a single test case. Every test should
//! support all languages, except for a few that exercise language-specific features like
//! `#[facet(swift = "Equatable")]` or `#[facet(kotlin = "Parcelable")]`.
//!
//! Gated on `#[cfg(all(test, feature = "generate"))]`.
//!
//! ## Compilation tests (`tests/<lang>_generation.rs`)
//!
//! Integration tests that generate code **and** a project scaffold into a temporary directory,
//! then invoke the real compiler (`dotnet build`, `gradle build`, `swift build`, `deno check`).
//! They verify that the generated code is syntactically and type-correct in the target language.
//! Each file is feature-gated (e.g. `#![cfg(feature = "kotlin")]`) so tests only run when the
//! corresponding toolchain is available.
//!
//! ## Runtime tests (`tests/<lang>_runtime.rs`)
//!
//! End-to-end tests that go one step further: they serialize sample data in Rust (typically with
//! bincode), generate target-language code that deserializes the same bytes, compile and **run**
//! the resulting program, and assert that the round-trip is correct. These catch subtle encoding
//! bugs that snapshot and compilation tests cannot.

// Re-export attribute macros from facet-generate-attrs.
// This allows users to write e.g. `#[facet(facet_generate::bytes)]`
// or `use facet_generate as fg; #[facet(fg::bytes)]`
pub use facet_generate_attrs::*;

pub mod error;
pub mod generation;
pub mod reflection;

#[cfg(test)]
mod tests;

use std::collections::BTreeMap;

use crate::{
    error::Error,
    reflection::format::{ContainerFormat, QualifiedTypeName},
};

pub type Result<T, E = Error> = std::result::Result<T, E>;

/// The registry of reflected types — a flat map from qualified type names to their container formats.
///
/// Built by [`reflection::RegistryBuilder`] (typically via the [`reflect!`] macro) and consumed by
/// language-specific code generators in [`generation`].
///
/// Only named container types (structs and enums) get top-level entries. Primitives, `Option`,
/// `Vec`, `Map`, etc. are represented inline as [`Format`](reflection::format::Format) variants
/// within the containers that use them. Cross-type references are expressed as
/// `Format::TypeName(QualifiedTypeName)` — symbolic lookups back into this same map.
///
/// Keys are namespace-qualified, so a type `Foo` in the root namespace and a type `Foo` in
/// namespace `Bar` are separate entries. For example, in Kotlin these would generate as `Foo`
/// and `Bar.Foo` respectively.
pub type Registry = BTreeMap<QualifiedTypeName, ContainerFormat>;

/// Test/convenience macro: reflects the given types and emits code for each
/// container using the specified language tag and encoding.
///
/// Returns `anyhow::Result<String>` containing the generated source.
///
/// ```ignore
/// let code = emit!(MyStruct, MyEnum as Kotlin with Encoding::Json)?;
/// ```
///
/// This skips the [`Module`](generation::module::Module) header (no `package`
/// or `import` statements) — it only emits the type declarations. Useful in
/// tests to assert on individual type output without the file-level boilerplate.
#[cfg(test)]
#[macro_export]
macro_rules! emit {
    ($($ty:ident),* as $language:ident with $encoding:path) => {
        || -> anyhow::Result<String> {
            use $crate::generation::{Container, indent::{IndentConfig, IndentedWriter}};
            use std::io::Write as _;
            let mut out = Vec::new();
            let mut w = IndentedWriter::new(&mut out, IndentConfig::Space(4));
            let registry = $crate::reflect!($($ty),*)?;
            for container in registry.iter().map(|pair| Container::from(pair).with_registry(&registry)) {
                writeln!(&mut w)?;
                let lang = $language::new($encoding);
                container.write(&mut w, lang)?;
            }
            let out = String::from_utf8(out)?;

            Ok(out)
        }()
    };
}

/// **Deprecated since 0.16.0:** The Java generator is deprecated. Use the Kotlin generator instead.
#[cfg(test)]
#[macro_export]
#[deprecated(
    since = "0.16.0",
    note = "The Java generator is deprecated. Use the Kotlin generator instead."
)]
macro_rules! emit_java {
    ($($ty:ident),* as $encoding:path) => {
        #[allow(deprecated)]
        || -> anyhow::Result<String> {
            use $crate::generation::{Encoding, indent::{IndentConfig, IndentedWriter}};
            let mut out = Vec::new();
            let w = IndentedWriter::new(&mut out, IndentConfig::Space(4));
            let config = $crate::generation::CodeGeneratorConfig::new("com.example".to_string())
                .with_encoding($encoding);
            let generator = $crate::generation::java::CodeGenerator::new(&config);
            let mut emitter = $crate::generation::java::emitter::JavaEmitter {
                out: w,
                generator: &generator,
                current_namespace: Vec::new(),
                current_reserved_names: HashMap::new(),
            };
            let registry = $crate::reflect!($($ty),*)?;
            for (name, format) in &registry {
                emitter.output_container(&name.name, format).unwrap();
            }
            let out = String::from_utf8(out)?;

            Ok(out)
        }()
    };
}

/// Reflects one or more types into a [`Registry`], recursively capturing all reachable types.
///
/// This is a convenience wrapper around [`RegistryBuilder`](reflection::RegistryBuilder) —
/// used directly by the [`emit!`] macro and available for cases where you need the registry
/// without code generation.
///
/// ```ignore
/// let registry = reflect!(MyStruct, MyEnum)?;
/// ```
#[macro_export]
macro_rules! reflect {
    ($($ty:ident),*) => {
        || -> anyhow::Result<std::collections::BTreeMap<$crate::reflection::format::QualifiedTypeName, $crate::reflection::format::ContainerFormat>> {
            let registry = $crate::reflection::RegistryBuilder::new()
                $(.add_type::<$ty>().map_err(|e| anyhow::anyhow!("failed to add type {}: {}", stringify!($ty), e))?)*
                .build()
                .map_err(|e| anyhow::anyhow!("failed to build registry: {e}"))?;
            Ok(registry)
        }()
    };
}

/// Test-only macro for multi-namespace generation tests.
///
/// Reflects `$facet`, splits the resulting registry by namespace (expecting
/// exactly two namespaces), and runs the full [`CodeGen`](generation::CodeGen)
/// pipeline for each. Returns `(String, String)` — the generated source for
/// the non-root module and the root module, sorted alphabetically by module
/// name.
///
/// This exercises the complete generator path *including* the
/// [`Module`](generation::module::Module) header (package declaration,
/// imports), unlike [`emit!`] which skips it.
#[cfg(test)]
#[macro_export]
macro_rules! emit_two_modules {
    ($generator:ty, $facet:ident, $root:expr) => {{
        use $crate::generation::CodeGen;
        use $crate::generation::module::{self, Module};
        use $crate::{Registry, reflect};

        fn emit_module<'a, G: CodeGen<'a>>(module: &'a Module, registry: &Registry) -> String {
            let mut out = Vec::new();
            let mut generator = G::new(module.config());
            generator.write_output(&mut out, registry).unwrap();
            String::from_utf8(out).unwrap()
        }

        let registry = reflect!($facet).unwrap();
        let mut modules: Vec<_> = module::split($root, &registry).into_iter().collect();
        modules.sort_by(|a, b| a.0.config().module_name.cmp(&b.0.config().module_name));

        let modules: [(Module, Registry); 2] = modules.try_into().expect("Two modules expected");
        let [(other_module, other_registry), (root_module, root_registry)] = modules;

        let module_1 = emit_module::<$generator>(&other_module, &other_registry);
        let module_2 = emit_module::<$generator>(&root_module, &root_registry);
        (module_1, module_2)
    }};
}