# M4a task 10: Mattermost's events and REST calls **Branch:** `m4a` (run `git switch m4a`; `git status --short` must be empty, otherwise stop) **Commit subject:** `gatewayd: mm, Mattermost's events and the four REST calls, typed` ## Goal Mattermost's JSON, typed, and the REST calls `gatewayd` makes. This is **another program's format**: unknown fields are ignored. But every id we keep ends up in a session id or a URL path, so every id must be a valid Mattermost id (26 characters of `a-z0-9`), or the whole post is refused. Facts from Mattermost's source at the owner's server version, v11.11.0 (checked by the design model; the tests use these shapes): - A WebSocket event is `{"event", "data", "broadcast", "seq"}`. The first is `hello`. `posted` carries the post as a **JSON string** in `data.post` (a string holding JSON, not an object), and the channel's type in `data.channel_type`: `D` direct, `G` group, `O` open, `P` private. A reply to one of our requests has no `event`. - `user_typing` is a request we send: `{"action": "user_typing", "seq": , "data": {"channel_id", "parent_id"}}`; it shows the bot as typing in that thread. - `GET /api/v4/channels/{id}/posts?since=` returns `{"order": [ids], "posts": {id: post}}` with the posts **changed** after that time. Only the ids in `order` changed; `posts` also holds the roots of their threads, which may be old. Edited and deleted posts come back too. The server takes at most 1000 (`SINCE_LIMIT`); a full answer may have left some out. - REST errors: 401 or 403 is a refused token, 429 is a rate limit (wait for `X-Ratelimit-Reset`), 5xx is worth trying again. ## Files - Copy: `crates/gatewayd/tests/mm_json.rs`, `crates/gatewayd/tests/mm_rest.rs`, `crates/gatewayd/tests/support/http_server.rs`, and the skeletons `crates/gatewayd/src/mm/mod.rs` and `crates/gatewayd/src/mm/rest.rs` - Modify: `crates/gatewayd/src/lib.rs` (`pub mod mm;`), `docs/implementer-log.md` ## The skeletons `mm/mod.rs`, written: `SINCE_LIMIT`; `MmError` (`Net`, `Auth(u16)`, `RateLimited(Duration)`, `Status(u16, String)`, `Json`) and its `Display`, which **quotes** a server's body (`{:?}`) so that a newline in it cannot forge a log line; `Me { id, username }`; `Post { id, user_id, channel_id, root_id, message, create_at, delete_at, kind }` (`kind` is the JSON `type`); `Event` (`Hello`, `Posted { post, channel_type }`, `Other(String)`); `Since { posts, full }`; and the private serde shapes `RawEvent` and `PostList`. To fill: `Post::check`, `json`, `parse_event`, `typing`, `since_list`. `mm/rest.rs`, written: `RETRY_5XX` (500 ms), `RETRIES` (2), `BODY_KEPT` (200), `Client`. To fill: `new`, `connector`, `token`, `once`, `call`, `me`, `create_post`, `posts_since`, `direct_channel`. The token reaches the wire only in `once`, as `Authorization: Bearer `; nowhere else calls `expose()`. ## Steps - [ ] **1. Copy.** `git switch m4a`, then `mkdir -p crates/gatewayd/src/mm && cp docs/plans/M4a/files/crates/gatewayd/src/mm/mod.rs docs/plans/M4a/files/crates/gatewayd/src/mm/rest.rs crates/gatewayd/src/mm/ && cp docs/plans/M4a/files/crates/gatewayd/tests/mm_json.rs docs/plans/M4a/files/crates/gatewayd/tests/mm_rest.rs crates/gatewayd/tests/ && cp docs/plans/M4a/files/crates/gatewayd/tests/support/http_server.rs crates/gatewayd/tests/support/`. Add `pub mod mm;` to `lib.rs`. - [ ] **2. See it fail.** `cargo test -p gatewayd --no-fail-fast --test mm_json --test mm_rest`. Expected: it compiles; `mm_json` 6 fail and 1 passes (the `Display` test), `mm_rest` 10 fail. - [ ] **3. Fill `mm/mod.rs`, then `mm/rest.rs`**, one function at a time, `cargo check -p gatewayd` after each. - [ ] **4. See it pass.** `cargo test -p gatewayd --test mm_json --test mm_rest`. Expected: 7 and 10 passed. `mm_rest` takes about 3 s: two tests wait out a rate limit on purpose. - [ ] **5. Run the gate.** `cargo fmt --all`, then `make gate`. Expected last line: `gate: ok`. - [ ] **6. Log and commit.** `git add crates/gatewayd docs/implementer-log.md Cargo.lock && git commit` ## Done when - Both suites pass; `make gate` prints `gate: ok`. ## Stop and report if - A test needs a post with an invalid id to be accepted.