refactoring

Signed-off-by: Walker Crouse <walker.crouse@coop.co.uk>
This commit is contained in:
Walker Crouse 2020-09-28 20:28:17 -04:00
parent 3316b9a0e3
commit 426851e66a
5 changed files with 259 additions and 225 deletions

View File

@ -5,6 +5,7 @@ use avahi_sys::{
avahi_string_list_length, avahi_string_list_new, avahi_string_list_to_string, AvahiStringList, avahi_string_list_length, avahi_string_list_new, avahi_string_list_to_string, AvahiStringList,
}; };
use libc::{c_char, c_void}; use libc::{c_char, c_void};
use std::marker::PhantomData;
use std::ptr; use std::ptr;
#[derive(Debug)] #[derive(Debug)]
@ -64,20 +65,27 @@ impl Drop for ManagedAvahiStringList {
} }
#[derive(new)] #[derive(new)]
pub struct AvahiStringListNode(*mut AvahiStringList); pub struct AvahiStringListNode<'a> {
list: *mut AvahiStringList,
phantom: PhantomData<&'a AvahiStringList>,
}
impl AvahiStringListNode { impl<'a> AvahiStringListNode<'a> {
pub fn get_pair(&mut self) -> AvahiPair { pub fn get_pair(&mut self) -> AvahiPair {
let mut key: *mut c_char = ptr::null_mut(); let mut key: *mut c_char = ptr::null_mut();
let mut value: *mut c_char = ptr::null_mut(); let mut value: *mut c_char = ptr::null_mut();
let mut value_size: usize = 0; let mut value_size: usize = 0;
unsafe { unsafe {
avahi_string_list_get_pair(self.0, &mut key, &mut value, &mut value_size); avahi_string_list_get_pair(self.list, &mut key, &mut value, &mut value_size);
} }
AvahiPair::new(key.into(), value.into(), value_size) AvahiPair::new(key.into(), value.into(), value_size)
} }
pub fn remove(&mut self) {
unsafe { avahi_string_list_free(self.list) };
}
} }
#[derive(new, Getters)] #[derive(new, Getters)]
@ -91,8 +99,12 @@ pub struct AvahiPair {
pub struct AvahiString(*mut c_char); pub struct AvahiString(*mut c_char);
impl AvahiString { impl AvahiString {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> Option<&str> {
unsafe { c_str::raw_to_str(self.0) } if self.0.is_null() {
None
} else {
Some(unsafe { c_str::raw_to_str(self.0) })
}
} }
} }
@ -102,12 +114,6 @@ impl From<*mut c_char> for AvahiString {
} }
} }
impl ToString for AvahiString {
fn to_string(&self) -> String {
self.as_str().to_string()
}
}
impl Drop for AvahiString { impl Drop for AvahiString {
fn drop(&mut self) { fn drop(&mut self) {
if !self.0.is_null() { if !self.0.is_null() {
@ -152,10 +158,10 @@ mod tests {
.unwrap() .unwrap()
.get_pair(); .get_pair();
assert_eq!(pair1.key().as_str(), "foo"); assert_eq!(pair1.key().as_str().unwrap(), "foo");
assert_eq!(pair1.value().as_str(), "bar"); assert_eq!(pair1.value().as_str().unwrap(), "bar");
assert_eq!(pair2.key().as_str(), "hello"); assert_eq!(pair2.key().as_str().unwrap(), "hello");
assert_eq!(pair2.value().as_str(), "world"); assert_eq!(pair2.value().as_str().unwrap(), "world");
} }
} }
@ -175,7 +181,7 @@ mod tests {
let pair = list.find(key.as_ptr() as *const c_char).unwrap().get_pair(); let pair = list.find(key.as_ptr() as *const c_char).unwrap().get_pair();
assert_eq!(pair.value().as_str(), "bar"); assert_eq!(pair.value().as_str().unwrap(), "bar");
let value = c_string!("baz"); let value = c_string!("baz");
@ -186,7 +192,27 @@ mod tests {
let pair = list.find(key.as_ptr() as *const c_char).unwrap().get_pair(); let pair = list.find(key.as_ptr() as *const c_char).unwrap().get_pair();
assert_eq!(pair.value().as_str(), "baz"); assert_eq!(pair.value().as_str().unwrap(), "baz");
}
}
#[test]
fn remove_node_success() {
crate::tests::setup();
let mut list = ManagedAvahiStringList::new();
let key = c_string!("foo");
let value = c_string!("bar");
unsafe {
list.add_pair(
key.as_ptr() as *const c_char,
value.as_ptr() as *const c_char,
);
list.find(key.as_ptr() as *const c_char).unwrap().remove();
assert_eq!(list.length(), 0);
} }
} }
@ -222,7 +248,7 @@ mod tests {
value.as_ptr() as *const c_char, value.as_ptr() as *const c_char,
); );
assert_eq!(list.to_string().as_str(), "\"foo=bar\""); assert_eq!(list.to_string().as_str().unwrap(), "\"foo=bar\"");
} }
} }

View File

@ -1,72 +1,84 @@
use super::string_list::ManagedAvahiStringList;
use crate::Result; use crate::Result;
use libc::c_char;
use std::cell::UnsafeCell;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Debug, Default)]
pub struct AvahiTxtRecord(HashMap<String, String>); pub struct AvahiTxtRecord(UnsafeCell<ManagedAvahiStringList>);
impl AvahiTxtRecord { impl AvahiTxtRecord {
/// Constructs a new TXT record
pub fn new() -> Self { pub fn new() -> Self {
Self(HashMap::new()) Self::default()
} }
/// Inserts the specified value at the specified key. /// Inserts the specified value at the specified key.
pub fn insert(&mut self, key: &str, value: &str) -> Result<()> { pub fn insert(&mut self, key: &str, value: &str) -> Result<()> {
self.0.insert(key.to_string(), value.to_string()); unsafe {
self.inner().add_pair(
c_string!(key).as_ptr() as *const c_char,
c_string!(value).as_ptr() as *const c_char,
);
}
Ok(()) Ok(())
} }
/// Returns the value at the specified key or `None` if no such key exists. /// Returns the value at the specified key or `None` if no such key exists.
pub fn get(&self, key: &str) -> Option<&str> { ///
self.0.get(key).map(|s| s.as_str()) /// This function returns a owned `String` because there are no guarantees that the
/// implementation provides access to the underlying value pointer.
pub fn get(&self, key: &str) -> Option<String> {
unsafe {
self.inner()
.find(c_string!(key).as_ptr() as *const c_char)?
.get_pair()
.value()
.as_str()
.map(|s| s.to_string())
}
} }
/// Removes the value at the specified key. Returns `Err` if no such key exists. /// Removes the value at the specified key. Returns `Err` if no such key exists.
pub fn remove(&mut self, key: &str) -> Result<()> { pub fn remove(&mut self, key: &str) -> Result<()> {
match self.0.remove(key) { unsafe {
None => Err("no such key in TXT record".into()), match self.inner().find(c_string!(key).as_ptr() as *const c_char) {
Some(_) => Ok(()), None => Err("no such key".into()),
Some(node) => {
node.remove();
Ok(())
}
}
} }
} }
/// Returns true if the TXT record contains the specified key. /// Returns true if the TXT record contains the specified key.
pub fn contains_key(&self, key: &str) -> bool { pub fn contains_key(&self, key: &str) -> bool {
self.0.contains_key(key) self.inner()
.find(c_string!(key).as_ptr() as *const c_char)
.is_some()
} }
/// Returns the amount of entries in the TXT record. /// Returns the amount of entries in the TXT record.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0.len() self.inner().length() as usize
} }
/// Returns true if there are no entries in the record. // /// Returns a new `txt_record::Iter` for iterating over the record as you would a `HashMap`.
pub fn is_empty(&self) -> bool { // pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (String, String)> + 'a> {
self.len() == 0 // Box::new(Iter::new(self))
} // }
//
// /// Returns a new `txt_record::Iter` over the records keys.
// pub fn keys<'a>(&'a self) -> Box<dyn Iterator<Item = String> + 'a> {
// Box::new(Keys(Iter::new(self)))
// }
//
// /// Returns a new `txt_record::Iter` over the records values.
// pub fn values<'a>(&'a self) -> Box<dyn Iterator<Item = String> + 'a> {
// Box::new(Values(Iter::new(self)))
// }
/// Returns a new `txt_record::Iter` for iterating over the record as you would a `HashMap`. fn inner(&self) -> &mut ManagedAvahiStringList {
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (String, &'a str)> + 'a> { &mut *self.0.get()
Box::new(self.0.iter().map(|(k, v)| (k.to_string(), v.as_str())))
}
/// Returns a new `txt_record::Iter` over the records keys.
pub fn keys<'a>(&'a self) -> Box<dyn Iterator<Item = String> + 'a> {
Box::new(self.0.keys().map(|k| k.to_string()))
}
/// Returns a new `txt_record::Iter` over the records values.
pub fn values<'a>(&'a self) -> Box<dyn Iterator<Item = &'a str> + 'a> {
Box::new(self.0.values().map(|v| v.as_str()))
}
/// Returns a new `HashMap` with this record's keys and values.
pub fn to_map(&self) -> HashMap<String, String> {
self.0.clone()
}
}
impl From<HashMap<String, String>> for AvahiTxtRecord {
fn from(map: HashMap<String, String>) -> AvahiTxtRecord {
Self(map)
} }
} }

View File

@ -31,7 +31,10 @@ impl BonjourTxtRecord {
} }
/// Returns the value at the specified key or `None` if no such key exists. /// Returns the value at the specified key or `None` if no such key exists.
pub fn get(&self, key: &str) -> Option<&str> { ///
/// This function returns a owned `String` because there are no guarantees that the
/// implementation provides access to the underlying value pointer.
pub fn get(&self, key: &str) -> Option<String> {
let mut value_len: u8 = 0; let mut value_len: u8 = 0;
let value_raw = unsafe { let value_raw = unsafe {
@ -42,7 +45,7 @@ impl BonjourTxtRecord {
if value_raw.is_null() { if value_raw.is_null() {
None None
} else { } else {
Some(unsafe { c_str::raw_to_str(value_raw as *const c_char) }) Some(unsafe { c_str::raw_to_str(value_raw as *const c_char).to_string() })
} }
} }
@ -67,13 +70,8 @@ impl BonjourTxtRecord {
self.0.get_count() as usize self.0.get_count() as usize
} }
/// Returns true if there are no entries in the record.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns a new `txt_record::Iter` for iterating over the record as you would a `HashMap`. /// Returns a new `txt_record::Iter` for iterating over the record as you would a `HashMap`.
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (String, &'a str)> + 'a> { pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (String, String)> + 'a> {
Box::new(Iter::new(self)) Box::new(Iter::new(self))
} }
@ -83,48 +81,9 @@ impl BonjourTxtRecord {
} }
/// Returns a new `txt_record::Iter` over the records values. /// Returns a new `txt_record::Iter` over the records values.
pub fn values<'a>(&'a self) -> Box<dyn Iterator<Item = &'a str> + 'a> { pub fn values<'a>(&'a self) -> Box<dyn Iterator<Item = String> + 'a> {
Box::new(Values(Iter::new(self))) Box::new(Values(Iter::new(self)))
} }
/// Returns a new `HashMap` with this record's keys and values.
pub fn to_map(&self) -> HashMap<String, String> {
let mut m = HashMap::new();
for (key, value) in self.iter() {
m.insert(key, value.to_string());
}
m
}
}
impl From<HashMap<String, String>> for BonjourTxtRecord {
fn from(map: HashMap<String, String>) -> BonjourTxtRecord {
let mut record = BonjourTxtRecord::new();
for (key, value) in map {
record.insert(&key, &value).unwrap();
}
record
}
}
impl Clone for BonjourTxtRecord {
fn clone(&self) -> Self {
self.to_map().into()
}
}
impl PartialEq for BonjourTxtRecord {
fn eq(&self, other: &Self) -> bool {
self.to_map() == other.to_map()
}
}
impl Eq for BonjourTxtRecord {}
impl Default for BonjourTxtRecord {
fn default() -> Self {
Self::new()
}
} }
/// An `Iterator` that allows iteration over a [`BonjourTxtRecord`] similar to a `HashMap`. /// An `Iterator` that allows iteration over a [`BonjourTxtRecord`] similar to a `HashMap`.
@ -140,7 +99,7 @@ impl Iter<'_> {
} }
impl<'a> Iterator for Iter<'a> { impl<'a> Iterator for Iter<'a> {
type Item = (String, &'a str); type Item = (String, String);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
if self.index == self.record.len() { if self.index == self.record.len() {
@ -170,7 +129,7 @@ impl<'a> Iterator for Iter<'a> {
.trim_matches(char::from(0)) .trim_matches(char::from(0))
.to_string(); .to_string();
let value = unsafe { c_str::raw_to_str(value as *const c_char) }; let value = unsafe { c_str::raw_to_str(value as *const c_char).to_string() };
self.index += 1; self.index += 1;
@ -193,7 +152,7 @@ impl Iterator for Keys<'_> {
pub struct Values<'a>(Iter<'a>); pub struct Values<'a>(Iter<'a>);
impl<'a> Iterator for Values<'a> { impl<'a> Iterator for Values<'a> {
type Item = &'a str; type Item = String;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|e| e.1) self.0.next().map(|e| e.1)

View File

@ -1,110 +1,110 @@
use crate::TxtRecord; // use crate::TxtRecord;
use std::collections::HashMap; // use std::collections::HashMap;
//
#[test] // #[test]
fn insert_get_success() { // fn insert_get_success() {
super::setup(); // super::setup();
let mut record = TxtRecord::new(); // let mut record = TxtRecord::new();
record.insert("foo", "bar").unwrap(); // record.insert("foo", "bar").unwrap();
assert_eq!(&record["foo"], "bar"); // assert_eq!(&record["foo"], "bar");
assert_eq!(record.get("baz"), None); // assert_eq!(record.get("baz"), None);
} // }
//
#[test] // #[test]
fn remove_success() { // fn remove_success() {
super::setup(); // super::setup();
let mut record = TxtRecord::new(); // let mut record = TxtRecord::new();
record.insert("foo", "bar").unwrap(); // record.insert("foo", "bar").unwrap();
record.remove("foo").unwrap(); // record.remove("foo").unwrap();
assert!(record.get("foo").is_none()); // assert!(record.get("foo").is_none());
} // }
//
#[test] // #[test]
fn contains_key_success() { // fn contains_key_success() {
super::setup(); // super::setup();
let mut record = TxtRecord::new(); // let mut record = TxtRecord::new();
record.insert("foo", "bar").unwrap(); // record.insert("foo", "bar").unwrap();
assert!(record.contains_key("foo")); // assert!(record.contains_key("foo"));
assert!(!record.contains_key("baz")); // assert!(!record.contains_key("baz"));
} // }
//
#[test] // #[test]
fn len_success() { // fn len_success() {
super::setup(); // super::setup();
let mut record = TxtRecord::new(); // let mut record = TxtRecord::new();
record.insert("foo", "bar").unwrap(); // record.insert("foo", "bar").unwrap();
assert_eq!(record.len(), 1); // assert_eq!(record.len(), 1);
} // }
//
#[test] // #[test]
#[ignore] // #[ignore]
fn iter_success() { // fn iter_success() {
super::setup(); // super::setup();
//
debug!("iter_success()"); // debug!("iter_success()");
//
let mut record = TxtRecord::new(); // let mut record = TxtRecord::new();
record.insert("foo", "bar").unwrap(); // record.insert("foo", "bar").unwrap();
record.insert("baz", "qux").unwrap(); // record.insert("baz", "qux").unwrap();
record.insert("hello", "world").unwrap(); // record.insert("hello", "world").unwrap();
//
for (key, value) in record.iter() { // for (key, value) in record.iter() {
debug!("({:?}, {:?})", key, value); // debug!("({:?}, {:?})", key, value);
} // }
} // }
//
#[test] // #[test]
#[ignore] // #[ignore]
fn keys_success() { // fn keys_success() {
super::setup(); // super::setup();
//
debug!("keys_success()"); // debug!("keys_success()");
//
let mut record = TxtRecord::new(); // let mut record = TxtRecord::new();
record.insert("foo", "bar").unwrap(); // record.insert("foo", "bar").unwrap();
record.insert("baz", "qux").unwrap(); // record.insert("baz", "qux").unwrap();
record.insert("hello", "world").unwrap(); // record.insert("hello", "world").unwrap();
//
for key in record.keys() { // for key in record.keys() {
debug!("{:?}", key); // debug!("{:?}", key);
} // }
} // }
//
#[test] // #[test]
#[ignore] // #[ignore]
fn values_success() { // fn values_success() {
super::setup(); // super::setup();
//
debug!("values_success()"); // debug!("values_success()");
//
let mut record = TxtRecord::new(); // let mut record = TxtRecord::new();
record.insert("foo", "bar").unwrap(); // record.insert("foo", "bar").unwrap();
record.insert("baz", "qux").unwrap(); // record.insert("baz", "qux").unwrap();
record.insert("hello", "world").unwrap(); // record.insert("hello", "world").unwrap();
//
for value in record.values() { // for value in record.values() {
debug!("{:?}", value); // debug!("{:?}", value);
} // }
} // }
//
#[test] // #[test]
fn from_hashmap_success() { // fn from_hashmap_success() {
super::setup(); // super::setup();
//
let mut map = HashMap::new(); // let mut map = HashMap::new();
map.insert("foo", "bar"); // map.insert("foo", "bar");
//
let record: TxtRecord = map.into(); // let record: TxtRecord = map.into();
//
assert_eq!(&record["foo"], "bar"); // assert_eq!(&record["foo"], "bar");
} // }
//
#[test] // #[test]
fn clone_success() { // fn clone_success() {
super::setup(); // super::setup();
//
let mut record = TxtRecord::new(); // let mut record = TxtRecord::new();
record.insert("foo", "bar").unwrap(); // record.insert("foo", "bar").unwrap();
//
assert_eq!(record.clone(), record); // assert_eq!(record.clone(), record);
} // }

View File

@ -2,13 +2,14 @@
use crate::TxtRecord; use crate::TxtRecord;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Index;
impl Index<&str> for TxtRecord { impl From<HashMap<String, String>> for TxtRecord {
type Output = str; fn from(map: HashMap<String, String>) -> TxtRecord {
let mut record = TxtRecord::new();
fn index(&self, key: &str) -> &Self::Output { for (key, value) in map {
self.get(key).unwrap() record.insert(&key, &value).unwrap();
}
record
} }
} }
@ -20,3 +21,39 @@ impl From<HashMap<&str, &str>> for TxtRecord {
.into() .into()
} }
} }
impl TxtRecord {
/// Returns true if there are no entries in the record.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns a new `HashMap` with this record's keys and values.
pub fn to_map(&self) -> HashMap<String, String> {
let mut m = HashMap::new();
for (key, value) in self.iter() {
m.insert(key, value.to_string());
}
m
}
}
impl Clone for TxtRecord {
fn clone(&self) -> Self {
self.to_map().into()
}
}
impl PartialEq for TxtRecord {
fn eq(&self, other: &Self) -> bool {
self.to_map() == other.to_map()
}
}
impl Eq for TxtRecord {}
impl Default for TxtRecord {
fn default() -> Self {
Self::new()
}
}