Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
911f186629 | ||
|
|
a63f4ed2c9 | ||
|
|
e854947c86 | ||
|
|
660ccf3e76 | ||
|
|
8832160db1 | ||
|
|
dbaaa4ec8c | ||
|
|
126fdec243 | ||
|
|
a9429dfb1d |
+100
@@ -6,6 +6,106 @@ this crate adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.5.0] — 2026-09-18
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **`alkcall` dependency bumped to 0.8.0** (review 008's remediation
|
||||||
|
wave: the graduation upstream asks + the in-tree channel relay). No
|
||||||
|
code change in this crate — alkcall 0.8.0 is additive on the
|
||||||
|
call-plane JSON and the registry seams, and none of its new surfaces
|
||||||
|
are constructed here: this crate builds no `Establishment` reply
|
||||||
|
fields (the WS ferry passes `Option<OpenEstablisher>` through
|
||||||
|
unchanged; reply-field projection is an establisher-supplier
|
||||||
|
feature), imports no marked specs through `from_call` (the
|
||||||
|
`from_wss` operation filter excludes the protocol-session ops; any
|
||||||
|
domain op the remote serves imports as a forwarding bundle exactly
|
||||||
|
as before), and consumes neither `ChannelRelay` nor
|
||||||
|
`HubLegImports`/`HubLegTemplate` (hub-leg assembly remains the
|
||||||
|
consumer's concern at the assembly layer). One ride-through visible
|
||||||
|
on this crate's discovery surface: for a deployment's
|
||||||
|
`with_ws_openable_alpns` open-op specs whose names are not standard
|
||||||
|
`channels/<seg>/(sub|pub)` shapes, the served `services/list` /
|
||||||
|
`services/schema` output now carries an explicit
|
||||||
|
`channel_open_alpn` string alongside the boolean marker (alkcall
|
||||||
|
0.8.0's U-1, ADR-047 amendment 3) — so hubs relaying through a
|
||||||
|
`from_call` import of those ops reconstruct the marker instead of
|
||||||
|
seeing plain forwarding stubs. Standard-shape open-op names stay
|
||||||
|
byte-stable. Minor bump per the dep-wave convention (0.3.0 rode
|
||||||
|
alkcall 0.6.0; 0.4.0 rode 0.7.0).
|
||||||
|
|
||||||
|
## [0.4.1] — 2026-09-10
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **`rust-version` floor raised 1.85 → 1.88** (patch bump, no API change).
|
||||||
|
The 1.85 claim was already false at the dependency level: the resolved
|
||||||
|
lockfile pulls `icu_*` 2.x (MSRV 1.86 via `jsonschema` → `idna`) and —
|
||||||
|
since alkcall 0.7.1 — alkcall itself declares 1.88, so 1.85 toolchains
|
||||||
|
cannot build the tree regardless of what Cargo.toml declared. Raising
|
||||||
|
the floor to 1.88 aligns with the ecosystem MSRV decision (noq QUIC
|
||||||
|
path; recorded for alktls in OQ-TLS-08) and breaks no downstream that
|
||||||
|
could build the crate before. Verified: 1.88 toolchain `cargo test
|
||||||
|
--locked` + clippy `--all-targets -D warnings` clean; one 1.88 clippy
|
||||||
|
lint fixed (`uninlined_format_args` in a `forward.rs` test).
|
||||||
|
|
||||||
|
- **`alkcall` dependency consumed at 0.7.1** (lockfile bump; the
|
||||||
|
`version = "0.7"` requirement already covered it). No code change
|
||||||
|
forced — alkcall 0.7.1's changes are its own MSRV-floor raise plus the
|
||||||
|
cf-006/cf-007 feature work already consumed in the 0.7.0 wave.
|
||||||
|
|
||||||
|
## [0.4.0] — 2026-09-07
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **`alkcall` dependency bumped to 0.7.0** (CF-005/006/007 — the
|
||||||
|
connect-side caller-identity seam + the downstream-findings sweep).
|
||||||
|
No code change in this crate: it constructs no `ServingConfig`
|
||||||
|
(the one struct-literal break — `from_wss` keeps the pure-consumer
|
||||||
|
`ChannelClient::from_connection`; opt-in serving remains the
|
||||||
|
review-006 Part C deferred decision) and the per-call opener-identity
|
||||||
|
overlay (CF-006) is behavior-only upstream — hub-forwarded opens on
|
||||||
|
the WS ferry now resolve the end client's identity instead of the
|
||||||
|
hub's, the intended effect. alkcall 0.7.0's ADR-016 amendment (the
|
||||||
|
protocol-code list is eight codes) resolves the upstream half of
|
||||||
|
review-006 Part C's doc-drift note.
|
||||||
|
|
||||||
|
### Changed (wire-surface behavior)
|
||||||
|
|
||||||
|
- **Gateway error table maps `CONNECTION_CLOSED` → 503 Service
|
||||||
|
Unavailable** (the alkcall 0.7.0 CF-007 amendment makes it the eighth
|
||||||
|
protocol code; ADR-023 amended). Previously it fell to the catch-all
|
||||||
|
500, which misreported a retryable, provably-undelivered call as a
|
||||||
|
permanent server failure. Reachable through the gateway when an
|
||||||
|
imported op's forwarding handler calls into a dropped transport
|
||||||
|
(`from_wss`'s pending-call fail-all is retryable
|
||||||
|
`CONNECTION_CLOSED`). The code is `retryable: true`, so the existing
|
||||||
|
`retry_after` details path applies on 503. Tests pin the mapping and
|
||||||
|
the `Retry-After` passthrough. Behavioral break for HTTP callers
|
||||||
|
matching on the 500 previously emitted for this code — hence a minor
|
||||||
|
bump, not a patch.
|
||||||
|
|
||||||
|
## [0.3.0] — 2026-09-07
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **`alkcall` dependency bumped to 0.6.0** (review 007's
|
||||||
|
establishment follow-ups sweep). One consumer-visible change: the
|
||||||
|
`OpenHandler` signature gained a plan parameter —
|
||||||
|
`Fn(Value, Option<ChannelPlan>, Connection, AuthContext) ->
|
||||||
|
JoinHandle<()>` — so `OpenableAlpn::new` callers' handler closures
|
||||||
|
gain a `_plan` argument (mechanical). With an establisher attached
|
||||||
|
(ADR-049), its `Establishment.plan` (a typed-opaque
|
||||||
|
`Arc<dyn Any + Send + Sync>`) is threaded process-locally to the
|
||||||
|
handler's plan parameter: ALPN crates dial/allocate in the
|
||||||
|
establisher and deliver the live handle without a side-channel
|
||||||
|
handoff. This crate's WS ferry passes the `Option<OpenEstablisher>`
|
||||||
|
through unchanged and constructs no `Establishment` itself, so the
|
||||||
|
ride is otherwise additive — alkcall 0.6.0's `channels::pump_bidi`
|
||||||
|
helper (ADR-050) is available to data-plane handlers this crate
|
||||||
|
ferries. Docs updated (websocket.md, `OpenableAlpn` field docs,
|
||||||
|
adapter.rs).
|
||||||
|
|
||||||
## [0.2.0] — 2026-09-06
|
## [0.2.0] — 2026-09-06
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
Generated
+3
-3
@@ -27,9 +27,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alkcall"
|
name = "alkcall"
|
||||||
version = "0.5.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bbaeb718c370b74f9136d1d59b4608249bd7d1dd2332c0259f98351d709737f"
|
checksum = "8695d47cee5b5a5fe68471f04a505bc257a7e7ed9e64d827de33ff83adf4db12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -48,7 +48,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alkhttp"
|
name = "alkhttp"
|
||||||
version = "0.2.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alkcall",
|
"alkcall",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "alkhttp"
|
name = "alkhttp"
|
||||||
version = "0.2.0"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.85"
|
rust-version = "1.88"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "HTTP interface for the alk stack: serves HTTP/1.1 + HTTP/2 on standard ALPNs (with WebSocket upgrade carrying the channels protocol) and hosts the HTTP-backed call-protocol adapters"
|
description = "HTTP interface for the alk stack: serves HTTP/1.1 + HTTP/2 on standard ALPNs (with WebSocket upgrade carrying the channels protocol) and hosts the HTTP-backed call-protocol adapters"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -42,7 +42,7 @@ h2 = ["server", "dep:hyper", "hyper-util/http2", "hyper/http2"]
|
|||||||
http1 = ["server", "dep:hyper", "hyper-util/http1", "hyper/http1"]
|
http1 = ["server", "dep:hyper", "hyper-util/http1", "hyper/http1"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
alkcall = { version = "0.5", features = ["gateway"] }
|
alkcall = { version = "0.8", features = ["gateway"] }
|
||||||
arc-swap = { version = "1", optional = true }
|
arc-swap = { version = "1", optional = true }
|
||||||
axum = { version = "0.8", optional = true, features = ["ws"] }
|
axum = { version = "0.8", optional = true, features = ["ws"] }
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ A lean single-side build takes `default-features = false` plus the side
|
|||||||
it needs:
|
it needs:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
alkhttp = { version = "0.2", default-features = false, features = ["server"] }
|
alkhttp = { version = "0.5.0", default-features = false, features = ["server"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Security posture
|
## Security posture
|
||||||
|
|||||||
@@ -4,21 +4,25 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Accepted (amended by alknet ADR-049 — protocol-level code list extended to six; alkcall ADR-021)
|
Accepted (amended by alknet ADR-049 — protocol-level code list extended to six; alkcall ADR-021; alkcall 0.7.0 CF-007 — the list is eight: `ALREADY_EXISTS` (alkcall ADR-022, mapped 409 Conflict by review 006 UP-01) and `CONNECTION_CLOSED` (alkcall ADR-016 CF-001, mapped 503 Service Unavailable in this crate's gateway error table))
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
The `OperationSpec` in the call crate (now alkcall) has `input_schema` and
|
The `OperationSpec` in the call crate (now alkcall) has `input_schema` and
|
||||||
`output_schema` but no `error_schemas`. The `call.error` payload (the alkcall
|
`output_schema` but no `error_schemas`. The `call.error` payload (the alkcall
|
||||||
crate's `call-protocol.md`) carries a `code` and `message`, where `code` is
|
crate's `call-protocol.md`) carries a `code` and `message`, where `code` is
|
||||||
one of six infrastructure codes: `NOT_FOUND`, `FORBIDDEN`, `INVALID_INPUT`,
|
one of eight infrastructure codes: `NOT_FOUND`, `FORBIDDEN`, `INVALID_INPUT`,
|
||||||
`INVALID_OPERATION_TYPE`, `INTERNAL`, `TIMEOUT`.
|
`INVALID_OPERATION_TYPE`, `INTERNAL`, `TIMEOUT`, `ALREADY_EXISTS`,
|
||||||
|
`CONNECTION_CLOSED`.
|
||||||
|
|
||||||
These six codes cover **protocol-level failures** — the call protocol
|
These eight codes cover **protocol-level failures** — the call protocol
|
||||||
itself can always fail to find an operation, deny access, reject bad input,
|
itself can always fail to find an operation, deny access, reject bad input,
|
||||||
reject the wrong dispatch method for the operation type, time out, or hit
|
reject the wrong dispatch method for the operation type, time out, hit an
|
||||||
an internal error. They are emitted by the dispatch machinery (the registry,
|
internal error, reject an `op/register` collision (`ALREADY_EXISTS`, alkcall
|
||||||
the adapter), not by operation handlers. `INVALID_OPERATION_TYPE` was added
|
ADR-022), or observe a provably-undelivered call (`CONNECTION_CLOSED`,
|
||||||
|
alkcall ADR-016 CF-001 — the only retryable protocol code). They are emitted
|
||||||
|
by the dispatch machinery (the registry, the adapter), not by operation
|
||||||
|
handlers. `INVALID_OPERATION_TYPE` was added
|
||||||
by alknet ADR-049 (streaming handler for subscriptions — now alkcall ADR-021;
|
by alknet ADR-049 (streaming handler for subscriptions — now alkcall ADR-021;
|
||||||
`invoke()` called on a `Sub`, or `invoke_streaming()` on a `Query`/`Mutation`).
|
`invoke()` called on a `Sub`, or `invoke_streaming()` on a `Query`/`Mutation`).
|
||||||
|
|
||||||
@@ -173,8 +177,8 @@ optional-array convention.
|
|||||||
|
|
||||||
- `code` — the error code. Either a protocol-level code (`NOT_FOUND`,
|
- `code` — the error code. Either a protocol-level code (`NOT_FOUND`,
|
||||||
`FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`,
|
`FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`,
|
||||||
`TIMEOUT`) or an operation-level domain code from `error_schemas` (e.g.,
|
`TIMEOUT`, `ALREADY_EXISTS`, `CONNECTION_CLOSED`) or an operation-level
|
||||||
`FILE_NOT_FOUND`, `RATE_LIMITED`).
|
domain code from `error_schemas` (e.g., `FILE_NOT_FOUND`, `RATE_LIMITED`).
|
||||||
- `message` — human-readable error message. Unstructured — for logging and
|
- `message` — human-readable error message. Unstructured — for logging and
|
||||||
debugging, not for programmatic handling. Consumers should switch on
|
debugging, not for programmatic handling. Consumers should switch on
|
||||||
`code`, not parse `message`.
|
`code`, not parse `message`.
|
||||||
@@ -190,8 +194,8 @@ optional-array convention.
|
|||||||
|
|
||||||
### 3. Protocol-level vs operation-level error codes
|
### 3. Protocol-level vs operation-level error codes
|
||||||
|
|
||||||
The six existing codes are **protocol-level** — emitted by the dispatch
|
The eight protocol-level codes are **protocol-level** — emitted by the
|
||||||
machinery, not by handlers:
|
dispatch machinery, not by handlers:
|
||||||
|
|
||||||
| Code | Emitted by | Meaning |
|
| Code | Emitted by | Meaning |
|
||||||
|------|-----------|---------|
|
|------|-----------|---------|
|
||||||
@@ -201,6 +205,8 @@ machinery, not by handlers:
|
|||||||
| `INVALID_OPERATION_TYPE` | Registry / `OperationEnv` | Wrong dispatch path for the operation's type (`invoke()` on a `Sub`, `invoke_streaming()` on a `Query`/`Mutation`, `invoke_sink()` on a `Query`/`Mutation`/`Sub`, or `OperationEnv::invoke()` on a `Sub` during composition — alkcall ADR-021; `Pub` ops dispatch via `invoke_sink()`, alkcall ADR-046) |
|
| `INVALID_OPERATION_TYPE` | Registry / `OperationEnv` | Wrong dispatch path for the operation's type (`invoke()` on a `Sub`, `invoke_streaming()` on a `Query`/`Mutation`, `invoke_sink()` on a `Query`/`Mutation`/`Sub`, or `OperationEnv::invoke()` on a `Sub` during composition — alkcall ADR-021; `Pub` ops dispatch via `invoke_sink()`, alkcall ADR-046) |
|
||||||
| `INTERNAL` | Registry / Adapter | Handler panic, unhandled error, connection failure |
|
| `INTERNAL` | Registry / Adapter | Handler panic, unhandled error, connection failure |
|
||||||
| `TIMEOUT` | Adapter | Request timed out |
|
| `TIMEOUT` | Adapter | Request timed out |
|
||||||
|
| `ALREADY_EXISTS` | Registry / collision gate | `op/register` collision — a peer-announced op may not replace the serving side's own registrations (alkcall ADR-022 G-03) |
|
||||||
|
| `CONNECTION_CLOSED` | Client write path | The `call.requested` frame could not be written — the call is provably undelivered (alkcall ADR-016 CF-001). `retryable: true` — the only protocol code a caller may auto-retry. |
|
||||||
|
|
||||||
Operation-level domain codes are emitted by **handlers** — the operation's
|
Operation-level domain codes are emitted by **handlers** — the operation's
|
||||||
own logic determines what went wrong. They are declared in `error_schemas`
|
own logic determines what went wrong. They are declared in `error_schemas`
|
||||||
@@ -251,9 +257,9 @@ accordingly.
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Normative rule (review #002 W20)**: `from_openapi` must not produce error
|
**Normative rule (review #002 W20)**: `from_openapi` must not produce error
|
||||||
codes that collide with the six protocol-level codes (`NOT_FOUND`,
|
codes that collide with the protocol-level codes (`NOT_FOUND`,
|
||||||
`FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`,
|
`FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`,
|
||||||
`TIMEOUT`). The adapter prefixes
|
`TIMEOUT`, `ALREADY_EXISTS`, `CONNECTION_CLOSED`). The adapter prefixes
|
||||||
imported error codes with `HTTP_` and the status number (e.g., `HTTP_404`,
|
imported error codes with `HTTP_` and the status number (e.g., `HTTP_404`,
|
||||||
`HTTP_429`) to avoid collision. This is a requirement for the adapter, not
|
`HTTP_429`) to avoid collision. This is a requirement for the adapter, not
|
||||||
a naming convention — the `from_openapi` example above was previously shown
|
a naming convention — the `from_openapi` example above was previously shown
|
||||||
|
|||||||
@@ -553,8 +553,9 @@ for the factoring recommendation (thin shared struct, not a trait).
|
|||||||
`from_openapi` maps OpenAPI non-2xx response status codes to
|
`from_openapi` maps OpenAPI non-2xx response status codes to
|
||||||
`ErrorDefinition`s (alkcall ADR-016 §5). The normative rule (review
|
`ErrorDefinition`s (alkcall ADR-016 §5). The normative rule (review
|
||||||
#002 W20): `from_openapi` must not produce error codes that collide
|
#002 W20): `from_openapi` must not produce error codes that collide
|
||||||
with the six protocol-level codes (`NOT_FOUND`, `FORBIDDEN`,
|
with the protocol-level codes (`NOT_FOUND`, `FORBIDDEN`,
|
||||||
`INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`, `TIMEOUT`). The
|
`INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`, `TIMEOUT`,
|
||||||
|
`ALREADY_EXISTS`, `CONNECTION_CLOSED`). The
|
||||||
adapter prefixes imported error codes with `HTTP_` and the status
|
adapter prefixes imported error codes with `HTTP_` and the status
|
||||||
number:
|
number:
|
||||||
|
|
||||||
@@ -601,7 +602,7 @@ request-level cap failure (422; GW-16 unified it with the
|
|||||||
`INVALID_INPUT → 422` mapping — it was hand-rolled as 400 before
|
`INVALID_INPUT → 422` mapping — it was hand-rolled as 400 before
|
||||||
review-002). `BatchResultEntry.error` references
|
review-002). `BatchResultEntry.error` references
|
||||||
the `BatchError` component (review-002 PRJ-16b): the serialized
|
the `BatchError` component (review-002 PRJ-16b): the serialized
|
||||||
`CallError` as a oneOf over the six protocol-code envelopes plus a
|
`CallError` as a oneOf over the protocol-code envelopes plus a
|
||||||
generic arm carrying the operation-declared codes. See alkcall ADR-016.
|
generic arm carrying the operation-declared codes. See alkcall ADR-016.
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
|
|||||||
@@ -285,7 +285,15 @@ semantically-validating hook dials/prepares the backend before the
|
|||||||
reply, and a bounded failure resolves `channel:open_failed` with
|
reply, and a bounded failure resolves `channel:open_failed` with
|
||||||
`details.reason` (the channel never exists consumer-side). The
|
`details.reason` (the channel never exists consumer-side). The
|
||||||
per-registration timeout override bounds the establisher when the
|
per-registration timeout override bounds the establisher when the
|
||||||
dispatch carries no deadline. The `OpenableAlpns` request-extension
|
dispatch carries no deadline. The establisher's `Establishment.plan`
|
||||||
|
payload (alkcall 0.6.0 / ADR-049 amendment 2 — a typed-opaque
|
||||||
|
`ChannelPlan`) is threaded process-locally to the pump handler's
|
||||||
|
second parameter: the establisher and the handler agree on the
|
||||||
|
concrete type, so ALPN crates dial/allocate in the establisher and
|
||||||
|
deliver the live handle to their handler without a side-channel
|
||||||
|
handoff. Data-plane handlers that pump a channel stream against a
|
||||||
|
peer's split halves can use alkcall's `channels::pump_bidi` helper
|
||||||
|
(alkcall 0.6.0 / ADR-050). The `OpenableAlpns` request-extension
|
||||||
fallback is available for bare-registry/custom
|
fallback is available for bare-registry/custom
|
||||||
upgrade routes. Cap policy is the `ChannelsPolicy` extension (one
|
upgrade routes. Cap policy is the `ChannelsPolicy` extension (one
|
||||||
instance consulted by both the open wrappers and the demux teardown).
|
instance consulted by both the open wrappers and the demux teardown).
|
||||||
|
|||||||
@@ -2125,8 +2125,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
!segments.iter().any(|s| s == "." || s == ".."),
|
!segments.iter().any(|s| s == "." || s == ".."),
|
||||||
"value `{value:?}` must not leave lone dot segments: {:?}",
|
"value `{value:?}` must not leave lone dot segments: {segments:?}"
|
||||||
segments
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
+27
-3
@@ -4,9 +4,11 @@
|
|||||||
//! (`NOT_FOUND`, `FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`,
|
//! (`NOT_FOUND`, `FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`,
|
||||||
//! `TIMEOUT`, `ALREADY_EXISTS` — the seventh code, alkcall ADR-022's
|
//! `TIMEOUT`, `ALREADY_EXISTS` — the seventh code, alkcall ADR-022's
|
||||||
//! `op/register` collision policy, mapped 409 Conflict, review-006
|
//! `op/register` collision policy, mapped 409 Conflict, review-006
|
||||||
//! UP-01) map to fixed statuses; operation-level codes imported from
|
//! UP-01; `CONNECTION_CLOSED` — the eighth code, alkcall ADR-016's
|
||||||
//! external HTTP APIs are prefixed `HTTP_<status>` and map to their
|
//! retryable undelivered-call code, mapped 503 Service Unavailable,
|
||||||
//! declared status.
|
//! alkcall 0.7.0's CF-007 amendment made the list formal) map to fixed
|
||||||
|
//! statuses; operation-level codes imported from external HTTP APIs are
|
||||||
|
//! prefixed `HTTP_<status>` and map to their declared status.
|
||||||
//!
|
//!
|
||||||
//! The identity-aware variant maps the ambiguous protocol codes
|
//! The identity-aware variant maps the ambiguous protocol codes
|
||||||
//! (`FORBIDDEN`, `INVALID_OPERATION_TYPE`) to `401` when no token
|
//! (`FORBIDDEN`, `INVALID_OPERATION_TYPE`) to `401` when no token
|
||||||
@@ -30,6 +32,7 @@ const PROTOCOL_CODE_INVALID_INPUT: &str = "INVALID_INPUT";
|
|||||||
const PROTOCOL_CODE_INVALID_OPERATION_TYPE: &str = "INVALID_OPERATION_TYPE";
|
const PROTOCOL_CODE_INVALID_OPERATION_TYPE: &str = "INVALID_OPERATION_TYPE";
|
||||||
const PROTOCOL_CODE_TIMEOUT: &str = "TIMEOUT";
|
const PROTOCOL_CODE_TIMEOUT: &str = "TIMEOUT";
|
||||||
const PROTOCOL_CODE_ALREADY_EXISTS: &str = "ALREADY_EXISTS";
|
const PROTOCOL_CODE_ALREADY_EXISTS: &str = "ALREADY_EXISTS";
|
||||||
|
const PROTOCOL_CODE_CONNECTION_CLOSED: &str = "CONNECTION_CLOSED";
|
||||||
const PROTOCOL_CODE_INTERNAL: &str = "INTERNAL";
|
const PROTOCOL_CODE_INTERNAL: &str = "INTERNAL";
|
||||||
|
|
||||||
const HTTP_PREFIX: &str = "HTTP_";
|
const HTTP_PREFIX: &str = "HTTP_";
|
||||||
@@ -40,6 +43,7 @@ const STATUS_FORBIDDEN: u16 = 403;
|
|||||||
const STATUS_UNPROCESSABLE: u16 = 422;
|
const STATUS_UNPROCESSABLE: u16 = 422;
|
||||||
const STATUS_TIMEOUT: u16 = 504;
|
const STATUS_TIMEOUT: u16 = 504;
|
||||||
const STATUS_CONFLICT: u16 = 409;
|
const STATUS_CONFLICT: u16 = 409;
|
||||||
|
const STATUS_SERVICE_UNAVAILABLE: u16 = 503;
|
||||||
const STATUS_INTERNAL: u16 = 500;
|
const STATUS_INTERNAL: u16 = 500;
|
||||||
|
|
||||||
const RETRY_AFTER_STATUSES: &[u16] = &[429, 503];
|
const RETRY_AFTER_STATUSES: &[u16] = &[429, 503];
|
||||||
@@ -77,6 +81,7 @@ pub fn call_error_to_http_status_with_identity(
|
|||||||
}
|
}
|
||||||
PROTOCOL_CODE_TIMEOUT => STATUS_TIMEOUT,
|
PROTOCOL_CODE_TIMEOUT => STATUS_TIMEOUT,
|
||||||
PROTOCOL_CODE_ALREADY_EXISTS => STATUS_CONFLICT,
|
PROTOCOL_CODE_ALREADY_EXISTS => STATUS_CONFLICT,
|
||||||
|
PROTOCOL_CODE_CONNECTION_CLOSED => STATUS_SERVICE_UNAVAILABLE,
|
||||||
PROTOCOL_CODE_INTERNAL => STATUS_INTERNAL,
|
PROTOCOL_CODE_INTERNAL => STATUS_INTERNAL,
|
||||||
code if code.starts_with(HTTP_PREFIX) => code[HTTP_PREFIX.len()..]
|
code if code.starts_with(HTTP_PREFIX) => code[HTTP_PREFIX.len()..]
|
||||||
.parse::<u16>()
|
.parse::<u16>()
|
||||||
@@ -193,6 +198,25 @@ mod tests {
|
|||||||
assert_eq!(call_error_to_http_status(&error), 409);
|
assert_eq!(call_error_to_http_status(&error), 409);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn connection_closed_maps_to_503() {
|
||||||
|
let error = CallError::connection_closed("undelivered");
|
||||||
|
assert_eq!(call_error_to_http_status(&error), 503);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn connection_closed_carries_retry_after_when_details_present() {
|
||||||
|
let error = CallError::connection_closed("undelivered")
|
||||||
|
.with_details(serde_json::json!({ "retry_after": "5" }));
|
||||||
|
let resp = call_error_to_http_response(&error);
|
||||||
|
assert_eq!(resp.status(), 503);
|
||||||
|
let retry_after = resp
|
||||||
|
.headers()
|
||||||
|
.get(header::RETRY_AFTER)
|
||||||
|
.map(|v| v.to_str().unwrap().to_string());
|
||||||
|
assert_eq!(retry_after.as_deref(), Some("5"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn internal_maps_to_500() {
|
fn internal_maps_to_500() {
|
||||||
let error = CallError::internal("boom");
|
let error = CallError::internal("boom");
|
||||||
|
|||||||
@@ -273,7 +273,10 @@ impl HttpAdapter {
|
|||||||
/// `channel_open` marker), the ALPN-specific
|
/// `channel_open` marker), the ALPN-specific
|
||||||
/// [`OpenHandler`](alkcall::channels::operations::OpenHandler),
|
/// [`OpenHandler`](alkcall::channels::operations::OpenHandler),
|
||||||
/// and the optional establisher + per-registration timeout
|
/// and the optional establisher + per-registration timeout
|
||||||
/// (ADR-049, threaded through with `None` defaults). Each WS
|
/// (ADR-049, threaded through with `None` defaults). With an
|
||||||
|
/// establisher attached, its `Establishment.plan` (alkcall 0.6.0 /
|
||||||
|
/// ADR-049 amendment 2) is threaded process-locally to the pump
|
||||||
|
/// handler's second parameter. Each WS
|
||||||
/// session's per-session fork registers the set (plus the generic
|
/// session's per-session fork registers the set (plus the generic
|
||||||
/// channel ops, bootstrap discovery, and `op/register`), so a WS
|
/// channel ops, bootstrap discovery, and `op/register`), so a WS
|
||||||
/// client can open data channels exactly as any channels consumer
|
/// client can open data channels exactly as any channels consumer
|
||||||
|
|||||||
@@ -271,13 +271,24 @@ pub struct OpenableAlpn {
|
|||||||
/// per the upstream C-08 stub until channel adoption lands).
|
/// per the upstream C-08 stub until channel adoption lands).
|
||||||
pub spec: OperationSpec,
|
pub spec: OperationSpec,
|
||||||
/// The data-plane protocol handler spawned on the allocated
|
/// The data-plane protocol handler spawned on the allocated
|
||||||
/// channel's `Connection`.
|
/// channel's `Connection` — `Fn(Value, Option<ChannelPlan>,
|
||||||
|
/// Connection, AuthContext) -> JoinHandle<()>`. With an
|
||||||
|
/// establisher attached, the plan (alkcall 0.6.0 /
|
||||||
|
/// ADR-049 amendment 2) is the establisher's `Establishment.plan`:
|
||||||
|
/// the establisher and the handler agree on the concrete type
|
||||||
|
/// (downcast in the ALPN crate); `None` when no establisher is
|
||||||
|
/// registered or it returned `Establishment::default()`
|
||||||
|
/// (`alkcall::channels::operations::Establishment::default`).
|
||||||
pub open_handler: OpenHandler,
|
pub open_handler: OpenHandler,
|
||||||
/// The awaited establishment phase (ADR-049 §1): validate params
|
/// The awaited establishment phase (ADR-049 §1): validate params
|
||||||
/// semantically, consult ownership, prepare/dial the backend —
|
/// semantically, consult ownership, prepare/dial the backend —
|
||||||
/// before the open reply. `None` (the default) = an always-OK
|
/// before the open reply. `None` (the default) = an always-OK
|
||||||
/// establisher (the pre-ADR-049 shape; existing registrations
|
/// establisher (the pre-ADR-049 shape; existing registrations
|
||||||
/// behave unchanged).
|
/// behave unchanged). A successful establisher returns
|
||||||
|
/// `Establishment::new(plan)` (to deliver a channel plan to the
|
||||||
|
/// pump handler) or `Establishment::default()` when it only
|
||||||
|
/// validates — both in
|
||||||
|
/// `alkcall::channels::operations`.
|
||||||
pub establisher: Option<OpenEstablisher>,
|
pub establisher: Option<OpenEstablisher>,
|
||||||
/// The per-registration bound on the establisher await
|
/// The per-registration bound on the establisher await
|
||||||
/// (ADR-049 §2). `None` = [`ESTABLISHMENT_TIMEOUT`] (10s) when the
|
/// (ADR-049 §2). `None` = [`ESTABLISHMENT_TIMEOUT`] (10s) when the
|
||||||
|
|||||||
@@ -1057,7 +1057,7 @@ use alkcall::channels::operations::OpenHandler;
|
|||||||
/// payload) chunks; the handler reads the channel's BiStream and
|
/// payload) chunks; the handler reads the channel's BiStream and
|
||||||
/// writes the echo — the exact data-plane shape ADR-067 promises.
|
/// writes the echo — the exact data-plane shape ADR-067 promises.
|
||||||
fn echo_open_handler() -> OpenHandler {
|
fn echo_open_handler() -> OpenHandler {
|
||||||
Arc::new(move |_input, channel_conn, _auth| {
|
Arc::new(move |_input, _plan, channel_conn, _auth| {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut stream = match channel_conn.accept_bi().await {
|
let mut stream = match channel_conn.accept_bi().await {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
|
|||||||
Reference in New Issue
Block a user