bc_envelope/extension/expressions/
request.rs1use anyhow::{ Error, Result };
2use bc_components::{ tags, ARID };
3use dcbor::prelude::*;
4use dcbor::Date;
5
6use crate::{
7 known_values,
8 Envelope,
9 EnvelopeEncodable,
10 Expression,
11 ExpressionBehavior,
12 Function,
13 Parameter,
14};
15
16#[derive(Debug, Clone, PartialEq)]
48pub struct Request {
49 body: Expression,
50 id: ARID,
51 note: String,
52 date: Option<Date>,
53}
54
55impl std::fmt::Display for Request {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 write!(f, "Request({})", self.summary())
59 }
60}
61
62impl Request {
63 pub fn summary(&self) -> String {
65 format!(
66 "id: {}, body: {}",
67 self.id.short_description(),
68 self.body.expression_envelope().format_flat()
69 )
70 }
71}
72
73pub trait RequestBehavior: ExpressionBehavior {
79 fn with_note(self, note: impl Into<String>) -> Self;
87
88 fn with_date(self, date: impl AsRef<Date>) -> Self;
92
93 fn body(&self) -> &Expression;
99
100 fn id(&self) -> ARID;
102
103 fn note(&self) -> &str;
105
106 fn date(&self) -> Option<&Date>;
108}
109
110impl Request {
111 pub fn new_with_body(body: Expression, id: ARID) -> Self {
118 Self {
119 body,
120 id,
121 note: String::new(),
122 date: None,
123 }
124 }
125
126 pub fn new(function: impl Into<Function>, id: ARID) -> Self {
149 Self::new_with_body(Expression::new(function), id)
150 }
151}
152
153impl ExpressionBehavior for Request {
157 fn with_parameter(
159 mut self,
160 parameter: impl Into<Parameter>,
161 value: impl EnvelopeEncodable
162 ) -> Self {
163 self.body = self.body.with_parameter(parameter, value);
164 self
165 }
166
167 fn with_optional_parameter(
169 mut self,
170 parameter: impl Into<Parameter>,
171 value: Option<impl EnvelopeEncodable>
172 ) -> Self {
173 self.body = self.body.with_optional_parameter(parameter, value);
174 self
175 }
176
177 fn function(&self) -> &Function {
179 self.body.function()
180 }
181
182 fn expression_envelope(&self) -> &Envelope {
184 self.body.expression_envelope()
185 }
186
187 fn object_for_parameter(&self, param: impl Into<Parameter>) -> Result<Envelope> {
189 self.body.object_for_parameter(param)
190 }
191
192 fn objects_for_parameter(&self, param: impl Into<Parameter>) -> Vec<Envelope> {
194 self.body.objects_for_parameter(param)
195 }
196
197 fn extract_object_for_parameter<T>(&self, param: impl Into<Parameter>) -> Result<T>
199 where T: TryFrom<CBOR, Error = dcbor::Error> + 'static
200 {
201 self.body.extract_object_for_parameter(param)
202 }
203
204 fn extract_optional_object_for_parameter<T: TryFrom<CBOR, Error = dcbor::Error> + 'static>(
206 &self,
207 param: impl Into<Parameter>
208 ) -> Result<Option<T>> {
209 self.body.extract_optional_object_for_parameter(param)
210 }
211
212 fn extract_objects_for_parameter<T>(&self, param: impl Into<Parameter>) -> Result<Vec<T>>
214 where T: TryFrom<CBOR, Error = dcbor::Error> + 'static
215 {
216 self.body.extract_objects_for_parameter(param)
217 }
218}
219
220impl RequestBehavior for Request {
222 fn with_note(mut self, note: impl Into<String>) -> Self {
224 self.note = note.into();
225 self
226 }
227
228 fn with_date(mut self, date: impl AsRef<Date>) -> Self {
230 self.date = Some(date.as_ref().clone());
231 self
232 }
233
234 fn body(&self) -> &Expression {
236 &self.body
237 }
238
239 fn id(&self) -> ARID {
241 self.id
242 }
243
244 fn note(&self) -> &str {
246 &self.note
247 }
248
249 fn date(&self) -> Option<&Date> {
251 self.date.as_ref()
252 }
253}
254
255impl From<Request> for Expression {
259 fn from(request: Request) -> Self {
260 request.body
261 }
262}
263
264impl From<Request> for Envelope {
269 fn from(request: Request) -> Self {
270 Envelope::new(CBOR::to_tagged_value(tags::TAG_REQUEST, request.id))
271 .add_assertion(known_values::BODY, request.body.into_envelope())
272 .add_assertion_if(!request.note.is_empty(), known_values::NOTE, request.note)
273 .add_optional_assertion(known_values::DATE, request.date)
274 }
275}
276
277impl TryFrom<(Envelope, Option<&Function>)> for Request {
282 type Error = Error;
283
284 fn try_from((envelope, expected_function): (Envelope, Option<&Function>)) -> Result<Self> {
285 let body_envelope = envelope.object_for_predicate(known_values::BODY)?;
286 Ok(Self {
287 body: Expression::try_from((body_envelope, expected_function))?,
288 id: envelope
289 .subject()
290 .try_leaf()?
291 .try_into_expected_tagged_value(tags::TAG_REQUEST)?
292 .try_into()?,
293 note: envelope.extract_object_for_predicate_with_default(
294 known_values::NOTE,
295 "".to_string()
296 )?,
297 date: envelope.extract_optional_object_for_predicate(known_values::DATE)?,
298 })
299 }
300}
301
302impl TryFrom<Envelope> for Request {
306 type Error = Error;
307
308 fn try_from(envelope: Envelope) -> Result<Self> {
309 Self::try_from((envelope, None))
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use hex_literal::hex;
317 use indoc::indoc;
318
319 fn request_id() -> ARID {
320 ARID::from_data(hex!("c66be27dbad7cd095ca77647406d07976dc0f35f0d4d654bb0e96dd227a1e9fc"))
321 }
322
323 #[test]
324 fn test_basic_request() -> Result<()> {
325 crate::register_tags();
326
327 let request = Request::new("test", request_id())
328 .with_parameter("param1", 42)
329 .with_parameter("param2", "hello");
330
331 let envelope: Envelope = request.clone().into();
332 #[rustfmt::skip]
333 let expected = indoc!{r#"
334 request(ARID(c66be27d)) [
335 'body': «"test"» [
336 ❰"param1"❱: 42
337 ❰"param2"❱: "hello"
338 ]
339 ]
340 "#}.trim();
341 assert_eq!(envelope.format(), expected);
342
343 let parsed_request = Request::try_from(envelope)?;
344 assert_eq!(parsed_request.extract_object_for_parameter::<i32>("param1")?, 42);
345 assert_eq!(parsed_request.extract_object_for_parameter::<String>("param2")?, "hello");
346 assert_eq!(parsed_request.note(), "");
347 assert_eq!(parsed_request.date(), None);
348
349 assert_eq!(request, parsed_request);
350
351 Ok(())
352 }
353
354 #[test]
355 fn test_request_with_metadata() -> Result<()> {
356 crate::register_tags();
357
358 let request_date = Date::try_from("2024-07-04T11:11:11Z")?;
359 let request = Request::new("test", request_id())
360 .with_parameter("param1", 42)
361 .with_parameter("param2", "hello")
362 .with_note("This is a test")
363 .with_date(&request_date);
364
365 let envelope: Envelope = request.clone().into();
366 #[rustfmt::skip]
368 assert_eq!(envelope.format(), indoc!{r#"
369 request(ARID(c66be27d)) [
370 'body': «"test"» [
371 ❰"param1"❱: 42
372 ❰"param2"❱: "hello"
373 ]
374 'date': 2024-07-04T11:11:11Z
375 'note': "This is a test"
376 ]
377 "#}.trim());
378
379 let parsed_request = Request::try_from(envelope)?;
380 assert_eq!(parsed_request.extract_object_for_parameter::<i32>("param1")?, 42);
381 assert_eq!(parsed_request.extract_object_for_parameter::<String>("param2")?, "hello");
382 assert_eq!(parsed_request.note(), "This is a test");
383 assert_eq!(parsed_request.date(), Some(&request_date));
384
385 assert_eq!(request, parsed_request);
386
387 Ok(())
388 }
389}