Write an operation without a server

Goal: implement “prepare a task” and call it directly. No HTTP, database, or async runtime is needed for this operation.

An operation is simply work your application performs. Here, it checks a title and trims whitespace. A real create operation would also save the task; this one deliberately stops before persistence.

This is also the seed of a module contract. Moving these types into their own crate later lets a web adapter, a command-line tool, and tests share the same rule without sharing a transport.

1. Create a small Rust program

From the repository root, use a new directory (choose another name if it exists):

sh
mkdir -p examples/my-operation/src

Create examples/my-operation/Cargo.toml:

examples/my-operation/Cargo.tomltoml
[package]
name = "my-operation"
version = "0.1.0"
edition = "2024"
publish = false

[workspace]

[dependencies]
iet-core = { path = "../../crates/core/iet-core" }

iet-core supplies shared application types. It does not install a server, database, authentication provider, or global container.

2. Write the rule and the operation

Create examples/my-operation/src/main.rs:

examples/my-operation/src/main.rsrust
use iet_core::{AppError, AppResult, ErrorKind, Validate};

struct PrepareTask {
    title: String,
}

impl Validate for PrepareTask {
    fn validate(&self) -> AppResult<()> {
        if self.title.trim().is_empty() {
            return Err(AppError::new(
                ErrorKind::Validation,
                "task.title_required",
                "A task needs a title",
            ));
        }
        Ok(())
    }
}

fn prepare_task(input: PrepareTask) -> AppResult<String> {
    input.validate()?;
    Ok(input.title.trim().to_owned())
}

fn main() {
    for title in ["  Review the draft  ", "   "] {
        let input = PrepareTask { title: title.into() };
        match prepare_task(input) {
            Ok(title) => println!("Prepared: {title}"),
            Err(error) => println!("Rejected: {}", error.code),
        }
    }
}

3. Run it

sh
cargo run --manifest-path examples/my-operation/Cargo.toml
Expected output
Prepared: Review the draft
Rejected: task.title_required
Your codeFramework contribution
PrepareTask describes the input.No prescribed task model.
Validate implementation defines the rule.A shared interface for checking inputs.
prepare_task decides what work happens.AppResult / AppError describe success or failure without transport types.
main calls it and prints the outcome.No web runtime required.

What changes when I add another caller?

The operation can stay the same:

  • A browser handler reads a form, calls it, and renders HTML.
  • An API handler reads JSON, calls it, and returns an HTTP response.
  • Your background worker builds input from its work item and records the result.
  • A test calls it and checks the returned value.

You must write those callers; the framework does not generate adapters from this function. Authentication and resource authorization also require explicit work when the operation needs them. This example is intentionally public and stateless.

The important boundary

There is no request, response, cookie, or status code in prepare_task. Its job is application behavior, not presentation.

Next: see how contracts, implementations, and adapters become a reusable module, then choose a browser application or the quickstart HTTP endpoint. For persistent operations, read persistence after you understand sessions and storage.

IET Framework documentation — built with the IET Framework.