Writing a service
Declaring an api gets you a validated system and a deploy that refuses:
deploy: component `api` declares a `cargo` toolchain, so the deploy reads a
crate manifest at systems/shop/src/components/api/main/ and there is none.
This page is that crate.
Before you can build
While the core is unpublished, a system's crates depend on it by path, so building requires a checkout of the platform repository beside your own:
[workspace.dependencies]
bluetext = { path = "../bluetext/crates/bluetext" }
bluetext-macros = { path = "../bluetext/crates/bluetext-macros" }
bluetext is not on crates.io. Declaring and validating a system needs nothing
but b; compiling one needs that checkout. Everything below is what the code
looks like once you have it.
The workspace
A system is its own cargo workspace, at the system's root, and it is the one place the core is pinned:
# systems/shop/Cargo.toml
[workspace]
members = ["src/models", "src/components/api/main"]
resolver = "3"
[workspace.dependencies]
bluetext = { path = "../bluetext/crates/bluetext" }
bluetext-macros = { path = "../bluetext/crates/bluetext-macros" }
Members inherit with { workspace = true } and none spells a path of its own,
so a moved checkout is one edit.
The component's crate
# systems/shop/src/components/api/main/Cargo.toml
[package]
name = "api"
version = "0.1.0"
edition = "2024"
[dependencies]
bluetext = { workspace = true }
bluetext-macros = { workspace = true }
shop_models = { path = "../../../models" }
wire = { package = "api_wire", path = "../wire" }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net"] }
[features]
default = ["bluetext-unconfigured"]
bluetext-unconfigured = []
bluetext-development = []
Two entries are not obvious.
wire is generated. b generate writes that crate from the shapes your
endpoints name, so it exists before you build and you never edit it.
One feature per environment, plus bluetext-unconfigured as the default. A
deploy builds with the feature for the environment it is landing, and that
feature is what compiles this environment's resolved answers into the artifact.
The default is a sentinel: a binary built without any of them refuses at startup
rather than running with no configuration. Add a feature here for every
environment your system declares.
The handlers
Handlers live in a module marked as the interface's endpoint set. The platform holds the module to the declaration: a handler whose name is not a declared endpoint, or whose types disagree with the shapes, does not compile.
// systems/shop/src/components/api/main/src/main.rs
use ::bluetext::Deadline;
use shop_models::{Context, OrdersCore};
#[bluetext_macros::endpoint_set(interface = "orders")]
pub mod orders {
use super::{Context, Deadline, OrdersCore};
/// The posture the endpoint file's `caller = { admit = "admit_anyone" }` names.
pub async fn admit_anyone(_part: &OrdersCore) -> Context {
Context
}
/// `GET /orders`
#[bluetext_macros::endpoint]
pub async fn list_orders(
part: &OrdersCore,
_ctx: Context,
deadline: Option<Deadline>,
) -> Result<wire::OrderListWire, wire::OrderRefusalWire> {
// …
}
}
Read the signature, because it is the whole contract:
partis the business logic, handed in. A handler holds no state of its own and opens no connection.ctxis what the admission function returned, so whatever a handler knows about its caller was decided in one place.deadlineis thetimeoutthe endpoint file declared, arriving as a value the part can honour.Result<response, refusal>is the endpoint's two declared shapes.Okis the response;Erris the refusal, which is a normal answer rather than an error.
When something is neither
A store that will not answer is not an empty list, and it is not the endpoint's
declared refusal either. There is no third channel in the signature, so a handler
panics, and the platform renders that as a 500. A calling driver reads that as
the peer is there and could not answer and retries, while the message stays in
your log. Reaching for Ok with an empty body is the mistake that shape exists
to prevent: it tells the caller there is nothing, which is a different and
false statement.