mod boundary;
mod identifier;
mod initialize;
mod output;
mod package;
mod profile;
mod providers;
mod render;
use crate::builtin::BuiltInProvider;
use crate::error::CliError;
use crate::project::{
ResolvedProject, prepare_dependencies, read_existing_resolved_project,
restore_locked_dependencies,
};
use crate::provider::CratesIoRegistry;
use crate::provider::registry::ProviderRegistry;
use boundary::PlainBindings;
use camino::Utf8Path;
use package::{EmbeddingPackage, EmbeddingProject};
use profile::HostedBindings;
use std::collections::BTreeSet;
use std::io::{BufRead, BufReader, IsTerminal, Write};
const GENERATED_HEADER: &str = "// Generated by `geam embedding sync`. Do not edit.\n";
struct GeneratedBindings {
package: EmbeddingPackage,
source: String,
}
pub(super) fn init(current_directory: &Utf8Path) -> Result<(), CliError> {
init_with_progress(current_directory, &mut std::io::stderr().lock())
}
pub(super) fn sync(current_directory: &Utf8Path) -> Result<(), CliError> {
sync_with_project_reader(current_directory, read_existing_resolved_project)
}
pub(super) fn check(current_directory: &Utf8Path) -> Result<(), CliError> {
check_with_project_reader(current_directory, read_existing_resolved_project)
}
fn init_with_progress(
current_directory: &Utf8Path,
progress: &mut dyn Write,
) -> Result<(), CliError> {
let project = EmbeddingProject::load(current_directory)?;
output::validate_generated(project.output_path())?;
report(
progress,
format_args!("Initializing {}", project.project_root()),
)?;
initialize::initialize(&project)?;
prepare(project, read_existing_resolved_project, progress)
}
fn check_with_project_reader(
current_directory: &Utf8Path,
read_project: fn(&Utf8Path) -> Result<ResolvedProject, CliError>,
) -> Result<(), CliError> {
check_with_progress(
current_directory,
read_project,
&mut std::io::stderr().lock(),
)
}
fn check_with_progress(
current_directory: &Utf8Path,
read_project: fn(&Utf8Path) -> Result<ResolvedProject, CliError>,
progress: &mut dyn Write,
) -> Result<(), CliError> {
report(
progress,
format_args!("Checking Cargo dependencies in {current_directory}"),
)?;
let package = EmbeddingPackage::load(current_directory)?;
report(
progress,
format_args!("Checking Gleam dependencies in {}", package.project_root()),
)?;
restore_locked_dependencies(package.project_root())?;
let program = geam_core::compile_typed_project(package.project_root(), package.root_module())?;
let requirements = geam_core::required_host_functions(&program);
let bindings = PlainBindings::from_program(package.geam_alias().clone(), &program)?;
let generated = generate(package, bindings, &requirements, read_project)?;
output::check(
generated.package.manifest(),
generated.package.output_path(),
generated.source.as_bytes(),
)?;
report(
progress,
format_args!("Checked {}", generated.package.output_path()),
)
}
fn sync_with_project_reader(
current_directory: &Utf8Path,
read_project: fn(&Utf8Path) -> Result<ResolvedProject, CliError>,
) -> Result<(), CliError> {
let project = EmbeddingProject::load(current_directory)?;
output::validate_generated(project.output_path())?;
project.validate_gleam_config()?;
prepare(project, read_project, &mut std::io::stderr().lock())
}
fn prepare(
project: EmbeddingProject,
read_project: fn(&Utf8Path) -> Result<ResolvedProject, CliError>,
progress: &mut dyn Write,
) -> Result<(), CliError> {
let input = std::io::stdin();
prepare_with_registry(
project,
read_project,
&CratesIoRegistry::default(),
input.is_terminal(),
&mut BufReader::new(input),
progress,
)
}
fn prepare_with_registry(
project: EmbeddingProject,
read_project: fn(&Utf8Path) -> Result<ResolvedProject, CliError>,
registry: &dyn ProviderRegistry,
terminal: bool,
input: &mut dyn BufRead,
progress: &mut dyn Write,
) -> Result<(), CliError> {
report(
progress,
format_args!("Preparing Gleam dependencies in {}", project.project_root()),
)?;
prepare_dependencies(project.project_root())?;
let program = geam_core::compile_typed_project(project.project_root(), project.root_module())?;
let requirements = geam_core::required_host_functions(&program);
let mut features = BTreeSet::from(["embedding"]);
for requirement in &requirements {
if let Some(builtin) = BuiltInProvider::from_package(requirement.package()) {
features.insert(builtin.geam_feature());
}
}
project.prepare_features(&features.into_iter().collect::<Vec<_>>())?;
report(
progress,
format_args!("Resolving Cargo dependencies for {}", project.manifest()),
)?;
let mut package = EmbeddingPackage::resolve(project)?;
let bindings = PlainBindings::from_program(package.geam_alias().clone(), &program)?;
if !requirements.is_empty() {
let required_packages = requirements
.iter()
.map(|requirement| requirement.package().to_string())
.collect();
let resolved_project = read_project(package.project_root())?;
let missing = profile::missing_providers(&package, &required_packages, &resolved_project)?;
let approved = providers::select_missing(&missing, registry, terminal, input, progress)?;
if !approved.is_empty() {
report(
progress,
format_args!(
"Resolving approved Cargo providers for {}",
package.manifest()
),
)?;
}
package = package.add_providers(&approved)?;
}
let generated = generate(package, bindings, &requirements, read_project)?;
let outcome = output::sync(
generated.package.output_directory(),
generated.package.output_path(),
generated.source.as_bytes(),
)?;
let action = match outcome {
output::SyncOutcome::Unchanged => "Unchanged",
output::SyncOutcome::Updated => "Updated",
};
report(
progress,
format_args!("{action} {}", generated.package.output_path()),
)
}
fn generate(
package: EmbeddingPackage,
bindings: PlainBindings,
requirements: &[geam_core::RequiredHostFunction],
read_project: fn(&Utf8Path) -> Result<ResolvedProject, CliError>,
) -> Result<GeneratedBindings, CliError> {
package.require_geam_feature("embedding", "to generate Rust embedding bindings")?;
let source = match requirements {
[] => render::plain(&bindings, package.project_path()),
[first, remaining @ ..] => {
let remaining_packages = remaining
.iter()
.map(|requirement| requirement.package().to_string())
.collect::<BTreeSet<_>>();
let resolved_project = read_project(package.project_root())?;
let hosted = HostedBindings::resolve(
&package,
bindings,
first.package(),
&remaining_packages,
&resolved_project,
)?;
render::hosted(&hosted, package.project_path())
}
};
Ok(GeneratedBindings { package, source })
}
fn report(writer: &mut dyn Write, message: std::fmt::Arguments<'_>) -> Result<(), CliError> {
writeln!(writer, "geam: {message}")
.and_then(|()| writer.flush())
.map_err(CliError::EmbeddingProgressIo)
}
#[cfg(test)]
mod tests {
use super::{
check, check_with_progress, check_with_project_reader, init, init_with_progress, prepare,
report, sync, sync_with_project_reader,
};
use crate::embedding::package::EmbeddingProject;
use crate::error::CliError;
use crate::project::read_existing_resolved_project;
use camino::Utf8PathBuf;
use std::fs;
use std::io::{self, Write};
use std::process::{Command, Output};
use tempfile::{TempDir, tempdir};
struct ClosedProgress {
remaining: usize,
}
impl Write for ClosedProgress {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
if self.remaining == 0 {
Err(io::Error::other("progress closed"))
} else {
self.remaining -= 1;
Ok(())
}
}
}
#[test]
fn initializes_and_runs_a_fresh_rust_package_without_manual_dependency_setup() {
let fixture = ApplicationFixture::new();
fs::create_dir(fixture.root.join("src")).expect("Rust source directory");
fs::create_dir(fixture.root.join(".cargo")).expect("fixture Cargo config directory");
fs::write(
fixture.root.join(".cargo/config.toml"),
"[net]\noffline = true\n",
)
.expect("fixture-only offline Cargo resolution");
let manifest = format!(
r#"[package]
name = "first-library"
version = "0.1.0"
edition = "2024"
# The unpublished checkout is used only by this acceptance fixture.
[patch.crates-io]
geam = {{ path = {:?} }}
[workspace]
resolver = "3"
"#,
fixture.repository
);
fs::write(fixture.root.join("Cargo.toml"), &manifest).expect("Cargo package");
fs::write(fixture.root.join("src/main.rs"), "fn main() {}\n").expect("handwritten Rust");
let mut progress = Vec::new();
init_with_progress(&fixture.root, &mut progress).expect("fresh initialization");
assert_eq!(
String::from_utf8(progress).expect("UTF-8 progress"),
format!(
"geam: Initializing {0}/gleam\ngeam: Preparing Gleam dependencies in {0}/gleam\ngeam: Resolving Cargo dependencies for {0}/Cargo.toml\ngeam: Updated {0}/src/geam_bindings.rs\n",
fixture.root
)
);
assert_eq!(
fs::read_to_string(fixture.root.join("src/main.rs")).expect("Rust source remains"),
"fn main() {}\n"
);
let cargo = fs::read_to_string(fixture.root.join("Cargo.toml")).expect("prepared manifest");
assert_eq!(
cargo,
format!(
"{manifest}\n[dependencies]\ngeam = {{ version = \"={}\", default-features = false, features = [\"embedding\"] }}\n",
env!("CARGO_PKG_VERSION")
)
);
let cargo_lock = fs::read(fixture.root.join("Cargo.lock")).expect("Cargo lock");
let gleam_lock = fs::read(fixture.root.join("gleam/manifest.toml")).expect("Gleam lock");
let generated =
fs::read(fixture.root.join("src/geam_bindings.rs")).expect("generated bindings");
let mut progress = Vec::new();
init_with_progress(&fixture.root, &mut progress).expect("repeat initialization");
assert_eq!(
String::from_utf8(progress).expect("UTF-8 progress"),
format!(
"geam: Initializing {0}/gleam\ngeam: Preparing Gleam dependencies in {0}/gleam\ngeam: Resolving Cargo dependencies for {0}/Cargo.toml\ngeam: Unchanged {0}/src/geam_bindings.rs\n",
fixture.root
)
);
assert_eq!(
fs::read(fixture.root.join("Cargo.lock")).expect("unchanged Cargo lock"),
cargo_lock
);
assert_eq!(
fs::read(fixture.root.join("gleam/manifest.toml")).expect("unchanged Gleam lock"),
gleam_lock
);
assert_eq!(
fs::read(fixture.root.join("src/geam_bindings.rs")).expect("unchanged bindings"),
generated
);
check(&fixture.root).expect("ready initial bindings");
let tree = success_output(
fixture
.cargo("tree")
.args(["--locked", "--offline", "--edges", "normal"]),
"minimal dependency graph",
);
let tree = String::from_utf8(tree.stdout).expect("UTF-8 graph");
assert!(!tree.contains("geam-cli"));
assert!(!tree.contains("geam-macros"));
fs::write(
fixture.root.join("src/main.rs"),
r#"mod geam_bindings;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let program = geam_bindings::project().compile()?;
let builder = geam::embedding::ModuleBuilder::from_program(program)?;
let (bindings, functions) = geam_bindings::bind(builder)?;
let module = bindings.seal();
let mut echo = Vec::new();
assert_eq!(module.call(&functions.double, (21.into(),), &mut echo)?, 42.into());
assert!(echo.is_empty());
Ok(())
}
"#,
)
.expect("application uses generated starter");
assert_success(
fixture
.cargo("run")
.args(["--quiet", "--locked", "--offline"]),
"initialized application",
);
fs::write(
fixture.root.join("src/geam_bindings.rs"),
"// handwritten module\n",
)
.expect("user-owned output");
for operation in [init, sync] {
assert_eq!(
operation(&fixture.root)
.expect_err("handwritten output must be preserved")
.to_string(),
format!(
"refusing to replace existing embedding file {}/src/geam_bindings.rs: the file was not generated by geam embedding sync",
fixture.root
)
);
}
assert_eq!(
fs::read_to_string(fixture.root.join("src/geam_bindings.rs"))
.expect("preserved output"),
"// handwritten module\n"
);
}
#[test]
fn preserves_progress_write_and_flush_failures() {
struct FailedProgress {
flush: bool,
}
impl Write for FailedProgress {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
if self.flush {
Ok(bytes.len())
} else {
Err(io::Error::other("write failed"))
}
}
fn flush(&mut self) -> io::Result<()> {
Err(io::Error::other("flush failed"))
}
}
for (flush, expected) in [(false, "write failed"), (true, "flush failed")] {
let error = report(
&mut FailedProgress { flush },
format_args!("Initializing gleam"),
)
.expect_err("progress failure must propagate");
assert_eq!(error.to_string(), "failed to write embedding progress");
assert!(
matches!(error, CliError::EmbeddingProgressIo(error) if error.to_string() == expected)
);
}
}
#[test]
fn stops_preparation_at_the_failed_phase_without_publishing_bindings() {
let fixture = ApplicationFixture::new();
fixture.write_plain_project();
for remaining in 0..3 {
let error = init_with_progress(&fixture.root, &mut ClosedProgress { remaining })
.expect_err("stop at the closed progress stream");
assert_eq!(error.to_string(), "failed to write embedding progress");
assert!(!fixture.root.join("src/geam_bindings.rs").exists());
}
let config_path = fixture.root.join("gleam/gleam.toml");
let config = fs::read_to_string(&config_path).expect("valid Gleam configuration");
fs::write(&config_path, "name = 'different'\n").expect("conflicting project name");
for operation in [init, sync] {
let error = operation(&fixture.root).expect_err("project name conflict");
assert_eq!(
error.to_string(),
format!(
"invalid Rust embedding project for package plain-embedding-application at {0}/Cargo.toml: {0}/gleam/gleam.toml declares Gleam package `different`; expected `plain_embedding_application` from the Cargo package name",
fixture.root
)
);
}
fs::write(
&config_path,
"name = 'plain_embedding_application'\n[dependencies]\nmissing = { path = 'missing' }\n",
)
.expect("missing local Gleam dependency");
let error = sync(&fixture.root).expect_err("Gleam preparation must fail");
assert!(
matches!(error, CliError::ProcessFailure { command, .. } if command == "gleam deps download")
);
fs::write(&config_path, config).expect("restore valid Gleam configuration");
let project = EmbeddingProject::load(&fixture.root).expect("select Cargo package");
let manifest_path = fixture.root.join("Cargo.toml");
let manifest = fs::read_to_string(&manifest_path).expect("Cargo manifest");
fs::write(&manifest_path, "[").expect("external edit during preparation");
let error = prepare(project, read_existing_resolved_project, &mut Vec::new())
.expect_err("invalid Cargo edit must not be replaced");
assert!(matches!(
error,
CliError::InvalidToml {
kind: "Cargo manifest",
..
}
));
assert_eq!(
fs::read_to_string(&manifest_path).expect("preserved external edit"),
"["
);
fs::write(
&manifest_path,
manifest.replace(
"features = [\"embedding\"]",
"features = [\"embedding\", \"missing-feature\"]",
),
)
.expect("unresolvable user-selected Cargo feature");
let error = sync(&fixture.root).expect_err("Cargo resolution must fail");
assert!(
matches!(error, CliError::ProcessFailure { command, .. } if command == format!("cargo metadata --format-version 1 --manifest-path {manifest_path}"))
);
assert!(!fixture.root.join("src/geam_bindings.rs").exists());
}
#[test]
fn synchronizes_formats_compiles_and_runs_plain_project_bindings() {
let fixture = ApplicationFixture::new();
fixture.write_plain_project();
fixture.generate_lockfile();
let lock_before =
fs::read(fixture.root.join("Cargo.lock")).expect("fixture lockfile should be readable");
sync(&fixture.root.join("src/nested")).expect("plain bindings should synchronize");
check(&fixture.root.join("src/nested"))
.expect("exact plain bindings should pass checking from a nested directory");
let generated_path = fixture.root.join("src/geam_bindings.rs");
let generated = fs::read(&generated_path).expect("generated source should be readable");
assert!(String::from_utf8_lossy(&generated).contains("use runtime::embedding::EcoString;"));
assert!(
String::from_utf8_lossy(&generated)
.contains("pub double: Function<(BigInt,), BigInt, Function1Input>")
);
assert_eq!(
fs::read(fixture.root.join("Cargo.lock"))
.expect("fixture lockfile should remain readable"),
lock_before,
);
assert_success(
Command::new("rustfmt").arg("--check").arg(&generated_path),
"generated Rust formatting",
);
assert_success(
fixture.cargo("clippy").args([
"--locked",
"--offline",
"--all-targets",
"--",
"-D",
"warnings",
]),
"plain generated Rust Clippy",
);
assert_success(
fixture
.cargo("run")
.arg("--locked")
.arg("--offline")
.arg("--quiet"),
"generated Rust application",
);
sync(&fixture.root).expect("identical synchronization from the root should succeed");
check(&fixture.root).expect("exact plain bindings should pass checking from the root");
assert_eq!(
fs::read(&generated_path).expect("unchanged generated source should be readable"),
generated,
);
let manifest_path = fixture.root.join("Cargo.toml");
let manifest = fs::read_to_string(&manifest_path)
.expect("fixture manifest should be readable before metadata failure");
fs::write(
&manifest_path,
format!("{manifest}\n[package.metadata.geam.embedding]\nproject = \"gleam\"\nmodule = \"obsolete\"\n"),
)
.expect("invalid embedding metadata should be written");
sync(&fixture.root).expect_err("invalid embedding metadata should fail synchronization");
assert_eq!(
fs::read(&generated_path).expect("metadata failure should preserve previous output"),
generated,
);
fs::write(&manifest_path, manifest).expect("valid embedding metadata should be restored");
fs::write(
fixture
.root
.join("gleam/src/plain_embedding_application.gleam"),
"pub fn invalid(",
)
.expect("invalid source fixture should be written");
sync(&fixture.root).expect_err("invalid source should fail synchronization");
assert_eq!(
fs::read(&generated_path).expect("previous output should remain readable"),
generated,
);
fs::write(
fixture
.root
.join("gleam/src/plain_embedding_application.gleam"),
"pub fn unsupported(_value: List(fn(Int) -> Int)) -> Int { 1 }\n",
)
.expect("unsupported boundary fixture should be written");
for operation in [sync, check] {
let error = operation(&fixture.root).expect_err("unsupported boundary must fail");
assert!(
error
.to_string()
.contains("invalid Rust embedding boundary module plain_embedding_application")
);
}
assert_eq!(
fs::read(&generated_path).expect("boundary failure should preserve previous output"),
generated,
);
fs::write(
fixture
.root
.join("gleam/src/plain_embedding_application.gleam"),
r#"
@external(erlang, "native", "normalize")
pub fn normalize(value: String) -> String
"#,
)
.expect("host-required source fixture should be written");
for operation in [sync_with_project_reader, check_with_project_reader] {
let error = operation(&fixture.root, |project_root| {
Err(CliError::FileRead {
path: project_root.join("manifest.toml"),
error: std::io::Error::new(
std::io::ErrorKind::NotFound,
"fixture resolution is unavailable",
),
})
})
.expect_err("hosted generation requires an existing resolution");
assert!(matches!(
&error,
CliError::FileRead { path, error }
if path == &fixture.root.join("gleam/manifest.toml")
&& error.kind() == std::io::ErrorKind::NotFound
));
}
let error = check(&fixture.root)
.expect_err("host-required checking should require a direct provider");
assert!(matches!(
error,
CliError::InvalidEmbeddingProvider { package, manifest, reason }
if package == "plain_embedding_application"
&& manifest == fixture.root.join("Cargo.toml")
&& reason.contains("no enabled direct provider dependency")
));
assert_eq!(
fs::read(&generated_path).expect("host failure should preserve previous output"),
generated,
);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::write(
fixture
.root
.join("gleam/src/plain_embedding_application.gleam"),
"pub fn changed() -> Int { 1 }\n",
)
.expect("changed source fixture should be written");
let output_directory = fixture.root.join("src");
fs::set_permissions(&output_directory, fs::Permissions::from_mode(0o500))
.expect("output directory should become read-only");
let result = sync(&fixture.root);
fs::set_permissions(&output_directory, fs::Permissions::from_mode(0o700))
.expect("output directory permissions should be restored");
let error = result.expect_err("read-only output directory should reject sync");
assert!(matches!(
error,
CliError::FileWrite { path, error }
if path == generated_path
&& error.kind() == std::io::ErrorKind::PermissionDenied
));
assert_eq!(
fs::read(&generated_path)
.expect("output failure should preserve previous generated source"),
generated,
);
}
}
#[test]
fn synchronizes_formats_lints_and_runs_source_backed_hosted_bindings() {
let fixture = ApplicationFixture::new();
fixture.write_hosted_project();
fixture.generate_lockfile();
sync(&fixture.root.join("src")).expect("hosted bindings should synchronize");
check(&fixture.root).expect("exact hosted bindings should pass checking");
let generated_path = fixture.root.join("src/geam_bindings.rs");
let generated = fs::read(&generated_path).expect("generated source should be readable");
let source = String::from_utf8_lossy(&generated);
assert!(source.contains("pub struct Profile;"));
assert!(source.contains("pub struct RunStateInputs"));
assert!(source.contains("flags::Component"));
assert!(source.contains("patterns::Component"));
assert!(source.contains("pub example_feature_flags: HostProviderConfiguration"));
assert!(source.contains("pub example_text_pattern: HostProviderConfiguration"));
let flags_input = source
.find("pub example_feature_flags:")
.expect("feature flags input should be generated");
let pattern_input = source
.find("pub example_text_pattern:")
.expect("text pattern input should be generated");
assert!(flags_input < pattern_input);
assert!(!source.contains("ProviderConfigurations"));
assert!(!source.contains("runtime::gleam_stdlib::Component"));
assert!(!source.contains("runtime::gleam_json::Component"));
assert!(!source.contains("runtime::gleam_time::Component"));
assert!(!source.contains("unused_provider::Component"));
assert_success(
Command::new("rustfmt").arg("--check").arg(&generated_path),
"hosted generated Rust formatting",
);
assert_success(
fixture
.cargo("clippy")
.arg("--locked")
.arg("--offline")
.arg("--all-targets")
.arg("--")
.arg("-D")
.arg("warnings"),
"hosted generated Rust Clippy",
);
let output = success_output(
fixture
.cargo("run")
.arg("--locked")
.arg("--offline")
.arg("--quiet"),
"hosted generated Rust application",
);
assert_eq!(output.stdout, b"<Geam> + <Gleam> 2026\n");
assert_eq!(output.stderr, b"");
let manifest_path = fixture.root.join("Cargo.toml");
let manifest = fs::read_to_string(&manifest_path)
.expect("hosted application manifest should be readable");
let without_provider = manifest
.lines()
.filter(|line| !line.starts_with("patterns = "))
.collect::<Vec<_>>()
.join("\n");
fs::write(&manifest_path, format!("{without_provider}\n"))
.expect("provider dependency should be removed");
fixture.generate_lockfile();
let checked_error =
check(&fixture.root).expect_err("missing direct provider should fail hosted checking");
assert!(
matches!(
&checked_error,
CliError::InvalidEmbeddingProvider { package, manifest: path, reason }
if package == "example_text_pattern"
&& path == &manifest_path
&& reason.contains("no enabled direct provider dependency")
),
"unexpected provider check error: {checked_error:?}"
);
assert_eq!(
fs::read(&generated_path)
.expect("provider check failure should preserve previous output"),
generated,
);
}
#[test]
fn synchronizes_and_runs_json_time_with_caller_owned_capabilities() {
let fixture = ApplicationFixture::new();
fixture.write_built_in_project();
fixture.generate_lockfile();
sync(&fixture.root).expect("built-in hosted bindings should synchronize");
check(&fixture.root).expect("exact built-in bindings should pass checking");
let generated_path = fixture.root.join("src/geam_bindings.rs");
let generated = fs::read_to_string(&generated_path)
.expect("built-in generated source should be readable");
assert!(generated.contains("pub struct Profile<Io, Source>"));
assert!(generated.contains("runtime::gleam_stdlib::Component<Io>"));
assert!(generated.contains("runtime::gleam_json::Component"));
assert!(generated.contains("runtime::gleam_time::Component<Source>"));
assert!(generated.contains("pub struct RunStateInputs<Io, Source>"));
assert!(generated.contains("pub stdlib: runtime::gleam_stdlib::GleamStdlibRunState<Io>"));
assert!(generated.contains(" pub time: Source,"));
assert!(!generated.contains(" pub json:"));
assert!(generated.contains(" json: (),"));
assert!(generated.contains(" time: self.time,"));
assert!(!generated.contains("HostProviderComponentInitialization"));
assert_success(
Command::new("rustfmt").arg("--check").arg(&generated_path),
"built-in generated Rust formatting",
);
assert_success(
fixture
.cargo("clippy")
.arg("--locked")
.arg("--offline")
.arg("--all-targets")
.arg("--")
.arg("-D")
.arg("warnings"),
"built-in generated Rust Clippy",
);
assert_success(
fixture
.cargo("run")
.arg("--locked")
.arg("--offline")
.arg("--quiet"),
"built-in generated Rust application",
);
}
#[test]
fn checks_missing_stale_and_plain_boundary_drift_without_writing() {
let fixture = ApplicationFixture::new();
fixture.write_plain_project();
fixture.generate_lockfile();
let manifest_path = fixture.root.join("Cargo.toml");
let generated_path = fixture.root.join("src/geam_bindings.rs");
let missing =
check(&fixture.root).expect_err("missing generated bindings should fail checking");
assert_eq!(
missing.to_string(),
format!(
"Rust embedding bindings at {generated_path} are missing or stale for {manifest_path}; run `geam embedding sync` from the Cargo package directory"
),
);
assert!(!generated_path.exists());
sync(&fixture.root).expect("plain fixture should synchronize");
let original = fs::read(&generated_path).expect("generated source should be readable");
let support_path = fixture.root.join("gleam/src/support.gleam");
let support = fs::read_to_string(&support_path).expect("support source should be readable");
fs::write(
&support_path,
format!(
"{}\nfn private_helper() -> Nil {{ Nil }}\n",
support.replace("value * 2", "value + value"),
),
)
.expect("body-only and private changes should be written");
check(&fixture.root).expect("body-only and private changes should not alter bindings");
assert_eq!(
fs::read(&generated_path).expect("clean generated source should remain readable"),
original,
);
let boundary_path = fixture
.root
.join("gleam/src/plain_embedding_application.gleam");
let boundary =
fs::read_to_string(&boundary_path).expect("boundary source should be readable");
fs::write(
&boundary_path,
format!("{boundary}\npub fn added() -> Int {{ 1 }}\n"),
)
.expect("public addition should be written");
assert!(matches!(
check(
&fixture.root,
),
Err(CliError::EmbeddingBindingsOutOfDate { manifest, output })
if manifest == manifest_path && output == generated_path
));
assert_eq!(
fs::read(&generated_path).expect("stale source should remain readable"),
original,
);
sync(&fixture.root).expect("public addition should synchronize");
let with_addition =
fs::read(&generated_path).expect("updated generated source should be readable");
fs::write(&boundary_path, &boundary).expect("public addition should be removed");
assert!(matches!(
check(
&fixture.root,
),
Err(CliError::EmbeddingBindingsOutOfDate { manifest, output })
if manifest == manifest_path && output == generated_path
));
assert_eq!(
fs::read(&generated_path).expect("removed-boundary output should remain readable"),
with_addition,
);
sync(&fixture.root).expect("restored boundary should synchronize");
let restored = fs::read(&generated_path).expect("restored output should be readable");
fs::write(
&boundary_path,
boundary.replace(
"pub fn bindings() -> Int { 7 }",
"pub fn bindings() -> Bool { True }",
),
)
.expect("signature change should be written");
assert!(matches!(
check(
&fixture.root,
),
Err(CliError::EmbeddingBindingsOutOfDate { manifest, output })
if manifest == manifest_path && output == generated_path
));
assert_eq!(
fs::read(&generated_path).expect("signature-drift output should remain readable"),
restored,
);
fs::write(&boundary_path, "pub fn invalid(")
.expect("invalid boundary source should be written");
check(&fixture.root).expect_err("invalid source should fail checking");
assert_eq!(
fs::read(&generated_path).expect("invalid-source output should remain readable"),
restored,
);
}
#[test]
fn detects_host_requirement_drift_without_using_unused_dependencies() {
let fixture = ApplicationFixture::new();
fixture.write_hosted_project();
fixture.generate_lockfile();
sync(&fixture.root).expect("hosted fixture should synchronize");
let generated_path = fixture.root.join("src/geam_bindings.rs");
let generated = fs::read(&generated_path).expect("hosted output should be readable");
let manifest_path = fixture.root.join("Cargo.toml");
fs::write(
fixture
.root
.join("gleam/src/hosted_embedding_application.gleam"),
r#"pub fn format_words() -> String { "plain" }
pub fn contains_only_words(_text: String) -> Bool { True }
"#,
)
.expect("plain replacement boundary should be written");
assert!(matches!(
check(
&fixture.root,
),
Err(CliError::EmbeddingBindingsOutOfDate { manifest, output })
if manifest == manifest_path && output == generated_path
));
assert_eq!(
fs::read(&generated_path).expect("host-requirement output should remain readable"),
generated,
);
}
#[test]
fn checks_missing_features_and_sync_adds_only_required_embedding_and_builtin_features() {
let plain = ApplicationFixture::new();
plain.write_plain_project();
let manifest_path = plain.root.join("Cargo.toml");
let manifest =
fs::read_to_string(&manifest_path).expect("plain fixture manifest should be readable");
fs::write(
&manifest_path,
manifest.replace("features = [\"embedding\"]", "features = []"),
)
.expect("plain fixture should omit the embedding feature");
plain.generate_lockfile();
let error = check(&plain.root).expect_err("missing embedding feature should fail checking");
assert!(matches!(
error,
CliError::InvalidEmbeddingDependency { package, manifest, reason }
if package == "plain-embedding-application"
&& manifest == manifest_path
&& reason.contains("Geam feature `embedding`")
&& reason.contains("direct Geam dependency")
));
assert!(!plain.root.join("src/geam_bindings.rs").exists());
sync(&plain.root).expect("sync should add the missing embedding feature");
check(&plain.root).expect("prepared plain project should check");
assert_eq!(
fs::read_to_string(&manifest_path).expect("updated manifest"),
manifest
);
let hosted = ApplicationFixture::new();
hosted.write_built_in_project();
let manifest_path = hosted.root.join("Cargo.toml");
let manifest =
fs::read_to_string(&manifest_path).expect("hosted fixture manifest should be readable");
fs::write(&manifest_path, manifest.replace(", \"gleam-time\"", ""))
.expect("hosted fixture should omit the required Time feature");
hosted.generate_lockfile();
let error = check(&hosted.root).expect_err("missing built-in feature should fail checking");
assert!(matches!(
error,
CliError::InvalidEmbeddingDependency { package, manifest, reason }
if package == "built-in-embedding-application"
&& manifest == manifest_path
&& reason.contains("Geam feature `gleam-time`")
&& reason.contains("Gleam package `gleam_time`")
));
assert!(!hosted.root.join("src/geam_bindings.rs").exists());
sync(&hosted.root).expect("sync should add the required Time feature");
check(&hosted.root).expect("prepared built-in project should check");
assert_eq!(
fs::read_to_string(&manifest_path).expect("updated manifest"),
manifest
);
}
#[test]
fn checks_locked_hex_sources_and_drift_without_rewriting_project_files() {
let fixture = ApplicationFixture::new();
fixture.write_plain_project();
fs::create_dir(fixture.root.join(".cargo")).expect("Cargo configuration directory");
fs::write(
fixture.root.join(".cargo/config.toml"),
"[net]\noffline = true\n",
)
.expect("use previously acquired Rust packages");
fs::write(
fixture.root.join("src/main.rs"),
"fn main() { panic!(\"check must not execute applications\"); }\n",
)
.expect("non-executing application");
fs::write(
fixture.root.join("build.rs"),
"fn main() { panic!(\"check must not execute build scripts\"); }\n",
)
.expect("non-executing build script");
let config = "name = \"plain_embedding_application\"\nversion = \"1.0.0\"\n[dependencies]\ngleam_stdlib = \">= 1.0.3 and < 1.0.4\"\n";
let lock = r#"packages = [
{ name = "gleam_stdlib", version = "1.0.3", build_tools = ["gleam"], requirements = [], source = "hex", outer_checksum = "1F543AFBA5D33DA493E6087F4E4C4F20D899411343512686C98A8ABB2963CF22" },
]
[requirements]
gleam_stdlib = { version = ">= 1.0.3 and < 1.0.4" }
"#;
fs::write(fixture.root.join("gleam/gleam.toml"), config).expect("Hex declaration");
fs::write(fixture.root.join("gleam/manifest.toml"), lock).expect("committed Hex selection");
fs::write(fixture.root.join("gleam/src/plain_embedding_application.gleam"), "import gleam/order\npub fn ascending() -> Bool { order.negate(order.Lt) == order.Gt }\n").expect("source using a published Gleam module");
fixture.generate_lockfile();
sync(&fixture.root).expect("prepare committed bindings and locks");
let paths = [
"Cargo.toml",
"Cargo.lock",
"build.rs",
"src/main.rs",
"src/geam_bindings.rs",
"gleam/gleam.toml",
"gleam/manifest.toml",
"gleam/src/plain_embedding_application.gleam",
];
let project_files =
|| paths.map(|path| fs::read(fixture.root.join(path)).map_err(|error| error.kind()));
let prepared = project_files();
fs::remove_dir_all(fixture.root.join("gleam/build")).expect("cold Gleam package cache");
let mut progress = Vec::new();
check_with_progress(&fixture.root, read_existing_resolved_project, &mut progress)
.expect("check restores locked Hex sources without preparation");
assert_eq!(
String::from_utf8(progress).expect("progress text"),
format!(
"geam: Checking Cargo dependencies in {0}\ngeam: Checking Gleam dependencies in {0}/gleam\ngeam: Checked {0}/src/geam_bindings.rs\n",
fixture.root
)
);
assert_eq!(project_files(), prepared);
assert!(
fixture
.root
.join("gleam/build/packages/gleam_stdlib/src/gleam/order.gleam")
.is_file()
);
assert_eq!(
fs::read_to_string(
fixture
.root
.join("gleam/build/packages/gleam_stdlib/gleam.toml")
)
.expect("locked Hex package")
.parse::<toml_edit::DocumentMut>()
.expect("package metadata")["version"]
.as_str(),
Some("1.0.3")
);
for remaining in 0..3 {
let error = check_with_progress(
&fixture.root,
read_existing_resolved_project,
&mut ClosedProgress { remaining },
)
.expect_err("closed progress stream");
assert_eq!(error.to_string(), "failed to write embedding progress");
assert_eq!(project_files(), prepared);
}
let manifest =
fs::read_to_string(fixture.root.join("Cargo.toml")).expect("application manifest");
for (path, changed, diagnostic_path) in [
(
"src/geam_bindings.rs",
Some("// stale generated module\n".to_owned()),
"src/geam_bindings.rs",
),
("gleam/manifest.toml", None, "gleam/manifest.toml"),
(
"gleam/gleam.toml",
Some(config.replace("1.0.3 and < 1.0.4", "1.0.4 and < 1.0.5")),
"gleam/manifest.toml",
),
("Cargo.lock", None, "Cargo.toml"),
(
"Cargo.toml",
Some(manifest.replace("0.0.0", "0.0.1")),
"Cargo.toml",
),
(
"gleam/src/plain_embedding_application.gleam",
Some("pub fn invalid(".to_owned()),
"gleam/src/plain_embedding_application.gleam",
),
] {
let original = fs::read(fixture.root.join(path)).expect("original project file");
match changed {
Some(source) => fs::write(fixture.root.join(path), source).expect("project drift"),
None => fs::remove_file(fixture.root.join(path)).expect("missing committed lock"),
}
let before = project_files();
let error = check(&fixture.root).expect_err("drift must not be repaired by check");
assert!(
error
.to_string()
.contains(fixture.root.join(diagnostic_path).as_str()),
"{error}"
);
assert_eq!(project_files(), before);
fs::write(fixture.root.join(path), original).expect("restore test input");
}
fs::remove_dir_all(fixture.root.join("gleam/build"))
.expect("cold cache before acquisition failure");
fs::write(fixture.root.join("gleam/build"), "blocked cache")
.expect("external download workspace failure");
let error = check(&fixture.root).expect_err("cannot acquire into a blocked cache");
assert_eq!(
error.to_string(),
format!(
"failed to read {}",
fixture.root.join("gleam/build/packages/packages.toml")
)
);
assert_eq!(project_files(), prepared);
}
struct ApplicationFixture {
_directory: TempDir,
root: Utf8PathBuf,
repository: Utf8PathBuf,
target: Utf8PathBuf,
}
impl ApplicationFixture {
fn new() -> Self {
let directory = tempdir().expect("temporary directory should be created");
let root = Utf8PathBuf::from_path_buf(
fs::canonicalize(directory.path()).expect("temporary path should canonicalize"),
)
.expect("temporary path should be valid UTF-8");
let repository = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.map(camino::Utf8Path::to_path_buf)
.expect("CLI package should be inside the repository");
let target = repository.join("target/embedding-sync-acceptance");
Self {
_directory: directory,
root,
repository,
target,
}
}
fn write_plain_project(&self) {
fs::create_dir_all(self.root.join("src/nested"))
.expect("Rust source directory should be created");
fs::create_dir_all(self.root.join("gleam/src"))
.expect("Gleam source directory should be created");
fs::create_dir_all(self.root.join("gleam/packages/gleam_stdlib/src/gleam"))
.expect("plain Option dependency directory should be created");
fs::write(
self.root.join("gleam/packages/gleam_stdlib/gleam.toml"),
"name = \"gleam_stdlib\"\nversion = \"1.0.0\"\n",
)
.expect("plain Option package should be written");
fs::write(
self.root
.join("gleam/packages/gleam_stdlib/src/gleam/option.gleam"),
"pub type Option(a) { Some(a) None }\n",
)
.expect("plain Option source should be written");
fs::write(
self.root.join("Cargo.toml"),
format!(
r#"[package]
name = "plain-embedding-application"
version = "0.0.0"
edition = "2024"
[dependencies]
runtime = {{ package = "geam", path = {:?}, default-features = false, features = ["embedding"] }}
[workspace]
resolver = "3"
"#,
self.repository
),
)
.expect("Rust manifest should be written");
fs::write(
self.root.join("src/main.rs"),
r#"mod geam_bindings;
use std::error::Error;
use runtime::embedding::{BigInt, EcoString};
fn main() -> Result<(), Box<dyn Error>> {
let program = geam_bindings::project().compile()?;
let builder = runtime::embedding::ModuleBuilder::from_program(program)?;
let (bindings, functions) = geam_bindings::bind(builder)?;
let module = bindings.seal();
let mut echo = Vec::new();
let first = module.call(&functions.normalize, ("AB-12".into(),), &mut echo)?;
let second = module.call(&functions.normalize, ("C-4".into(),), &mut echo)?;
let doubled = module.call(&functions.double, (21.into(),), &mut echo)?;
let binding_name = module.call(&functions.bindings, (), &mut echo)?;
let float = module.call(&functions.keep_float, (1.25,), &mut echo)?;
let string = module.call(&functions.keep_string, ("value".into(),), &mut echo)?;
let bits = runtime::embedding::BitArrayValue::try_from_parts(vec![0b1010_0000], 3)?;
let returned_bits = module.call(&functions.keep_bits, (bits.clone(),), &mut echo)?;
let codepoint = module.call(&functions.keep_codepoint, ('a',), &mut echo)?;
let boolean = module.call(&functions.keep_bool, (true,), &mut echo)?;
module.call(&functions.keep_nil, ((),), &mut echo)?;
let mixed = module.call(
&functions.mixed,
(
1.into(),
2.5,
"mixed".into(),
bits.clone(),
'b',
true,
(),
),
&mut echo,
)?;
assert_eq!(first, "SKU:AB-12");
assert_eq!(second, "SKU:C-4");
assert_eq!(doubled, runtime::embedding::BigInt::from(42));
assert_eq!(binding_name, runtime::embedding::BigInt::from(7));
assert_eq!(float, 1.25);
assert_eq!(string, "value");
assert_eq!(returned_bits, bits);
assert_eq!(codepoint, 'a');
assert!(boolean);
assert!(mixed);
let rows = module.call(&functions.keep_rows, (vec![("first".into(), 3.into())],), &mut echo)?;
assert_eq!(rows.get(0), Some(("first".into(), BigInt::from(3))));
let again = module.call(&functions.other_rows, (&rows,), &mut echo)?;
assert_eq!(again.to_vec(), rows.to_vec());
let (left, right) = module.call(
&functions.combine_rows, (&rows, vec![("second".into(), 4.into())]), &mut echo,
)?;
assert_eq!(left.to_vec(), rows.to_vec());
assert_eq!(right.get(0), Some(("second".into(), BigInt::from(4))));
let nested = module.call(&functions.nested, (vec![vec!["nested".into()]],), &mut echo)?;
assert_eq!(nested.get(0).expect("nested row").get(0), Some("nested".into()));
let nested_again = module.call(&functions.nested, (&nested,), &mut echo)?;
assert_eq!(nested_again.get(0).expect("retained nested row").get(0), Some("nested".into()));
let optional = module.call(&functions.optional_rows, (Some(&rows),), &mut echo)?;
assert_eq!(optional.expect("populated Option").to_vec(), rows.to_vec());
let absent: Option<Vec<(EcoString, BigInt)>> = None;
assert!(module.call(&functions.optional_rows, (absent,), &mut echo)?.is_none());
let accepted = module.call(
&functions.result_rows, (Ok(vec![("accepted".into(), 5.into())]),), &mut echo,
)?;
assert_eq!(accepted.expect("Ok rows").get(0), Some(("accepted".into(), BigInt::from(5))));
let rejected: Result<Vec<(EcoString, BigInt)>, EcoString> = Err("rejected".into());
assert_eq!(module.call(&functions.result_rows, (rejected,), &mut echo)?.err(), Some("rejected".into()));
let (numbers, labels, tag) = module.call(
&functions.mixed_data, ((vec![8.into()], Some(vec!["label".into()]), "tag".into()),), &mut echo,
)?;
assert_eq!(numbers.get(0), Some(BigInt::from(8)));
assert_eq!(labels.expect("populated nested Option").get(0), Some("label".into()));
assert_eq!(tag, "tag");
module.call(
&functions.many_lists,
(
(&numbers, vec![1.into()], &numbers, vec![2.into()], &numbers, vec![3.into()], &numbers),
(vec![4.into()], &numbers, vec![5.into()], &numbers, vec![6.into()], &numbers, vec![7.into()]),
),
&mut echo,
)?;
assert!(echo.is_empty());
Ok(())
}
"#,
)
.expect("Rust application should be written");
fs::write(
self.root.join("gleam/gleam.toml"),
"name = \"plain_embedding_application\"\nversion = \"1.0.0\"\n\n[dependencies]\ngleam_stdlib = { path = \"packages/gleam_stdlib\" }\n",
)
.expect("Gleam package config should be written");
fs::write(
self.root.join("gleam/manifest.toml"),
"packages = [{ name = \"gleam_stdlib\", version = \"1.0.0\", build_tools = [\"gleam\"], requirements = [], source = \"local\", path = \"packages/gleam_stdlib\" }]\n\n[requirements]\ngleam_stdlib = { path = \"packages/gleam_stdlib\" }\n",
)
.expect("Gleam manifest should be written");
fs::write(
self.root
.join("gleam/src/plain_embedding_application.gleam"),
r#"import support
import gleam/option.{type Option}
pub fn normalize(value: String) -> String {
support.label(value)
}
pub fn double(value: Int) -> Int {
support.double(value)
}
pub fn bindings() -> Int { 7 }
pub fn keep_float(value: Float) -> Float { value }
pub fn keep_string(value: String) -> String { value }
pub fn keep_bits(value: BitArray) -> BitArray { value }
pub fn keep_codepoint(value: UtfCodepoint) -> UtfCodepoint { value }
pub fn keep_bool(value: Bool) -> Bool { value }
pub fn keep_nil(value: Nil) -> Nil { value }
pub fn mixed(
_int: Int,
_float: Float,
_string: String,
_bits: BitArray,
_codepoint: UtfCodepoint,
value: Bool,
_nil: Nil,
) -> Bool {
value
}
pub type Row = #(String, Int)
pub fn keep_rows(value: List(Row)) { value }
pub fn other_rows(value: List(Row)) { value }
pub fn combine_rows(left: List(Row), right: List(Row)) { #(left, right) }
pub fn nested(value: List(List(String))) { value }
pub fn optional_rows(value: Option(List(Row))) { value }
pub fn result_rows(value: Result(List(Row), String)) { value }
pub fn mixed_data(value: #(List(Int), Option(List(String)), String)) { value }
pub fn many_lists(
_left: #(List(Int), List(Int), List(Int), List(Int), List(Int), List(Int), List(Int)),
_right: #(List(Int), List(Int), List(Int), List(Int), List(Int), List(Int), List(Int)),
) { Nil }
"#,
)
.expect("Gleam boundary source should be written");
fs::write(
self.root.join("gleam/src/support.gleam"),
r#"pub fn label(value: String) -> String {
"SKU:" <> value
}
pub fn double(value: Int) -> Int {
value * 2
}
"#,
)
.expect("imported Gleam source should be written");
}
fn write_hosted_project(&self) {
fs::create_dir_all(self.root.join("src"))
.expect("Rust source directory should be created");
fs::create_dir_all(self.root.join("gleam/src"))
.expect("Gleam source directory should be created");
let pattern_provider = self
.repository
.join("examples/provider/text_pattern/provider");
let flags_provider = self
.repository
.join("examples/provider/feature_flags/provider");
let text_pattern = self
.repository
.join("examples/provider/text_pattern/project/packages/example_text_pattern");
let text_pattern_config = fs::read_to_string(text_pattern.join("gleam.toml"))
.expect("text pattern Gleam config should be readable");
let text_pattern_config = text_pattern_config
.parse::<toml_edit::DocumentMut>()
.expect("text pattern Gleam config should be valid TOML");
let text_pattern_version = text_pattern_config["version"]
.as_str()
.expect("text pattern Gleam version should be a string");
let feature_flags = self
.repository
.join("examples/provider/feature_flags/project/packages/example_feature_flags");
fs::write(
self.root.join("Cargo.toml"),
format!(
r#"[package]
name = "hosted-embedding-application"
version = "0.0.0"
edition = "2024"
[dependencies]
runtime = {{ package = "geam", path = {:?}, default-features = false, features = ["embedding"] }}
flags = {{ package = "geam-example-feature-flags", path = {flags_provider:?} }}
patterns = {{ package = "geam-example-text-pattern", path = {pattern_provider:?} }}
[patch.crates-io]
geam = {{ path = {:?} }}
[workspace]
resolver = "3"
"#,
self.repository, self.repository,
),
)
.expect("hosted Rust manifest should be written");
fs::write(
self.root.join("src/main.rs"),
r#"mod geam_bindings;
use runtime::embedding::HostedModuleBuilder;
use runtime::{HostProviderConfiguration, HostProviderConfigurationValue};
use std::collections::BTreeMap;
use std::error::Error;
fn feature_flags_configuration() -> HostProviderConfiguration {
HostProviderConfiguration::new(BTreeMap::from([
(
"environment".into(),
HostProviderConfigurationValue::from("staging"),
),
(
"enabled".into(),
vec![HostProviderConfigurationValue::from("new_checkout")].into(),
),
]))
}
fn main() -> Result<(), Box<dyn Error>> {
let program = geam_bindings::project().compile()?;
let builder = HostedModuleBuilder::new(program)?;
let (bindings, functions) = geam_bindings::bind(builder)?;
let module = bindings.seal()?;
let initialization_error = match (geam_bindings::RunStateInputs {
example_feature_flags: HostProviderConfiguration::empty(),
example_text_pattern: HostProviderConfiguration::empty(),
})
.initialize()
{
Ok(_) => panic!("missing feature flag configuration should fail"),
Err(error) => error,
};
assert_eq!(
initialization_error.component_id(),
"geam-example-feature-flags"
);
assert_eq!(
initialization_error.reason(),
"configuration key `environment` must be a String"
);
let mut state = geam_bindings::RunStateInputs {
example_feature_flags: feature_flags_configuration(),
example_text_pattern: HostProviderConfiguration::empty(),
}
.initialize()?;
let mut echo = Vec::new();
let value = module.call(&functions.format_words, (), &mut state, &mut echo)?;
let words = module.call(
&functions.contains_only_words,
("Geam and Gleam".into(),),
&mut state,
&mut echo,
)?;
let numbers = module.call(
&functions.contains_only_words,
("Geam 2026".into(),),
&mut state,
&mut echo,
)?;
let environment = module.call(&functions.environment, (), &mut state, &mut echo)?;
let checkout = module.call(&functions.checkout_enabled, (), &mut state, &mut echo)?;
assert_eq!(value, "<Geam> + <Gleam> 2026");
assert!(words);
assert!(!numbers);
assert_eq!(environment, "staging");
assert!(checkout);
let checked = module.call(
&functions.validate_words, (vec!["Geam".into(), "2026".into()],), &mut state, &mut echo,
)?;
assert_eq!(checked.to_vec(), [Ok("Geam".into()), Err("2026".into())]);
let retained = module.call(&functions.words_again, (&checked,), &mut state, &mut echo)?;
assert_eq!(retained.to_vec(), checked.to_vec());
assert!(echo.is_empty());
println!("{value}");
Ok(())
}
"#,
)
.expect("hosted Rust application should be written");
fs::write(
self.root.join("gleam/gleam.toml"),
format!(
r#"name = "hosted_embedding_application"
version = "1.0.0"
[dependencies]
example_feature_flags = {{ path = {feature_flags:?} }}
example_text_pattern = {{ path = {text_pattern:?} }}
"#,
),
)
.expect("hosted Gleam config should be written");
fs::write(
self.root.join("gleam/manifest.toml"),
format!(
r#"packages = [
{{ name = "example_feature_flags", version = "1.0.0", build_tools = ["gleam"], requirements = [], source = "local", path = {feature_flags:?} }},
{{ name = "example_text_pattern", version = {text_pattern_version:?}, build_tools = ["gleam"], requirements = [], source = "local", path = {text_pattern:?} }},
]
[requirements]
example_feature_flags = {{ path = {feature_flags:?} }}
example_text_pattern = {{ path = {text_pattern:?} }}
"#,
),
)
.expect("hosted Gleam manifest should be written");
fs::write(
self.root
.join("gleam/src/hosted_embedding_application.gleam"),
r#"import example_feature_flags as flags
import example_text_pattern as pattern
pub fn format_words() -> String {
let assert Ok(words) = pattern.compile("[A-Za-z]+")
pattern.replace_all(words, "Geam + Gleam 2026", "<$0>")
}
pub fn contains_only_words(text: String) -> Bool {
let assert Ok(words) = pattern.compile("^[A-Za-z ]+$")
pattern.is_match(words, text)
}
pub fn environment() -> String {
flags.environment()
}
pub fn checkout_enabled() -> Bool {
flags.enabled("new_checkout")
}
pub fn validate_words(values: List(String)) -> List(Result(String, String)) {
case values {
[] -> []
[text, ..rest] -> {
let checked = case contains_only_words(text) {
True -> Ok(text)
False -> Error(text)
}
[checked, ..validate_words(rest)]
}
}
}
pub fn words_again(values: List(Result(String, String))) { values }
"#,
)
.expect("hosted Gleam boundary should be written");
}
fn write_built_in_project(&self) {
fs::create_dir_all(self.root.join("src"))
.expect("Rust source directory should be created");
fs::create_dir_all(self.root.join("gleam/src"))
.expect("Gleam source directory should be created");
for (package, module) in [("gleam_json", "json_native"), ("gleam_time", "time_native")]
{
let package_root = self.root.join("gleam/packages").join(package);
fs::create_dir_all(package_root.join("src"))
.expect("built-in source package should be created");
fs::write(
package_root.join("gleam.toml"),
format!("name = {package:?}\nversion = \"1.0.0\"\n"),
)
.expect("built-in package config should be written");
fs::write(
package_root.join("src").join(format!("{module}.gleam")),
"@external(erlang, \"native\", \"touch\")\npub fn touch() -> Nil\n",
)
.expect("built-in package source should be written");
}
fs::write(
self.root.join("Cargo.toml"),
format!(
r#"[package]
name = "built-in-embedding-application"
version = "0.0.0"
edition = "2024"
[dependencies]
runtime = {{ package = "geam", path = {:?}, default-features = false, features = ["embedding", "gleam-json", "gleam-time"] }}
[workspace]
resolver = "3"
"#,
self.repository,
),
)
.expect("built-in Rust manifest should be written");
fs::write(
self.root.join("src/main.rs"),
r#"mod geam_bindings;
use runtime::gleam_stdlib::{GleamStdlibRunState, IoOutput};
use runtime::gleam_time::TimeSource;
use runtime::HostFailure;
use std::error::Error;
use std::time::{SystemTime, UNIX_EPOCH};
struct FixedTime;
impl TimeSource for FixedTime {
fn system_time(&mut self) -> Result<SystemTime, HostFailure> {
Ok(UNIX_EPOCH)
}
fn local_offset_seconds(&mut self) -> Result<i32, HostFailure> {
Ok(0)
}
}
fn consume_functions(functions: geam_bindings::Functions) {
let _ = functions.ready;
}
fn main() -> Result<(), Box<dyn Error>> {
assert_eq!(geam_bindings::ROOT_MODULE, "built_in_embedding_application");
let _consume = consume_functions;
let _bind = geam_bindings::bind::<Vec<IoOutput>, FixedTime>;
let _program = geam_bindings::project::<Vec<IoOutput>, FixedTime>().compile()?;
let mut state = geam_bindings::RunStateInputs {
stdlib: GleamStdlibRunState::from_seed([7; 32]),
time: FixedTime,
}
.initialize();
assert!(state.stdlib().io_outputs().is_empty());
assert!(state.stdlib_mut().take_io_outputs().is_empty());
Ok(())
}
"#,
)
.expect("built-in Rust application should be written");
fs::write(
self.root.join("gleam/gleam.toml"),
r#"name = "built_in_embedding_application"
version = "1.0.0"
[dependencies]
gleam_json = { path = "packages/gleam_json" }
gleam_time = { path = "packages/gleam_time" }
"#,
)
.expect("built-in Gleam config should be written");
fs::write(
self.root.join("gleam/manifest.toml"),
r#"packages = [
{ name = "gleam_json", version = "1.0.0", build_tools = ["gleam"], requirements = [], source = "local", path = "packages/gleam_json" },
{ name = "gleam_time", version = "1.0.0", build_tools = ["gleam"], requirements = [], source = "local", path = "packages/gleam_time" },
]
[requirements]
gleam_json = { path = "packages/gleam_json" }
gleam_time = { path = "packages/gleam_time" }
"#,
)
.expect("built-in Gleam manifest should be written");
fs::write(
self.root
.join("gleam/src/built_in_embedding_application.gleam"),
r#"import json_native
import time_native
pub fn ready() -> Bool {
json_native.touch()
time_native.touch()
True
}
"#,
)
.expect("built-in Gleam boundary should be written");
}
fn generate_lockfile(&self) {
assert_success(
self.cargo("generate-lockfile").arg("--offline"),
"fixture lockfile generation",
);
}
fn cargo(&self, command: &str) -> Command {
let mut cargo = Command::new("cargo");
cargo
.arg(command)
.arg("--manifest-path")
.arg(self.root.join("Cargo.toml"))
.env("CARGO_TARGET_DIR", &self.target)
.current_dir(&self.root);
for variable in [
"CARGO_ENCODED_RUSTFLAGS",
"LLVM_PROFILE_FILE",
"RUSTDOCFLAGS",
"RUSTFLAGS",
] {
cargo.env_remove(variable);
}
cargo
}
}
fn assert_success(command: &mut Command, operation: &str) {
success_output(command, operation);
}
fn success_output(command: &mut Command, operation: &str) -> Output {
let output = command.output().expect("fixture command should start");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"{operation} failed\nstdout:\n{stdout}\nstderr:\n{stderr}",
);
output
}
}