use std::fmt;
use irc_proto::chan::ChannelExt;
macro_rules! string_newtype {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct $name(String);
impl $name {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for $name {
fn from(s: String) -> Self {
$name(s)
}
}
impl From<&str> for $name {
fn from(s: &str) -> Self {
$name(s.to_string())
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
&self.0
}
}
impl PartialEq<str> for $name {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl PartialEq<&str> for $name {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
};
}
string_newtype! {
Nick
}
string_newtype! {
Channel
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Target {
Channel(Channel),
User(Nick),
}
impl Target {
#[must_use]
pub fn from_raw(target: &str) -> Self {
if target.is_channel_name() {
Target::Channel(Channel::from(target))
} else {
Target::User(Nick::from(target))
}
}
#[must_use]
pub fn is_channel(&self) -> bool {
matches!(self, Target::Channel(_))
}
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Target::Channel(c) => c.as_str(),
Target::User(n) => n.as_str(),
}
}
}
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn as_str_and_display_round_trip() {
let n = Nick::from("alice");
assert_eq!(n.as_str(), "alice");
assert_eq!(n.to_string(), "alice");
}
#[test]
fn from_string_and_str_are_equivalent() {
assert_eq!(Channel::from("#rust"), Channel::from("#rust".to_string()));
}
#[test]
fn compares_against_str_slices() {
assert_eq!(Nick::from("bob"), "bob");
assert_ne!(Nick::from("bob"), "alice");
}
#[test]
fn into_string_unwraps() {
assert_eq!(Channel::from("#a").into_string(), "#a".to_string());
}
#[test]
fn target_from_raw_picks_variant_by_prefix() {
assert_eq!(
Target::from_raw("#rust"),
Target::Channel(Channel::from("#rust"))
);
assert_eq!(Target::from_raw("alice"), Target::User(Nick::from("alice")));
assert!(!Target::from_raw("").is_channel());
}
#[test]
fn target_as_str_and_display_delegate_to_inner() {
let t = Target::Channel(Channel::from("#a"));
assert_eq!(t.as_str(), "#a");
assert_eq!(t.to_string(), "#a");
assert!(t.is_channel());
}
#[test]
fn nick_and_channel_are_distinct_types() {
let n = Nick::from("x");
let c = Channel::from("#x");
assert_eq!(n.as_str(), "x");
assert_eq!(c.as_str(), "#x");
}
}