diff --git a/README.md b/README.md index 8d57e61..32c5269 100644 --- a/README.md +++ b/README.md @@ -14,48 +14,77 @@ $ sudo apt install xorg-dev libxcb-shape0-dev libxcb-xfixes0-dev clang avahi-dae On Windows: -Bonjour must be installed. It comes bundled with [iTunes](https://support.apple.com/en-us/HT210384) or [Bonjour Print Services](https://support.apple.com/kb/dl999). Further redistribution & bundling details are available on the [Apple Developer Site](https://developer.apple.com/licensing-trademarks/bonjour/). +Bonjour must be installed. It comes bundled with [iTunes][] or [Bonjour Print Services][]. Further redistribution & +bundling details are available on the [Apple Developer Site][]. -## TODO +## Examples -* You tell me... - -# Examples - -## Register a service +### Register a service When registering a service, you may optionally pass a "context" to pass state through the callback. The only requirement is that this context implements the [`Any`] trait, which most types will automatically. See `MdnsService` for more information about contexts. ```rust +#[macro_use] +extern crate log; + +use clap::Parser; + use std::any::Any; use std::sync::{Arc, Mutex}; use std::time::Duration; use zeroconf::prelude::*; use zeroconf::{MdnsService, ServiceRegistration, ServiceType, TxtRecord}; +#[derive(Parser, Debug)] +#[command(author, version, about)] +struct Args { + /// Name of the service type to register + #[clap(short, long, default_value = "http")] + name: String, + + /// Protocol of the service type to register + #[clap(short, long, default_value = "tcp")] + protocol: String, + + /// Sub-types of the service type to register + #[clap(short, long)] + sub_types: Vec, +} + #[derive(Default, Debug)] pub struct Context { service_name: String, } -fn main() { - let mut service = MdnsService::new(ServiceType::new("http", "tcp").unwrap(), 8080); +fn main() -> zeroconf::Result<()> { + env_logger::init(); + + let Args { + name, + protocol, + sub_types, + } = Args::parse(); + + let sub_types = sub_types.iter().map(|s| s.as_str()).collect::>(); + let service_type = ServiceType::with_sub_types(&name, &protocol, sub_types)?; + let mut service = MdnsService::new(service_type, 8080); let mut txt_record = TxtRecord::new(); let context: Arc> = Arc::default(); - txt_record.insert("foo", "bar").unwrap(); + txt_record.insert("foo", "bar")?; + service.set_name("zeroconf_example_service"); 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(); + let event_loop = service.register()?; loop { // calling `poll()` will keep this service alive - event_loop.poll(Duration::from_secs(0)).unwrap(); + event_loop.poll(Duration::from_secs(0))?; } } @@ -63,44 +92,85 @@ fn on_service_registered( result: zeroconf::Result, context: Option>, ) { - let service = result.unwrap(); + let service = result.expect("failed to register service"); - println!("Service registered: {:?}", service); + info!("Service registered: {:?}", service); let context = context .as_ref() - .unwrap() + .expect("could not get context") .downcast_ref::>>() - .unwrap() + .expect("error down-casting context") .clone(); - context.lock().unwrap().service_name = service.name().clone(); + context + .lock() + .expect("failed to obtain context lock") + .service_name = service.name().clone(); - println!("Context: {:?}", context); + info!("Context: {:?}", context); // ... } ``` -## Browsing services +### Browsing services ```rust +#[macro_use] +extern crate log; + +use clap::Parser; + use std::any::Any; use std::sync::Arc; use std::time::Duration; use zeroconf::prelude::*; use zeroconf::{MdnsBrowser, ServiceDiscovery, ServiceType}; -fn main() { - let mut browser = MdnsBrowser::new(ServiceType::new("http", "tcp").unwrap()); +/// Example of a simple mDNS browser +#[derive(Parser, Debug)] +#[command(author, version, about)] +struct Args { + /// Name of the service type to browse + #[clap(short, long, default_value = "http")] + name: String, + + /// Protocol of the service type to browse + #[clap(short, long, default_value = "tcp")] + protocol: String, + + /// Sub-type of the service type to browse + #[clap(short, long)] + sub_type: Option, +} + +fn main() -> zeroconf::Result<()> { + env_logger::init(); + + let Args { + name, + protocol, + sub_type, + } = Args::parse(); + + let sub_types: Vec<&str> = match sub_type.as_ref() { + Some(sub_type) => vec![sub_type], + None => vec![], + }; + + let service_type = + ServiceType::with_sub_types(&name, &protocol, sub_types).expect("invalid service type"); + + let mut browser = MdnsBrowser::new(service_type); browser.set_service_discovered_callback(Box::new(on_service_discovered)); - let event_loop = browser.browse_services().unwrap(); + let event_loop = browser.browse_services()?; loop { // calling `poll()` will keep this browser alive - event_loop.poll(Duration::from_secs(0)).unwrap(); + event_loop.poll(Duration::from_secs(0))?; } } @@ -108,7 +178,10 @@ fn on_service_discovered( result: zeroconf::Result, _context: Option>, ) { - println!("Service discovered: {:?}", result.unwrap()); + info!( + "Service discovered: {:?}", + result.expect("service discovery failed") + ); // ... } @@ -125,3 +198,6 @@ fn on_service_discovered( [`Any`]: https://doc.rust-lang.org/std/any/trait.Any.html [Avahi docs]: https://avahi.org/doxygen/html/ [Bonjour docs]: https://developer.apple.com/documentation/dnssd/dns_service_discovery_c +[iTunes]: https://support.apple.com/en-us/HT210384 +[Bonjour Print Services]: https://developer.apple.com/licensing-trademarks/bonjour/ +[Apple Developer Site]: https://developer.apple.com/licensing-trademarks/bonjour/ diff --git a/examples/browser/src/main.rs b/examples/browser/src/main.rs index 9eea5b9..0797c83 100644 --- a/examples/browser/src/main.rs +++ b/examples/browser/src/main.rs @@ -26,7 +26,7 @@ struct Args { sub_type: Option, } -fn main() { +fn main() -> zeroconf::Result<()> { env_logger::init(); let Args { @@ -47,11 +47,11 @@ fn main() { browser.set_service_discovered_callback(Box::new(on_service_discovered)); - let event_loop = browser.browse_services().unwrap(); + let event_loop = browser.browse_services()?; loop { // calling `poll()` will keep this browser alive - event_loop.poll(Duration::from_secs(0)).unwrap(); + event_loop.poll(Duration::from_secs(0))?; } } @@ -59,7 +59,10 @@ fn on_service_discovered( result: zeroconf::Result, _context: Option>, ) { - info!("Service discovered: {:?}", result.unwrap()); + info!( + "Service discovered: {:?}", + result.expect("service discovery failed") + ); // ... } diff --git a/examples/service/src/main.rs b/examples/service/src/main.rs index b8baaf2..92a11df 100644 --- a/examples/service/src/main.rs +++ b/examples/service/src/main.rs @@ -30,7 +30,7 @@ pub struct Context { service_name: String, } -fn main() { +fn main() -> zeroconf::Result<()> { env_logger::init(); let Args { @@ -40,23 +40,23 @@ fn main() { } = Args::parse(); let sub_types = sub_types.iter().map(|s| s.as_str()).collect::>(); - let service_type = ServiceType::with_sub_types(&name, &protocol, sub_types).unwrap(); + let service_type = ServiceType::with_sub_types(&name, &protocol, sub_types)?; let mut service = MdnsService::new(service_type, 8080); let mut txt_record = TxtRecord::new(); let context: Arc> = Arc::default(); - txt_record.insert("foo", "bar").unwrap(); + txt_record.insert("foo", "bar")?; service.set_name("zeroconf_example_service"); 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(); + let event_loop = service.register()?; loop { // calling `poll()` will keep this service alive - event_loop.poll(Duration::from_secs(0)).unwrap(); + event_loop.poll(Duration::from_secs(0))?; } } @@ -64,18 +64,21 @@ fn on_service_registered( result: zeroconf::Result, context: Option>, ) { - let service = result.unwrap(); + let service = result.expect("failed to register service"); info!("Service registered: {:?}", service); let context = context .as_ref() - .unwrap() + .expect("could not get context") .downcast_ref::>>() - .unwrap() + .expect("error down-casting context") .clone(); - context.lock().unwrap().service_name = service.name().clone(); + context + .lock() + .expect("failed to obtain context lock") + .service_name = service.name().clone(); info!("Context: {:?}", context); diff --git a/zeroconf-macros/src/lib.rs b/zeroconf-macros/src/lib.rs index 9c50bff..b41d58c 100644 --- a/zeroconf-macros/src/lib.rs +++ b/zeroconf-macros/src/lib.rs @@ -6,7 +6,7 @@ use syn::{self, DeriveInput, Ident}; #[proc_macro_derive(FromRaw)] pub fn from_raw_macro_derive(input: TokenStream) -> TokenStream { - impl_from_raw(&syn::parse(input).unwrap()) + impl_from_raw(&syn::parse(input).expect("could not parse input")) } fn impl_from_raw(ast: &DeriveInput) -> TokenStream { @@ -22,7 +22,7 @@ fn impl_from_raw(ast: &DeriveInput) -> TokenStream { #[proc_macro_derive(CloneRaw)] pub fn clone_raw_macro_derive(input: TokenStream) -> TokenStream { - impl_clone_raw(&syn::parse(input).unwrap()) + impl_clone_raw(&syn::parse(input).expect("could not parse input")) } fn impl_clone_raw(ast: &DeriveInput) -> TokenStream { @@ -38,7 +38,7 @@ fn impl_clone_raw(ast: &DeriveInput) -> TokenStream { #[proc_macro_derive(AsRaw)] pub fn as_raw_macro_derive(input: TokenStream) -> TokenStream { - impl_as_raw(&syn::parse(input).unwrap()) + impl_as_raw(&syn::parse(input).expect("could not parse input")) } fn impl_as_raw(ast: &DeriveInput) -> TokenStream { @@ -54,12 +54,15 @@ fn impl_as_raw(ast: &DeriveInput) -> TokenStream { #[proc_macro_derive(BuilderDelegate)] pub fn builder_delegate_macro_derive(input: TokenStream) -> TokenStream { - impl_builder_delegate(&syn::parse(input).unwrap()) + impl_builder_delegate(&syn::parse(input).expect("could not parse input")) } fn impl_builder_delegate(ast: &DeriveInput) -> TokenStream { let name = &ast.ident; - let builder: Ident = syn::parse_str(&format!("{}Builder", name)).unwrap(); + + let builder: Ident = + syn::parse_str(&format!("{}Builder", name)).expect("could not parse builder name"); + let generics = &ast.generics; let gen = quote! { diff --git a/zeroconf/src/avahi/avahi_util.rs b/zeroconf/src/avahi/avahi_util.rs index 59bcb85..8c3c865 100644 --- a/zeroconf/src/avahi/avahi_util.rs +++ b/zeroconf/src/avahi/avahi_util.rs @@ -1,5 +1,6 @@ //! Utilities related to Avahi +use crate::ffi::c_str; use avahi_sys::{ avahi_address_snprint, avahi_alternative_service_name, avahi_strerror, AvahiAddress, AvahiClient, @@ -27,7 +28,7 @@ pub unsafe fn avahi_address_to_string(addr: *const AvahiAddress) -> String { addr, ); - String::from(addr_str.to_str().unwrap()) + String::from(c_str::to_str(&addr_str)) .trim_matches(char::from(0)) .to_string() } diff --git a/zeroconf/src/avahi/browser.rs b/zeroconf/src/avahi/browser.rs index ff91d89..b4e4be5 100644 --- a/zeroconf/src/avahi/browser.rs +++ b/zeroconf/src/avahi/browser.rs @@ -79,7 +79,12 @@ impl TMdnsBrowser for AvahiMdnsBrowser { self.client = Some(Rc::new(ManagedAvahiClient::new( ManagedAvahiClientParams::builder() - .poll(self.poll.as_ref().unwrap().clone()) + .poll( + self.poll + .as_ref() + .ok_or("could not get poll as ref")? + .clone(), + ) .flags(AvahiClientFlags(0)) .callback(Some(client_callback)) .userdata(self.context.as_raw()) @@ -94,7 +99,12 @@ impl TMdnsBrowser for AvahiMdnsBrowser { } } - Ok(EventLoop::new(self.poll.as_ref().unwrap().clone())) + Ok(EventLoop::new( + self.poll + .as_ref() + .ok_or("could not get poll as ref")? + .clone(), + )) } } @@ -161,7 +171,12 @@ unsafe fn create_browser(context: &mut AvahiBrowserContext) -> Result<()> { .flags(0) .callback(Some(browse_callback)) .userdata(context.as_raw()) - .client(Rc::clone(context.client.as_ref().unwrap())) + .client(Rc::clone( + context + .client + .as_ref() + .ok_or("could not get client as ref")?, + )) .build()?, )?); @@ -305,8 +320,7 @@ unsafe fn handle_resolver_found( .address(address) .port(port) .txt(txt) - .build() - .unwrap(); + .build()?; debug!("Service resolved: {:?}", result); diff --git a/zeroconf/src/avahi/service.rs b/zeroconf/src/avahi/service.rs index fc4433e..444fdde 100644 --- a/zeroconf/src/avahi/service.rs +++ b/zeroconf/src/avahi/service.rs @@ -61,7 +61,7 @@ impl TMdnsService for AvahiMdnsService { } fn name(&self) -> Option<&str> { - self.context.name.as_ref().map(|n| n.to_str().unwrap()) + self.context.name.as_ref().map(c_str::to_str) } fn set_network_interface(&mut self, interface: NetworkInterface) { @@ -77,7 +77,7 @@ impl TMdnsService for AvahiMdnsService { } fn domain(&self) -> Option<&str> { - self.context.domain.as_ref().map(|d| d.to_str().unwrap()) + self.context.domain.as_ref().map(c_str::to_str) } fn set_host(&mut self, host: &str) { @@ -85,7 +85,7 @@ impl TMdnsService for AvahiMdnsService { } fn host(&self) -> Option<&str> { - self.context.host.as_ref().map(|h| h.to_str().unwrap()) + self.context.host.as_ref().map(c_str::to_str) } fn set_txt_record(&mut self, txt_record: TxtRecord) { @@ -115,7 +115,12 @@ impl TMdnsService for AvahiMdnsService { self.client = Some(Rc::new(ManagedAvahiClient::new( ManagedAvahiClientParams::builder() - .poll(self.poll.as_ref().unwrap().clone()) + .poll( + self.poll + .as_ref() + .ok_or("could not get poll as ref")? + .clone(), + ) .flags(AvahiClientFlags(0)) .callback(Some(client_callback)) .userdata(self.context.as_raw()) @@ -130,7 +135,12 @@ impl TMdnsService for AvahiMdnsService { } } - Ok(EventLoop::new(self.poll.as_ref().unwrap().clone())) + Ok(EventLoop::new( + self.poll + .as_ref() + .ok_or("could not get poll as ref")? + .clone(), + )) } } @@ -221,20 +231,32 @@ unsafe fn create_service(context: &mut AvahiServiceContext) -> Result<()> { context.group = Some(ManagedAvahiEntryGroup::new( ManagedAvahiEntryGroupParams::builder() - .client(Rc::clone(context.client.as_ref().unwrap())) + .client(Rc::clone( + context + .client + .as_ref() + .ok_or("could not get client as ref")?, + )) .callback(Some(entry_group_callback)) .userdata(context.as_raw()) .build()?, )?); } - let group = context.group.as_mut().unwrap(); + let group = context + .group + .as_mut() + .ok_or("could not borrow group as mut")?; if !group.is_empty() { return Ok(()); } - let name = context.name.as_ref().unwrap().clone(); + let name = context + .name + .as_ref() + .ok_or("could not get name as ref")? + .clone(); add_services(context, &name) } @@ -242,7 +264,10 @@ unsafe fn create_service(context: &mut AvahiServiceContext) -> Result<()> { fn add_services(context: &mut AvahiServiceContext, name: &CStr) -> Result<()> { debug!("Adding service: {}", context.kind.to_string_lossy()); - let group = context.group.as_mut().unwrap(); + let group = context + .group + .as_mut() + .ok_or("could not borrow group as mut")?; group.add_service( AddServiceParams::builder() @@ -284,15 +309,25 @@ unsafe extern "C" fn entry_group_callback( ) { let context = AvahiServiceContext::from_raw(userdata); + let client = context + .client + .as_ref() + .expect("expected initialized client"); + match state { avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_ESTABLISHED => { context.invoke_callback(handle_group_established(context)) } - avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_FAILURE => context.invoke_callback(Err( - avahi_util::get_last_error(context.group.as_ref().unwrap().get_client()).into(), - )), + avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_FAILURE => { + context.invoke_callback(Err(avahi_util::get_last_error(client.inner).into())) + } avahi_sys::AvahiEntryGroupState_AVAHI_ENTRY_GROUP_COLLISION => { - let name = context.name.as_ref().unwrap().clone(); + let name = context + .name + .as_ref() + .expect("expected initialized name") + .clone(); + let new_name = avahi_util::alternative_service_name(name.as_c_str()); let result = add_services(context, new_name); @@ -309,8 +344,16 @@ unsafe extern "C" fn entry_group_callback( unsafe fn handle_group_established(context: &AvahiServiceContext) -> Result { debug!("Group established"); + let name = c_str::copy_raw( + context + .name + .as_ref() + .ok_or("could not get name as ref")? + .as_ptr(), + ); + Ok(ServiceRegistration::builder() - .name(c_str::copy_raw(context.name.as_ref().unwrap().as_ptr())) + .name(name) .service_type(ServiceType::from_str(&c_str::copy_raw( context.kind.as_ptr(), ))?) diff --git a/zeroconf/src/avahi/txt_record.rs b/zeroconf/src/avahi/txt_record.rs index 633a90a..5badf7f 100644 --- a/zeroconf/src/avahi/txt_record.rs +++ b/zeroconf/src/avahi/txt_record.rs @@ -139,8 +139,14 @@ impl Iterator for Iter<'_> { self.node = n.next(); Some(( - pair.key().as_str().unwrap().to_string(), - pair.value().as_str().unwrap().to_string(), + pair.key() + .as_str() + .expect("could not key as str") + .to_string(), + pair.value() + .as_str() + .expect("could not get value as str") + .to_string(), )) } } diff --git a/zeroconf/src/bonjour/bonjour_util.rs b/zeroconf/src/bonjour/bonjour_util.rs index 022b71d..511ccaf 100644 --- a/zeroconf/src/bonjour/bonjour_util.rs +++ b/zeroconf/src/bonjour/bonjour_util.rs @@ -11,7 +11,12 @@ use bonjour_sys::DNSServiceErrorType; /// Bonjour suffixes domains with a final `'.'` character in some contexts but is not required by /// the standard. This function removes the final dot if present. pub fn normalize_domain(domain: &str) -> String { - if domain.chars().nth(domain.len() - 1).unwrap() == '.' { + let end = domain + .chars() + .nth(domain.len() - 1) + .expect("could not index domain string"); + + if end == '.' { String::from(&domain[..domain.len() - 1]) } else { String::from(domain) diff --git a/zeroconf/src/bonjour/browser.rs b/zeroconf/src/bonjour/browser.rs index 0a488f4..45fa1b3 100644 --- a/zeroconf/src/bonjour/browser.rs +++ b/zeroconf/src/bonjour/browser.rs @@ -281,11 +281,26 @@ unsafe fn handle_get_address_info( }; let hostname = c_str::copy_raw(hostname); - let domain = bonjour_util::normalize_domain(&ctx.resolved_domain.take().unwrap()); - let kind = bonjour_util::normalize_domain(&ctx.resolved_kind.take().unwrap()); + + let domain = bonjour_util::normalize_domain( + &ctx.resolved_domain + .take() + .ok_or("could not get domain from BonjourBrowserContext")?, + ); + + let kind = bonjour_util::normalize_domain( + &ctx.resolved_kind + .take() + .ok_or("could not get kind from BonjourBrowserContext")?, + ); + + let name = ctx + .resolved_name + .take() + .ok_or("could not get name from BonjourBrowserContext")?; let result = ServiceDiscovery::builder() - .name(ctx.resolved_name.take().unwrap()) + .name(name) .service_type(bonjour_util::parse_regtype(&kind)?) .domain(domain) .host_name(hostname) diff --git a/zeroconf/src/bonjour/service.rs b/zeroconf/src/bonjour/service.rs index 066ec7b..7cff935 100644 --- a/zeroconf/src/bonjour/service.rs +++ b/zeroconf/src/bonjour/service.rs @@ -52,7 +52,7 @@ impl TMdnsService for BonjourMdnsService { } fn name(&self) -> Option<&str> { - self.name.as_ref().map(|n| n.to_str().unwrap()) + self.name.as_ref().map(c_str::to_str) } fn set_network_interface(&mut self, interface: NetworkInterface) { @@ -68,7 +68,7 @@ impl TMdnsService for BonjourMdnsService { } fn domain(&self) -> Option<&str> { - self.domain.as_ref().map(|d| d.to_str().unwrap()) + self.domain.as_ref().map(c_str::to_str) } fn set_host(&mut self, host: &str) { @@ -76,7 +76,7 @@ impl TMdnsService for BonjourMdnsService { } fn host(&self) -> Option<&str> { - self.host.as_ref().map(|h| h.to_str().unwrap()) + self.host.as_ref().map(c_str::to_str) } fn set_txt_record(&mut self, txt_record: TxtRecord) { diff --git a/zeroconf/src/bonjour/txt_record.rs b/zeroconf/src/bonjour/txt_record.rs index b3161de..03c56c8 100644 --- a/zeroconf/src/bonjour/txt_record.rs +++ b/zeroconf/src/bonjour/txt_record.rs @@ -1,6 +1,7 @@ //! Bonjour implementation for cross-platform TXT record. 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}; @@ -54,7 +55,7 @@ impl TTxtRecord for BonjourTxtRecord { unsafe { self.0 .remove_value(c_str.as_ptr() as *const c_char) - .unwrap() + .expect("could not remove value") }; prev.into() @@ -134,12 +135,12 @@ impl Iterator for Iter<'_> { &mut value_len, &mut value, ) - .unwrap(); + .expect("could not get item at index"); } assert_not_null!(value); - let key = String::from(raw_key.to_str().unwrap()) + let key = String::from(c_str::to_str(&raw_key)) .trim_matches(char::from(0)) .to_string(); @@ -176,5 +177,5 @@ impl<'a> Iterator for Values<'a> { unsafe fn read_value(value: *const c_void, value_len: u8) -> String { let value_len = value_len as usize; let value_raw = slice::from_raw_parts(value as *const u8, value_len); - String::from_utf8(value_raw.to_vec()).unwrap() + String::from_utf8(value_raw.to_vec()).expect("could not read value") } diff --git a/zeroconf/src/bonjour/txt_record_ref.rs b/zeroconf/src/bonjour/txt_record_ref.rs index a49e3ef..a74c4b7 100644 --- a/zeroconf/src/bonjour/txt_record_ref.rs +++ b/zeroconf/src/bonjour/txt_record_ref.rs @@ -169,7 +169,8 @@ impl Default for ManagedTXTRecordRef { impl Clone for ManagedTXTRecordRef { fn clone(&self) -> Self { unsafe { - Self::clone_raw(self.get_bytes_ptr() as *const c_uchar, self.get_length()).unwrap() + Self::clone_raw(self.get_bytes_ptr() as *const c_uchar, self.get_length()) + .expect("could not clone TXT record") } } } diff --git a/zeroconf/src/ffi/c_str.rs b/zeroconf/src/ffi/c_str.rs index 4babad6..bbb01b0 100644 --- a/zeroconf/src/ffi/c_str.rs +++ b/zeroconf/src/ffi/c_str.rs @@ -23,7 +23,9 @@ impl AsCChars for Option<&CString> { /// [`CStr::from_ptr()`]: https://doc.rust-lang.org/std/ffi/struct.CStr.html#method.from_ptr pub unsafe fn raw_to_str<'a>(s: *const c_char) -> &'a str { assert_not_null!(s); - CStr::from_ptr(s).to_str().unwrap() + CStr::from_ptr(s) + .to_str() + .expect("could not convert raw to str") } /// Copies the specified `*const c_char` into a `String`. @@ -37,6 +39,11 @@ pub unsafe fn copy_raw(s: *const c_char) -> String { String::from(raw_to_str(s)) } +/// Converts the specified [`CString`] to a `&str`. +pub fn to_str(s: &CString) -> &str { + s.to_str().expect("could not convert CString to str") +} + #[cfg(test)] mod tests { use super::*; diff --git a/zeroconf/src/lib.rs b/zeroconf/src/lib.rs index 7e1a1eb..4e9c93e 100644 --- a/zeroconf/src/lib.rs +++ b/zeroconf/src/lib.rs @@ -49,7 +49,7 @@ //! service_name: String, //! } //! -//! fn main() { +//! fn main() -> zeroconf::Result<()> { //! env_logger::init(); //! //! let Args { @@ -59,23 +59,23 @@ //! } = Args::parse(); //! //! let sub_types = sub_types.iter().map(|s| s.as_str()).collect::>(); -//! let service_type = ServiceType::with_sub_types(&name, &protocol, sub_types).unwrap(); +//! let service_type = ServiceType::with_sub_types(&name, &protocol, sub_types)?; //! let mut service = MdnsService::new(service_type, 8080); //! let mut txt_record = TxtRecord::new(); //! let context: Arc> = Arc::default(); //! -//! txt_record.insert("foo", "bar").unwrap(); +//! txt_record.insert("foo", "bar")?; //! //! service.set_name("zeroconf_example_service"); //! 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(); +//! let event_loop = service.register()?; //! //! loop { //! // calling `poll()` will keep this service alive -//! event_loop.poll(Duration::from_secs(0)).unwrap(); +//! event_loop.poll(Duration::from_secs(0))?; //! } //! } //! @@ -83,18 +83,21 @@ //! result: zeroconf::Result, //! context: Option>, //! ) { -//! let service = result.unwrap(); +//! let service = result.expect("failed to register service"); //! //! info!("Service registered: {:?}", service); //! //! let context = context //! .as_ref() -//! .unwrap() +//! .expect("could not get context") //! .downcast_ref::>>() -//! .unwrap() +//! .expect("error down-casting context") //! .clone(); //! -//! context.lock().unwrap().service_name = service.name().clone(); +//! context +//! .lock() +//! .expect("failed to obtain context lock") +//! .service_name = service.name().clone(); //! //! info!("Context: {:?}", context); //! @@ -132,7 +135,7 @@ //! sub_type: Option, //! } //! -//! fn main() { +//! fn main() -> zeroconf::Result<()> { //! env_logger::init(); //! //! let Args { @@ -153,11 +156,11 @@ //! //! browser.set_service_discovered_callback(Box::new(on_service_discovered)); //! -//! let event_loop = browser.browse_services().unwrap(); +//! let event_loop = browser.browse_services()?; //! //! loop { //! // calling `poll()` will keep this browser alive -//! event_loop.poll(Duration::from_secs(0)).unwrap(); +//! event_loop.poll(Duration::from_secs(0))?; //! } //! } //! @@ -165,7 +168,10 @@ //! result: zeroconf::Result, //! _context: Option>, //! ) { -//! info!("Service discovered: {:?}", result.unwrap()); +//! info!( +//! "Service discovered: {:?}", +//! result.expect("service discovery failed") +//! ); //! //! // ... //! } diff --git a/zeroconf/src/macros.rs b/zeroconf/src/macros.rs index 30c6e6b..d5ee975 100644 --- a/zeroconf/src/macros.rs +++ b/zeroconf/src/macros.rs @@ -11,7 +11,7 @@ macro_rules! c_string { ::std::ffi::CString::from_vec_unchecked(vec![0; $len]) }; ($x:expr) => { - ::std::ffi::CString::new($x).unwrap() + ::std::ffi::CString::new($x).expect("could not create new CString") }; } @@ -35,6 +35,9 @@ mod tests { #[test] fn c_string_success() { - assert_eq!(c_string!("foo"), CString::new("foo").unwrap()); + assert_eq!( + c_string!("foo"), + CString::new("foo").expect("could not create new CString") + ); } } diff --git a/zeroconf/src/txt_record.rs b/zeroconf/src/txt_record.rs index 487a88c..67e5f79 100644 --- a/zeroconf/src/txt_record.rs +++ b/zeroconf/src/txt_record.rs @@ -59,7 +59,9 @@ impl From> for TxtRecord { fn from(map: HashMap) -> TxtRecord { let mut record = TxtRecord::new(); for (key, value) in map { - record.insert(&key, &value).unwrap(); + record + .insert(&key, &value) + .expect("could not insert key/value pair"); } record } @@ -114,7 +116,8 @@ impl<'de> Visitor<'de> for TxtRecordVisitor { let mut map = TxtRecord::new(); while let Some((key, value)) = access.next_entry()? { - map.insert(key, value).unwrap(); + map.insert(key, value) + .expect("could not insert key/value pair"); } Ok(map)