Fluorite
Fluorite generates Rust, TypeScript, and Swift code from a shared schema language. Define your types once in .fl files, then generate type-safe, serialization-ready code for all three languages.
All generated code uses camelCase as the JSON serialization format, ensuring consistent cross-language interoperability without any configuration.
Quick Start
1. Install
# via Cargo
# or via npm
2. Write a Schema
Create schema.fl:
package myapp;
3. Generate Code
# Rust
# TypeScript
# Swift
That's it. You now have type-safe structs (Rust), interfaces (TypeScript), and Codable types (Swift) with full serialization support.
The Fluorite IDL
Fluorite uses .fl files with a Rust-like syntax. Here's what you can express:
Structs
/// A customer order
Fields are automatically serialized as camelCase in JSON across all languages. No annotation needed — order_id becomes "orderId" in JSON.
Rust output — a #[derive(Serialize, Deserialize)] struct with #[serde(rename_all = "camelCase")].
TypeScript output — an exported interface with camelCase field names.
Swift output — a Codable struct with camelCase properties and CodingKeys.
Enums
Rust — a standard enum with serde derives. TypeScript — a string literal union type. Swift — a String-backed enum with Codable conformance.
Tagged Unions
Fluorite uses adjacently tagged unions, producing consistent JSON across all languages:
union OrderEvent
This serializes as:
Rust output:
TypeScript output:
export type OrderEvent =
| { type: "Created"; value: Order }
| { type: "StatusChanged"; value: StatusChange }
| { type: "Cancelled" };
Swift output:
public enum OrderEvent: Codable, Equatable, Sendable {
case created(Order)
case statusChanged(StatusChange)
case cancelled
// Custom Codable implementation for adjacently tagged format
}
Type Aliases
type OrderList = ;
type OrderMap = ;
Packages and Imports
Split schemas across files with dotted package names:
// common.fl
package myapp.common;
// users.fl
package myapp.users;
use myapp.common.Address;
Doc Comments
Lines starting with /// become doc comments in Rust, JSDoc comments in TypeScript, and documentation comments in Swift:
/// A user in the system.
/// Created during registration.
Attributes
| Attribute | Applies to | Effect |
|---|---|---|
#[rename = "name"] |
fields, variants | Rename in JSON |
#[alias = "alt"] |
fields | Accept alternate name during deserialization |
#[default] |
fields | Use Default::default() if missing |
#[skip_if_none] |
fields | Omit if None |
#[skip_if_default] |
fields | Omit if default value |
#[flatten] |
fields | Flatten nested struct into parent |
#[deprecated] |
types, fields | Mark as deprecated |
#[type_tag = "..."] |
unions | Tag field name (default: "type") |
#[content_tag = "..."] |
unions | Content field name (default: "value") |
Note: All fields are serialized as camelCase by default. Use
#[rename = "..."]to override individual fields when needed.
Type Reference
| Fluorite Type | Rust | TypeScript | Swift |
|---|---|---|---|
String |
String |
string |
String |
bool |
bool |
boolean |
Bool |
i32, i64 |
i32, i64 |
number |
Int32, Int64 |
u32, u64 |
u32, u64 |
number |
UInt32, UInt64 |
f32, f64 |
f32, f64 |
number |
Float, Double |
Uuid |
uuid::Uuid |
string |
UUID |
Decimal |
rust_decimal::Decimal |
string |
Decimal |
Bytes |
Vec<u8> |
string |
Data |
Url |
url::Url |
string |
URL |
DateTime, DateTimeUtc, DateTimeTz |
chrono types |
string |
Date |
Date, Time, Duration |
chrono types |
string |
String |
Timestamp, TimestampMillis |
i64 |
number |
Date |
Any |
fluorite::Any |
unknown |
AnyCodable |
Option<T> |
Option<T> |
T | undefined (optional field) |
T? |
Vec<T> |
Vec<T> |
T[] |
[T] |
Map<K, V> |
HashMap<K, V> |
Record<K, V> |
[K: V] |
Using Fluorite in a Rust Project
For Rust projects, the recommended approach is build.rs integration so types are generated at compile time.
See the examples/demo project for a complete working example.
1. Add Dependencies
[]
= { = "1.0", = ["serde_derive"] }
= "0.2"
= "0.7"
[]
= "0.2"
2. Create build.rs
use RustOptions;
3. Include Generated Code
use User;
Rust Options
new
.with_single_file // All types in one mod.rs (default: true)
.with_any_type // Map `Any` to this type
.with_derives // Replace default derives
.with_additional_derives // Add extra derives
.with_generate_new // Add derive_new::new (default: true)
.with_visibility // Type visibility (default: public)
Using Fluorite in a TypeScript Project
Via npm
Add to package.json:
See the examples/demo-ts project for a complete working example.
Via Rust API
use TypeScriptOptions;
let options = new
.with_single_file
.with_readonly;
compile_ts_with_options.unwrap;
TypeScript Options
new
.with_single_file // All types in index.ts (default: false)
.with_any_type // Map `Any` to this type (default: "unknown")
.with_readonly // Generate readonly properties (default: false)
.with_package_name // Override output directory name
Using Fluorite for Swift
Generate Swift Codable types for iOS/macOS projects.
Via CLI
Via Rust API
use SwiftOptions;
let options = new
.with_single_file
.with_visibility;
compile_swift_with_options.unwrap;
Swift Options
new
.with_single_file // Separate file per type (default: false)
.with_any_type // Map `Any` to this type (default: "AnyCodable")
.with_visibility // public, internal, or package
Generated Swift types conform to Codable, Equatable, and Sendable. Unions include a custom Codable implementation for adjacently tagged JSON format.
CLI Reference
fluorite <COMMAND>
Commands:
rust Generate Rust code
ts Generate TypeScript code
swift Generate Swift code
fluorite rust
| Flag | Default | Description |
|---|---|---|
--inputs |
required | Input .fl files |
--output |
required | Output directory |
--single-file |
true |
Put all types in one mod.rs |
--any-type |
fluorite::Any |
Rust type for Any |
--derives |
Custom derives (replaces defaults) | |
--extra-derives |
Additional derives | |
--generate-new |
true |
Generate derive_new::new |
--visibility |
public |
Type visibility |
fluorite ts
| Flag | Default | Description |
|---|---|---|
--inputs |
required | Input .fl files |
--output |
required | Output directory |
--single-file |
false |
Put all types in one index.ts |
--any-type |
unknown |
TypeScript type for Any |
--readonly |
false |
Generate readonly properties |
--package-name |
Override output directory name |
fluorite swift
| Flag | Default | Description |
|---|---|---|
--inputs |
required | Input .fl files |
--output |
required | Output directory |
--single-file |
false |
Separate file per type or all in one |
--any-type |
AnyCodable |
Swift type for Any |
--visibility |
public |
Access level: public, internal, package |
Examples
The examples/ directory contains complete projects:
- examples/demo — Rust project with
build.rsintegration, multi-package schemas, and cross-package imports - examples/demo-ts — TypeScript project using generated types from the same schemas
The demo schemas in examples/demo/fluorite/ show real-world patterns:
| File | What it demonstrates |
|---|---|
common.fl |
Shared types, skip_if_none, Any type |
users.fl |
Cross-package imports, tagged unions, type aliases |
orders.fl |
Multiple imports, enums, complex structs |
notifications.fl |
Unions with primitive variants (PlainText(String)) |
Development
# Build
# Run all tests
# Run all CI checks (format, lint, test)
# Run Rust <-> TypeScript interop tests
| Make target | Description |
|---|---|
make all |
Format check + lint + test |
make test |
Run all tests |
make fmt |
Format code |
make lint |
Run clippy |
make interop-test |
Rust/TypeScript round-trip tests |
License
MIT