use std::ops::Range;
#[derive(Clone)]
pub struct TxtRecord(Vec<u8>);
impl TxtRecord {
pub fn new() -> Self {
TxtRecord(Vec::new())
}
pub fn with_capacity(capacity: usize) -> Self {
TxtRecord(Vec::with_capacity(capacity))
}
pub fn reserve(&mut self, additional: usize) {
self.0.reserve(additional);
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn clear(&mut self) {
self.0.clear();
}
pub fn data(&self) -> &[u8] {
&self.0
}
pub fn rdata(&self) -> &[u8] {
if self.0.is_empty() {
&[0x00] } else {
&self.0
}
}
fn _position_keys(&self) -> PositionKeyIter {
PositionKeyIter {
pos: 0,
data: &self.0,
}
}
pub fn iter(&self) -> TxtRecordIter {
TxtRecordIter {
pos: 0,
data: &self.0,
}
}
#[cfg_attr(feature = "cargo-clippy", allow(option_option))]
pub fn get(&self, key: &[u8]) -> Option<Option<&[u8]>> {
self.iter().find(|&(k, _)| key == k).map(|(_, value)| value)
}
pub fn remove(&mut self, key: &[u8]) {
if let Some((loc, _)) = self._position_keys().find(|&(_, k)| key == k) {
self.0.drain(loc);
}
}
pub fn set(
&mut self,
key: &[u8],
value: Option<&[u8]>,
) -> Result<(), TxtRecordError> {
for &k in key {
if k == b'=' || k < 0x20 || k > 0x7e {
return Err(TxtRecordError::InvalidKey);
}
}
let entry_len = key.len() + value.map(|v| v.len() + 1).unwrap_or(0);
if entry_len > 255 {
return Err(TxtRecordError::EntryTooLong);
}
self.remove(key);
self.0.push(entry_len as u8);
self.0.extend_from_slice(key);
if let Some(value) = value {
self.0.push(b'=');
self.0.extend_from_slice(value);
}
Ok(())
}
pub fn set_no_value(&mut self, key: &[u8]) -> Result<(), TxtRecordError> {
self.set(key, None)
}
pub fn set_value(
&mut self,
key: &[u8],
value: &[u8],
) -> Result<(), TxtRecordError> {
self.set(key, Some(value))
}
}
impl Default for TxtRecord {
fn default() -> Self {
TxtRecord::new()
}
}
impl<'a> IntoIterator for &'a TxtRecord {
type IntoIter = TxtRecordIter<'a>;
type Item = (&'a [u8], Option<&'a [u8]>);
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum TxtRecordError {
InvalidKey,
EntryTooLong,
}
struct PositionKeyIter<'a> {
pos: usize,
data: &'a [u8],
}
impl<'a> Iterator for PositionKeyIter<'a> {
type Item = (Range<usize>, &'a [u8]);
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
let len = self.data[0] as usize;
let entry_pos = self.pos;
let entry = &self.data[1..][..len];
self.data = &self.data[len + 1..];
self.pos += len + 1;
Some(match entry.iter().position(|&b| b == b'=') {
Some(pos) => (entry_pos..self.pos, &entry[..pos]),
None => (entry_pos..self.pos, entry),
})
}
}
pub struct TxtRecordIter<'a> {
pos: usize,
data: &'a [u8],
}
impl<'a> Iterator for TxtRecordIter<'a> {
type Item = (&'a [u8], Option<&'a [u8]>);
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
let len = self.data[0] as usize;
let entry = &self.data[1..][..len];
self.data = &self.data[len + 1..];
self.pos += len + 1;
Some(match entry.iter().position(|&b| b == b'=') {
Some(pos) => (&entry[..pos], Some(&entry[pos + 1..])),
None => (entry, None),
})
}
}
#[cfg(test)]
mod tests {
use super::TxtRecord;
#[test]
fn modifications() {
let mut r = TxtRecord::new();
assert!(r.is_empty());
assert_eq!(r.data(), b"");
assert_eq!(r.rdata(), b"\x00");
r.set(b"foo", Some(b"bar")).unwrap();
assert!(!r.is_empty());
assert_eq!(r.data(), b"\x07foo=bar");
assert_eq!(r.rdata(), b"\x07foo=bar");
r.set(b"u", Some(b"vw")).unwrap();
assert!(!r.is_empty());
assert_eq!(r.data(), b"\x07foo=bar\x04u=vw");
assert_eq!(r.rdata(), b"\x07foo=bar\x04u=vw");
assert_eq!(
r.iter().collect::<Vec<_>>(),
vec![
(b"foo" as &[u8], Some(b"bar" as &[u8])),
(b"u", Some(b"vw")),
]
);
r.set(b"foo", None).unwrap();
assert!(!r.is_empty());
assert_eq!(r.data(), b"\x04u=vw\x03foo");
assert_eq!(r.rdata(), b"\x04u=vw\x03foo");
assert_eq!(
r.iter().collect::<Vec<_>>(),
vec![(b"u" as &[u8], Some(b"vw" as &[u8])), (b"foo", None),]
);
r.set(b"foo", Some(b"bar")).unwrap();
assert!(!r.is_empty());
assert_eq!(r.data(), b"\x04u=vw\x07foo=bar");
assert_eq!(r.rdata(), b"\x04u=vw\x07foo=bar");
r.remove(b"foo");
assert!(!r.is_empty());
assert_eq!(r.data(), b"\x04u=vw");
assert_eq!(r.rdata(), b"\x04u=vw");
}
}