coap_handler_implementations/
simple_rendered.rs

1//! Module containing the [SimpleRendered] handler and the [SimpleRenderable] trait its users need.
2
3use crate::helpers::block2_write_with_cf;
4use crate::{Error, wkc};
5use coap_handler::{Handler, Reporting};
6use coap_message::{
7    Code as _, MessageOption, MinimalWritableMessage, MutableWritableMessage, ReadableMessage,
8};
9use coap_message_utils::option_value::Block2RequestData;
10use coap_numbers::code::{CONTENT, GET};
11use coap_numbers::option::{ACCEPT, BLOCK2, Criticality, get_criticality};
12
13/// Information a SimpleRenderable needs to carry from request to response.
14// Newtype wrapper to avoid exposing Block2RequestData
15pub struct SimpleRenderableData(Block2RequestData);
16
17/// A simplified Handler trait that can react to GET requests and will render to a fmt::Write
18/// object with blockwise backing.
19///
20/// Anything that implements it (which includes plain &str, for example) can be packed into a
21/// [SimpleRendered] to form a Handler.
22pub trait SimpleRenderable {
23    fn render<W: embedded_io::blocking::Write + core::fmt::Write>(&mut self, writer: &mut W);
24
25    /// If something is returned, GETs with differing Accept options will be rejecected, and the
26    /// value will be set in responses.
27    fn content_format(&self) -> Option<u16> {
28        None
29    }
30}
31
32/// A container that turns any [SimpleRenderable] (including [&str]) into a CoAP resource
33/// [Handler].
34///
35/// See [SimpleRenderable] for further information.
36#[derive(Debug, Copy, Clone)]
37pub struct SimpleRendered<T: SimpleRenderable>(pub T);
38
39impl<'a> SimpleRendered<TypedStaticRenderable<'a>> {
40    pub fn new_typed_slice(data: &'a [u8], content_format: Option<u16>) -> Self {
41        SimpleRendered(TypedStaticRenderable {
42            data,
43            content_format,
44        })
45    }
46
47    pub fn new_typed_str(data: &'a str, content_format: Option<u16>) -> Self {
48        let data = data.as_bytes();
49        Self::new_typed_slice(data, content_format)
50    }
51}
52
53impl<T> Handler for SimpleRendered<T>
54where
55    T: SimpleRenderable,
56{
57    type RequestData = SimpleRenderableData;
58    type ExtractRequestError = Error;
59    type BuildResponseError<M: MinimalWritableMessage> = M::UnionError;
60
61    fn extract_request_data<M: ReadableMessage>(
62        &mut self,
63        request: &M,
64    ) -> Result<Self::RequestData, Error> {
65        let expected_accept = self.0.content_format();
66
67        let mut block2 = None;
68
69        for o in request.options() {
70            match o.number() {
71                ACCEPT => {
72                    if expected_accept.is_some() && o.value_uint() != expected_accept {
73                        return Err(Error::bad_option(ACCEPT));
74                    }
75                }
76                BLOCK2 => {
77                    block2 = match block2 {
78                        Some(_) => return Err(Error::bad_request()),
79                        None => Block2RequestData::from_option(&o)
80                            .map(Some)
81                            // Unprocessed CoAP Option is also for "is aware but could not process"
82                            .map_err(|_| Error::bad_option(BLOCK2))?,
83                    }
84                }
85                o if get_criticality(o) == Criticality::Critical => {
86                    return Err(Error::bad_option(o));
87                }
88                _ => (),
89            }
90        }
91
92        let reqdata = match request.code().into() {
93            GET => block2.unwrap_or_default(),
94            _ => return Err(Error::method_not_allowed()),
95        };
96        Ok(SimpleRenderableData(reqdata))
97    }
98
99    fn estimate_length(&mut self, _request: &Self::RequestData) -> usize {
100        1280 - 40 - 4 // does this correclty calculate the IPv6 minimum MTU?
101    }
102
103    fn build_response<M: MutableWritableMessage>(
104        &mut self,
105        response: &mut M,
106        request: Self::RequestData,
107    ) -> Result<(), Self::BuildResponseError<M>> {
108        let cf = self.0.content_format();
109        let block2data = request.0;
110        response.set_code(M::Code::new(CONTENT)?);
111        block2_write_with_cf(block2data, response, |w| self.0.render(w), cf);
112
113        Ok(())
114    }
115}
116
117impl<T> Reporting for SimpleRendered<T>
118where
119    T: SimpleRenderable,
120{
121    type Record<'a>
122        = wkc::EmptyRecord
123    where
124        Self: 'a;
125    type Reporter<'a>
126        = core::iter::Once<wkc::EmptyRecord>
127    where
128        Self: 'a;
129
130    fn report(&self) -> Self::Reporter<'_> {
131        // Using a ConstantSliceRecord instead would be tempting, but that'd need a const return
132        // value from self.0.content_format()
133        core::iter::once(wkc::EmptyRecord {})
134    }
135}
136
137impl SimpleRenderable for &str {
138    fn render<W>(&mut self, writer: &mut W)
139    where
140        W: core::fmt::Write,
141    {
142        writer
143            .write_str(self)
144            .expect("The backend of SimpleRenderable supports infallible writing");
145    }
146
147    fn content_format(&self) -> Option<u16> {
148        coap_numbers::content_format::from_str("text/plain; charset=utf-8")
149    }
150}
151
152pub struct TypedStaticRenderable<'a> {
153    data: &'a [u8],
154    content_format: Option<u16>,
155}
156
157impl SimpleRenderable for TypedStaticRenderable<'_> {
158    fn render<W: embedded_io::blocking::Write + core::fmt::Write>(&mut self, writer: &mut W) {
159        writer.write_all(self.data).unwrap();
160    }
161
162    fn content_format(&self) -> Option<u16> {
163        self.content_format
164    }
165}