coap-request-implementations 0.1.0-alpha.4

Implementations of basic CoAP requests
Documentation
//! Simple implementations of a [coap_request::Request]
//!
//! While the [coap_request] interfaces are geared toward portability, this crate provides
//! implementations of the [coap_request::Request] trait that are easy to set up. They follow a
//! builder pattern:
//!
//! ```
//! # struct X<S: coap_request::Stack>(S);
//! # impl<S: coap_request::Stack> X<S> {
//! #     fn to(self, addr: u128) -> S {
//! #         panic!("Not really")
//! #     }
//! # }
//! # async fn run<S: coap_request::Stack>(stack: X<S>) {
//! stack
//!     .to("[2001:db8::1]:5683".parse().unwrap())
//!     .request(
//!         coap_request_implementations::Code::get()
//!             .with_path("/.well-known/core")
//!             .processing_response_payload_through(|p| println!("Data: {:?}", p)),
//!     )
//!     .await;
//! # }
//! ```
//!
//! ## Stability
//!
//! This crate is in an early experimental stage, do not expect these APIs to persist. (Then
//! again, the crate that needs to be stable is [coap_request], as a single stack can easily be
//! used with request builders from multiple versions of this crate).
#![no_std]

mod builder;
mod conversions;

use coap_message::{MinimalWritableMessage, ReadableMessage};
use coap_request::{Request, Stack};

use builder::Builder;
pub use conversions::AsUriPath;

/// A request that merely sends a code, and returns successful if the returned code as successful
/// and no critical options were present.
///
/// While not terribly useful on its own, its main purpose is as the start of a builder.
pub struct Code {
    code: u8,
}

/// A request that sets a request Uri-Path.
///
/// This is based on a [Code], and built in [Code::with_path].
pub struct WithPath<S: Stack + ?Sized, Prev: Builder<S>, P: AsUriPath> {
    previous: Prev,
    path: P,
    _phantom: core::marker::PhantomData<S>,
}

/// A request into which the user can populate a custom payload.
pub struct WithRequestCallback<S: Stack + ?Sized, Prev: Builder<S>, F>
where
    F: FnMut(&mut S::RequestMessage<'_>) -> Result<(), S::RequestUnionError>,
{
    previous: Prev,
    f: F,
    _phantom: core::marker::PhantomData<S>,
}

impl<S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>, F1>
    WithRequestCallback<S, Prev, F1>
where
    F1: FnMut(&mut S::RequestMessage<'_>) -> Result<(), S::RequestUnionError>,
{
    // FIXME: This is duplicated from WithPath -- should we offer that through Builder, or is this
    // really more practical?
    /// Provide the response payload to a user callback.
    pub fn processing_response_payload_through<O, F2>(
        self,
        f: F2,
    ) -> ProcessingResponse<S, Self, O, F2>
    where
        F2: for<'a> FnOnce(&'a [u8]) -> O,
    {
        ProcessingResponse {
            previous: self,
            f: Some(f),
            _phantom: Default::default(),
        }
    }
}

pub struct WithRequestPayloadSlice<'payload, S: Stack + ?Sized, Prev: Builder<S>> {
    previous: Prev,
    payload: &'payload [u8],
    _phantom: core::marker::PhantomData<S>,
}

impl<'payload, S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>>
    WithRequestPayloadSlice<'payload, S, Prev>
{
    // FIXME: This is duplicated from WithPath -- should we offer that through Builder, or is this
    // really more practical?
    /// Provide the response payload to a user callback.
    pub fn processing_response_payload_through<O, F2>(
        self,
        f: F2,
    ) -> ProcessingResponse<S, Self, O, F2>
    where
        F2: for<'a> FnOnce(&'a [u8]) -> O,
    {
        ProcessingResponse {
            previous: self,
            f: Some(f),
            _phantom: Default::default(),
        }
    }
}

/// A request that runs a user provided closure to read the response message's payload.
///
/// This is based on a [WithPath], and built in [WithPath::processing_response_payload_through].
// FIXME: Can we easily build this with an async closure as well? So far this has failed with
// lifetime trouble when
//     F: for<'a> FnOnce(&'a [u8]) -> FO,
//     for<'a> FO: core::future::Future<Output = O>,
// because we couldn't make the FO be 'a (even though it could be from its definition) and thus
// prolong 'a to its drop time.
pub struct ProcessingResponse<S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>, O, F>
where
    F: for<'a> FnOnce(&'a [u8]) -> O,
{
    previous: Prev,
    /// When this is None, a response has already been processed -- this does not expect multiple
    /// responses.
    f: Option<F>,
    _phantom: core::marker::PhantomData<S>,
}

macro_rules! code_macro {
    ($l:ident, $u:ident) => {
        #[doc=concat!("A request sending the ", stringify!($u), " code")]
        pub fn $l() -> Self {
            Self {
                code: coap_numbers::code::$u,
            }
        }
    };
}

impl Code {
    code_macro!(get, GET);
    code_macro!(post, POST);
    code_macro!(put, PUT);
    code_macro!(delete, DELETE);
    code_macro!(fetch, FETCH);
    code_macro!(patch, PATCH);
    code_macro!(ipatch, IPATCH);

    /// Set the Uri-Path options on a request.
    pub fn with_path<S: Stack + ?Sized, P: conversions::AsUriPath>(
        self,
        path: P,
    ) -> WithPath<S, Self, P> {
        WithPath {
            previous: self,
            path,
            _phantom: Default::default(),
        }
    }
}

impl<S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>, P: conversions::AsUriPath>
    WithPath<S, Prev, P>
{
    /// Set the request payload in a user callback.
    ///
    /// ## Caveats
    ///
    /// Note that while the user may do all sorts of writes to the request message, they better
    /// stick to writing to the payload (for other uses might lead to panics).
    ///
    /// Also, it is not sure whether this can actualy be used in a way that satisfies all its
    /// constraints.
    pub fn with_request_callback<F>(self, f: F) -> WithRequestCallback<S, Self, F>
    where
        F: FnMut(&'_ mut S::RequestMessage<'_>) -> Result<(), S::RequestUnionError>,
    {
        WithRequestCallback {
            previous: self,
            f,
            _phantom: Default::default(),
        }
    }

    pub fn with_request_payload_slice(
        self,
        payload: &[u8],
    ) -> WithRequestPayloadSlice<'_, S, Self> {
        WithRequestPayloadSlice {
            previous: self,
            payload,
            _phantom: Default::default(),
        }
    }

    /// Provide the response payload to a user callback.
    pub fn processing_response_payload_through<O, F>(
        self,
        f: F,
    ) -> ProcessingResponse<S, Self, O, F>
    where
        F: for<'a> FnOnce(&'a [u8]) -> O,
    {
        ProcessingResponse {
            previous: self,
            f: Some(f),
            _phantom: Default::default(),
        }
    }
}

impl<S: Stack + ?Sized> Request<S> for Code {
    type Carry = ();
    type Output = Result<(), ()>;
    async fn build_request(
        &mut self,
        req: &mut S::RequestMessage<'_>,
    ) -> Result<(), S::RequestUnionError> {
        use coap_message::Code;
        let code = <S::RequestMessage<'_> as MinimalWritableMessage>::Code::new(self.code)
            .map_err(S::RequestMessage::convert_code_error)?;
        req.set_code(code);
        Ok(())
    }
    async fn process_response(&mut self, res: &S::ResponseMessage<'_>, _carry: ()) -> Self::Output {
        let code: u8 = res.code().into();
        use coap_numbers::code::{classify, Class, Range};
        if !matches!(classify(code), Range::Response(Class::Success)) {
            // This encompasses both unsuccessful responses and errors that the stack let through
            // (it should have rejected them)
            return Err(());
        }

        use coap_message_utils::OptionsExt;
        res.options().ignore_elective_others().map_err(|_| ())
    }
}

impl<S: Stack + ?Sized> Builder<S> for Code {}

impl<S: Stack + ?Sized, Prev: Builder<S>, P: conversions::AsUriPath> Request<S>
    for WithPath<S, Prev, P>
{
    type Carry = Prev::Carry;
    type Output = Prev::Output;
    async fn build_request(
        &mut self,
        req: &mut S::RequestMessage<'_>,
    ) -> Result<Self::Carry, S::RequestUnionError> {
        use coap_message::OptionNumber;
        // FIXME: This is just `self.previous.build_request(req).await`; let's see if having M1/M2
        // in the trait pays off elsewhere, otherwise the need to be this explicit here is
        // horrible. (It looked even worse when we had M1 and M2)
        let carry = <_ as coap_request::Request<S>>::build_request(&mut self.previous, req).await;
        for p in self.path.as_uri_path() {
            req.add_option(
                <S::RequestMessage<'_> as MinimalWritableMessage>::OptionNumber::new(
                    coap_numbers::option::URI_PATH,
                )
                .map_err(S::RequestMessage::convert_option_number_error)?,
                p.as_bytes(),
            )
            .map_err(S::RequestMessage::convert_add_option_error)?;
        }
        carry
    }
    async fn process_response(
        &mut self,
        res: &S::ResponseMessage<'_>,
        carry: Prev::Carry,
    ) -> Self::Output {
        <_ as coap_request::Request<S>>::process_response(&mut self.previous, res, carry).await
    }
}
impl<S: Stack + ?Sized, Prev: Builder<S>, P: conversions::AsUriPath> Builder<S>
    for WithPath<S, Prev, P>
{
}

impl<S: Stack + ?Sized, Prev: Builder<S>, F> Request<S> for WithRequestCallback<S, Prev, F>
where
    F: FnMut(&mut S::RequestMessage<'_>) -> Result<(), S::RequestUnionError>,
{
    type Carry = Prev::Carry;
    type Output = Prev::Output;
    async fn build_request(
        &mut self,
        req: &mut S::RequestMessage<'_>,
    ) -> Result<Self::Carry, S::RequestUnionError> {
        let carry = <_ as coap_request::Request<S>>::build_request(&mut self.previous, req).await?;
        (self.f)(req)?;
        Ok(carry)
    }

    async fn process_response(
        &mut self,
        res: &S::ResponseMessage<'_>,
        carry: Self::Carry,
    ) -> Self::Output {
        <_ as coap_request::Request<S>>::process_response(&mut self.previous, res, carry).await
    }
}
impl<S: Stack + ?Sized, Prev: Builder<S>, F> Builder<S> for WithRequestCallback<S, Prev, F> where
    F: FnMut(&mut S::RequestMessage<'_>) -> Result<(), S::RequestUnionError>
{
}

impl<'payload, S: Stack + ?Sized, Prev: Builder<S>> Request<S>
    for WithRequestPayloadSlice<'payload, S, Prev>
{
    type Carry = Prev::Carry;
    type Output = Prev::Output;
    async fn build_request(
        &mut self,
        req: &mut S::RequestMessage<'_>,
    ) -> Result<Self::Carry, S::RequestUnionError> {
        let carry = <_ as coap_request::Request<S>>::build_request(&mut self.previous, req).await?;
        req.set_payload(self.payload)
            .map_err(S::RequestMessage::convert_set_payload_error)?;
        Ok(carry)
    }

    async fn process_response(
        &mut self,
        res: &S::ResponseMessage<'_>,
        carry: Self::Carry,
    ) -> Self::Output {
        <_ as coap_request::Request<S>>::process_response(&mut self.previous, res, carry).await
    }
}
impl<'payload, S: Stack + ?Sized, Prev: Builder<S>> Builder<S>
    for WithRequestPayloadSlice<'payload, S, Prev>
{
}

impl<S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>, O, F> Request<S>
    for ProcessingResponse<S, Prev, O, F>
where
    F: for<'a> FnOnce(&'a [u8]) -> O,
{
    type Carry = Prev::Carry;
    type Output = Result<O, ()>;
    async fn build_request(
        &mut self,
        req: &mut S::RequestMessage<'_>,
    ) -> Result<Self::Carry, S::RequestUnionError> {
        <_ as coap_request::Request<S>>::build_request(&mut self.previous, req).await
    }

    async fn process_response(
        &mut self,
        res: &S::ResponseMessage<'_>,
        carry: Self::Carry,
    ) -> Self::Output {
        <_ as coap_request::Request<S>>::process_response(&mut self.previous, res, carry).await?;
        if let Some(f) = self.f.take() {
            Ok(f(res.payload()))
        } else {
            Err(())
        }
    }
}
impl<S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>, O, F> Builder<S>
    for ProcessingResponse<S, Prev, O, F>
where
    F: for<'a> FnOnce(&'a [u8]) -> O,
{
}