Skip to content

The graph, drawn

This is asgimachine's actual walk — not the canonical webmachine v3 diagram it descends from. It adds nodes webmachine predates (dashed, below), omits Accept-Charset, and collapses webmachine's O/P success-status sub-tree into a single finalizer. For the node-by-node accounting, see Coverage vs. webmachine; this page is the map.

Dashed nodes are additive (a boolean callback with a correct default, traced only when it fires). Double-boxed nodes are sub-graphs — expand them in the drill-downs below.

flowchart TD
    START(["request"]) --> B13

    B13{"B13 service_available?"} -->|no| S503(["503 (+ Retry-After)"])
    B13 -->|yes| B13a{"B13a within_rate_limit?"}
    B13a -->|no| S429(["429 (+ Retry-After)"])
    B13a -->|yes| B12{"B12 known_method?"}
    B12 -->|no| S501(["501"])
    B12 -->|yes| B11{"B11 uri_too_long?"}
    B11 -->|yes| S414(["414"])
    B11 -->|no| B10{"B10 method_allowed?"}
    B10 -->|no| S405(["405 + Allow"])
    B10 -->|yes| B9{"B9 malformed_request?"}
    B9 -->|yes| S400(["400"])
    B9 -->|no| B8{"B8 is_authorized?"}
    B8 -->|no| S401(["401"])
    B8 -->|yes| B7{"B7 forbidden?"}
    B7 -->|yes| S403(["403"])
    B7 -->|no| B7a{"B7a is_legally_restricted?"}
    B7a -->|yes| S451(["451"])
    B7a -->|no| B654{"B6/B5/B4 content headers valid?"}
    B654 -->|no| S4xx(["501 / 415 / 413"])
    B654 -->|yes| B3{"B3 OPTIONS?"}
    B3 -->|yes| S200o(["200 + Allow"])
    B3 -->|no| C4{"C3/C4 Accept acceptable?"}

    C4 -->|no| C4a{"C4a ignore_unacceptable?"}
    C4a -->|no| S406(["406"])
    C4a -->|"yes, serve default"| D5
    C4 -->|yes| D5{"D4/D5 language?"}
    D5 -->|no| S406
    D5 -->|yes| F7{"F6/F7 encoding?"}
    F7 -->|no| S406
    F7 -->|yes| G7{"G7 resource_exists?"}

    G7 -->|no| MISSING[["missing-resource branch"]]
    G7 -->|yes| W1{"W1 require_conditional_write?"}
    W1 -->|"write, no precondition"| S428(["428"])
    W1 -->|yes| PRE[["preconditions (G8–L17)"]]
    PRE -->|failed| S412(["412 / 304"])
    PRE -->|ok| DISPATCH{"method?"}

    DISPATCH -->|GET/HEAD| O18{"O18 multiple_choices?"}
    O18 -->|yes| S300(["300"])
    O18 -->|no| S200(["200"])
    DISPATCH -->|DELETE| M20{"M16/M20 delete_resource"}
    M20 --> S204d(["204 / 202"])
    DISPATCH -->|POST| POST[["POST dispatch"]]
    DISPATCH -->|PUT/PATCH| WRITE[["write dispatch"]]

    POST --> FIN[["finalize"]]
    WRITE --> FIN
    FIN --> O20a{"O20a accepted?"}
    O20a -->|yes| S202(["202 + Location"])
    O20a -->|no| S201(["201 / 200 / 204"])

    classDef additive stroke-dasharray:5 5,stroke:#7e57c2,stroke-width:2px;
    classDef status fill:#eef2ff,stroke:#8892d8,color:#333;
    class B13a,B7a,B11,C4a,W1,O20a additive;
    class S503,S429,S501,S414,S405,S400,S401,S403,S451,S4xx,S200o,S406,S428,S412,S300,S200,S204d,S202,S201 status;

The trunk is the happy path top-to-bottom; every decision branches sideways to the status it halts with. 4xx/5xx halts additionally get a negotiated RFC 9457 problem+json body (see Negotiation & errors); a raised exception is handled separately by on_exception.

Drill-downs

Request gates — B13 → B3

The cheap, ordered rejections before any representation work. The body-validation trio (B9/B6/B5/B4) is traversed only for body-bearing methods.

Node Question Halt Callback / declaration
B13 service available? 503 (+ Retry-After) service_available
B13a within rate limit? (additive) 429 (+ Retry-After) within_rate_limit
B12 known method? 501 KNOWN_METHODS
B11 URI too long? (additive) 414 uri_too_long
B10 method allowed? 405 + Allow allowed_methods / ALLOWED_METHODS
B9 malformed request? 400 malformed_request
B8 authorized? 401 (+ WWW-Authenticate) is_authorized
B7 forbidden? 403 forbidden
B7a legally restricted? (additive) 451 is_legally_restricted
B6 / B5 / B4 content headers / type / length 501 / 415 / 413 valid_content_headers / CONSUMES / MAX_BODY_BYTES
B3 OPTIONS? 200 + Allow
Negotiation — C3/C4, D4/D5, F6/F7

Proactive negotiation. Each axis is negotiated only when the resource offers choices; Accept-Charset (webmachine's E nodes) is omitted by design.

flowchart TD
    C4{"C3/C4 Accept?"} -->|no| C4a{"C4a serve-anyway?"}
    C4a -->|no| X406(["406"])
    C4a -->|yes| D5
    C4 -->|yes| D5{"D4/D5 language?"}
    D5 -->|no| X406
    D5 -->|yes| F7{"F6/F7 encoding?"}
    F7 -->|no| X406
    F7 -->|yes| OUT(["-> G7"])
    classDef additive stroke-dasharray:5 5,stroke:#7e57c2,stroke-width:2px;
    classDef status fill:#eef2ff,stroke:#8892d8,color:#333;
    class C4a additive;
    class X406,OUT status;

Chosen values land on ctx (chosen_media_type / chosen_language / chosen_encoding) and set Content-Type / Content-Language; each offered axis is added to Vary.

Missing-resource branch — G7 = false

Where a resource that doesn't (currently) exist routes: create, redirect, gone, or not-found.

flowchart TD
    G7(["G7 = false"]) --> H7{"If-Match present?"}
    H7 -->|yes| Y412(["412"])
    H7 -->|no| I7{"PUT?"}
    I7 -->|yes| P3{"P3 is_conflict?"}
    P3 -->|yes| Y409(["409"])
    P3 -->|no| Ycreate(["201 (create)"])
    I7 -->|no| K7{"K7 previously_existed?"}
    K7 -->|no| Y404(["404"])
    K7 -->|yes| K5{"moved?"}
    K5 -->|"moved_permanently"| Y301(["301"])
    K5 -->|"permanent_redirect (additive)"| Y308(["308"])
    K5 -->|"moved_temporarily"| Y307(["307"])
    K5 -->|no| Y410(["410"])
    classDef additive stroke-dasharray:5 5,stroke:#7e57c2,stroke-width:2px;
    classDef status fill:#eef2ff,stroke:#8892d8,color:#333;
    class Y412,Y409,Ycreate,Y404,Y301,Y308,Y307,Y410 status;

Labelling note: asgimachine reuses L7 for the terminal 404, where the canonical diagram uses L7 for the "POST?" decision — the one knowing divergence.

Preconditions — G8 → L17

Conditional requests, evaluated in canonical order; each node is recorded only when it fires. W1 (428) sits just before this block for writes.

Header Nodes Result
If-Match G8/G11 412 on mismatch (strong comparison)
If-Unmodified-Since H10/H12 412 if known-modified (ignored when If-Match present)
If-None-Match I12/K13 304 (GET/HEAD) / 412 (writes)
If-Modified-Since L13/L17 304 (GET/HEAD, if If-None-Match absent)
Write dispatch — POST / PUT / finalize

The method handlers converge on _finish, where see_other (303, POST only) and accepted (202) can override the completed-work status.

flowchart TD
    subgraph sPOST["POST"]
        N11{"N11 post_is_create?"} -->|yes| CP["create_path + apply"]
        N11 -->|no| PP["process_post"]
    end
    subgraph sPUT["PUT / PATCH"]
        O14{"O14 is_conflict?"} -->|yes| Z409(["409"])
        O14 -->|no| AP["apply"]
    end
    CP --> SO{"N11a see_other?"}
    PP --> SO
    SO -->|yes| Z303(["303"])
    SO -->|no| FIN{"O20a accepted?"}
    AP --> FIN
    FIN -->|yes| Z202(["202 + Location"])
    FIN -->|"no, entity"| Z200(["200 / 201"])
    FIN -->|"no, none"| Z204(["204"])
    classDef additive stroke-dasharray:5 5,stroke:#7e57c2,stroke-width:2px;
    classDef status fill:#eef2ff,stroke:#8892d8,color:#333;
    class SO,FIN additive;
    class Z409,Z303,Z202,Z200,Z204 status;

DELETE has its own terminal (M16/M20 → 204/202 via delete_completed) and doesn't reach _finish.