1use std::collections::BTreeMap;
4use std::future::{Future, IntoFuture};
5use std::pin::Pin;
6
7use futures::Stream;
8use reqwest::Method;
9use serde_json::{Map, Value};
10
11use crate::client::Client;
12use crate::dispatch::{decode_json, message_path, room_path};
13use crate::error::Result;
14use crate::pagination::{Fetch, Page, run_stream};
15use crate::reactions::Reactions;
16use crate::types::{Direction, Message, Metadata, RoomName, Serial, Timestamp};
17
18fn string_map(map: &BTreeMap<String, String>) -> Value {
20 Value::Object(
21 map.iter()
22 .map(|(k, v)| (k.clone(), Value::String(v.clone())))
23 .collect(),
24 )
25}
26
27fn idempotency(key: &Option<String>) -> (Vec<(&'static str, String)>, bool) {
30 match key {
31 Some(k) => (vec![("idempotencyKey", k.clone())], true),
32 None => (Vec::new(), false),
33 }
34}
35
36#[derive(Clone, Debug)]
40pub struct Messages {
41 pub(crate) client: Client,
42 pub(crate) room: RoomName,
43}
44
45impl Messages {
46 pub(crate) fn new(client: Client, room: RoomName) -> Self {
47 Self { client, room }
48 }
49
50 pub fn reactions(&self) -> Reactions {
52 Reactions::new(self.client.clone(), self.room.clone())
53 }
54
55 pub fn send(&self, text: impl Into<String>) -> SendMessage {
60 SendMessage {
61 client: self.client.clone(),
62 room: self.room.clone(),
63 text: text.into(),
64 metadata: None,
65 headers: None,
66 idempotency_key: None,
67 }
68 }
69
70 pub fn get(&self, serial: impl Into<Serial>) -> GetMessage {
74 GetMessage {
75 client: self.client.clone(),
76 room: self.room.clone(),
77 serial: serial.into(),
78 }
79 }
80
81 pub fn update(&self, serial: impl Into<Serial>, text: impl Into<String>) -> UpdateMessage {
92 UpdateMessage {
93 client: self.client.clone(),
94 room: self.room.clone(),
95 serial: serial.into(),
96 text: text.into(),
97 metadata: None,
98 headers: None,
99 description: None,
100 idempotency_key: None,
101 }
102 }
103
104 pub fn delete(&self, serial: impl Into<Serial>) -> DeleteMessage {
112 DeleteMessage {
113 client: self.client.clone(),
114 room: self.room.clone(),
115 serial: serial.into(),
116 description: None,
117 metadata: None,
118 idempotency_key: None,
119 }
120 }
121
122 pub fn history(&self) -> History {
129 History {
130 client: self.client.clone(),
131 room: self.room.clone(),
132 start: None,
133 end: None,
134 direction: Direction::Backwards,
135 limit: 100,
136 from_serial: None,
137 }
138 }
139
140 pub fn versions(&self, serial: impl Into<Serial>) -> Versions {
146 Versions {
147 client: self.client.clone(),
148 room: self.room.clone(),
149 serial: serial.into(),
150 }
151 }
152}
153
154#[derive(Clone, Debug)]
156pub struct GetMessage {
157 client: Client,
158 room: RoomName,
159 serial: Serial,
160}
161
162impl IntoFuture for GetMessage {
163 type Output = Result<Message>;
164 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
165
166 fn into_future(self) -> Self::IntoFuture {
167 Box::pin(async move {
168 let resp = self
169 .client
170 .inner
171 .send(
172 Method::GET,
173 &message_path(self.room.as_str(), self.serial.as_str(), ""),
174 &[],
175 None,
176 false,
177 )
178 .await?;
179 decode_json(&resp.body)
180 })
181 }
182}
183
184#[derive(Clone, Debug)]
187pub struct SendMessage {
188 client: Client,
189 room: RoomName,
190 text: String,
191 metadata: Option<Metadata>,
192 headers: Option<BTreeMap<String, String>>,
193 idempotency_key: Option<String>,
194}
195
196impl SendMessage {
197 pub fn metadata(mut self, metadata: Metadata) -> Self {
199 self.metadata = Some(metadata);
200 self
201 }
202
203 pub fn headers(mut self, headers: BTreeMap<String, String>) -> Self {
205 self.headers = Some(headers);
206 self
207 }
208
209 pub fn idempotency_key(mut self, key: impl Into<String>) -> Self {
211 self.idempotency_key = Some(key.into());
212 self
213 }
214}
215
216impl IntoFuture for SendMessage {
217 type Output = Result<Message>;
218 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
219
220 fn into_future(self) -> Self::IntoFuture {
221 Box::pin(async move {
222 let mut obj = Map::new();
223 obj.insert("text".to_owned(), Value::String(self.text));
224 if let Some(metadata) = self.metadata {
225 obj.insert("metadata".to_owned(), Value::Object(metadata));
226 }
227 if let Some(headers) = &self.headers {
228 obj.insert("headers".to_owned(), string_map(headers));
229 }
230 let (query, has_idem) = idempotency(&self.idempotency_key);
231 let resp = self
232 .client
233 .inner
234 .send(
235 Method::POST,
236 &room_path(self.room.as_str(), "/messages"),
237 &query,
238 Some(Value::Object(obj)),
239 has_idem,
240 )
241 .await?;
242 decode_json(&resp.body)
243 })
244 }
245}
246
247#[derive(Clone, Debug)]
252pub struct UpdateMessage {
253 client: Client,
254 room: RoomName,
255 serial: Serial,
256 text: String,
257 metadata: Option<Metadata>,
258 headers: Option<BTreeMap<String, String>>,
259 description: Option<String>,
260 idempotency_key: Option<String>,
261}
262
263impl UpdateMessage {
264 pub fn metadata(mut self, metadata: Metadata) -> Self {
267 self.metadata = Some(metadata);
268 self
269 }
270
271 pub fn headers(mut self, headers: BTreeMap<String, String>) -> Self {
274 self.headers = Some(headers);
275 self
276 }
277
278 pub fn description(mut self, description: impl Into<String>) -> Self {
280 self.description = Some(description.into());
281 self
282 }
283
284 pub fn idempotency_key(mut self, key: impl Into<String>) -> Self {
286 self.idempotency_key = Some(key.into());
287 self
288 }
289}
290
291impl IntoFuture for UpdateMessage {
292 type Output = Result<Message>;
293 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
294
295 fn into_future(self) -> Self::IntoFuture {
296 Box::pin(async move {
297 let mut message = Map::new();
298 message.insert("text".to_owned(), Value::String(self.text));
299 if let Some(metadata) = self.metadata {
300 message.insert("metadata".to_owned(), Value::Object(metadata));
301 }
302 if let Some(headers) = &self.headers {
303 message.insert("headers".to_owned(), string_map(headers));
304 }
305 let mut obj = Map::new();
306 obj.insert("message".to_owned(), Value::Object(message));
307 if let Some(description) = self.description {
308 obj.insert("description".to_owned(), Value::String(description));
309 }
310 let (query, has_idem) = idempotency(&self.idempotency_key);
311 let resp = self
312 .client
313 .inner
314 .send(
315 Method::PUT,
316 &message_path(self.room.as_str(), self.serial.as_str(), ""),
317 &query,
318 Some(Value::Object(obj)),
319 has_idem,
320 )
321 .await?;
322 decode_json(&resp.body)
323 })
324 }
325}
326
327#[derive(Clone, Debug)]
330pub struct DeleteMessage {
331 client: Client,
332 room: RoomName,
333 serial: Serial,
334 description: Option<String>,
335 metadata: Option<BTreeMap<String, String>>,
336 idempotency_key: Option<String>,
337}
338
339impl DeleteMessage {
340 pub fn description(mut self, description: impl Into<String>) -> Self {
342 self.description = Some(description.into());
343 self
344 }
345
346 pub fn metadata(mut self, metadata: BTreeMap<String, String>) -> Self {
348 self.metadata = Some(metadata);
349 self
350 }
351
352 pub fn idempotency_key(mut self, key: impl Into<String>) -> Self {
354 self.idempotency_key = Some(key.into());
355 self
356 }
357}
358
359impl IntoFuture for DeleteMessage {
360 type Output = Result<Message>;
361 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
362
363 fn into_future(self) -> Self::IntoFuture {
364 Box::pin(async move {
365 let mut obj = Map::new();
366 if let Some(description) = self.description {
367 obj.insert("description".to_owned(), Value::String(description));
368 }
369 if let Some(metadata) = &self.metadata {
370 obj.insert("metadata".to_owned(), string_map(metadata));
371 }
372 let body = if obj.is_empty() {
374 None
375 } else {
376 Some(Value::Object(obj))
377 };
378 let (query, has_idem) = idempotency(&self.idempotency_key);
379 let resp = self
380 .client
381 .inner
382 .send(
383 Method::POST,
384 &message_path(self.room.as_str(), self.serial.as_str(), "/delete"),
385 &query,
386 body,
387 has_idem,
388 )
389 .await?;
390 decode_json(&resp.body)
391 })
392 }
393}
394
395fn direction_str(d: Direction) -> &'static str {
397 match d {
398 Direction::Forwards => "forwards",
399 Direction::Backwards => "backwards",
400 }
401}
402
403#[derive(Clone, Debug)]
406pub struct History {
407 client: Client,
408 room: RoomName,
409 start: Option<i64>,
410 end: Option<i64>,
411 direction: Direction,
412 limit: u32,
413 from_serial: Option<Serial>,
414}
415
416impl History {
417 pub fn start(mut self, start: impl Into<Timestamp>) -> Self {
419 self.start = Some(start.into().as_millis());
420 self
421 }
422
423 pub fn end(mut self, end: impl Into<Timestamp>) -> Self {
425 self.end = Some(end.into().as_millis());
426 self
427 }
428
429 pub fn direction(mut self, direction: Direction) -> Self {
431 self.direction = direction;
432 self
433 }
434
435 pub fn limit(mut self, limit: u32) -> Self {
437 self.limit = limit;
438 self
439 }
440
441 pub fn from_serial(mut self, serial: impl Into<Serial>) -> Self {
443 self.from_serial = Some(serial.into());
444 self
445 }
446
447 fn query(&self) -> Vec<(&'static str, String)> {
450 let mut query: Vec<(&'static str, String)> = Vec::new();
451 if let Some(start) = self.start {
452 query.push(("start", start.to_string()));
453 }
454 if let Some(end) = self.end {
455 query.push(("end", end.to_string()));
456 }
457 query.push(("direction", direction_str(self.direction).to_owned()));
458 query.push(("limit", self.limit.to_string()));
459 if let Some(from_serial) = &self.from_serial {
460 query.push(("fromSerial", from_serial.as_str().to_owned()));
461 }
462 query
463 }
464
465 pub fn into_stream(self) -> impl Stream<Item = Result<Message>> + Send {
468 let path = room_path(self.room.as_str(), "/messages");
469 let query = self.query();
470 run_stream(self.client, Vec::new(), Fetch::First { path, query })
471 }
472}
473
474impl IntoFuture for History {
475 type Output = Result<Page<Message>>;
476 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
477
478 fn into_future(self) -> Self::IntoFuture {
479 Box::pin(async move {
480 let path = room_path(self.room.as_str(), "/messages");
481 let query = self.query();
482 Page::fetch_first(self.client, path, query).await
483 })
484 }
485}
486
487#[derive(Clone, Debug)]
490pub struct Versions {
491 client: Client,
492 room: RoomName,
493 serial: Serial,
494}
495
496impl Versions {
497 fn path(&self) -> String {
498 message_path(self.room.as_str(), self.serial.as_str(), "/versions")
499 }
500
501 pub fn into_stream(self) -> impl Stream<Item = Result<Message>> + Send {
504 let path = self.path();
505 run_stream(
506 self.client,
507 Vec::new(),
508 Fetch::First {
509 path,
510 query: Vec::new(),
511 },
512 )
513 }
514}
515
516impl IntoFuture for Versions {
517 type Output = Result<Page<Message>>;
518 type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
519
520 fn into_future(self) -> Self::IntoFuture {
521 Box::pin(async move {
522 let path = self.path();
523 Page::fetch_first(self.client, path, Vec::new()).await
524 })
525 }
526}