use std::collections::BTreeMap;
use std::future::{Future, IntoFuture};
use std::pin::Pin;
use futures::Stream;
use reqwest::Method;
use serde_json::{Map, Value};
use crate::client::Client;
use crate::dispatch::{decode_json, message_path, room_path};
use crate::error::Result;
use crate::pagination::{Fetch, Page, run_stream};
use crate::reactions::Reactions;
use crate::types::{Direction, Message, Metadata, RoomName, Serial, Timestamp};
fn string_map(map: &BTreeMap<String, String>) -> Value {
Value::Object(
map.iter()
.map(|(k, v)| (k.clone(), Value::String(v.clone())))
.collect(),
)
}
fn idempotency(key: &Option<String>) -> (Vec<(&'static str, String)>, bool) {
match key {
Some(k) => (vec![("idempotencyKey", k.clone())], true),
None => (Vec::new(), false),
}
}
#[derive(Clone, Debug)]
pub struct Messages {
pub(crate) client: Client,
pub(crate) room: RoomName,
}
impl Messages {
pub(crate) fn new(client: Client, room: RoomName) -> Self {
Self { client, room }
}
pub fn reactions(&self) -> Reactions {
Reactions::new(self.client.clone(), self.room.clone())
}
pub fn send(&self, text: impl Into<String>) -> SendMessage {
SendMessage {
client: self.client.clone(),
room: self.room.clone(),
text: text.into(),
metadata: None,
headers: None,
idempotency_key: None,
}
}
pub fn get(&self, serial: impl Into<Serial>) -> GetMessage {
GetMessage {
client: self.client.clone(),
room: self.room.clone(),
serial: serial.into(),
}
}
pub fn update(&self, serial: impl Into<Serial>, text: impl Into<String>) -> UpdateMessage {
UpdateMessage {
client: self.client.clone(),
room: self.room.clone(),
serial: serial.into(),
text: text.into(),
metadata: None,
headers: None,
description: None,
idempotency_key: None,
}
}
pub fn delete(&self, serial: impl Into<Serial>) -> DeleteMessage {
DeleteMessage {
client: self.client.clone(),
room: self.room.clone(),
serial: serial.into(),
description: None,
metadata: None,
idempotency_key: None,
}
}
pub fn history(&self) -> History {
History {
client: self.client.clone(),
room: self.room.clone(),
start: None,
end: None,
direction: Direction::Backwards,
limit: 100,
from_serial: None,
}
}
pub fn versions(&self, serial: impl Into<Serial>) -> Versions {
Versions {
client: self.client.clone(),
room: self.room.clone(),
serial: serial.into(),
}
}
}
#[derive(Clone, Debug)]
pub struct GetMessage {
client: Client,
room: RoomName,
serial: Serial,
}
impl IntoFuture for GetMessage {
type Output = Result<Message>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let resp = self
.client
.inner
.send(
Method::GET,
&message_path(self.room.as_str(), self.serial.as_str(), ""),
&[],
None,
false,
)
.await?;
decode_json(&resp.body)
})
}
}
#[derive(Clone, Debug)]
pub struct SendMessage {
client: Client,
room: RoomName,
text: String,
metadata: Option<Metadata>,
headers: Option<BTreeMap<String, String>>,
idempotency_key: Option<String>,
}
impl SendMessage {
pub fn metadata(mut self, metadata: Metadata) -> Self {
self.metadata = Some(metadata);
self
}
pub fn headers(mut self, headers: BTreeMap<String, String>) -> Self {
self.headers = Some(headers);
self
}
pub fn idempotency_key(mut self, key: impl Into<String>) -> Self {
self.idempotency_key = Some(key.into());
self
}
}
impl IntoFuture for SendMessage {
type Output = Result<Message>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let mut obj = Map::new();
obj.insert("text".to_owned(), Value::String(self.text));
if let Some(metadata) = self.metadata {
obj.insert("metadata".to_owned(), Value::Object(metadata));
}
if let Some(headers) = &self.headers {
obj.insert("headers".to_owned(), string_map(headers));
}
let (query, has_idem) = idempotency(&self.idempotency_key);
let resp = self
.client
.inner
.send(
Method::POST,
&room_path(self.room.as_str(), "/messages"),
&query,
Some(Value::Object(obj)),
has_idem,
)
.await?;
decode_json(&resp.body)
})
}
}
#[derive(Clone, Debug)]
pub struct UpdateMessage {
client: Client,
room: RoomName,
serial: Serial,
text: String,
metadata: Option<Metadata>,
headers: Option<BTreeMap<String, String>>,
description: Option<String>,
idempotency_key: Option<String>,
}
impl UpdateMessage {
pub fn metadata(mut self, metadata: Metadata) -> Self {
self.metadata = Some(metadata);
self
}
pub fn headers(mut self, headers: BTreeMap<String, String>) -> Self {
self.headers = Some(headers);
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn idempotency_key(mut self, key: impl Into<String>) -> Self {
self.idempotency_key = Some(key.into());
self
}
}
impl IntoFuture for UpdateMessage {
type Output = Result<Message>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let mut message = Map::new();
message.insert("text".to_owned(), Value::String(self.text));
if let Some(metadata) = self.metadata {
message.insert("metadata".to_owned(), Value::Object(metadata));
}
if let Some(headers) = &self.headers {
message.insert("headers".to_owned(), string_map(headers));
}
let mut obj = Map::new();
obj.insert("message".to_owned(), Value::Object(message));
if let Some(description) = self.description {
obj.insert("description".to_owned(), Value::String(description));
}
let (query, has_idem) = idempotency(&self.idempotency_key);
let resp = self
.client
.inner
.send(
Method::PUT,
&message_path(self.room.as_str(), self.serial.as_str(), ""),
&query,
Some(Value::Object(obj)),
has_idem,
)
.await?;
decode_json(&resp.body)
})
}
}
#[derive(Clone, Debug)]
pub struct DeleteMessage {
client: Client,
room: RoomName,
serial: Serial,
description: Option<String>,
metadata: Option<BTreeMap<String, String>>,
idempotency_key: Option<String>,
}
impl DeleteMessage {
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn metadata(mut self, metadata: BTreeMap<String, String>) -> Self {
self.metadata = Some(metadata);
self
}
pub fn idempotency_key(mut self, key: impl Into<String>) -> Self {
self.idempotency_key = Some(key.into());
self
}
}
impl IntoFuture for DeleteMessage {
type Output = Result<Message>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let mut obj = Map::new();
if let Some(description) = self.description {
obj.insert("description".to_owned(), Value::String(description));
}
if let Some(metadata) = &self.metadata {
obj.insert("metadata".to_owned(), string_map(metadata));
}
let body = if obj.is_empty() {
None
} else {
Some(Value::Object(obj))
};
let (query, has_idem) = idempotency(&self.idempotency_key);
let resp = self
.client
.inner
.send(
Method::POST,
&message_path(self.room.as_str(), self.serial.as_str(), "/delete"),
&query,
body,
has_idem,
)
.await?;
decode_json(&resp.body)
})
}
}
fn direction_str(d: Direction) -> &'static str {
match d {
Direction::Forwards => "forwards",
Direction::Backwards => "backwards",
}
}
#[derive(Clone, Debug)]
pub struct History {
client: Client,
room: RoomName,
start: Option<i64>,
end: Option<i64>,
direction: Direction,
limit: u32,
from_serial: Option<Serial>,
}
impl History {
pub fn start(mut self, start: impl Into<Timestamp>) -> Self {
self.start = Some(start.into().as_millis());
self
}
pub fn end(mut self, end: impl Into<Timestamp>) -> Self {
self.end = Some(end.into().as_millis());
self
}
pub fn direction(mut self, direction: Direction) -> Self {
self.direction = direction;
self
}
pub fn limit(mut self, limit: u32) -> Self {
self.limit = limit;
self
}
pub fn from_serial(mut self, serial: impl Into<Serial>) -> Self {
self.from_serial = Some(serial.into());
self
}
fn query(&self) -> Vec<(&'static str, String)> {
let mut query: Vec<(&'static str, String)> = Vec::new();
if let Some(start) = self.start {
query.push(("start", start.to_string()));
}
if let Some(end) = self.end {
query.push(("end", end.to_string()));
}
query.push(("direction", direction_str(self.direction).to_owned()));
query.push(("limit", self.limit.to_string()));
if let Some(from_serial) = &self.from_serial {
query.push(("fromSerial", from_serial.as_str().to_owned()));
}
query
}
pub fn into_stream(self) -> impl Stream<Item = Result<Message>> + Send {
let path = room_path(self.room.as_str(), "/messages");
let query = self.query();
run_stream(self.client, Vec::new(), Fetch::First { path, query })
}
}
impl IntoFuture for History {
type Output = Result<Page<Message>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let path = room_path(self.room.as_str(), "/messages");
let query = self.query();
Page::fetch_first(self.client, path, query).await
})
}
}
#[derive(Clone, Debug)]
pub struct Versions {
client: Client,
room: RoomName,
serial: Serial,
}
impl Versions {
fn path(&self) -> String {
message_path(self.room.as_str(), self.serial.as_str(), "/versions")
}
pub fn into_stream(self) -> impl Stream<Item = Result<Message>> + Send {
let path = self.path();
run_stream(
self.client,
Vec::new(),
Fetch::First {
path,
query: Vec::new(),
},
)
}
}
impl IntoFuture for Versions {
type Output = Result<Page<Message>>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let path = self.path();
Page::fetch_first(self.client, path, Vec::new()).await
})
}
}