Each task's tests were run against a reference at its end state; the end states were replayed from master in order with the gate at each step (650 to 762 tests); each skeleton compiles against its tests and fails them. The reference is kept off this machine. Lessons T27 (every wait in a test has a limit) and T28 (mutate the reference before hand-over) come from this work. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
96 lines
3.7 KiB
Rust
96 lines
3.7 KiB
Rust
//! Mattermost's REST calls, one connection per request, with the token in the `Authorization`
|
|
//! header. A 429 waits as asked; a 5xx is tried twice more; a 401 or 403 is `Auth` at once.
|
|
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
use serde::Deserialize;
|
|
|
|
use crate::config::valid_id;
|
|
use crate::http::{self, Response, rate_limit_wait};
|
|
use crate::mm::{Me, MmError, Post, Since, json, since_list};
|
|
use crate::net::Connector;
|
|
use crate::secrets::Secret;
|
|
use crate::ws::conn::host_header;
|
|
|
|
/// The pause before trying a call again after a 5xx.
|
|
pub const RETRY_5XX: Duration = Duration::from_millis(500);
|
|
/// How many times a call is tried again after a 5xx, and waits after a 429.
|
|
pub const RETRIES: u32 = 2;
|
|
/// How much of an error body is kept for the message.
|
|
const BODY_KEPT: usize = 200;
|
|
|
|
pub struct Client {
|
|
connector: Connector,
|
|
token: Secret,
|
|
timeout: Duration,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct Channel {
|
|
id: String,
|
|
}
|
|
|
|
impl Client {
|
|
/// `timeout` bounds connecting and each read: no bytes for that long is an error.
|
|
pub fn new(connector: Connector, token: Secret, timeout: Duration) -> Client {
|
|
// Store the three.
|
|
todo!()
|
|
}
|
|
|
|
pub fn connector(&self) -> &Connector {
|
|
// The Connector.
|
|
todo!()
|
|
}
|
|
|
|
pub fn token(&self) -> &Secret {
|
|
// The Secret.
|
|
todo!()
|
|
}
|
|
|
|
fn once(&self, method: &str, path: &str, body: Option<&[u8]>) -> Result<Response, MmError> {
|
|
// 1. `self.connector.connect(self.timeout)`, then `set_read_timeout(Some(self.timeout))`.
|
|
// 2. Headers: Authorization "Bearer <token>" (the only use of `expose`), Accept
|
|
// "application/json", and Content-Type "application/json" when there is a body. Host is
|
|
// `host_header(self.connector.server())`.
|
|
// 3. `http::request`. Every error on the way is Net("<method> <path>: <error>").
|
|
todo!()
|
|
}
|
|
|
|
/// One call, tried again as the module comment says; the body of a 2xx answer.
|
|
fn call(&self, method: &str, path: &str, body: Option<&[u8]>) -> Result<Vec<u8>, MmError> {
|
|
// Loop over `once`: 2xx -> the body. 401 or 403 -> Auth(status) at once. 429 -> after
|
|
// RETRIES waits already, RateLimited(wait); else sleep `rate_limit_wait(&head,
|
|
// SystemTime::now())` and try again. 5xx -> up to RETRIES more tries, sleeping RETRY_5XX
|
|
// before each. Anything else -> Status(status, the first BODY_KEPT characters of the body,
|
|
// lossy UTF-8).
|
|
todo!()
|
|
}
|
|
|
|
/// `GET /api/v4/users/me`: who this token is.
|
|
pub fn me(&self) -> Result<Me, MmError> {
|
|
// GET /api/v4/users/me into Me; an id that is not `valid_id` or an empty username is Json.
|
|
todo!()
|
|
}
|
|
|
|
/// `POST /api/v4/posts`: a post in `channel`, in the thread of `root` (empty: top level).
|
|
pub fn create_post(&self, channel: &str, root: &str, message: &str) -> Result<Post, MmError> {
|
|
// POST /api/v4/posts with {"channel_id", "root_id", "message"}; the answer is a Post,
|
|
// `check`ed.
|
|
todo!()
|
|
}
|
|
|
|
/// `GET /api/v4/channels/{channel}/posts?since=<ms>`: the posts created after `since`.
|
|
pub fn posts_since(&self, channel: &str, since: i64) -> Result<Since, MmError> {
|
|
// A channel that is not `valid_id` is Json, and nothing is sent. Otherwise GET
|
|
// /api/v4/channels/<channel>/posts?since=<since>, through `since_list`.
|
|
todo!()
|
|
}
|
|
|
|
/// `POST /api/v4/channels/direct`: the id of the direct channel between two users.
|
|
pub fn direct_channel(&self, a: &str, b: &str) -> Result<String, MmError> {
|
|
// POST /api/v4/channels/direct with the JSON array [a, b]; the answer has an "id", which
|
|
// must be `valid_id` (else Json).
|
|
todo!()
|
|
}
|
|
}
|