cargo-tupa 0.9.0

Cargo subcommand for Tupã language tooling
cargo-tupa-0.9.0 is not a library.

cargo-tupa

Cargo subcommand for Tupã pipelines — typecheck, format, lint, test, and execute your policies.

Installation

cargo install cargo-tupa

After installation, cargo tupa will be available as a Cargo subcommand.

Quick Start

1. Create a new project from template

cargo generate --git https://github.com/marciopaiva/tupalang --alias tupa-template
# or locally:
cargo generate --path crates/tupa-template

2. Develop your pipeline

Edit src/lib.rs:

use tupa_core::pipeline;
use tupa_engine::Executor;

pipeline! {
    name: MyPolicy,
    input: Input,
    steps: [
        step("enrich") { enrich(input) } produces ["enriched"],
        step("score")  { score(input) }  requires ["enriched"]
    ],
    constraints: [
        metric("score").ge(0.0)
    ]
}

3. Build & Check

cargo tupa check        # typecheck pipeline macro
cargo tupa lint         # lint for issues
cargo tupa test         # run example tests (includes pipeline assertions)

4. Run

# With a binary target (default)
cargo tupa run

# With a specific example
cargo tupa run --example minimal

# With a specific binary target
cargo tupa run --bin my-pipeline

# With JSON input from environment variable
TUPA_INPUT='{"amount":1000.0,"risk_score":0.5}' cargo tupa run

# With JSON input from file
cargo tupa run --input data.json

# With JSON from stdin
echo '{"x":42}' | cargo tupa run --example minimal

# Parallel execution ( Tokio runtime required in your binary )
TUPA_PARALLEL=1 cargo tupa run --example fraud_complete

Your src/main.rs (or example binary) should read the TUPA_INPUT environment variable:

use std::env;
let input: Input = if let Ok(json) = env::var("TUPA_INPUT") {
    serde_json::from_str(&json).unwrap_or_else(|_| Input::default())
} else {
    Input::default()
};

Or read from stdin if TUPA_INPUT not set:

let mut buffer = String::new();
std::io::stdin().read_to_string(&mut buffer).ok();
let input: Input = if buffer.is_empty() {
    Input::default()
} else {
    serde_json::from_str(&buffer).unwrap()
};

Subcommands

Command Description
cargo tupa check Validate the pipeline! macro (runs cargo check)
cargo tupa run Execute a pipeline binary or example with JSON input
cargo tupa test Run tests (including example pipelines)
cargo tupa lint Lint .tp legacy files and surface Rust warnings
cargo tupa fmt Format legacy .tp source files
cargo tupa plugin new [FILENAME] Generate a plugin scaffold for dynamic step functions

Run Examples (Workspace)

# From workspace root, run an example from tupa-engine
cargo tupa run --example minimal -m crates/tupa-engine/Cargo.toml

# With input and parallel mode
cargo tupa run --example credit_decision -m crates/tupa-engine/Cargo.toml --parallel

# Test all examples
cargo tupa test -m crates/tupa-engine/Cargo.toml

# Check the engine examples (Rust DSL)
cargo tupa check -m crates/tupa-engine/Cargo.toml

How It Works

  • check: Delegates to cargo check --message-format=json, filters Tupã macro errors.
  • run: Spawns cargo run --example or --bin with TUPA_INPUT set; your binary calls Executor::run or Executor::run_parallel. Reads stdin if --input not provided.
  • test: Runs cargo test --examples to validate example pipelines.
  • lint: Scans for .tp files and runs tupa-lint; also shows cargo check warnings.
  • fmt: Calls tupa-fmt on all .tp files found in the project.

Project Template

The tupa-template crate provides a complete scaffold:

cargo generate --path crates/tupa-template

Includes:

  • src/lib.rs with a sample pipeline
  • src/main.rs integrating with cargo-tupa run and TUPA_INPUT
  • Cargo.toml with dependencies: tupa-core, tupa-engine, serde, tokio
  • README with usage instructions

Notes

  • cargo tupa works on the current Cargo package; use -m/--manifest-path to point elsewhere.
  • Parallel execution requires Tokio runtime (#[tokio::main]) and step-level produces/requires annotations.
  • For .tp legacy files, use cargo tupa fmt and cargo tupa lint (migration to Rust DSL recommended).
  • cargo tupa run --input <file> reads JSON from file; falls back to TUPA_INPUT then stdin.
  • cargo tupa test runs cargo test --examples, ensuring pipeline unit tests pass.

Examples

# Check the engine examples (Rust DSL)
cargo tupa check -m crates/tupa-engine/Cargo.toml

# Run fraud_complete example with custom input and parallel mode
cargo tupa run --example fraud_complete -m crates/tupa-engine/Cargo.toml --parallel

# Lint all .tp files in workspace
cargo tupa lint -m crates/tupa-engine/Cargo.toml

Plugin Development

Create a new dynamic plugin scaffold:

cargo tupa plugin new my_plugin.rs

This generates a template my_plugin.rs that exports two C symbols (_tupa_plugin_name, _tupa_plugin_register) and a step function my_step. Build it as a cdylib:

cargo build --crate-type=cdylib --release
# produces target/release/libmy_plugin.so (or .dll/.dylib)

Load the plugin in your pipeline using tupa_plugin::PluginManager:

use tupa_core::pipeline;
use tupa_engine::Executor;
use tupa_plugin::PluginManager;

let mut pm = PluginManager::new();
pm.load_plugin("./target/release/libmy_plugin.so")?;

// In a step:
fn use_plugin(pm: &PluginManager, input: &MyInput) -> Result<serde_json::Value, String> {
    pm.call("my_step", serde_json::to_value(input)?).map_err(|e| e.to_string())
}

pipeline! {
    name: MyPipeline,
    input: MyInput,
    steps: [
        step("plugin") { use_plugin(&pm, input)? }
    ],
    constraints: []
}

For more details, see the tupa-plugin crate documentation.


0.9.0 — API may change before 1.0.