coap_request_implementations/
lib.rs

1//! Simple implementations of a [coap_request::Request]
2//!
3//! While the [coap_request] interfaces are geared toward portability, this crate provides
4//! implementations of the [coap_request::Request] trait that are easy to set up. They follow a
5//! builder pattern:
6//!
7//! ```
8//! # struct X<S: coap_request::Stack>(S);
9//! # impl<S: coap_request::Stack> X<S> {
10//! #     fn to(self, addr: u128) -> S {
11//! #         panic!("Not really")
12//! #     }
13//! # }
14//! # async fn run<S: coap_request::Stack>(stack: X<S>) {
15//! stack
16//!     .to("[2001:db8::1]:5683".parse().unwrap())
17//!     .request(
18//!         coap_request_implementations::Code::get()
19//!             .with_path("/.well-known/core")
20//!             .processing_response_payload_through(|p| println!("Data: {:?}", p)),
21//!     )
22//!     .await;
23//! # }
24//! ```
25//!
26//! ## Stability
27//!
28//! This crate is in an early experimental stage, do not expect these APIs to persist. (Then
29//! again, the crate that needs to be stable is [coap_request], as a single stack can easily be
30//! used with request builders from multiple versions of this crate).
31#![no_std]
32
33mod builder;
34mod conversions;
35
36use coap_message::{MinimalWritableMessage, ReadableMessage};
37use coap_request::{Request, Stack};
38
39use builder::Builder;
40pub use conversions::AsUriPath;
41
42/// A request that merely sends a code, and returns successful if the returned code as successful
43/// and no critical options were present.
44///
45/// While not terribly useful on its own, its main purpose is as the start of a builder.
46pub struct Code {
47    code: u8,
48}
49
50/// A request that sets a request Uri-Path.
51///
52/// This is based on a [Code], and built in [Code::with_path].
53pub struct WithPath<S: Stack + ?Sized, Prev: Builder<S>, P: AsUriPath> {
54    previous: Prev,
55    path: P,
56    _phantom: core::marker::PhantomData<S>,
57}
58
59/// A request into which the user can populate a custom payload.
60pub struct WithRequestCallback<S: Stack + ?Sized, Prev: Builder<S>, F>
61where
62    F: FnMut(&mut S::RequestMessage<'_>) -> Result<(), S::RequestUnionError>,
63{
64    previous: Prev,
65    f: F,
66    _phantom: core::marker::PhantomData<S>,
67}
68
69impl<S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>, F1>
70    WithRequestCallback<S, Prev, F1>
71where
72    F1: FnMut(&mut S::RequestMessage<'_>) -> Result<(), S::RequestUnionError>,
73{
74    // FIXME: This is duplicated from WithPath -- should we offer that through Builder, or is this
75    // really more practical?
76    /// Provide the response payload to a user callback.
77    pub fn processing_response_payload_through<O, F2>(
78        self,
79        f: F2,
80    ) -> ProcessingResponse<S, Self, O, F2>
81    where
82        F2: for<'a> FnOnce(&'a [u8]) -> O,
83    {
84        ProcessingResponse {
85            previous: self,
86            f: Some(f),
87            _phantom: Default::default(),
88        }
89    }
90}
91
92pub struct WithRequestPayloadSlice<'payload, S: Stack + ?Sized, Prev: Builder<S>> {
93    previous: Prev,
94    payload: &'payload [u8],
95    _phantom: core::marker::PhantomData<S>,
96}
97
98impl<'payload, S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>>
99    WithRequestPayloadSlice<'payload, S, Prev>
100{
101    // FIXME: This is duplicated from WithPath -- should we offer that through Builder, or is this
102    // really more practical?
103    /// Provide the response payload to a user callback.
104    pub fn processing_response_payload_through<O, F2>(
105        self,
106        f: F2,
107    ) -> ProcessingResponse<S, Self, O, F2>
108    where
109        F2: for<'a> FnOnce(&'a [u8]) -> O,
110    {
111        ProcessingResponse {
112            previous: self,
113            f: Some(f),
114            _phantom: Default::default(),
115        }
116    }
117}
118
119/// A request that runs a user provided closure to read the response message's payload.
120///
121/// This is based on a [WithPath], and built in [WithPath::processing_response_payload_through].
122// FIXME: Can we easily build this with an async closure as well? So far this has failed with
123// lifetime trouble when
124//     F: for<'a> FnOnce(&'a [u8]) -> FO,
125//     for<'a> FO: core::future::Future<Output = O>,
126// because we couldn't make the FO be 'a (even though it could be from its definition) and thus
127// prolong 'a to its drop time.
128pub struct ProcessingResponse<S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>, O, F>
129where
130    F: for<'a> FnOnce(&'a [u8]) -> O,
131{
132    previous: Prev,
133    /// When this is None, a response has already been processed -- this does not expect multiple
134    /// responses.
135    f: Option<F>,
136    _phantom: core::marker::PhantomData<S>,
137}
138
139macro_rules! code_macro {
140    ($l:ident, $u:ident) => {
141        #[doc=concat!("A request sending the ", stringify!($u), " code")]
142        pub fn $l() -> Self {
143            Self {
144                code: coap_numbers::code::$u,
145            }
146        }
147    };
148}
149
150impl Code {
151    code_macro!(get, GET);
152    code_macro!(post, POST);
153    code_macro!(put, PUT);
154    code_macro!(delete, DELETE);
155    code_macro!(fetch, FETCH);
156    code_macro!(patch, PATCH);
157    code_macro!(ipatch, IPATCH);
158
159    /// Set the Uri-Path options on a request.
160    pub fn with_path<S: Stack + ?Sized, P: conversions::AsUriPath>(
161        self,
162        path: P,
163    ) -> WithPath<S, Self, P> {
164        WithPath {
165            previous: self,
166            path,
167            _phantom: Default::default(),
168        }
169    }
170}
171
172impl<S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>, P: conversions::AsUriPath>
173    WithPath<S, Prev, P>
174{
175    /// Set the request payload in a user callback.
176    ///
177    /// ## Caveats
178    ///
179    /// Note that while the user may do all sorts of writes to the request message, they better
180    /// stick to writing to the payload (for other uses might lead to panics).
181    ///
182    /// Also, it is not sure whether this can actualy be used in a way that satisfies all its
183    /// constraints.
184    pub fn with_request_callback<F>(self, f: F) -> WithRequestCallback<S, Self, F>
185    where
186        F: FnMut(&'_ mut S::RequestMessage<'_>) -> Result<(), S::RequestUnionError>,
187    {
188        WithRequestCallback {
189            previous: self,
190            f,
191            _phantom: Default::default(),
192        }
193    }
194
195    pub fn with_request_payload_slice(
196        self,
197        payload: &[u8],
198    ) -> WithRequestPayloadSlice<'_, S, Self> {
199        WithRequestPayloadSlice {
200            previous: self,
201            payload,
202            _phantom: Default::default(),
203        }
204    }
205
206    /// Provide the response payload to a user callback.
207    pub fn processing_response_payload_through<O, F>(
208        self,
209        f: F,
210    ) -> ProcessingResponse<S, Self, O, F>
211    where
212        F: for<'a> FnOnce(&'a [u8]) -> O,
213    {
214        ProcessingResponse {
215            previous: self,
216            f: Some(f),
217            _phantom: Default::default(),
218        }
219    }
220}
221
222impl<S: Stack + ?Sized> Request<S> for Code {
223    type Carry = ();
224    type Output = Result<(), ()>;
225    async fn build_request(
226        &mut self,
227        req: &mut S::RequestMessage<'_>,
228    ) -> Result<(), S::RequestUnionError> {
229        use coap_message::Code;
230        let code = <S::RequestMessage<'_> as MinimalWritableMessage>::Code::new(self.code)
231            .map_err(S::RequestMessage::convert_code_error)?;
232        req.set_code(code);
233        Ok(())
234    }
235    async fn process_response(&mut self, res: &S::ResponseMessage<'_>, _carry: ()) -> Self::Output {
236        let code: u8 = res.code().into();
237        use coap_numbers::code::{classify, Class, Range};
238        if !matches!(classify(code), Range::Response(Class::Success)) {
239            // This encompasses both unsuccessful responses and errors that the stack let through
240            // (it should have rejected them)
241            return Err(());
242        }
243
244        use coap_message_utils::OptionsExt;
245        res.options().ignore_elective_others().map_err(|_| ())
246    }
247}
248
249impl<S: Stack + ?Sized> Builder<S> for Code {}
250
251impl<S: Stack + ?Sized, Prev: Builder<S>, P: conversions::AsUriPath> Request<S>
252    for WithPath<S, Prev, P>
253{
254    type Carry = Prev::Carry;
255    type Output = Prev::Output;
256    async fn build_request(
257        &mut self,
258        req: &mut S::RequestMessage<'_>,
259    ) -> Result<Self::Carry, S::RequestUnionError> {
260        use coap_message::OptionNumber;
261        // FIXME: This is just `self.previous.build_request(req).await`; let's see if having M1/M2
262        // in the trait pays off elsewhere, otherwise the need to be this explicit here is
263        // horrible. (It looked even worse when we had M1 and M2)
264        let carry = <_ as coap_request::Request<S>>::build_request(&mut self.previous, req).await;
265        for p in self.path.as_uri_path() {
266            req.add_option(
267                <S::RequestMessage<'_> as MinimalWritableMessage>::OptionNumber::new(
268                    coap_numbers::option::URI_PATH,
269                )
270                .map_err(S::RequestMessage::convert_option_number_error)?,
271                p.as_bytes(),
272            )
273            .map_err(S::RequestMessage::convert_add_option_error)?;
274        }
275        carry
276    }
277    async fn process_response(
278        &mut self,
279        res: &S::ResponseMessage<'_>,
280        carry: Prev::Carry,
281    ) -> Self::Output {
282        <_ as coap_request::Request<S>>::process_response(&mut self.previous, res, carry).await
283    }
284}
285impl<S: Stack + ?Sized, Prev: Builder<S>, P: conversions::AsUriPath> Builder<S>
286    for WithPath<S, Prev, P>
287{
288}
289
290impl<S: Stack + ?Sized, Prev: Builder<S>, F> Request<S> for WithRequestCallback<S, Prev, F>
291where
292    F: FnMut(&mut S::RequestMessage<'_>) -> Result<(), S::RequestUnionError>,
293{
294    type Carry = Prev::Carry;
295    type Output = Prev::Output;
296    async fn build_request(
297        &mut self,
298        req: &mut S::RequestMessage<'_>,
299    ) -> Result<Self::Carry, S::RequestUnionError> {
300        let carry = <_ as coap_request::Request<S>>::build_request(&mut self.previous, req).await?;
301        (self.f)(req)?;
302        Ok(carry)
303    }
304
305    async fn process_response(
306        &mut self,
307        res: &S::ResponseMessage<'_>,
308        carry: Self::Carry,
309    ) -> Self::Output {
310        <_ as coap_request::Request<S>>::process_response(&mut self.previous, res, carry).await
311    }
312}
313impl<S: Stack + ?Sized, Prev: Builder<S>, F> Builder<S> for WithRequestCallback<S, Prev, F> where
314    F: FnMut(&mut S::RequestMessage<'_>) -> Result<(), S::RequestUnionError>
315{
316}
317
318impl<'payload, S: Stack + ?Sized, Prev: Builder<S>> Request<S>
319    for WithRequestPayloadSlice<'payload, S, Prev>
320{
321    type Carry = Prev::Carry;
322    type Output = Prev::Output;
323    async fn build_request(
324        &mut self,
325        req: &mut S::RequestMessage<'_>,
326    ) -> Result<Self::Carry, S::RequestUnionError> {
327        let carry = <_ as coap_request::Request<S>>::build_request(&mut self.previous, req).await?;
328        req.set_payload(self.payload)
329            .map_err(S::RequestMessage::convert_set_payload_error)?;
330        Ok(carry)
331    }
332
333    async fn process_response(
334        &mut self,
335        res: &S::ResponseMessage<'_>,
336        carry: Self::Carry,
337    ) -> Self::Output {
338        <_ as coap_request::Request<S>>::process_response(&mut self.previous, res, carry).await
339    }
340}
341impl<'payload, S: Stack + ?Sized, Prev: Builder<S>> Builder<S>
342    for WithRequestPayloadSlice<'payload, S, Prev>
343{
344}
345
346impl<S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>, O, F> Request<S>
347    for ProcessingResponse<S, Prev, O, F>
348where
349    F: for<'a> FnOnce(&'a [u8]) -> O,
350{
351    type Carry = Prev::Carry;
352    type Output = Result<O, ()>;
353    async fn build_request(
354        &mut self,
355        req: &mut S::RequestMessage<'_>,
356    ) -> Result<Self::Carry, S::RequestUnionError> {
357        <_ as coap_request::Request<S>>::build_request(&mut self.previous, req).await
358    }
359
360    async fn process_response(
361        &mut self,
362        res: &S::ResponseMessage<'_>,
363        carry: Self::Carry,
364    ) -> Self::Output {
365        <_ as coap_request::Request<S>>::process_response(&mut self.previous, res, carry).await?;
366        if let Some(f) = self.f.take() {
367            Ok(f(res.payload()))
368        } else {
369            Err(())
370        }
371    }
372}
373impl<S: Stack + ?Sized, Prev: Builder<S, Output = Result<(), ()>>, O, F> Builder<S>
374    for ProcessingResponse<S, Prev, O, F>
375where
376    F: for<'a> FnOnce(&'a [u8]) -> O,
377{
378}