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