feat(sidecar): protocol 12 — POST /titles (phase 17) #12
@@ -57,6 +57,7 @@ still authenticate). Every response carries `X-RustLink-Version`.
|
||||
| `GET /feed?since=&limit=` | store | **Oldest first, from a cursor.** For a consumer that must not miss a row. Omitting `since` asks where the end is |
|
||||
| `GET /status` | plugin (RPC) | A live round trip. `503` with no plugin, `504` on no reply |
|
||||
| `GET /ws` | broadcast | The live feed. Sends `ws.hello` on connect |
|
||||
| every later route | plugin (RPC) | One thin forward per command, from `POST /link/confirm` (protocol 3) to `POST /titles` (protocol 12). Each is listed beside its protocol in `src/web.rs`, and its body in `docs/rust-link/PROTOCOL.md` |
|
||||
|
||||
The split is the point: the store-backed reads answer while the game is off, which is what lets the
|
||||
website render a server list during a wipe or a restart. `/status` is the one route that fails when
|
||||
|
||||
@@ -163,8 +163,8 @@ use tracing_subscriber::EnvFilter;
|
||||
///
|
||||
/// `docs/rust-link/PROTOCOL.md` is the specification — §8 the read path, §9 identity, §10 the
|
||||
/// mirror, §11 configuration, §12 clans, §13 the raid frame, §14 the leases, §15 the world verbs,
|
||||
/// §16 the rewards, §17 the map; this constant is one of its four declaration sites.
|
||||
pub const PROTOCOL_VERSION: u32 = 11;
|
||||
/// §16 the rewards, §17 the map, §18 the optional mods; this constant is one of its four declaration sites.
|
||||
pub const PROTOCOL_VERSION: u32 = 12;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = match cli::parse(std::env::args().skip(1)) {
|
||||
|
||||
@@ -135,6 +135,10 @@ pub async fn serve(addr: &str, state: AppState) -> anyhow::Result<()> {
|
||||
.route("/map/chunk", get(map_chunk))
|
||||
.route("/map/render", post(map_render))
|
||||
.route("/map/live", get(map_live))
|
||||
// Protocol 12 (§33): the chat titles a player has earned. A whole set, replaced each time,
|
||||
// held by the plugin in memory and read by BetterChat on the chat path. Nothing here knows
|
||||
// what a title is.
|
||||
.route("/titles", post(titles))
|
||||
.route_layer(middleware::from_fn_with_state(state.clone(), gate));
|
||||
|
||||
let app = Router::new()
|
||||
@@ -565,6 +569,12 @@ async fn chat(State(st): State<AppState>, Json(body): Json<Value>) -> Response {
|
||||
forward_object(&st, body, "chat.say", "a chat line").await
|
||||
}
|
||||
|
||||
/// Replace the chat titles this server's plugin holds (protocol 12). The set is whole, so a retry
|
||||
/// is harmless, and the plugin answers whether BetterChat is there to show them.
|
||||
async fn titles(State(st): State<AppState>, Json(body): Json<Value>) -> Response {
|
||||
forward_object(&st, body, "titles.set", "a title set").await
|
||||
}
|
||||
|
||||
/// What this map is, where its picture comes from, and its monuments (protocol 11, stage one).
|
||||
/// Live, and never cached here: a wipe changes the answer, and the module compares its key and
|
||||
/// hash against what it holds to decide whether to fetch at all.
|
||||
@@ -1097,6 +1107,24 @@ mod tests {
|
||||
assert!(stamp(json!(["9"]), "tally.close", "r-4").is_none());
|
||||
}
|
||||
|
||||
/// Protocol 12: a title set is forwarded whole. The markup inside each `text` is the module's
|
||||
/// and the plugin's business, so it must arrive byte for byte as the website composed it.
|
||||
#[test]
|
||||
fn a_protocol_12_title_set_passes_through_untouched() {
|
||||
let body = json!({
|
||||
"cmd": "chat.say",
|
||||
"setId": "s-1",
|
||||
"titles": [{ "steamId": "76561198000000001", "text": "[#ff8800]Top Killer[/#]" }],
|
||||
});
|
||||
let stamped = stamp(body, "titles.set", "r-5").expect("an object is stamped");
|
||||
|
||||
assert_eq!(stamped["cmd"], "titles.set");
|
||||
assert_eq!(stamped["reqId"], "r-5");
|
||||
assert_eq!(stamped["setId"], "s-1");
|
||||
assert_eq!(stamped["titles"][0]["text"], "[#ff8800]Top Killer[/#]");
|
||||
assert!(stamp(json!("titles"), "titles.set", "r-6").is_none());
|
||||
}
|
||||
|
||||
/// Protocol 11's one opaque forward is stamped like the rest: a render cannot become a world
|
||||
/// placement by naming one.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user