bc_envelope/extension/expressions/
request.rs1use anyhow::{Error, Result};
2use bc_components::{ARID, tags};
3use dcbor::{Date, prelude::*};
4
5use crate::{
6 Envelope, EnvelopeEncodable, Expression, ExpressionBehavior, Function,
7 Parameter, known_values,
8};
9
10#[derive(Debug, Clone, PartialEq)]
45pub struct Request {
46 body: Expression,
47 id: ARID,
48 note: String,
49 date: Option<Date>,
50}
51
52impl std::fmt::Display for Request {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(f, "Request({})", self.summary())
56 }
57}
58
59impl Request {
60 pub fn summary(&self) -> String {
62 format!(
63 "id: {}, body: {}",
64 self.id.short_description(),
65 self.body.expression_envelope().format_flat()
66 )
67 }
68}
69
70pub trait RequestBehavior: ExpressionBehavior {
77 fn with_note(self, note: impl Into<String>) -> Self;
85
86 fn with_date(self, date: impl AsRef<Date>) -> Self;
90
91 fn body(&self) -> &Expression;
98
99 fn id(&self) -> ARID;
101
102 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 { body, id, note: String::new(), date: None }
119 }
120
121 pub fn new(function: impl Into<Function>, id: ARID) -> Self {
144 Self::new_with_body(Expression::new(function), id)
145 }
146}
147
148impl ExpressionBehavior for Request {
152 fn with_parameter(
154 mut self,
155 parameter: impl Into<Parameter>,
156 value: impl EnvelopeEncodable,
157 ) -> Self {
158 self.body = self.body.with_parameter(parameter, value);
159 self
160 }
161
162 fn with_optional_parameter(
164 mut self,
165 parameter: impl Into<Parameter>,
166 value: Option<impl EnvelopeEncodable>,
167 ) -> Self {
168 self.body = self.body.with_optional_parameter(parameter, value);
169 self
170 }
171
172 fn function(&self) -> &Function { self.body.function() }
174
175 fn expression_envelope(&self) -> &Envelope {
177 self.body.expression_envelope()
178 }
179
180 fn object_for_parameter(
182 &self,
183 param: impl Into<Parameter>,
184 ) -> Result<Envelope> {
185 self.body.object_for_parameter(param)
186 }
187
188 fn objects_for_parameter(
190 &self,
191 param: impl Into<Parameter>,
192 ) -> Vec<Envelope> {
193 self.body.objects_for_parameter(param)
194 }
195
196 fn extract_object_for_parameter<T>(
198 &self,
199 param: impl Into<Parameter>,
200 ) -> Result<T>
201 where
202 T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
203 {
204 self.body.extract_object_for_parameter(param)
205 }
206
207 fn extract_optional_object_for_parameter<
209 T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
210 >(
211 &self,
212 param: impl Into<Parameter>,
213 ) -> Result<Option<T>> {
214 self.body.extract_optional_object_for_parameter(param)
215 }
216
217 fn extract_objects_for_parameter<T>(
219 &self,
220 param: impl Into<Parameter>,
221 ) -> Result<Vec<T>>
222 where
223 T: TryFrom<CBOR, Error = dcbor::Error> + 'static,
224 {
225 self.body.extract_objects_for_parameter(param)
226 }
227}
228
229impl RequestBehavior for Request {
231 fn with_note(mut self, note: impl Into<String>) -> Self {
233 self.note = note.into();
234 self
235 }
236
237 fn with_date(mut self, date: impl AsRef<Date>) -> Self {
239 self.date = Some(date.as_ref().clone());
240 self
241 }
242
243 fn body(&self) -> &Expression { &self.body }
245
246 fn id(&self) -> ARID { self.id }
248
249 fn note(&self) -> &str { &self.note }
251
252 fn date(&self) -> Option<&Date> { self.date.as_ref() }
254}
255
256impl From<Request> for Expression {
260 fn from(request: Request) -> Self { request.body }
261}
262
263impl 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(
273 !request.note.is_empty(),
274 known_values::NOTE,
275 request.note,
276 )
277 .add_optional_assertion(known_values::DATE, request.date)
278 }
279}
280
281impl TryFrom<(Envelope, Option<&Function>)> for Request {
287 type Error = Error;
288
289 fn try_from(
290 (envelope, expected_function): (Envelope, Option<&Function>),
291 ) -> Result<Self> {
292 let body_envelope =
293 envelope.object_for_predicate(known_values::BODY)?;
294 Ok(Self {
295 body: Expression::try_from((body_envelope, expected_function))?,
296 id: envelope
297 .subject()
298 .try_leaf()?
299 .try_into_expected_tagged_value(tags::TAG_REQUEST)?
300 .try_into()?,
301 note: envelope.extract_object_for_predicate_with_default(
302 known_values::NOTE,
303 "".to_string(),
304 )?,
305 date: envelope
306 .extract_optional_object_for_predicate(known_values::DATE)?,
307 })
308 }
309}
310
311impl TryFrom<Envelope> for Request {
315 type Error = Error;
316
317 fn try_from(envelope: Envelope) -> Result<Self> {
318 Self::try_from((envelope, None))
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use hex_literal::hex;
325 use indoc::indoc;
326
327 use super::*;
328
329 fn request_id() -> ARID {
330 ARID::from_data(hex!(
331 "c66be27dbad7cd095ca77647406d07976dc0f35f0d4d654bb0e96dd227a1e9fc"
332 ))
333 }
334
335 #[test]
336 fn test_basic_request() -> Result<()> {
337 crate::register_tags();
338
339 let request = Request::new("test", request_id())
340 .with_parameter("param1", 42)
341 .with_parameter("param2", "hello");
342
343 let envelope: Envelope = request.clone().into();
344 #[rustfmt::skip]
345 let expected = indoc!{r#"
346 request(ARID(c66be27d)) [
347 'body': «"test"» [
348 ❰"param1"❱: 42
349 ❰"param2"❱: "hello"
350 ]
351 ]
352 "#}.trim();
353 assert_eq!(envelope.format(), expected);
354
355 let parsed_request = Request::try_from(envelope)?;
356 assert_eq!(
357 parsed_request.extract_object_for_parameter::<i32>("param1")?,
358 42
359 );
360 assert_eq!(
361 parsed_request.extract_object_for_parameter::<String>("param2")?,
362 "hello"
363 );
364 assert_eq!(parsed_request.note(), "");
365 assert_eq!(parsed_request.date(), None);
366
367 assert_eq!(request, parsed_request);
368
369 Ok(())
370 }
371
372 #[test]
373 fn test_request_with_metadata() -> Result<()> {
374 crate::register_tags();
375
376 let request_date = Date::try_from("2024-07-04T11:11:11Z")?;
377 let request = Request::new("test", request_id())
378 .with_parameter("param1", 42)
379 .with_parameter("param2", "hello")
380 .with_note("This is a test")
381 .with_date(&request_date);
382
383 let envelope: Envelope = request.clone().into();
384 #[rustfmt::skip]
386 assert_eq!(envelope.format(), indoc!{r#"
387 request(ARID(c66be27d)) [
388 'body': «"test"» [
389 ❰"param1"❱: 42
390 ❰"param2"❱: "hello"
391 ]
392 'date': 2024-07-04T11:11:11Z
393 'note': "This is a test"
394 ]
395 "#}.trim());
396
397 let parsed_request = Request::try_from(envelope)?;
398 assert_eq!(
399 parsed_request.extract_object_for_parameter::<i32>("param1")?,
400 42
401 );
402 assert_eq!(
403 parsed_request.extract_object_for_parameter::<String>("param2")?,
404 "hello"
405 );
406 assert_eq!(parsed_request.note(), "This is a test");
407 assert_eq!(parsed_request.date(), Some(&request_date));
408
409 assert_eq!(request, parsed_request);
410
411 Ok(())
412 }
413
414 #[test]
415 fn test_parameter_format() {
416 crate::register_tags();
417
418 let parameter = Parameter::new_named("testParam");
419 let envelope = parameter.into_envelope();
420 let expected = r#"❰"testParam"❱"#;
421 assert_eq!(envelope.format(), expected);
422 }
423}