magnetar/magnetar_common/src/util.rs

219 lines
5.5 KiB
Rust

use magnetar_core::web_model::acct::Acct;
use magnetar_sdk::mmm;
use magnetar_sdk::mmm::Token;
use percent_encoding::percent_decode_str;
use std::fmt::Display;
use std::str::FromStr;
use thiserror::Error;
use url::Host;
#[derive(Clone, Debug)]
pub struct ValidName(String);
impl FromStr for ValidName {
type Err = FediverseTagParseError;
fn from_str(name: &str) -> Result<Self, Self::Err> {
if name
.chars()
.any(|c| !c.is_alphanumeric() && c != '-' && c != '_' && c != '.')
{
return Err(FediverseTagParseError::InvalidChar(name.to_owned()));
}
Ok(Self(name.to_owned()))
}
}
impl AsRef<str> for ValidName {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Display for ValidName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Debug)]
pub struct FediverseTag {
pub name: ValidName,
pub host: Option<Host>,
}
impl FediverseTag {
pub fn from_parts(parts: (&str, Option<&str>)) -> Result<Self, FediverseTagParseError> {
Self::try_from(FediverseTagDisplay::from_parts(parts)?)
}
}
impl TryFrom<FediverseTagDisplay> for FediverseTag {
type Error = FediverseTagParseError;
fn try_from(
FediverseTagDisplay { name, host }: FediverseTagDisplay,
) -> Result<Self, FediverseTagParseError> {
Ok(FediverseTag {
name,
host: host.as_deref().map(Host::parse).transpose()?,
})
}
}
impl TryFrom<&FediverseTagDisplay> for FediverseTag {
type Error = FediverseTagParseError;
fn try_from(
FediverseTagDisplay { name, host }: &FediverseTagDisplay,
) -> Result<Self, FediverseTagParseError> {
Ok(FediverseTag {
name: name.clone(),
host: host.as_deref().map(Host::parse).transpose()?,
})
}
}
impl From<&FediverseTag> for Acct {
fn from(tag: &FediverseTag) -> Self {
Acct::new(tag.to_string().into())
}
}
impl FromStr for FediverseTag {
type Err = FediverseTagParseError;
fn from_str(tag: &str) -> Result<Self, Self::Err> {
Self::try_from(FediverseTagDisplay::from_str(tag)?)
}
}
impl Display for FediverseTag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(ref host) = self.host {
write!(f, "{}@{}", self.name, host)
} else {
write!(f, "{}", self.name)
}
}
}
#[derive(Clone, Debug)]
pub struct FediverseTagDisplay {
pub name: ValidName,
pub host: Option<String>,
}
impl FediverseTagDisplay {
pub fn from_parts((name, host): (&str, Option<&str>)) -> Result<Self, FediverseTagParseError> {
Ok(FediverseTagDisplay {
name: name.parse()?,
host: host.map(str::to_owned),
})
}
}
impl From<&FediverseTag> for FediverseTagDisplay {
fn from(FediverseTag { name, host }: &FediverseTag) -> Self {
FediverseTagDisplay {
name: name.clone(),
host: host
.as_ref()
.map(Host::to_string)
.as_deref()
.map(idna::domain_to_unicode)
.map(|v| v.0),
}
}
}
impl TryFrom<&Acct> for FediverseTagDisplay {
type Error = FediverseTagParseError;
fn try_from(acct: &Acct) -> Result<Self, Self::Error> {
acct.as_ref().parse()
}
}
impl FromStr for FediverseTagDisplay {
type Err = FediverseTagParseError;
fn from_str(tag: &str) -> Result<Self, Self::Err> {
Self::from_parts(split_tag_inner(tag))
}
}
impl Display for FediverseTagDisplay {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(ref host) = self.host {
write!(f, "{}@{}", self.name, host)
} else {
write!(f, "{}", self.name)
}
}
}
#[derive(Debug, Error)]
pub enum FediverseTagParseError {
#[error("Invalid char in tag: {0}")]
InvalidChar(String),
#[error("Invalid UTF-8 in tag: {0}")]
InvalidUtf8(#[from] std::str::Utf8Error),
#[error("Invalid host in tag: {0}")]
InvalidHost(#[from] url::ParseError),
}
impl From<&FediverseTagParseError> for &str {
fn from(_: &FediverseTagParseError) -> Self {
"FediverseTagParseError"
}
}
impl From<FediverseTag> for Acct {
fn from(value: FediverseTag) -> Self {
value.to_string().into()
}
}
fn split_tag_inner(tag: &str) -> (&str, Option<&str>) {
let tag = tag.strip_prefix('@').unwrap_or(tag.as_ref());
match tag.split_once('@') {
Some((name, "")) => (name, None),
Some((name, host)) => (name, Some(host)),
None => (tag, None),
}
}
pub fn lenient_parse_tag_decode(
tag: impl AsRef<str>,
) -> Result<FediverseTag, FediverseTagParseError> {
percent_decode_str(tag.as_ref()).decode_utf8()?.parse()
}
pub fn lenient_parse_acct_decode(acct: &Acct) -> Result<FediverseTag, FediverseTagParseError> {
lenient_parse_tag_decode(acct.as_ref())
}
#[derive(Debug)]
pub enum RawReaction {
Unicode(String),
Shortcode {
shortcode: String,
host: Option<String>,
},
}
pub fn parse_reaction(tag: impl AsRef<str>) -> Option<RawReaction> {
let tok = mmm::Context::default().parse_ui(tag.as_ref());
match tok {
Token::UnicodeEmoji(text) => Some(RawReaction::Unicode(text)),
Token::ShortcodeEmoji { shortcode, host } => {
Some(RawReaction::Shortcode { shortcode, host })
}
_ => None,
}
}