Skip to content
Arxo Arxo

Examples

This page shows concrete integration patterns: loading the engine, running analysis, enforcing policies in CI, and handling multiple projects.

When you use arxo-loader, the engine is loaded as a dynamic library and analysis returns raw JSON. You can parse the JSON to read metrics and violations.

use arxo_loader::EngineLibrary;
use arxo_types::config::{default_config, schema::Config};
use std::path::Path;
fn main() -> anyhow::Result<()> {
let engine = EngineLibrary::load()?;
println!("Engine version: {}", engine.version());
let config = default_config();
// Optional: customize config (e.g. metric_preset, data.language, report.format)
let path = Path::new("./my-project");
let license = std::env::var("ARCH0_LICENSE_KEY").ok().as_deref();
let result = engine.analyze(path, &config, license)?;
let data: serde_json::Value = serde_json::from_str(&result.json)?;
// Example: read metric results or violations from data
if let Some(violations) = data.get("violations").and_then(|v| v.as_array()) {
println!("Policy violations: {}", violations.len());
}
if let Some(metrics) = data.get("results").and_then(|r| r.as_array()) {
println!("Metrics computed: {}", metrics.len());
}
Ok(())
}

Rust: Using the Orchestrator (Direct Engine)

Section titled “Rust: Using the Orchestrator (Direct Engine)”

If your build links against arxo-engine directly (e.g. in-tree or private build), you use the orchestrator and get structured results.

use arxo_engine::{Orchestrator, core::run_options::RunOptions};
use arxo_types::config::{load_config, default_config};
use std::path::PathBuf;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let orchestrator = Orchestrator::new();
let project_path = PathBuf::from("./my-project");
let config = load_config("arxo.yml").unwrap_or_else(|_| default_config());
let options = RunOptions::default();
let result = orchestrator.run(project_path, config, options).await?;
println!("Metrics: {}", result.results.len());
println!("Violations: {}", result.violations.len());
println!("Import graph nodes: {}", result.import_graph.node_count());
if !result.violations.is_empty() {
for v in &result.violations {
eprintln!(" {}: {}", v.metric, v.message);
}
}
result.report_with_options(options.quiet)?;
Ok(())
}

Run analysis and exit with a non-zero code when any policy invariant fails. Works with either the loader (parse JSON) or the orchestrator (use result.violations).

With orchestrator (Rust):

use arxo_engine::Orchestrator;
use arxo_types::config::{load_config, default_config};
use arxo_engine::core::run_options::RunOptions;
use std::path::PathBuf;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = load_config("arxo.yml").unwrap_or_else(|_| default_config());
// Ensure policy is set in config (policy.invariants)
let result = Orchestrator::new()
.run(
PathBuf::from("."),
config,
RunOptions { fail_fast: true, ..Default::default() },
)
.await?;
if result.violations.is_empty() {
println!("All policies passed.");
return Ok(());
}
eprintln!("{} policy violation(s):", result.violations.len());
for v in &result.violations {
eprintln!(" - {}: {}", v.metric, v.message);
}
std::process::exit(1);
}

With loader (Rust, JSON):

Parse result.json and check for a violations array; if non-empty, exit with code 1.

Define invariants in config so the engine evaluates them after metrics run. Example arxo.yml:

data:
language: typescript
import_graph:
group_by: folder
group_depth: 2
exclude: ["node_modules", "dist"]
metric_preset: quick
policy:
invariants:
- metric: scc.cycle_count
op: "<="
value: 0
- metric: propagation_cost.system.ratio
op: "<="
value: 0.15
report:
format: json
file: report.json

Then in code, check result.violations (or the violations field in the JSON result) and fail the build or gate the merge accordingly.

Reuse a single engine instance to analyze several directories. With the loader:

use arxo_loader::EngineLibrary;
use arxo_types::config::default_config;
use std::path::Path;
fn main() -> anyhow::Result<()> {
let engine = EngineLibrary::load()?;
let config = default_config();
let license = std::env::var("ARCH0_LICENSE_KEY").ok().as_deref();
let projects = ["./service-a", "./service-b", "./libs/shared"];
for project in &projects {
let path = Path::new(project);
if !path.exists() {
continue;
}
match engine.analyze(path, &config, license) {
Ok(result) => println!("{}: OK ({} bytes)", project, result.json.len()),
Err(e) => eprintln!("{}: {}", project, e),
}
}
Ok(())
}

With the orchestrator, call orchestrator.run(project_path, config.clone(), options).await in a loop for each project path.

Orchestrator: Set report.file in config and call result.report_with_options(quiet); the engine writes the report (e.g. JSON or HTML) to that path.

Loader: The result is only JSON in memory. Write it yourself:

let result = engine.analyze(path, &config, license)?;
std::fs::write("analysis-output.json", &result.json)?;