coap-message-utils 0.3.9

Utilities for using coap-message traits
Documentation
//! Helpers and implementations around the public [`show()`] wrapper.

use coap_message::{MessageOption as _, ReadableMessage};

pub struct ShowMessage<'a, M: ReadableMessage>(&'a M);

impl<M: ReadableMessage> core::fmt::Debug for ShowMessage<'_, M> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        // not used because unstable / see https://github.com/rust-lang/rust/issues/117729
        // (and not complete yet for lack of options display)
        #[cfg(any())] // aka. cfg(false)
        f.debug_struct("ReadableMessage")
            .field("type", &core::any::type_name::<M>())
            .field("code", self.0.code().show_dotted_and_named())
            .field_with("payload", |f| Ok(()))
            .finish();

        write!(
            f,
            "ReadableMessage {{\n    type: {},\n    code: {:?},\n    options: {{\n        ",
            core::any::type_name::<M>(),
            self.0.code().show_dotted_and_named(),
        )?;
        for (i, o) in self.0.options().enumerate() {
            if i != 0 {
                write!(f, ",\n        ")?;
            }
            if let Some(name) = coap_numbers::option::to_name(o.number()) {
                write!(f, "{} ({}): {:?}", name, o.number(), o.value())?;
            } else {
                write!(f, "{}: {:?}", o.number(), o.value())?;
            }
        }
        write!(f, "\n    }},\n    payload: {:?}\n}}", self.0.payload())?;

        Ok(())
    }
}

#[cfg(feature = "defmt_0_3")]
impl<'a, M: ReadableMessage> defmt_0_3::Format for ShowMessage<'a, M> {
    fn format(&self, f: defmt_0_3::Formatter) {
        use defmt::write;
        use defmt_0_3 as defmt;

        write!(
            f,
            "ReadableMessage {{\n    type: {=str},\n    code: {},\n    options: {{\n        ",
            core::any::type_name::<M>(),
            self.0.code().show_dotted_and_named(),
        );
        for (i, o) in self.0.options().enumerate() {
            if i != 0 {
                write!(f, ",\n        ");
            }
            if let Some(name) = coap_numbers::option::to_name(o.number()) {
                write!(f, "{} ({}): {:?}", name, o.number(), o.value());
            } else {
                write!(f, "{}: {:?}", o.number(), o.value());
            }
        }
        write!(f, "\n    }},\n    payload: {:?}\n}}", self.0.payload());
    }
}

/// Extension trait providing utility functions on [coap_message::ReadableMessage].
pub trait ShowMessageExt: ReadableMessage + Sized {
    /// Wraps the message to have a [`core::fmt::Debug`] imlementation, and also provide
    /// [`defmt_0_3::Format`] if the `defmt_0_3` feature is selected.
    ///
    /// ```
    /// # use coap_message::MinimalWritableMessage;
    /// # let mut message = coap_message_implementations::heap::HeapMessage::new();
    /// message.set_code(coap_numbers::code::GET);
    /// message.add_option_str(coap_numbers::option::URI_PATH, "hello");
    ///
    /// use coap_message_utils::ShowMessageExt;
    /// let shown = format!("{:?}", message.show());
    /// // The precise format is not fixed, but it contains some usable details:
    /// assert!(shown.contains("code: 0.01 GET"));
    /// assert!(shown.contains("Uri-Path (11):"));
    /// ```
    fn show(&self) -> ShowMessage<'_, Self> {
        ShowMessage(self)
    }
}

impl<M: ReadableMessage> ShowMessageExt for M {}

pub struct ShowDotted(u8);

impl core::fmt::Debug for ShowDotted {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        coap_numbers::code::format_dotted(self.0, f)
    }
}

#[cfg(feature = "defmt_0_3")]
impl defmt_0_3::Format for ShowDotted {
    fn format(&self, f: defmt_0_3::Formatter) {
        use defmt::write;
        use defmt_0_3 as defmt;

        write!(f, "{=u8}.{=u8:02}", self.0 >> 5, self.0 & 0x1f,)
    }
}

pub struct ShowNamed(u8);

impl core::fmt::Debug for ShowNamed {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        if let Some(name) = coap_numbers::code::to_name(self.0) {
            write!(f, "{name}")
        } else {
            write!(f, "{:?}", self.0.show_dotted())
        }
    }
}

#[cfg(feature = "defmt_0_3")]
impl defmt_0_3::Format for ShowNamed {
    fn format(&self, f: defmt_0_3::Formatter) {
        use defmt::write;
        use defmt_0_3 as defmt;

        if let Some(name) = coap_numbers::code::to_name(self.0) {
            write!(f, "{}", name)
        } else {
            write!(f, "{:?}", self.0.show_dotted())
        }
    }
}

pub struct ShowDottedAndNamed(u8);

impl core::fmt::Debug for ShowDottedAndNamed {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        if let Some(name) = coap_numbers::code::to_name(self.0) {
            write!(f, "{:?} {}", self.0.show_dotted(), name)
        } else {
            write!(f, "{:?}", self.0.show_dotted())
        }
    }
}

#[cfg(feature = "defmt_0_3")]
impl defmt_0_3::Format for ShowDottedAndNamed {
    fn format(&self, f: defmt_0_3::Formatter) {
        use defmt::write;
        use defmt_0_3 as defmt;

        if let Some(name) = coap_numbers::code::to_name(self.0) {
            write!(f, "{:?} {=str}", self.0.show_dotted(), name)
        } else {
            write!(f, "{:?}", self.0.show_dotted())
        }
    }
}

/// Extension trait providing utility functions on [coap_message::Code].
pub trait ShowCodeExt: coap_message::Code {
    /// Wraps the code to have a [`core::fmt::Debug`] imlementation that produces dotted format,
    /// and also provides [`defmt_0_3::Format`] if the `defmt_0_3` feature is selected.
    ///
    /// ```
    /// use coap_message_utils::ShowCodeExt;
    /// assert_eq!("2.05", format!("{:?}", coap_numbers::code::CONTENT.show_dotted()));
    /// ```
    fn show_dotted(self) -> ShowDotted {
        let numeric: u8 = self.into();
        ShowDotted(numeric)
    }

    /// Wraps the code to have a [`core::fmt::Debug`] imlementation that produces the code's name
    /// (falling back to dotted format), and also provides [`defmt_0_3::Format`] if the `defmt_0_3`
    /// feature is selected.
    ///
    /// ```
    /// use coap_message_utils::ShowCodeExt;
    /// assert_eq!("Content", format!("{:?}", coap_numbers::code::CONTENT.show_named()));
    /// assert_eq!("0.31", format!("{:?}", 31u8.show_named()));
    /// ```
    fn show_named(self) -> ShowNamed {
        let numeric: u8 = self.into();
        ShowNamed(numeric)
    }

    /// Wraps the code to have a [`core::fmt::Debug`] imlementation that produces the code's name
    /// (falling back to dotted format), and also provides [`defmt_0_3::Format`] if the `defmt_0_3`
    /// feature is selected.
    ///
    /// ```
    /// use coap_message_utils::ShowCodeExt;
    /// assert_eq!("2.05 Content", format!("{:?}", coap_numbers::code::CONTENT.show_dotted_and_named()));
    /// assert_eq!("0.31", format!("{:?}", 31u8.show_dotted_and_named()));
    /// ```
    fn show_dotted_and_named(self) -> ShowDottedAndNamed {
        let numeric: u8 = self.into();
        ShowDottedAndNamed(numeric)
    }
}

impl<C: coap_message::Code> ShowCodeExt for C {}