The engine (Rust)
Today, PDL schemas are compiled and run by phony-core, a Rust workspace. (A CLI and language libraries — PHP, JS, Python — are planned and will read the same portable artifacts; this page documents the engine you can use now.)
phony-core has two crates:
| Crate | Role | API |
|---|---|---|
phony-pdl | the composition layer — envelope, registry, all 7 generators, PEL, the entity layer, the locale resolver | phony-pdl API |
ngram-core | the N-gram engine — the Model generator (train + generate + the .ngram format) | ngram-core API |
phony-pdl calls into ngram-core for model-type generation.
Install
Until published to crates.io, use a path dependency:
toml
# Cargo.toml
phony-pdl = { path = "../phony-core/crates/pdl" }
ngram-core = { path = "../phony-core/crates/ngram-core" }
serde_json = "1"Quick start — run a scenario
Compile a PDL schema and generate tables:
rust
use std::sync::Arc;
use phony_pdl::{Dataset, InMemoryAssets, Registry};
use ngram_core::NgramModel;
use serde_json::json;
// 1. Assets — the models/lists the `model`/`list` generators look up by name.
let assets = InMemoryAssets::new()
.with_model("person.first_names", NgramModel::train_words(&["mehmet","ayse","mustafa","zeynep"], 3));
// 2. Registry — built-in generators + assets + (optionally) a frozen clock.
let registry = Registry::with_builtins()
.with_assets(Arc::new(assets))
.with_reference_time(1_704_067_200); // 2024-01-01T00:00:00Z
// 3. A PDL schema (see "The PDL Language" for the syntax).
let schema = json!({
"generators": {
"id": { "type": "logic", "algorithm": "uuid_v7" },
"first_name": { "type": "model", "source": "person.first_names",
"generation": { "mode": "word" } },
"email": { "type": "template",
"pattern": "{{lowercase(first_name)}}@example.com", "unique": true }
},
"entities": { "User": { "fields": {
"id": { "generator": "id", "primary_key": true },
"first_name": { "generator": "first_name" },
"email": { "generator": "email" }
}}},
"scenarios": { "dev": { "User": 100 } }
});
// 4. Compile + generate — byte-identical for the same seed.
let dataset = Dataset::from_pdl(&schema).unwrap();
let tables = dataset.generate_scenario(®istry, /* seed */ 2026, "dev").unwrap();
// tables: BTreeMap<String, Vec<serde_json::Value>>
let users = &tables["User"]; // 100 row objectsEntry points
Pick the smallest one that fits:
| Function | Generates | References resolve? |
|---|---|---|
generate_one(def, registry, seed, field, row) | one value from a standalone envelope | inline only |
generate_field(schema, name, registry, seed, scope, row) | one named generator | {{ref}} against the schema |
Dataset::generate_table(registry, seed, entity, count, parents) | one entity's rows | row consistency |
Dataset::generate_scenario(registry, seed, scenario) | all tables in a scenario | full consistency + FKs |
Assets
Generators never embed data — they resolve it from an AssetProvider:
rust
pub trait AssetProvider {
fn model(&self, name: &str) -> Option<Arc<NgramModel>>;
fn list(&self, name: &str) -> Option<Arc<serde_json::Value>>;
}InMemoryAssets— register models/lists directly.LocaleAssets— resolve(asset, locale)through a chain (see phony-pdl API → Assets & locales).
The frozen clock
now()/today()/age() and relative date ranges resolve against an injected reference instant, never the wall clock:
rust
let registry = Registry::with_builtins().with_reference_time(1_704_067_200);So (schema, seed, reference-time) fully determines the output — see How generation works.
Build & test (contributing)
bash
cargo test # whole workspace
cargo clippy -p phony-pdl
cargo bench -p ngram-core # engine throughput