← Back to Documentation

State Transitions

ARO handles state machines elegantly: define states as OpenAPI enums, transition with the <Accept> action, and let the runtime validate everything.

States Are Just Enums

No special state machine syntax. No transition tables. Define your states in OpenAPI, use <Accept> to transition, and get automatic validation with clear error messages.

State Machine Example

Consider an order lifecycle. Orders flow through these states:

draft placed paid shipped delivered

Defining States in OpenAPI

States are defined as string enums in your openapi.yaml:

# openapi.yaml
openapi: 3.0.3
info:
  title: Order Management
  version: 1.0.0

components:
  schemas:
    OrderStatus:
      type: string
      enum:
        - draft
        - placed
        - paid
        - shipped
        - delivered
        - cancelled

    Order:
      type: object
      properties:
        id:
          type: string
        status:
          $ref: '#/components/schemas/OrderStatus'
        customerId:
          type: string
      required:
        - id
        - status

The Accept Action

Use <Accept> to validate and apply state transitions. The syntax clearly shows the expected transition:

(* Transition from draft to placed *)
<Accept> the <transition: draft_to_placed> on <order: status>.

(* Transition from placed to paid *)
<Accept> the <transition: placed_to_paid> on <order: status>.

(* Transition from paid to shipped *)
<Accept> the <transition: paid_to_shipped> on <order: status>.

The syntax uses _to_ as the separator because to is a reserved preposition in ARO.

Complete Example

Here's how state transitions work in practice with order management:

(placeOrder: Order Management) {
    <Extract> the <order-id> from the <pathParameters: id>.
    <Retrieve> the <order> from the <order-repository>.

    (* Accept state transition from draft to placed *)
    <Accept> the <transition: draft_to_placed> on <order: status>.

    <Store> the <order> into the <order-repository>.
    <Emit> to <Send Order Confirmation> with <order>.
    <Return> an <OK: status> with <order>.
}

(payOrder: Order Management) {
    <Extract> the <order-id> from the <pathParameters: id>.
    <Retrieve> the <order> from the <order-repository>.

    (* Must be placed to accept payment *)
    <Accept> the <transition: placed_to_paid> on <order: status>.

    <Store> the <order> into the <order-repository>.
    <Return> an <OK: status> with <order>.
}

(shipOrder: Order Management) {
    <Extract> the <order-id> from the <pathParameters: id>.
    <Retrieve> the <order> from the <order-repository>.

    (* Must be paid to ship *)
    <Accept> the <transition: paid_to_shipped> on <order: status>.

    <Store> the <order> into the <order-repository>.
    <Emit> to <Send Shipping Notification> with <order>.
    <Return> an <OK: status> with <order>.
}

Error Handling

If the current state doesn't match the expected state, the runtime throws a clear error:

Cannot accept state draft->placed on order: status. Current state is "paid".

This follows ARO's "Code Is The Error Message" philosophy. The error tells you exactly what was expected and what was found.

Why This Approach?

Simplicity

No new keywords. No special constructs. Just OpenAPI enums + <Accept>. Use standard control flow (if, match) for complex logic.

Clarity

The transition is explicit in the code. Reading <Accept> the <transition: draft_to_placed> on <order: status> tells you exactly what's happening.

Safety

The runtime validates:

Learn More

See the full state objects specification in ARO-0013: State Objects.