use std::{fmt::Formatter, ops::Deref};
use serde::{de::Error, Deserialize, Deserializer};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct EmojiString(String);
impl Deref for EmojiString {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<&str> for EmojiString {
fn from(mut value: &str) -> Self {
let mut o = String::new();
while let Some((i, m, n, j)) = value
.find(':')
.map(|i| (i, i + 1))
.and_then(|(i, m)| value[m..].find(':').map(|x| (i, m, m + x, m + x + 1)))
{
if let Some(emoji) = emojis::get_by_shortcode(&value[m..n]) {
o.push_str(&value[..i]);
o.push_str(emoji.as_str());
value = &value[j..];
} else {
o.push_str(&value[..n]);
value = &value[n..];
}
}
o.push_str(value);
Self(o)
}
}
impl From<EmojiString> for String {
fn from(value: EmojiString) -> Self {
value.0
}
}
impl<'de> Deserialize<'de> for EmojiString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = EmojiString;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a string containing emoji shortcodes")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v.into())
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: Error,
{
let s = std::str::from_utf8(v)
.map_err(|_| E::invalid_value(serde::de::Unexpected::Bytes(v), &self))?;
Ok(s.into())
}
}
deserializer.deserialize_str(Visitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_from_str() {
let tests = [
("launch nothing", "launch nothing"),
("launch :rocket: something", "launch 🚀 something"),
("? :unknown: emoji", "? :unknown: emoji"),
("::very:naughty::", "::very:naughty::"),
(":maybe:rocket:", ":maybe🚀"),
(":rocket::rocket:", "🚀🚀"),
];
for (i, o) in tests {
let i: EmojiString = i.into();
assert_eq!(&*i, o);
}
}
#[test]
pub fn test_deserialize() {
let tests = [
("launch nothing", "launch nothing"),
("launch :rocket: something", "launch 🚀 something"),
("? :unknown: emoji", "? :unknown: emoji"),
("::very:naughty::", "::very:naughty::"),
(":maybe:rocket:", ":maybe🚀"),
(":rocket::rocket:", "🚀🚀"),
];
for (i, o) in tests {
let i: EmojiString = serde_json::from_str(&format!("\"{i}\"")).unwrap();
assert_eq!(&*i, o);
}
}
}