coap-message-utils 0.3.9

Utilities for using coap-message traits
Documentation
use crate::Error;
use crate::option_value::{Block2RequestData, TryFromOption};
use coap_message::MessageOption;
use coap_numbers::option;

/// Extensions implemented for any [MessageOption] iterator to enable simple and direct use
///
/// As an extension trait, this is not meant to be implemented (in fact, Rust's rules prohibit this
/// from being implemented anywhere else after this crate's `impl<T, O> OptionsExt<O> for T`), but
/// can be .
///
/// This will typically be used by filtering down a message's options, e.g. like this:
///
/// ```
/// # struct ReqData {
/// #     block2: coap_message_utils::option_value::Block2RequestData,
/// # }
///
/// use coap_message::{MessageOption, ReadableMessage};
/// use coap_message_utils::{Error, OptionsExt};
///
/// fn process_message(req: &impl ReadableMessage) -> Result<ReqData, Error>
/// {
///     let mut block2 = None;
///
///     req.options()
///            .take_block2(&mut block2)
///            .filter(|o| {
///                match o.number() {
///                    // my own option => my own behavior
///                    _ => true
///                }
///            })
///            .ignore_elective_others()
///            ?;
///
///     let block2 = block2.unwrap_or_default();
///     Ok(ReqData { block2 })
/// }
/// ```
// No need to seal this: the `for T` implementation already ensures that nobody implements it.
pub trait OptionsExt<O: MessageOption>: Iterator<Item = O> {
    /// Remove Uri-Host option from the iterator
    ///
    /// Note that not processing this may be inappropriate for security reasons, especially with
    /// security models that otherwise require DNS rebinding protection.
    fn ignore_uri_host(self) -> impl Iterator<Item = O>;

    /// Remove Uri-Query options from the iterator
    ///
    /// Note that this is *not* something that should simply be placed in a handler; it should only
    /// be used if the definition of the resource's interface explicitly allows the implementation
    /// to ignore unknown query parameters.
    fn ignore_uri_query(self) -> impl Iterator<Item = O>;

    /// Exhaust the iterator, successfully if no critical options are present, or indicating an
    /// error if critical options were not processed before.
    fn ignore_elective_others(self) -> Result<(), Error>;

    /// Store the first matching and parsable option into `out`, and return the iterator over the
    /// remaining options.
    ///
    /// Unparsable or duplicate options are simply left in the output and rely on their criticality
    /// to cause an error when eventually `.ignore_elective_others()` is called. When using this
    /// mechanism to parse a non-critical option that should not be ignored on parsing errors, that
    /// option should not implement [`TryFromOption`] on `Self`, but rather on `Result<Self,
    /// ParsingError>`, and `if Some(Err(_)) = out` after the options have been exhausted, the
    /// error needs to be raised.
    fn take_into<'a, T: TryFromOption>(self, out: &'a mut Option<T>) -> impl Iterator<Item = O>
    where
        Self: 'a;

    /// Set out to the parsed value of the found Block2 option, and return an iterator over the
    /// remaining options.
    ///
    /// Unparsable or repeated Block2 options are left in the output, leaving the error to show up
    /// in the eventual ignore_elective_others call.
    ///
    /// Note that this is merely a pre-typed version of take_into (and not deprecated yet because
    /// it's a convenient shortcut to spelling out the `None`'s type).
    fn take_block2<'a>(self, out: &'a mut Option<Block2RequestData>) -> impl Iterator<Item = O>
    where
        Self: 'a;

    /// Call a function (that typically cranks some path state machine) on every (valid) Uri-Path
    /// option in an iterator, hiding them from further iteration.
    ///
    /// Error handling of the UTF8 decoding is done by not removing invalid options from the
    /// iterator, thus leaving them for an eventual ignore_elective_others.
    fn take_uri_path<F: FnMut(&str)>(self, f: F) -> impl Iterator<Item = O>;
}

impl<T, O> OptionsExt<O> for T
where
    T: Iterator<Item = O>,
    O: MessageOption,
{
    fn ignore_uri_host(self) -> impl Iterator<Item = O> {
        self.filter(|o| o.number() != option::URI_HOST)
    }

    fn ignore_uri_query(self) -> impl Iterator<Item = O> {
        self.filter(|o| o.number() != option::URI_QUERY)
    }

    fn ignore_elective_others(mut self) -> Result<(), Error> {
        match self.find(|o| option::get_criticality(o.number()) == option::Criticality::Critical) {
            Some(o) => Err(Error::bad_option(o.number())),
            None => Ok(()),
        }
    }

    fn take_into<'a, T2: 'a + TryFromOption>(
        self,
        out: &'a mut Option<T2>,
    ) -> impl Iterator<Item = O>
    where
        T: 'a,
    {
        self.filter(move |o| {
            if out.is_none()
                && let Some(o) = T2::try_from(o)
            {
                *out = Some(o);
                return false;
            }
            true
        })
    }

    fn take_block2<'a>(self, out: &'a mut Option<Block2RequestData>) -> impl Iterator<Item = O>
    where
        Self: 'a,
    {
        self.take_into(out)
    }

    fn take_uri_path<F: FnMut(&str)>(self, mut f: F) -> impl Iterator<Item = O> {
        self.filter(move |o| {
            if o.number() == option::URI_PATH
                && let Ok(s) = core::str::from_utf8(o.value())
            {
                f(s);
                return false;
            }
            true
        })
    }
}