use harmony_derive::into_request;
use std::{
convert::TryFrom,
fmt::{self, Display, Formatter},
};
pub mod v1 {
#![allow(clippy::unit_arg)]
hrpc::include_proto!("protocol.chat.v1");
pub mod all_permissions {
#![allow(clippy::unit_arg)]
hrpc::include_proto!("permissions");
}
}
pub use v1::*;
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
Chat(stream_event::Event),
Profile(super::profile::stream_event::Event),
Emote(super::emote::stream_event::Event),
}
pub struct EventFromResponseError;
impl TryFrom<stream_events_response::Event> for Event {
type Error = EventFromResponseError;
fn try_from(value: stream_events_response::Event) -> Result<Self, Self::Error> {
(match value {
stream_events_response::Event::Chat(s) => s.event.map(Self::Chat),
stream_events_response::Event::Emote(e) => e.event.map(Self::Emote),
stream_events_response::Event::Profile(p) => p.event.map(Self::Profile),
})
.ok_or(EventFromResponseError)
}
}
impl From<Event> for stream_events_response::Event {
fn from(ev: Event) -> Self {
match ev {
Event::Chat(ev) => stream_events_response::Event::Chat(StreamEvent { event: Some(ev) }),
Event::Emote(ev) => {
stream_events_response::Event::Emote(super::emote::StreamEvent { event: Some(ev) })
}
Event::Profile(ev) => {
stream_events_response::Event::Profile(super::profile::StreamEvent {
event: Some(ev),
})
}
}
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum EventSource {
Guild(u64),
Homeserver,
Action,
}
impl From<EventSource> for StreamEventsRequest {
fn from(o: EventSource) -> StreamEventsRequest {
StreamEventsRequest {
request: Some(match o {
EventSource::Guild(id) => stream_events_request::Request::SubscribeToGuild(
stream_events_request::SubscribeToGuild { guild_id: id },
),
EventSource::Homeserver => {
stream_events_request::Request::SubscribeToHomeserverEvents(
stream_events_request::SubscribeToHomeserverEvents {},
)
}
EventSource::Action => stream_events_request::Request::SubscribeToActions(
stream_events_request::SubscribeToActions {},
),
}),
}
}
}
#[into_request("JoinGuildRequest", "PreviewGuildRequest")]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InviteId {
invite_id: String,
}
impl InviteId {
pub fn new(name: impl ToString) -> Option<Self> {
let name = name.to_string();
if name.is_empty() {
None
} else {
Some(Self { invite_id: name })
}
}
}
impl From<InviteId> for String {
fn from(other: InviteId) -> String {
other.invite_id
}
}
impl Display for InviteId {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
write!(fmt, "{}", self.invite_id)
}
}
pub mod color {
pub fn encode_rgb(color: impl Into<[u8; 3]>) -> i32 {
let color = color.into();
let mut c = color[0] as i32;
c = (c << 8) + color[1] as i32;
c = (c << 8) + color[2] as i32;
c as i32
}
pub fn decode_rgb(color: impl Into<i32>) -> [u8; 3] {
let color = color.into();
[
((color >> 16) & 255) as u8,
((color >> 8) & 255) as u8,
(color & 255) as u8,
]
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn encode() {
assert_eq!(encode_rgb([0, 0, 0]), 0);
}
#[test]
fn decode() {
assert_eq!(decode_rgb(0), [0, 0, 0]);
}
}
}
pub mod permission {
pub fn has_permission<'a, Perm, I>(perms: I, query: &str) -> Option<bool>
where
Perm: std::borrow::Borrow<(&'a str, bool)>,
I: Iterator<Item = Perm>,
{
use std::cmp::Ordering;
let mut matching_perms = perms
.filter(|p| {
let (matches, _) = p.borrow();
matches
.split('.')
.zip(query.split('.'))
.all(|(m, c)| m == "*" || c == m)
})
.collect::<Vec<_>>();
matching_perms.sort_unstable_by(|p, op| {
let (m, _) = p.borrow();
let (om, _) = op.borrow();
let get_depth = |matches: &str| matches.chars().filter(|c| '.'.eq(c)).count();
let ord = get_depth(m).cmp(&get_depth(om));
if let Ordering::Equal = ord {
let p_split = m.split('.');
let op_split = om.split('.');
match (p_split.last(), op_split.last()) {
(Some(p_last), Some(op_last)) => match (p_last, op_last) {
("*", _) => Ordering::Less,
(_, "*") => Ordering::Greater,
_ => Ordering::Equal,
},
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(None, None) => Ordering::Equal,
}
} else {
ord
}
});
matching_perms.pop().map(|p| p.borrow().1)
}
#[cfg(test)]
mod tests {
use super::has_permission;
#[test]
fn test_perm_compare_equal_allow() {
let ok = has_permission([("messages.send", true)].iter(), "messages.send");
assert_eq!(ok, Some(true));
}
#[test]
fn test_perm_compare_equal_deny() {
let ok = has_permission(
std::array::IntoIter::new([("messages.send", false)]),
"messages.send",
);
assert_eq!(ok, Some(false));
}
#[test]
fn test_perm_compare_nonequal_allow() {
let ok = has_permission([("messages.sendd", true)].iter(), "messages.send");
assert_eq!(ok, None);
}
#[test]
fn test_perm_compare_nonequal_deny() {
let ok = has_permission([("messages.sendd", false)].iter(), "messages.send");
assert_eq!(ok, None);
}
#[test]
fn test_perm_compare_glob_allow() {
let perms = [("messages.*", true)];
let ok = has_permission(perms.iter(), "messages.send");
assert_eq!(ok, Some(true));
let ok = has_permission(perms.iter(), "messages.view");
assert_eq!(ok, Some(true));
}
#[test]
fn test_perm_compare_glob_deny() {
let perms = [("messages.*", false)];
let ok = has_permission(perms.iter(), "messages.send");
assert_eq!(ok, Some(false));
let ok = has_permission(perms.iter(), "messages.view");
assert_eq!(ok, Some(false));
}
#[test]
fn test_perm_compare_specific_deny() {
let perms = [("messages.*", true), ("messages.send", false)];
let ok = has_permission(perms.iter(), "messages.send");
assert_eq!(ok, Some(false));
}
#[test]
fn test_perm_compare_specific_allow() {
let perms = [("messages.*", false), ("messages.send", true)];
let ok = has_permission(perms.iter(), "messages.send");
assert_eq!(ok, Some(true));
}
#[test]
fn test_perm_compare_depth_allow() {
let perms = [
("messages.*", false),
("messages.send", false),
("messages.send.send", true),
];
let ok = has_permission(perms.iter(), "messages.send.send");
assert_eq!(ok, Some(true));
}
#[test]
fn test_perm_compare_depth_deny() {
let perms = [
("messages.*", true),
("messages.send", true),
("messages.send.send", false),
];
let ok = has_permission(perms.iter(), "messages.send.send");
assert_eq!(ok, Some(false));
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[should_panic]
fn empty_invite_id() {
InviteId::new("").unwrap();
}
#[test]
fn invite_id() {
const ID: &str = "harmony";
assert_eq!(
InviteId::new(ID).unwrap(),
InviteId {
invite_id: ID.to_string()
}
);
}
}