coap_server/app/
resource_builder.rs

1use std::collections::HashMap;
2use std::fmt::Debug;
3use std::hash::Hash;
4use std::sync::Arc;
5
6use coap_lite::link_format::LINK_ATTR_OBSERVABLE;
7use coap_lite::RequestType;
8use tokio::sync::Mutex;
9
10use crate::app::app_builder::ConfigBuilder;
11use crate::app::block_handler_util::new_block_handler;
12use crate::app::core_link::{CoreLink, LinkAttributeValue};
13use crate::app::observable_resource::ObservableResource;
14use crate::app::observe_handler::ObserveHandler;
15use crate::app::request_handler::RequestHandler;
16use crate::app::request_type_key::RequestTypeKey;
17use crate::app::resource_handler::ResourceHandler;
18use crate::app::retransmission_manager::RetransmissionManager;
19
20/// Configure a specific resource handler, potentially with distinct per-method handlers.
21pub struct ResourceBuilder<Endpoint: Ord + Clone> {
22    path: String,
23    config: ConfigBuilder,
24    attributes: CoreLink,
25    handlers: HashMap<RequestTypeKey, Box<dyn RequestHandler<Endpoint> + Send + Sync>>,
26    observable: Option<Box<dyn ObservableResource + Send + Sync>>,
27}
28
29impl<Endpoint: Debug + Clone + Eq + Hash + Ord + Send + 'static> ResourceBuilder<Endpoint> {
30    pub fn new(path: &str) -> Self {
31        Self {
32            path: path.to_string(),
33            config: ConfigBuilder::default(),
34            attributes: CoreLink::new(path),
35            handlers: HashMap::new(),
36            observable: None,
37        }
38    }
39
40    /// See [`crate::app::AppBuilder::not_discoverable`].
41    pub fn not_discoverable(mut self) -> Self {
42        self.config.discoverable = Some(false);
43        self
44    }
45
46    /// See [`crate::app::AppBuilder::disable_block_transfer`].
47    pub fn disable_block_transfer(mut self) -> Self {
48        self.config.block_transfer = Some(false);
49        self
50    }
51
52    /// Add a new attribute to the CoRE Link response, assuming that this resource will be
53    /// discoverable.  For more information, see
54    /// [RFC 5785](https://datatracker.ietf.org/doc/html/rfc5785)
55    pub fn link_attr(
56        mut self,
57        attr_name: &'static str,
58        value: impl LinkAttributeValue<String> + 'static,
59    ) -> Self {
60        self.attributes.attr(attr_name, value);
61        self
62    }
63
64    /// Set a default catch-all handler if another more specific request handler does not match.
65    /// This can be useful if you wish to override the default behaviour of responding with
66    /// "4.05 Method not allowed".
67    pub fn default_handler(self, handler: impl RequestHandler<Endpoint> + Send + Sync) -> Self {
68        self.handler(RequestTypeKey::new_match_all(), handler)
69    }
70
71    /// Set a request handler for "Get" requests.
72    pub fn get(self, handler: impl RequestHandler<Endpoint> + Send + Sync) -> Self {
73        self.handler(RequestType::Get.into(), handler)
74    }
75
76    /// Set a request handler for "Post" requests.
77    pub fn post(self, handler: impl RequestHandler<Endpoint> + Send + Sync) -> Self {
78        self.handler(RequestType::Post.into(), handler)
79    }
80
81    /// Set a request handler for "Put" requests.
82    pub fn put(self, handler: impl RequestHandler<Endpoint> + Send + Sync) -> Self {
83        self.handler(RequestType::Put.into(), handler)
84    }
85
86    /// Set a request handler for "Delete" requests.
87    pub fn delete(self, handler: impl RequestHandler<Endpoint> + Send + Sync) -> Self {
88        self.handler(RequestType::Delete.into(), handler)
89    }
90
91    /// Set a request handler for "Fetch" requests.
92    pub fn fetch(self, handler: impl RequestHandler<Endpoint> + Send + Sync) -> Self {
93        self.handler(RequestType::Fetch.into(), handler)
94    }
95
96    /// Set a request handler for "Patch" requests.
97    pub fn patch(self, handler: impl RequestHandler<Endpoint> + Send + Sync) -> Self {
98        self.handler(RequestType::Patch.into(), handler)
99    }
100
101    /// Set a request handler for "iPatch" requests.
102    pub fn ipatch(self, handler: impl RequestHandler<Endpoint> + Send + Sync) -> Self {
103        self.handler(RequestType::IPatch.into(), handler)
104    }
105
106    /// Set a request handler for arbitrary request types.  This method is provided to enable
107    /// convenient dynamic building of resource handlers, but is not preferred for most
108    /// applications.
109    pub fn method_handler(
110        self,
111        request_type: RequestType,
112        handler: impl RequestHandler<Endpoint> + Send + Sync,
113    ) -> Self {
114        self.handler(request_type.into(), handler)
115    }
116
117    fn handler(
118        mut self,
119        key: RequestTypeKey,
120        handler: impl RequestHandler<Endpoint> + Send + Sync,
121    ) -> Self {
122        self.handlers.insert(key, Box::new(handler));
123        self
124    }
125
126    /// Enable Observe support for Get and/or Fetch requests in this resource.  An `observable`
127    /// argument is required to usefully take advantage of this support which requires
128    /// callers to invoke [`crate::app::Observers::notify_change`] in order to trigger
129    /// updates to be delivered to registered observers.
130    ///
131    /// For more information, see [RFC 7641](https://datatracker.ietf.org/doc/html/rfc7641)
132    pub fn observable(mut self, observable: impl ObservableResource + Send + Sync) -> Self {
133        self.observable = Some(Box::new(observable));
134        self.link_attr(LINK_ATTR_OBSERVABLE, ())
135    }
136
137    pub(crate) fn build(self, params: BuildParameters<Endpoint>) -> Resource<Endpoint> {
138        let discoverable = if self.config.discoverable.unwrap_or(true) {
139            Some(DiscoverableResource::from(self.attributes))
140        } else {
141            None
142        };
143
144        let observe_handler = self
145            .observable
146            .map(|x| Arc::new(Mutex::new(ObserveHandler::new(self.path.clone(), x))));
147
148        let block_transfer = self
149            .config
150            .block_transfer
151            .unwrap_or(crate::app::app_handler::DEFAULT_BLOCK_TRANSFER);
152        let block_handler = if block_transfer {
153            Some(Arc::new(Mutex::new(new_block_handler(params.mtu))))
154        } else {
155            None
156        };
157
158        let handler = ResourceHandler {
159            handlers: self.handlers,
160            observe_handler,
161            block_handler,
162            retransmission_manager: params.retransmission_manager,
163        };
164        Resource {
165            path: self.path,
166            discoverable,
167            handler,
168        }
169    }
170}
171
172#[derive(Clone)]
173pub(crate) struct BuildParameters<Endpoint: Debug + Clone + Eq + Hash> {
174    pub mtu: Option<u32>,
175    pub retransmission_manager: Arc<Mutex<RetransmissionManager<Endpoint>>>,
176}
177
178#[derive(Clone)]
179pub(crate) struct DiscoverableResource {
180    /// Preformatted individual link str so that we can just join them all by "," to form
181    /// the `/.well-known/core` response.  This prevents us from having to deal with
182    /// whacky lifetime issues and improves performance of the core query.
183    pub link_str: String,
184
185    /// Attributes converted to Strings that can be efficiently compared with query parameters
186    /// we might get in a filter request like `GET /.well-known/core?rt=foo`
187    pub attributes_as_string: HashMap<&'static str, String>,
188}
189
190pub(crate) struct Resource<Endpoint: Debug + Clone + Ord + Eq + Hash> {
191    pub path: String,
192    pub discoverable: Option<DiscoverableResource>,
193    pub handler: ResourceHandler<Endpoint>,
194}