lavina/crates/proto-irc/src/lib.rs

165 lines
4.4 KiB
Rust

//! Client-to-Server IRC protocol.
pub mod client;
mod prelude;
pub mod server;
#[cfg(test)]
mod testkit;
pub mod user;
use crate::prelude::Str;
use nom::{
branch::alt,
bytes::complete::{tag, take, take_while},
IResult,
};
use tokio::io::{AsyncWrite, AsyncWriteExt};
/// Single message tag value.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Tag {
pub key: Str,
pub value: Option<Str>,
}
impl Tag {
pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
writer.write_all(self.key.as_bytes()).await?;
if let Some(value) = &self.value {
writer.write_all(b"=").await?;
writer.write_all(value.as_bytes()).await?;
}
Ok(())
}
}
fn receiver(input: &str) -> IResult<&str, &str> {
take_while(|i| i != '\n' && i != '\r' && i != ' ')(input)
}
fn token(input: &str) -> IResult<&str, &str> {
take_while(|i| i != '\n' && i != '\r')(input)
}
fn params(input: &str) -> IResult<&str, &str> {
take_while(|i| i != '\n' && i != '\r' && i != ':')(input)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Chan {
/// `#<name>` — network-global channel, available from any server in the network.
Global(Str),
/// `&<name>` — server-local channel, available only to connections to the same server. Rarely used in practice.
Local(Str),
}
impl Chan {
pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
match self {
Chan::Global(name) => {
writer.write_all(b"#").await?;
writer.write_all(name.as_bytes()).await?;
}
Chan::Local(name) => {
writer.write_all(b"&").await?;
writer.write_all(name.as_bytes()).await?;
}
}
Ok(())
}
}
fn chan(input: &str) -> IResult<&str, Chan> {
fn chan_global(input: &str) -> IResult<&str, Chan> {
let (input, _) = tag("#")(input)?;
let (input, name) = receiver(input)?;
Ok((input, Chan::Global(name.into())))
}
fn chan_local(input: &str) -> IResult<&str, Chan> {
let (input, _) = tag("&")(input)?;
let (input, name) = receiver(input)?;
Ok((input, Chan::Local(name.into())))
}
alt((chan_global, chan_local))(input)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Recipient {
Nick(Str),
Chan(Chan),
}
impl Recipient {
pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> {
match self {
Recipient::Nick(nick) => writer.write_all(nick.as_bytes()).await?,
Recipient::Chan(chan) => chan.write_async(writer).await?,
}
Ok(())
}
}
fn recipient(input: &str) -> IResult<&str, Recipient> {
fn recipient_chan(input: &str) -> IResult<&str, Recipient> {
let (input, chan) = chan(input)?;
Ok((input, Recipient::Chan(chan)))
}
fn recipient_nick(input: &str) -> IResult<&str, Recipient> {
let (input, nick) = receiver(input)?;
Ok((input, Recipient::Nick(nick.into())))
}
alt((recipient_chan, recipient_nick))(input)
}
#[cfg(test)]
mod test {
use assert_matches::*;
use super::*;
use crate::testkit::*;
#[test]
fn test_chan_global() {
let input = "#testchan";
let expected = Chan::Global("testchan".into());
let result = chan(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
let mut bytes = vec![];
sync_future(expected.write_async(&mut bytes)).unwrap().unwrap();
assert_eq!(bytes.as_slice(), input.as_bytes());
}
#[test]
fn test_chan_local() {
let input = "&localchan";
let expected = Chan::Local("localchan".into());
let result = chan(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
let mut bytes = vec![];
sync_future(expected.write_async(&mut bytes)).unwrap().unwrap();
assert_eq!(bytes.as_slice(), input.as_bytes());
}
#[test]
fn test_recipient_user() {
let input = "User";
let expected = Recipient::Nick("User".into());
let result = recipient(input);
assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result));
let mut bytes = vec![];
sync_future(expected.write_async(&mut bytes)).unwrap().unwrap();
assert_eq!(bytes.as_slice(), input.as_bytes());
}
}