Skip to main content

Crate facet_generate

Crate facet_generate 

Source
Expand description

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 a plugin such as BincodePlugin or JsonPlugin 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 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).

§Getting Started

Add the crates to your project:

cargo add facet facet_generate

§1. Annotate your types

Derive facet::Facet on every type you want to share across language boundaries. Aliasing this crate as fg keeps attribute paths short:

use facet::Facet;
use facet_generate as fg;

#[derive(Facet)]
#[repr(C)]
enum HttpResult {
    Ok(HttpResponse),
    Err(HttpError),
}

#[derive(Facet)]
struct HttpResponse {
    status: u16,
    headers: Vec<HttpHeader>,
    #[facet(fg::bytes)]          // Vec<u8> → native byte-array type
    body: Vec<u8>,
}

#[derive(Facet)]
struct HttpHeader {
    name: String,
    value: String,
}

You only need to register root types — all referenced types are collected transitively.

§2. Build a Registry

use facet_generate::reflection::RegistryBuilder;

let registry = RegistryBuilder::new()
    .add_type::<HttpResult>()?
    .build()?;

§3. Generate code

Pass the Registry to a language-specific installer, optionally add plugins for serialization support, and call generate():

use facet_generate::generation::{bincode::BincodePlugin, kotlin, swift, typescript};

// Swift package with Bincode serialization
swift::Installer::new("MyPackage", &out_dir)
    .plugin(BincodePlugin)
    .generate(&registry)?;

// Kotlin package with Bincode serialization
kotlin::Installer::new("com.example", &out_dir)
    .plugin(BincodePlugin)
    .generate(&registry)?;

// TypeScript with Bincode serialization
typescript::Installer::new("my-package", &out_dir)
    .plugin(BincodePlugin)
    .generate(&registry)?;

Each installer writes a ready-to-build project to out_dir — type definitions plus, when a serialization plugin is configured, the appropriate runtime. Omit .plugin(...) to generate plain type definitions without any serialization code.

§Key attributes

AttributeEffect
#[facet(fg::bytes)]Emit Vec<u8> / &[u8] as a native byte-array type ([UInt8], ByteArray, Uint8Array)
#[facet(fg::namespace = "ns")]Group a type, transitively, into a named namespace, emitted as a separate module
#[facet(fg::namespace)]Group a type, transitively, into the ROOT namespace
#[facet(rename = "Name")]Override the generated name of a type, field, or variant
#[facet(rename_all = "camelCase")]Apply a naming convention across all fields or variants. Options are PascalCase, camelCase, snake_case, SCREAMING_SNAKE_CASE, kebab-case, SCREAMING-KEBAB-CASE
#[facet(skip)]Exclude a field or variant from the generated output
#[facet(opaque)]Do not descend into the field’s type
#[facet(transparent)]Unwrap a newtype wrapper in the generated output

§Testing

Tests are organized 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.

LayerLocationWhat it covers
Emittergeneration/<lang>/emitter/tests.rs (+ tests_bincode.rs, tests_json.rs)Output for individual types — no file headers, no imports. Uses the emit macro.
Generatorgeneration/<lang>/generator/tests.rsFull file output including package declarations, imports, and namespace-qualified names.
Installergeneration/<lang>/installer/tests.rsGenerated manifest strings (.csproj, build.gradle.kts, package.json, Package.swift). Still pure string assertions — no files written.

All three use the 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 CodeGenerator 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 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, tsc). 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.

Modules§

error
generation
Code generation — transforms a Registry into source code.
reflection
Type reflection engine — extracts Rust type metadata into a language-neutral Registry.

Macros§

reflect
Reflects one or more types into a Registry, recursively capturing all reachable types.

Enums§

Attr
Extension attributes for facet_generate code generation.

Type Aliases§

Registry
The registry of reflected types — a flat map from qualified type names to their container formats.
Result