documentation and `impl TryFrom<&[u8]> for Line`

This commit is contained in:
jesopo 2023-03-23 18:12:04 +00:00
parent 0c38b5bb80
commit 147eeff8d1
8 changed files with 64 additions and 27 deletions

View File

@ -1,7 +1,6 @@
use std::collections::BTreeMap;
use criterion::{criterion_group, criterion_main, Criterion};
use irctokens::Line;
use std::collections::BTreeMap;
fn criterion_benchmark(c: &mut Criterion) {
let line = Line {

View File

@ -2,7 +2,8 @@ use criterion::{criterion_group, criterion_main, Criterion};
use irctokens::Line;
fn basic() {
Line::tokenise(b"@tag1=tag1value;tag2=;tag3 :source COMMAND arg1 arg2 :arg3 with space").unwrap();
Line::tokenise(b"@tag1=tag1value;tag2=;tag3 :source COMMAND arg1 arg2 :arg3 with space")
.unwrap();
}
fn criterion_benchmark(c: &mut Criterion) {

View File

@ -18,6 +18,12 @@ fn tag_encode(input: &str) -> String {
}
impl Line {
#[allow(clippy::doc_markdown)]
/// Format `self` in to a byte string by [RFC1459] and [IRCv3] protocol rules.
///
/// [RFC1459]: https://www.rfc-editor.org/rfc/rfc1459#section-2.3
/// [IRCv3]: https://ircv3.net/specs/extensions/message-tags.html
#[must_use]
pub fn format(&self) -> Vec<u8> {
let mut output = Vec::new();
@ -45,9 +51,9 @@ impl Line {
output.extend_from_slice(self.command.as_bytes());
for (i, arg) in self.args.iter().enumerate() {
for (i, arg) in self.arguments.iter().enumerate() {
output.push(b' ');
if i == self.args.len() - 1 {
if i == self.arguments.len() - 1 {
output.push(b':');
}
output.extend_from_slice(arg);

View File

@ -1,6 +1,6 @@
mod format;
mod obj;
mod tokenise;
pub mod tokenise;
mod util;
pub use self::obj::{Error, Line};
pub use self::obj::Line;

View File

@ -1,19 +1,22 @@
use std::collections::BTreeMap;
/// A struct representing all the constituent pieces of an RFC1459/IRCv3 protocol line.
///
/// `@tagkey=tagvalue :source COMMAND arg1 arg2 :arg3 with space`
pub struct Line {
// tags are promised to be utf8 encoded
/// [Message tags] of an IRC line.
/// [`None`] if no message tags were present.
/// keys and values are [`String`] because they are promised to be utf8 encoded.
///
/// [Message tags]: https://ircv3.net/specs/extensions/message-tags.html
pub tags: Option<BTreeMap<String, Option<String>>>,
/// The `:source` of an IRC line, or [`None`] if source is not present.
/// This is a [`Vec<u8>`] as it may be unpredictably encoded.
pub source: Option<Vec<u8>>,
// commands are promised to be ascii encoded
/// The `COMMAND` of an IRC line (e.g. `PRIVMSG`.)
/// This is a [`String`] because commands are promised to be ascii encoded.
pub command: String,
pub args: Vec<Vec<u8>>,
}
#[derive(Debug)]
pub enum Error {
Empty,
MissingCommand,
CommandDecode,
TagKeyDecode,
TagValueDecode,
/// The arguments of an IRC line.
/// These are [`Vec<u8>`]s as they may be unpredictably encoded.
pub arguments: Vec<Vec<u8>>,
}

View File

@ -1,10 +1,24 @@
use std::collections::{BTreeMap, VecDeque};
use super::util::TakeWord as _;
use super::{Error, Line};
use super::Line;
const TAG_STOP: [&[u8]; 2] = [b"", b"="];
#[derive(Debug)]
pub enum Error {
/// An empty byte array was passed to the tokeniser
Empty,
/// A line is invalid if it has no `COMMAND` (e.g. `PRIVMSG`)
MissingCommand,
/// Commands must be ascii encoded
CommandDecode,
/// Message tag keys must be utf8 encoded
TagKeyDecode,
/// Message tag values must be utf8 encoded
TagValueDecode,
}
fn tag_decode(input: &str) -> String {
let mut escaped = false;
let mut output = String::with_capacity(input.len());
@ -21,7 +35,7 @@ fn tag_decode(input: &str) -> String {
};
output.push(replace);
} else if char == 0x5c as char {
} else if char == '\\' {
// backslash
escaped = true;
} else {
@ -33,6 +47,11 @@ fn tag_decode(input: &str) -> String {
}
impl Line {
#[allow(clippy::doc_markdown)]
/// Attempt to tokenise a byte string by [RFC1459] and [IRCv3] protocol rules.
///
/// [RFC1459]: https://www.rfc-editor.org/rfc/rfc1459#section-2.3
/// [IRCv3]: https://ircv3.net/specs/extensions/message-tags.html
pub fn tokenise(mut line: &[u8]) -> Result<Self, Error> {
let tags = if line.first() == Some(&b'@') {
let mut tags = &line.take_word(b' ')[1..];
@ -78,7 +97,16 @@ impl Line {
tags,
source,
command: String::from_utf8(command).map_err(|_| Error::CommandDecode)?,
args: args.into(),
arguments: args.into(),
})
}
}
impl TryFrom<&[u8]> for Line {
type Error = Error;
/// Utility function for [`Line::tokenise()`]
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Self::tokenise(value)
}
}

View File

@ -11,7 +11,7 @@ fn basic() {
])),
source: Some(b"source".to_vec()),
command: "COMMAND".to_string(),
args: Vec::from([
arguments: Vec::from([
b"arg1".to_vec(),
b"arg2".to_vec(),
b"arg3 with space".to_vec(),

View File

@ -9,10 +9,10 @@ fn basic() {
assert_eq!(line.source, Some(b"source".to_vec()));
assert_eq!(&line.command, "COMMAND");
assert_eq!(line.args.len(), 3);
assert_eq!(line.args[0], b"arg1");
assert_eq!(line.args[1], b"arg2");
assert_eq!(line.args[2], b"arg3 with space");
assert_eq!(line.arguments.len(), 3);
assert_eq!(line.arguments[0], b"arg1");
assert_eq!(line.arguments[1], b"arg2");
assert_eq!(line.arguments[2], b"arg3 with space");
let tags = line.tags.unwrap();
assert_eq!(tags.len(), 3);