Files
boxmaker/docs/plans/M4b/09-gatewayd-mm-edit.md
T
kyleandClaude Opus 5.5 2d415fc1be Plan M4b: approvals over Mattermost in 15 tasks
Skeletons for new files only; existing files change by exact edits, so no task overwrites an
earlier one's work. Each task's tests were run against a reference built in order from master,
with the gate at each step and mutations checked; each skeleton compiles and fails its tests, and
every todo!() comment was read beside its signature. The reference is kept off this machine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 03:34:21 -07:00

68 lines
2.7 KiB
Markdown

# M4b task 09: editing a post, and a username
**Branch:** `m4b` (run `git switch m4b`; `git status --short` must be empty, otherwise stop)
**Commit subject:** `gatewayd: mm, patch_post and username`
## Goal
When an approval ends, `gatewayd` edits its post to say how, naming who answered (M4b spec,
section 6). Two REST calls, checked against Mattermost v11.11.0, and confirmed on the owner's
server (the bot may edit its own posts):
- `PUT /api/v4/posts/{post_id}/patch` with `{"message": "…"}`: replaces the post's text; the
answer is the post.
- `GET /api/v4/users/{user_id}`: the user, with `username`.
Every id that goes into a path must be a Mattermost id first, or nothing is sent.
## Files
- Copy: `crates/gatewayd/tests/mm_edit.rs`
- Modify: `crates/gatewayd/src/mm/rest.rs`, `docs/implementer-log.md`
## The changes, exactly
In `crates/gatewayd/src/mm/rest.rs`, after `struct Channel`:
```rust
#[derive(Deserialize)]
struct User {
username: String,
}
/// A Mattermost username: 1 to 64 characters of `a-z`, `0-9`, `.`, `-` and `_`.
fn valid_username(name: &str) -> bool {
(1..=64).contains(&name.len())
&& name.bytes().all(|b| {
b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'.' | b'-' | b'_')
})
}
```
and two methods at the end of `impl Client`, written like `posts_since` and `create_post`:
1. `pub fn patch_post(&self, post: &str, message: &str) -> Result<(), MmError>`:
- `post` not `valid_id`: `Err(MmError::Json("a post path must be a Mattermost id".to_string()))`,
nothing sent.
- `PUT` `/api/v4/posts/{post}/patch` with the body
`serde_json::json!({ "message": message }).to_string()`, through `self.call`.
- The answer is a `Post`: `json::<Post>(&body)?.check().map(|_| ())`.
2. `pub fn username(&self, user: &str) -> Result<String, MmError>`:
- `user` not `valid_id`: `Err(MmError::Json("a user path must be a Mattermost id".to_string()))`.
- `GET` `/api/v4/users/{user}` into `User`; a username that is not `valid_username` is
`Err(MmError::Json("the user has no valid username".to_string()))`. Else `Ok(username)`.
## Steps
- [ ] **1. Copy.** `git switch m4b`, then `cp docs/plans/M4b/files/crates/gatewayd/tests/mm_edit.rs crates/gatewayd/tests/`
- [ ] **2. See it fail.** `cargo test -p gatewayd --test mm_edit`. Expected: it does not compile.
- [ ] **3. Make the changes.** Run `cargo fmt --all`.
- [ ] **4. See it pass.** `cargo test -p gatewayd --test mm_edit --test mm_rest`. Expected: 4 and
10 passed.
- [ ] **5. Run the gate.** `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`.