Skip to content

Pipeline mini-project (tutorial)

This tutorial is a lightweight, CI-friendly walkthrough for “step-based” automation in Incan.

Goal

Write a small program that:

  • defines a step-like function with typed inputs/outputs
  • returns typed failures (Result)
  • can be tested and run deterministically

Step 1: Create a file

Create this small project layout:

my_project/
├── incan.toml
├── src/
│   └── pipeline_step.incn
└── tests/
    └── test_pipeline_step.incn

Run the commands below from my_project/ (this matters for module resolution).

Create incan.toml:

[project]
name = "pipeline_step"
version = "0.1.0"
requires-incan = ">=0.5.0-0,<0.6.0"

[project.scripts]
main = "src/pipeline_step.incn"

Create src/pipeline_step.incn:

"""
A tiny step-style function that validates input and returns a typed error.
"""

pub def normalize_name(name: str) -> Result[str, str]:  # (1)
    if len(name.strip()) == 0:
        return Err("name must not be empty")  # (2)
    return Ok(name.strip().lower())  # (3)


def main() -> None:
    result = normalize_name("  Alice  ")
    match result:
        case Ok(value): println(f"ok: {value}")
        case Err(e): println(f"err: {e}")
  1. pub exposes the step to the test module; the Result[str, str] signature keeps both outcomes explicit.
  2. Invalid input returns a typed failure instead of logging and continuing with ambient state.
  3. The success branch contains the normalized value, so downstream stages cannot confuse it with the original input.

Step 2: Run it

incan run

Contributor source-build fallback

If you are working from a compiler checkout instead of a toolchain install, you can run the repository-built binary directly:

  • from the repository root:
./target/release/incan ...
  • or via an absolute path (from anywhere):
/absolute/path/to/incan run path/to/file.incn

Step 3: Test it

Create tests/test_pipeline_step.incn:

from pipeline_step import normalize_name
from std.testing import assert_eq

def test_normalize_name_ok() -> None:
    assert_eq(normalize_name("  Alice  "), Ok("alice"))

def test_normalize_name_err() -> None:
    assert_eq(normalize_name("   "), Err("name must not be empty"))

Run:

incan test

You built a deterministic workflow step whose input, successful output, failure, and tests are explicit.

Next