From 0881c308bdb931b2eb5a8dac5bdd663fe38f4c0d Mon Sep 17 00:00:00 2001 From: Walker Crouse Date: Mon, 28 Sep 2020 21:51:44 -0400 Subject: [PATCH] more work on TxtRecord Signed-off-by: Walker Crouse --- Cargo.lock | 7 + README.md | 1 + examples/service/src/main.rs | 1 + zeroconf/Cargo.toml | 1 + zeroconf/src/lib.rs | 30 ++-- zeroconf/src/linux/string_list.rs | 88 +++++++--- zeroconf/src/linux/txt_record.rs | 142 +++++++++++------ zeroconf/src/macos/txt_record.rs | 35 ++-- zeroconf/src/prelude.rs | 1 + zeroconf/src/tests/txt_record_test.rs | 221 +++++++++++++------------- zeroconf/src/txt_record.rs | 66 +++++--- 11 files changed, 358 insertions(+), 235 deletions(-) create mode 100644 zeroconf/src/prelude.rs diff --git a/Cargo.lock b/Cargo.lock index 8a29b6d..77ae8c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "memchr" version = "2.3.3" @@ -510,6 +516,7 @@ dependencies = [ "env_logger", "libc", "log", + "maplit", "serde", "zeroconf-macros", ] diff --git a/README.md b/README.md index 4eb2b2c..c29ecf1 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ use std::any::Any; use std::sync::{Arc, Mutex}; use std::time::Duration; use zeroconf::{MdnsService, ServiceRegistration}; +use zeroconf::prelude::*; #[derive(Default, Debug)] pub struct Context { diff --git a/examples/service/src/main.rs b/examples/service/src/main.rs index e40b1fd..452dd84 100644 --- a/examples/service/src/main.rs +++ b/examples/service/src/main.rs @@ -2,6 +2,7 @@ use std::any::Any; use std::sync::{Arc, Mutex}; use std::time::Duration; use zeroconf::{MdnsService, ServiceRegistration, TxtRecord}; +use zeroconf::prelude::*; #[derive(Default, Debug)] pub struct Context { diff --git a/zeroconf/Cargo.toml b/zeroconf/Cargo.toml index 30fce08..c880cf5 100644 --- a/zeroconf/Cargo.toml +++ b/zeroconf/Cargo.toml @@ -18,6 +18,7 @@ derive-getters = "0.2.0" derive_builder = "0.9.0" derive-new = "0.5.8" log = "0.4.11" +maplit = "1.0.2" libc = "0.2.77" zeroconf-macros = { path = "../zeroconf-macros", version = "0.1.1" } diff --git a/zeroconf/src/lib.rs b/zeroconf/src/lib.rs index 6dd83ad..62185f6 100644 --- a/zeroconf/src/lib.rs +++ b/zeroconf/src/lib.rs @@ -21,50 +21,51 @@ //! use std::sync::{Arc, Mutex}; //! use std::time::Duration; //! use zeroconf::{MdnsService, ServiceRegistration, TxtRecord}; -//! +//! use zeroconf::prelude::*; +//! //! #[derive(Default, Debug)] //! pub struct Context { //! service_name: String, //! } -//! +//! //! fn main() { //! let mut service = MdnsService::new("_http._tcp", 8080); //! let mut txt_record = TxtRecord::new(); //! let context: Arc> = Arc::default(); -//! +//! //! txt_record.insert("foo", "bar").unwrap(); -//! +//! //! service.set_registered_callback(Box::new(on_service_registered)); //! service.set_context(Box::new(context)); //! service.set_txt_record(txt_record); -//! +//! //! let event_loop = service.register().unwrap(); -//! +//! //! loop { //! // calling `poll()` will keep this service alive //! event_loop.poll(Duration::from_secs(0)).unwrap(); //! } //! } -//! +//! //! fn on_service_registered( //! result: zeroconf::Result, //! context: Option>, //! ) { //! let service = result.unwrap(); -//! +//! //! println!("Service registered: {:?}", service); -//! +//! //! let context = context //! .as_ref() //! .unwrap() //! .downcast_ref::>>() //! .unwrap() //! .clone(); -//! +//! //! context.lock().unwrap().service_name = service.name().clone(); -//! +//! //! println!("Context: {:?}", context); -//! +//! //! // ... //! } //! ``` @@ -124,6 +125,10 @@ extern crate log; #[macro_use] extern crate derive_new; +#[cfg(test)] +#[macro_use] +extern crate maplit; + mod discovery; mod registration; #[macro_use] @@ -136,6 +141,7 @@ mod txt_record; pub mod builder; pub mod error; pub mod ffi; +pub mod prelude; #[cfg(target_os = "linux")] pub mod linux; diff --git a/zeroconf/src/linux/string_list.rs b/zeroconf/src/linux/string_list.rs index 1627986..604312e 100644 --- a/zeroconf/src/linux/string_list.rs +++ b/zeroconf/src/linux/string_list.rs @@ -1,8 +1,9 @@ use crate::ffi::c_str; use avahi_sys::{ 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_length, avahi_string_list_new, avahi_string_list_to_string, AvahiStringList, + avahi_string_list_find, avahi_string_list_free, avahi_string_list_get_next, + 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 std::marker::PhantomData; @@ -29,6 +30,10 @@ impl ManagedAvahiStringList { } } + pub fn head(&mut self) -> AvahiStringListNode { + AvahiStringListNode::new(self.0) + } + pub fn length(&self) -> u32 { unsafe { avahi_string_list_length(self.0) } } @@ -71,6 +76,15 @@ pub struct AvahiStringListNode<'a> { } impl<'a> AvahiStringListNode<'a> { + pub fn next(self) -> Option> { + 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 { let mut key: *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) } - - pub fn remove(&mut self) { - unsafe { avahi_string_list_free(self.list) }; - } } #[derive(new, Getters)] @@ -125,6 +135,7 @@ impl Drop for AvahiString { #[cfg(test)] mod tests { use super::*; + use std::collections::HashMap; #[test] 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] fn length_success() { crate::tests::setup(); @@ -269,4 +260,49 @@ mod tests { 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 = hashmap! { + "foo".to_string() => "bar".to_string(), + "hello".to_string() => "world".to_string() + }; + + assert_eq!(map, expected); + } } diff --git a/zeroconf/src/linux/txt_record.rs b/zeroconf/src/linux/txt_record.rs index bcbb520..2fbc7d0 100644 --- a/zeroconf/src/linux/txt_record.rs +++ b/zeroconf/src/linux/txt_record.rs @@ -1,19 +1,24 @@ -use super::string_list::ManagedAvahiStringList; +use super::string_list::{AvahiStringListNode, ManagedAvahiStringList}; +use crate::txt_record::TTxtRecord; use crate::Result; use libc::c_char; use std::cell::UnsafeCell; -use std::collections::HashMap; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct AvahiTxtRecord(UnsafeCell); impl AvahiTxtRecord { - pub fn new() -> Self { - Self::default() + fn inner(&self) -> &mut ManagedAvahiStringList { + unsafe { &mut *self.0.get() } + } +} + +impl TTxtRecord for AvahiTxtRecord { + fn new() -> Self { + Self(UnsafeCell::default()) } - /// Inserts the specified value at the specified key. - pub fn insert(&mut self, key: &str, value: &str) -> Result<()> { + fn insert(&mut self, key: &str, value: &str) -> Result<()> { unsafe { self.inner().add_pair( c_string!(key).as_ptr() as *const c_char, @@ -23,11 +28,7 @@ impl AvahiTxtRecord { Ok(()) } - /// 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. - pub fn get(&self, key: &str) -> Option { + fn get(&self, key: &str) -> Option { unsafe { self.inner() .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. - pub fn remove(&mut self, key: &str) -> Result<()> { - unsafe { - match self.inner().find(c_string!(key).as_ptr() as *const c_char) { - None => Err("no such key".into()), - Some(node) => { - node.remove(); - Ok(()) - } + fn remove(&mut self, key: &str) -> Result<()> { + let mut list = ManagedAvahiStringList::new(); + let mut map = self.to_map(); + + map.remove(key); + + for (key, value) in map { + 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. - 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 { + fn len(&self) -> usize { self.inner().length() as usize } - // /// Returns a new `txt_record::Iter` for iterating over the record as you would a `HashMap`. - // pub fn iter<'a>(&'a self) -> Box + 'a> { - // Box::new(Iter::new(self)) - // } - // - // /// Returns a new `txt_record::Iter` over the records keys. - // pub fn keys<'a>(&'a self) -> Box + 'a> { - // Box::new(Keys(Iter::new(self))) - // } - // - // /// Returns a new `txt_record::Iter` over the records values. - // pub fn values<'a>(&'a self) -> Box + 'a> { - // Box::new(Values(Iter::new(self))) - // } + fn iter<'a>(&'a self) -> Box + 'a> { + Box::new(Iter::new(self.inner().head())) + } - fn inner(&self) -> &mut ManagedAvahiStringList { - &mut *self.0.get() + fn keys<'a>(&'a self) -> Box + 'a> { + Box::new(Keys(Iter::new(self.inner().head()))) + } + + fn values<'a>(&'a self) -> Box + 'a> { + Box::new(Values(Iter::new(self.inner().head()))) + } +} + +pub struct Iter<'a> { + node: Option>, +} + +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 { + 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.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.0.next().map(|e| e.1) } } diff --git a/zeroconf/src/macos/txt_record.rs b/zeroconf/src/macos/txt_record.rs index 746b174..6b138db 100644 --- a/zeroconf/src/macos/txt_record.rs +++ b/zeroconf/src/macos/txt_record.rs @@ -1,5 +1,6 @@ use super::txt_record_ref::ManagedTXTRecordRef; use crate::ffi::c_str; +use crate::txt_record::TTxtRecord; use crate::Result; use libc::{c_char, c_void}; use std::collections::HashMap; @@ -10,14 +11,12 @@ use std::{mem, ptr}; #[derive(Debug)] pub struct BonjourTxtRecord(pub(crate) ManagedTXTRecordRef); -impl BonjourTxtRecord { - /// Constructs a new TXT recoord - pub fn new() -> Self { +impl TTxtRecord for BonjourTxtRecord { + fn new() -> Self { Self(ManagedTXTRecordRef::new()) } - /// Inserts the specified value at the specified key. - pub fn insert(&mut self, key: &str, value: &str) -> Result<()> { + fn insert(&mut self, key: &str, value: &str) -> Result<()> { let key = c_string!(key); let value = c_string!(value); 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. - /// - /// 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 { + fn get(&self, key: &str) -> Option { let mut value_len: u8 = 0; let value_raw = unsafe { @@ -49,39 +44,33 @@ impl BonjourTxtRecord { } } - /// Removes the value at the specified key. Returns `Err` if no such key exists. - pub fn remove(&mut self, key: &str) -> Result<()> { + fn remove(&mut self, key: &str) -> Result<()> { unsafe { self.0 .remove_value(c_string!(key).as_ptr() as *const c_char) } } - /// Returns true if the TXT record contains the specified key. - pub fn contains_key(&self, key: &str) -> bool { + fn contains_key(&self, key: &str) -> bool { unsafe { self.0 .contains_key(c_string!(key).as_ptr() as *const c_char) } } - /// Returns the amount of entries in the TXT record. - pub fn len(&self) -> usize { + fn len(&self) -> usize { self.0.get_count() as usize } - /// Returns a new `txt_record::Iter` for iterating over the record as you would a `HashMap`. - pub fn iter<'a>(&'a self) -> Box + 'a> { + fn iter<'a>(&'a self) -> Box + 'a> { Box::new(Iter::new(self)) } - /// Returns a new `txt_record::Iter` over the records keys. - pub fn keys<'a>(&'a self) -> Box + 'a> { + fn keys<'a>(&'a self) -> Box + 'a> { Box::new(Keys(Iter::new(self))) } - /// Returns a new `txt_record::Iter` over the records values. - pub fn values<'a>(&'a self) -> Box + 'a> { + fn values<'a>(&'a self) -> Box + 'a> { Box::new(Values(Iter::new(self))) } } @@ -98,7 +87,7 @@ impl Iter<'_> { const KEY_LEN: u16 = 256; } -impl<'a> Iterator for Iter<'a> { +impl Iterator for Iter<'_> { type Item = (String, String); fn next(&mut self) -> Option { diff --git a/zeroconf/src/prelude.rs b/zeroconf/src/prelude.rs new file mode 100644 index 0000000..8280657 --- /dev/null +++ b/zeroconf/src/prelude.rs @@ -0,0 +1 @@ +pub use crate::txt_record::TTxtRecord; diff --git a/zeroconf/src/tests/txt_record_test.rs b/zeroconf/src/tests/txt_record_test.rs index bd9008e..cc90040 100644 --- a/zeroconf/src/tests/txt_record_test.rs +++ b/zeroconf/src/tests/txt_record_test.rs @@ -1,110 +1,111 @@ -// use crate::TxtRecord; -// use std::collections::HashMap; -// -// #[test] -// fn insert_get_success() { -// super::setup(); -// let mut record = TxtRecord::new(); -// record.insert("foo", "bar").unwrap(); -// assert_eq!(&record["foo"], "bar"); -// assert_eq!(record.get("baz"), None); -// } -// -// #[test] -// fn remove_success() { -// super::setup(); -// let mut record = TxtRecord::new(); -// record.insert("foo", "bar").unwrap(); -// record.remove("foo").unwrap(); -// assert!(record.get("foo").is_none()); -// } -// -// #[test] -// fn contains_key_success() { -// super::setup(); -// let mut record = TxtRecord::new(); -// record.insert("foo", "bar").unwrap(); -// assert!(record.contains_key("foo")); -// assert!(!record.contains_key("baz")); -// } -// -// #[test] -// fn len_success() { -// super::setup(); -// let mut record = TxtRecord::new(); -// record.insert("foo", "bar").unwrap(); -// assert_eq!(record.len(), 1); -// } -// -// #[test] -// #[ignore] -// fn iter_success() { -// super::setup(); -// -// debug!("iter_success()"); -// -// let mut record = TxtRecord::new(); -// record.insert("foo", "bar").unwrap(); -// record.insert("baz", "qux").unwrap(); -// record.insert("hello", "world").unwrap(); -// -// for (key, value) in record.iter() { -// debug!("({:?}, {:?})", key, value); -// } -// } -// -// #[test] -// #[ignore] -// fn keys_success() { -// super::setup(); -// -// debug!("keys_success()"); -// -// let mut record = TxtRecord::new(); -// record.insert("foo", "bar").unwrap(); -// record.insert("baz", "qux").unwrap(); -// record.insert("hello", "world").unwrap(); -// -// for key in record.keys() { -// debug!("{:?}", key); -// } -// } -// -// #[test] -// #[ignore] -// fn values_success() { -// super::setup(); -// -// debug!("values_success()"); -// -// let mut record = TxtRecord::new(); -// record.insert("foo", "bar").unwrap(); -// record.insert("baz", "qux").unwrap(); -// record.insert("hello", "world").unwrap(); -// -// for value in record.values() { -// debug!("{:?}", value); -// } -// } -// -// #[test] -// fn from_hashmap_success() { -// super::setup(); -// -// let mut map = HashMap::new(); -// map.insert("foo", "bar"); -// -// let record: TxtRecord = map.into(); -// -// assert_eq!(&record["foo"], "bar"); -// } -// -// #[test] -// fn clone_success() { -// super::setup(); -// -// let mut record = TxtRecord::new(); -// record.insert("foo", "bar").unwrap(); -// -// assert_eq!(record.clone(), record); -// } +use crate::prelude::*; +use crate::TxtRecord; +use std::collections::HashMap; + +#[test] +fn insert_get_success() { + super::setup(); + let mut record = TxtRecord::new(); + record.insert("foo", "bar").unwrap(); + assert_eq!(record.get("foo").unwrap(), "bar"); + assert_eq!(record.get("baz"), None); +} + +#[test] +fn remove_success() { + super::setup(); + let mut record = TxtRecord::new(); + record.insert("foo", "bar").unwrap(); + record.remove("foo").unwrap(); + assert!(record.get("foo").is_none()); +} + +#[test] +fn contains_key_success() { + super::setup(); + let mut record = TxtRecord::new(); + record.insert("foo", "bar").unwrap(); + assert!(record.contains_key("foo")); + assert!(!record.contains_key("baz")); +} + +#[test] +fn len_success() { + super::setup(); + let mut record = TxtRecord::new(); + record.insert("foo", "bar").unwrap(); + assert_eq!(record.len(), 1); +} + +#[test] +#[ignore] +fn iter_success() { + super::setup(); + + debug!("iter_success()"); + + let mut record = TxtRecord::new(); + record.insert("foo", "bar").unwrap(); + record.insert("baz", "qux").unwrap(); + record.insert("hello", "world").unwrap(); + + for (key, value) in record.iter() { + debug!("({:?}, {:?})", key, value); + } +} + +#[test] +#[ignore] +fn keys_success() { + super::setup(); + + debug!("keys_success()"); + + let mut record = TxtRecord::new(); + record.insert("foo", "bar").unwrap(); + record.insert("baz", "qux").unwrap(); + record.insert("hello", "world").unwrap(); + + for key in record.keys() { + debug!("{:?}", key); + } +} + +#[test] +#[ignore] +fn values_success() { + super::setup(); + + debug!("values_success()"); + + let mut record = TxtRecord::new(); + record.insert("foo", "bar").unwrap(); + record.insert("baz", "qux").unwrap(); + record.insert("hello", "world").unwrap(); + + for value in record.values() { + debug!("{:?}", value); + } +} + +#[test] +fn from_hashmap_success() { + super::setup(); + + let mut map = HashMap::new(); + map.insert("foo", "bar"); + + let record: TxtRecord = map.into(); + + assert_eq!(record.get("foo").unwrap(), "bar"); +} + +#[test] +fn clone_success() { + super::setup(); + + let mut record = TxtRecord::new(); + record.insert("foo", "bar").unwrap(); + + assert_eq!(record.clone(), record); +} diff --git a/zeroconf/src/txt_record.rs b/zeroconf/src/txt_record.rs index f2ee9e2..1814dbb 100644 --- a/zeroconf/src/txt_record.rs +++ b/zeroconf/src/txt_record.rs @@ -1,8 +1,54 @@ //! TxtRecord utilities common to all platforms -use crate::TxtRecord; +use crate::{Result, TxtRecord}; 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; + + /// 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 + 'a>; + + /// Returns a new iterator over the records keys. + fn keys<'a>(&'a self) -> Box + 'a>; + + /// Returns a new iterator over the records values. + fn values<'a>(&'a self) -> Box + '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 { + let mut m = HashMap::new(); + for (key, value) in self.iter() { + m.insert(key, value.to_string()); + } + m + } +} + impl From> for TxtRecord { fn from(map: HashMap) -> TxtRecord { let mut record = TxtRecord::new(); @@ -22,22 +68,6 @@ impl From> 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 { - 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() @@ -50,7 +80,7 @@ impl PartialEq for TxtRecord { } } -impl Eq for TxtRecord {} +// impl Eq for TxtRecord {} impl Default for TxtRecord { fn default() -> Self {