use std::collections::BTreeMap;
use std::fmt;
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Identifier {
channel: String,
params: BTreeMap<String, Value>,
}
impl Identifier {
pub fn new(channel: impl Into<String>) -> Identifier {
Identifier {
channel: channel.into(),
params: BTreeMap::new(),
}
}
pub fn param(mut self, name: impl Into<String>, value: impl Into<Value>) -> Identifier {
self.params.insert(name.into(), value.into());
self
}
pub fn channel(&self) -> &str {
&self.channel
}
pub fn params(&self) -> &BTreeMap<String, Value> {
&self.params
}
#[allow(
clippy::expect_used,
reason = "serializing a map of Values has no failure mode to report"
)]
pub fn key(&self) -> String {
let mut fields = self.params.clone();
fields.insert("channel".to_string(), Value::String(self.channel.clone()));
serde_json::to_string(&fields).expect("a map of JSON values always encodes")
}
}
impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.key())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_key_is_the_sorted_json_encoding() {
let identifiers = [
(
Identifier::new("RoomChannel"),
r#"{"channel":"RoomChannel"}"#,
),
(
Identifier::new("RoomChannel").param("id", 42),
r#"{"channel":"RoomChannel","id":42}"#,
),
(
Identifier::new("RoomChannel")
.param("since", "yesterday")
.param("id", 42),
r#"{"channel":"RoomChannel","id":42,"since":"yesterday"}"#,
),
(
Identifier::new("RoomChannel").param("channel", "impostor"),
r#"{"channel":"RoomChannel"}"#,
),
];
for (identifier, key) in identifiers {
assert_eq!(key, identifier.key());
assert_eq!(key, identifier.to_string());
}
}
#[test]
fn an_identifier_always_encodes() {
let identifier = Identifier::new("RoomChannel").param("id", f64::NAN);
assert_eq!(r#"{"channel":"RoomChannel","id":null}"#, identifier.key());
}
}