more work on TxtRecord

Signed-off-by: Walker Crouse <walker.crouse@coop.co.uk>
This commit is contained in:
Walker Crouse 2020-09-28 21:51:44 -04:00
parent 426851e66a
commit 0881c308bd
11 changed files with 358 additions and 235 deletions

7
Cargo.lock generated
View File

@ -288,6 +288,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.3.3" version = "2.3.3"
@ -510,6 +516,7 @@ dependencies = [
"env_logger", "env_logger",
"libc", "libc",
"log", "log",
"maplit",
"serde", "serde",
"zeroconf-macros", "zeroconf-macros",
] ]

View File

@ -31,6 +31,7 @@ use std::any::Any;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use zeroconf::{MdnsService, ServiceRegistration}; use zeroconf::{MdnsService, ServiceRegistration};
use zeroconf::prelude::*;
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Context { pub struct Context {

View File

@ -2,6 +2,7 @@ use std::any::Any;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use zeroconf::{MdnsService, ServiceRegistration, TxtRecord}; use zeroconf::{MdnsService, ServiceRegistration, TxtRecord};
use zeroconf::prelude::*;
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Context { pub struct Context {

View File

@ -18,6 +18,7 @@ derive-getters = "0.2.0"
derive_builder = "0.9.0" derive_builder = "0.9.0"
derive-new = "0.5.8" derive-new = "0.5.8"
log = "0.4.11" log = "0.4.11"
maplit = "1.0.2"
libc = "0.2.77" libc = "0.2.77"
zeroconf-macros = { path = "../zeroconf-macros", version = "0.1.1" } zeroconf-macros = { path = "../zeroconf-macros", version = "0.1.1" }

View File

@ -21,50 +21,51 @@
//! use std::sync::{Arc, Mutex}; //! use std::sync::{Arc, Mutex};
//! use std::time::Duration; //! use std::time::Duration;
//! use zeroconf::{MdnsService, ServiceRegistration, TxtRecord}; //! use zeroconf::{MdnsService, ServiceRegistration, TxtRecord};
//! //! use zeroconf::prelude::*;
//!
//! #[derive(Default, Debug)] //! #[derive(Default, Debug)]
//! pub struct Context { //! pub struct Context {
//! service_name: String, //! service_name: String,
//! } //! }
//! //!
//! fn main() { //! fn main() {
//! let mut service = MdnsService::new("_http._tcp", 8080); //! let mut service = MdnsService::new("_http._tcp", 8080);
//! let mut txt_record = TxtRecord::new(); //! let mut txt_record = TxtRecord::new();
//! let context: Arc<Mutex<Context>> = Arc::default(); //! let context: Arc<Mutex<Context>> = Arc::default();
//! //!
//! txt_record.insert("foo", "bar").unwrap(); //! txt_record.insert("foo", "bar").unwrap();
//! //!
//! service.set_registered_callback(Box::new(on_service_registered)); //! service.set_registered_callback(Box::new(on_service_registered));
//! service.set_context(Box::new(context)); //! service.set_context(Box::new(context));
//! service.set_txt_record(txt_record); //! service.set_txt_record(txt_record);
//! //!
//! let event_loop = service.register().unwrap(); //! let event_loop = service.register().unwrap();
//! //!
//! loop { //! loop {
//! // calling `poll()` will keep this service alive //! // calling `poll()` will keep this service alive
//! event_loop.poll(Duration::from_secs(0)).unwrap(); //! event_loop.poll(Duration::from_secs(0)).unwrap();
//! } //! }
//! } //! }
//! //!
//! fn on_service_registered( //! fn on_service_registered(
//! result: zeroconf::Result<ServiceRegistration>, //! result: zeroconf::Result<ServiceRegistration>,
//! context: Option<Arc<dyn Any>>, //! context: Option<Arc<dyn Any>>,
//! ) { //! ) {
//! let service = result.unwrap(); //! let service = result.unwrap();
//! //!
//! println!("Service registered: {:?}", service); //! println!("Service registered: {:?}", service);
//! //!
//! let context = context //! let context = context
//! .as_ref() //! .as_ref()
//! .unwrap() //! .unwrap()
//! .downcast_ref::<Arc<Mutex<Context>>>() //! .downcast_ref::<Arc<Mutex<Context>>>()
//! .unwrap() //! .unwrap()
//! .clone(); //! .clone();
//! //!
//! context.lock().unwrap().service_name = service.name().clone(); //! context.lock().unwrap().service_name = service.name().clone();
//! //!
//! println!("Context: {:?}", context); //! println!("Context: {:?}", context);
//! //!
//! // ... //! // ...
//! } //! }
//! ``` //! ```
@ -124,6 +125,10 @@ extern crate log;
#[macro_use] #[macro_use]
extern crate derive_new; extern crate derive_new;
#[cfg(test)]
#[macro_use]
extern crate maplit;
mod discovery; mod discovery;
mod registration; mod registration;
#[macro_use] #[macro_use]
@ -136,6 +141,7 @@ mod txt_record;
pub mod builder; pub mod builder;
pub mod error; pub mod error;
pub mod ffi; pub mod ffi;
pub mod prelude;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub mod linux; pub mod linux;

View File

@ -1,8 +1,9 @@
use crate::ffi::c_str; use crate::ffi::c_str;
use avahi_sys::{ use avahi_sys::{
avahi_free, avahi_string_list_add_pair, avahi_string_list_copy, avahi_string_list_equal, avahi_free, avahi_string_list_add_pair, avahi_string_list_copy, avahi_string_list_equal,
avahi_string_list_find, avahi_string_list_free, avahi_string_list_get_pair, avahi_string_list_find, avahi_string_list_free, avahi_string_list_get_next,
avahi_string_list_length, avahi_string_list_new, avahi_string_list_to_string, AvahiStringList, avahi_string_list_get_pair, 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::marker::PhantomData;
@ -29,6 +30,10 @@ impl ManagedAvahiStringList {
} }
} }
pub fn head(&mut self) -> AvahiStringListNode {
AvahiStringListNode::new(self.0)
}
pub fn length(&self) -> u32 { pub fn length(&self) -> u32 {
unsafe { avahi_string_list_length(self.0) } unsafe { avahi_string_list_length(self.0) }
} }
@ -71,6 +76,15 @@ pub struct AvahiStringListNode<'a> {
} }
impl<'a> AvahiStringListNode<'a> { impl<'a> AvahiStringListNode<'a> {
pub fn next(self) -> Option<AvahiStringListNode<'a>> {
let next = unsafe { avahi_string_list_get_next(self.list) };
if next.is_null() {
None
} else {
Some(AvahiStringListNode::new(next))
}
}
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();
@ -82,10 +96,6 @@ impl<'a> AvahiStringListNode<'a> {
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)]
@ -125,6 +135,7 @@ impl Drop for AvahiString {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::collections::HashMap;
#[test] #[test]
fn add_get_pair_success() { fn add_get_pair_success() {
@ -196,26 +207,6 @@ mod tests {
} }
} }
#[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);
}
}
#[test] #[test]
fn length_success() { fn length_success() {
crate::tests::setup(); crate::tests::setup();
@ -269,4 +260,49 @@ mod tests {
assert_eq!(list.clone(), list); assert_eq!(list.clone(), list);
} }
} }
#[test]
fn iterate_success() {
crate::tests::setup();
let mut list = ManagedAvahiStringList::new();
let key1 = c_string!("foo");
let value1 = c_string!("bar");
let key2 = c_string!("hello");
let value2 = c_string!("world");
unsafe {
list.add_pair(
key1.as_ptr() as *const c_char,
value1.as_ptr() as *const c_char,
);
list.add_pair(
key2.as_ptr() as *const c_char,
value2.as_ptr() as *const c_char,
);
}
let mut node = Some(list.head());
let mut map = HashMap::new();
while node.is_some() {
let mut n = node.unwrap();
let pair = n.get_pair();
map.insert(
pair.key().as_str().unwrap().to_string(),
pair.value().as_str().unwrap().to_string(),
);
node = n.next();
}
let expected: HashMap<String, String> = hashmap! {
"foo".to_string() => "bar".to_string(),
"hello".to_string() => "world".to_string()
};
assert_eq!(map, expected);
}
} }

View File

@ -1,19 +1,24 @@
use super::string_list::ManagedAvahiStringList; use super::string_list::{AvahiStringListNode, ManagedAvahiStringList};
use crate::txt_record::TTxtRecord;
use crate::Result; use crate::Result;
use libc::c_char; use libc::c_char;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::collections::HashMap;
#[derive(Debug, Default)] #[derive(Debug)]
pub struct AvahiTxtRecord(UnsafeCell<ManagedAvahiStringList>); pub struct AvahiTxtRecord(UnsafeCell<ManagedAvahiStringList>);
impl AvahiTxtRecord { impl AvahiTxtRecord {
pub fn new() -> Self { fn inner(&self) -> &mut ManagedAvahiStringList {
Self::default() unsafe { &mut *self.0.get() }
}
}
impl TTxtRecord for AvahiTxtRecord {
fn new() -> Self {
Self(UnsafeCell::default())
} }
/// Inserts the specified value at the specified key. fn insert(&mut self, key: &str, value: &str) -> Result<()> {
pub fn insert(&mut self, key: &str, value: &str) -> Result<()> {
unsafe { unsafe {
self.inner().add_pair( self.inner().add_pair(
c_string!(key).as_ptr() as *const c_char, c_string!(key).as_ptr() as *const c_char,
@ -23,11 +28,7 @@ impl AvahiTxtRecord {
Ok(()) Ok(())
} }
/// Returns the value at the specified key or `None` if no such key exists. fn get(&self, key: &str) -> Option<String> {
///
/// 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 { unsafe {
self.inner() self.inner()
.find(c_string!(key).as_ptr() as *const c_char)? .find(c_string!(key).as_ptr() as *const c_char)?
@ -38,47 +39,96 @@ impl AvahiTxtRecord {
} }
} }
/// Removes the value at the specified key. Returns `Err` if no such key exists. fn remove(&mut self, key: &str) -> Result<()> {
pub fn remove(&mut self, key: &str) -> Result<()> { let mut list = ManagedAvahiStringList::new();
unsafe { let mut map = self.to_map();
match self.inner().find(c_string!(key).as_ptr() as *const c_char) {
None => Err("no such key".into()), map.remove(key);
Some(node) => {
node.remove(); for (key, value) in map {
Ok(()) unsafe {
} list.add_pair(
c_string!(key).as_ptr() as *const c_char,
c_string!(value).as_ptr() as *const c_char,
);
} }
} }
self.0 = UnsafeCell::new(list);
Ok(())
}
fn contains_key(&self, key: &str) -> bool {
unsafe {
self.inner()
.find(c_string!(key).as_ptr() as *const c_char)
.is_some()
}
} }
/// Returns true if the TXT record contains the specified key. fn len(&self) -> usize {
pub fn contains_key(&self, key: &str) -> bool {
self.inner()
.find(c_string!(key).as_ptr() as *const c_char)
.is_some()
}
/// Returns the amount of entries in the TXT record.
pub fn len(&self) -> usize {
self.inner().length() as usize self.inner().length() as usize
} }
// /// Returns a new `txt_record::Iter` for iterating over the record as you would a `HashMap`. fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (String, String)> + 'a> {
// pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (String, String)> + 'a> { Box::new(Iter::new(self.inner().head()))
// 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)))
// }
fn inner(&self) -> &mut ManagedAvahiStringList { fn keys<'a>(&'a self) -> Box<dyn Iterator<Item = String> + 'a> {
&mut *self.0.get() Box::new(Keys(Iter::new(self.inner().head())))
}
fn values<'a>(&'a self) -> Box<dyn Iterator<Item = String> + 'a> {
Box::new(Values(Iter::new(self.inner().head())))
}
}
pub struct Iter<'a> {
node: Option<AvahiStringListNode<'a>>,
}
impl<'a> Iter<'a> {
pub fn new(node: AvahiStringListNode<'a>) -> Self {
Self { node: Some(node) }
}
}
impl Iterator for Iter<'_> {
type Item = (String, String);
fn next(&mut self) -> Option<Self::Item> {
if self.node.is_none() {
return None;
}
let mut n = self.node.take().unwrap();
let pair = n.get_pair();
self.node = n.next();
Some((
pair.key().as_str().unwrap().to_string(),
pair.value().as_str().unwrap().to_string(),
))
}
}
pub struct Keys<'a>(Iter<'a>);
impl Iterator for Keys<'_> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|e| e.0)
}
}
pub struct Values<'a>(Iter<'a>);
impl Iterator for Values<'_> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|e| e.1)
} }
} }

View File

@ -1,5 +1,6 @@
use super::txt_record_ref::ManagedTXTRecordRef; use super::txt_record_ref::ManagedTXTRecordRef;
use crate::ffi::c_str; use crate::ffi::c_str;
use crate::txt_record::TTxtRecord;
use crate::Result; use crate::Result;
use libc::{c_char, c_void}; use libc::{c_char, c_void};
use std::collections::HashMap; use std::collections::HashMap;
@ -10,14 +11,12 @@ use std::{mem, ptr};
#[derive(Debug)] #[derive(Debug)]
pub struct BonjourTxtRecord(pub(crate) ManagedTXTRecordRef); pub struct BonjourTxtRecord(pub(crate) ManagedTXTRecordRef);
impl BonjourTxtRecord { impl TTxtRecord for BonjourTxtRecord {
/// Constructs a new TXT recoord fn new() -> Self {
pub fn new() -> Self {
Self(ManagedTXTRecordRef::new()) Self(ManagedTXTRecordRef::new())
} }
/// Inserts the specified value at the specified key. fn insert(&mut self, key: &str, value: &str) -> Result<()> {
pub fn insert(&mut self, key: &str, value: &str) -> Result<()> {
let key = c_string!(key); let key = c_string!(key);
let value = c_string!(value); let value = c_string!(value);
let value_size = mem::size_of_val(&value) as u8; let value_size = mem::size_of_val(&value) as u8;
@ -30,11 +29,7 @@ impl BonjourTxtRecord {
} }
} }
/// Returns the value at the specified key or `None` if no such key exists. fn get(&self, key: &str) -> Option<String> {
///
/// 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 {
@ -49,39 +44,33 @@ impl BonjourTxtRecord {
} }
} }
/// Removes the value at the specified key. Returns `Err` if no such key exists. fn remove(&mut self, key: &str) -> Result<()> {
pub fn remove(&mut self, key: &str) -> Result<()> {
unsafe { unsafe {
self.0 self.0
.remove_value(c_string!(key).as_ptr() as *const c_char) .remove_value(c_string!(key).as_ptr() as *const c_char)
} }
} }
/// Returns true if the TXT record contains the specified key. fn contains_key(&self, key: &str) -> bool {
pub fn contains_key(&self, key: &str) -> bool {
unsafe { unsafe {
self.0 self.0
.contains_key(c_string!(key).as_ptr() as *const c_char) .contains_key(c_string!(key).as_ptr() as *const c_char)
} }
} }
/// Returns the amount of entries in the TXT record. fn len(&self) -> usize {
pub fn len(&self) -> usize {
self.0.get_count() as usize self.0.get_count() as usize
} }
/// Returns a new `txt_record::Iter` for iterating over the record as you would a `HashMap`. fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (String, String)> + 'a> {
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (String, String)> + 'a> {
Box::new(Iter::new(self)) Box::new(Iter::new(self))
} }
/// Returns a new `txt_record::Iter` over the records keys. fn keys<'a>(&'a self) -> Box<dyn Iterator<Item = String> + 'a> {
pub fn keys<'a>(&'a self) -> Box<dyn Iterator<Item = String> + 'a> {
Box::new(Keys(Iter::new(self))) Box::new(Keys(Iter::new(self)))
} }
/// Returns a new `txt_record::Iter` over the records values. fn values<'a>(&'a self) -> Box<dyn Iterator<Item = String> + '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)))
} }
} }
@ -98,7 +87,7 @@ impl Iter<'_> {
const KEY_LEN: u16 = 256; const KEY_LEN: u16 = 256;
} }
impl<'a> Iterator for Iter<'a> { impl Iterator for Iter<'_> {
type Item = (String, String); type Item = (String, String);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {

1
zeroconf/src/prelude.rs Normal file
View File

@ -0,0 +1 @@
pub use crate::txt_record::TTxtRecord;

View File

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

View File

@ -1,8 +1,54 @@
//! TxtRecord utilities common to all platforms //! TxtRecord utilities common to all platforms
use crate::TxtRecord; use crate::{Result, TxtRecord};
use std::collections::HashMap; use std::collections::HashMap;
pub trait TTxtRecord {
/// Constructs a new TXT record
fn new() -> Self;
/// Inserts the specified value at the specified key.
fn insert(&mut self, key: &str, value: &str) -> Result<()>;
/// Returns the value at the specified key or `None` if no such key exists.
///
/// This function returns a owned `String` because there are no guarantees that the
/// implementation provides access to the underlying value pointer.
fn get(&self, key: &str) -> Option<String>;
/// Removes the value at the specified key. Returns `Err` if no such key exists.
fn remove(&mut self, key: &str) -> Result<()>;
/// Returns true if the TXT record contains the specified key.
fn contains_key(&self, key: &str) -> bool;
/// Returns the amount of entries in the TXT record.
fn len(&self) -> usize;
/// Returns a new iterator for iterating over the record as you would a `HashMap`.
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (String, String)> + 'a>;
/// Returns a new iterator over the records keys.
fn keys<'a>(&'a self) -> Box<dyn Iterator<Item = String> + 'a>;
/// Returns a new iterator over the records values.
fn values<'a>(&'a self) -> Box<dyn Iterator<Item = String> + 'a>;
/// Returns true if there are no entries in the record.
fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns a new `HashMap` with this record's keys and values.
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 TxtRecord { impl From<HashMap<String, String>> for TxtRecord {
fn from(map: HashMap<String, String>) -> TxtRecord { fn from(map: HashMap<String, String>) -> TxtRecord {
let mut record = TxtRecord::new(); let mut record = TxtRecord::new();
@ -22,22 +68,6 @@ impl From<HashMap<&str, &str>> for TxtRecord {
} }
} }
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 { impl Clone for TxtRecord {
fn clone(&self) -> Self { fn clone(&self) -> Self {
self.to_map().into() self.to_map().into()
@ -50,7 +80,7 @@ impl PartialEq for TxtRecord {
} }
} }
impl Eq for TxtRecord {} // impl Eq for TxtRecord {}
impl Default for TxtRecord { impl Default for TxtRecord {
fn default() -> Self { fn default() -> Self {