up_rust/umessage/umessagebuilder.rs
1/********************************************************************************
2 * Copyright (c) 2024 Contributors to the Eclipse Foundation
3 *
4 * See the NOTICE file(s) distributed with this work for additional
5 * information regarding copyright ownership.
6 *
7 * This program and the accompanying materials are made available under the
8 * terms of the Apache License Version 2.0 which is available at
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * SPDX-License-Identifier: Apache-2.0
12 ********************************************************************************/
13
14use bytes::Bytes;
15use protobuf::{well_known_types::any::Any, Enum, EnumOrUnknown, Message, MessageFull};
16
17use crate::uattributes::NotificationValidator;
18use crate::{
19 PublishValidator, RequestValidator, ResponseValidator, UAttributes, UAttributesValidator,
20 UCode, UMessage, UMessageError, UMessageType, UPayloadFormat, UPriority, UUri, UUID,
21};
22
23/// A builder for creating [`UMessage`]s.
24///
25/// Messages are being used by a uEntity to inform other entities about the occurrence of events
26/// and/or to invoke service operations provided by other entities.
27pub struct UMessageBuilder {
28 comm_status: Option<EnumOrUnknown<UCode>>,
29 message_id: Option<UUID>,
30 message_type: UMessageType,
31 payload: Option<Bytes>,
32 payload_format: UPayloadFormat,
33 permission_level: Option<u32>,
34 priority: UPriority,
35 request_id: Option<UUID>,
36 sink: Option<UUri>,
37 source: Option<UUri>,
38 token: Option<String>,
39 traceparent: Option<String>,
40 ttl: Option<u32>,
41 validator: Box<dyn UAttributesValidator>,
42}
43
44impl Default for UMessageBuilder {
45 fn default() -> Self {
46 UMessageBuilder {
47 comm_status: None,
48 message_id: None,
49 message_type: UMessageType::UMESSAGE_TYPE_UNSPECIFIED,
50 payload: None,
51 payload_format: UPayloadFormat::UPAYLOAD_FORMAT_UNSPECIFIED,
52 permission_level: None,
53 priority: UPriority::UPRIORITY_UNSPECIFIED,
54 request_id: None,
55 sink: None,
56 source: None,
57 token: None,
58 traceparent: None,
59 ttl: None,
60 validator: Box::new(PublishValidator),
61 }
62 }
63}
64
65impl UMessageBuilder {
66 /// Gets a builder for creating *publish* messages.
67 ///
68 /// A publish message is used to notify all interested consumers of an event that has occurred.
69 /// Consumers usually indicate their interest by *subscribing* to a particular topic.
70 ///
71 /// # Arguments
72 ///
73 /// * `topic` - The topic to publish the message to.
74 ///
75 /// # Examples
76 ///
77 /// ```rust
78 /// use up_rust::{UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UUri};
79 ///
80 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
81 /// let topic = UUri::try_from("//my-vehicle/4210/1/B24D")?;
82 /// let message = UMessageBuilder::publish(topic.clone())
83 /// .build_with_payload("closed", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?;
84 /// assert_eq!(message.type_unchecked(), UMessageType::UMESSAGE_TYPE_PUBLISH);
85 /// assert_eq!(message.priority_unchecked(), UPriority::UPRIORITY_CS1);
86 /// assert_eq!(message.source_unchecked(), &topic);
87 /// # Ok(())
88 /// # }
89 /// ```
90 pub fn publish(topic: UUri) -> UMessageBuilder {
91 UMessageBuilder {
92 validator: Box::new(PublishValidator),
93 // [impl->dsn~up-attributes-publish-type~1]
94 message_type: UMessageType::UMESSAGE_TYPE_PUBLISH,
95 source: Some(topic),
96 ..Default::default()
97 }
98 }
99
100 /// Gets a builder for creating *notification* messages.
101 ///
102 /// A notification is used to inform a specific consumer about an event that has occurred.
103 ///
104 /// # Arguments
105 ///
106 /// * `origin` - The component that the notification originates from.
107 /// * `destination` - The URI identifying the destination to send the notification to.
108 ///
109 /// # Examples
110 ///
111 /// ```rust
112 /// use up_rust::{UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UUri};
113 ///
114 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
115 /// let origin = UUri::try_from("//my-vehicle/4210/5/F20B")?;
116 /// let destination = UUri::try_from("//my-cloud/CCDD/2/0")?;
117 /// let message = UMessageBuilder::notification(origin.clone(), destination.clone())
118 /// .build_with_payload("unexpected movement", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?;
119 /// assert_eq!(message.type_unchecked(), UMessageType::UMESSAGE_TYPE_NOTIFICATION);
120 /// assert_eq!(message.priority_unchecked(), UPriority::UPRIORITY_CS1);
121 /// assert_eq!(message.source_unchecked(), &origin);
122 /// assert_eq!(message.sink_unchecked(), &destination);
123 /// # Ok(())
124 /// # }
125 /// ```
126 pub fn notification(origin: UUri, destination: UUri) -> UMessageBuilder {
127 UMessageBuilder {
128 validator: Box::new(NotificationValidator),
129 // [impl->dsn~up-attributes-notification-type~1]
130 message_type: UMessageType::UMESSAGE_TYPE_NOTIFICATION,
131 source: Some(origin),
132 sink: Some(destination),
133 ..Default::default()
134 }
135 }
136
137 /// Gets a builder for creating RPC *request* messages.
138 ///
139 /// A request message is used to invoke a service's method with some input data, expecting
140 /// the service to reply with a response message which is correlated by means of the `request_id`.
141 ///
142 /// The builder will be initialized with [`UPriority::UPRIORITY_CS4`].
143 ///
144 /// # Arguments
145 ///
146 /// * `method_to_invoke` - The URI identifying the method to invoke.
147 /// * `reply_to_address` - The URI that the sender of the request expects the response message at.
148 /// * `ttl` - The number of milliseconds after which the request should no longer be processed
149 /// by the target service. The value is capped at [`i32::MAX`].
150 ///
151 /// # Examples
152 ///
153 /// ```rust
154 /// use up_rust::{UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UUri};
155 ///
156 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
157 /// let method_to_invoke = UUri::try_from("//my-vehicle/4210/5/64AB")?;
158 /// let reply_to_address = UUri::try_from("//my-cloud/BA4C/1/0")?;
159 /// let message = UMessageBuilder::request(method_to_invoke.clone(), reply_to_address.clone(), 5000)
160 /// .build_with_payload("lock", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?;
161 /// assert_eq!(message.type_unchecked(), UMessageType::UMESSAGE_TYPE_REQUEST);
162 /// assert_eq!(message.priority_unchecked(), UPriority::UPRIORITY_CS4);
163 /// assert_eq!(message.source_unchecked(), &reply_to_address);
164 /// assert_eq!(message.sink_unchecked(), &method_to_invoke);
165 /// assert_eq!(message.ttl_unchecked(), 5000);
166 /// # Ok(())
167 /// # }
168 /// ```
169 pub fn request(method_to_invoke: UUri, reply_to_address: UUri, ttl: u32) -> UMessageBuilder {
170 UMessageBuilder {
171 validator: Box::new(RequestValidator),
172 // [impl->dsn~up-attributes-request-type~1]
173 message_type: UMessageType::UMESSAGE_TYPE_REQUEST,
174 source: Some(reply_to_address),
175 sink: Some(method_to_invoke),
176 ttl: Some(ttl),
177 priority: UPriority::UPRIORITY_CS4,
178 ..Default::default()
179 }
180 }
181
182 /// Gets a builder for creating RPC *response* messages.
183 ///
184 /// A response message is used to send the outcome of processing a request message
185 /// to the original sender of the request message.
186 ///
187 /// The builder will be initialized with [`UPriority::UPRIORITY_CS4`].
188 ///
189 /// # Arguments
190 ///
191 /// * `reply_to_address` - The URI that the sender of the request expects to receive the response message at.
192 /// * `request_id` - The identifier of the request that this is the response to.
193 /// * `invoked_method` - The URI identifying the method that has been invoked and which the created message is
194 /// the outcome of.
195 ///
196 /// # Examples
197 ///
198 /// ```rust
199 /// use up_rust::{UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UUID, UUri};
200 ///
201 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
202 /// let invoked_method = UUri::try_from("//my-vehicle/4210/5/64AB")?;
203 /// let reply_to_address = UUri::try_from("//my-cloud/BA4C/1/0")?;
204 /// let request_id = UUID::build();
205 /// // a service implementation would normally use
206 /// // `UMessageBuilder::response_for_request(&request_message.attributes)` instead
207 /// let message = UMessageBuilder::response(reply_to_address.clone(), request_id.clone(), invoked_method.clone())
208 /// .build()?;
209 /// assert_eq!(message.type_unchecked(), UMessageType::UMESSAGE_TYPE_RESPONSE);
210 /// assert_eq!(message.priority_unchecked(), UPriority::UPRIORITY_CS4);
211 /// assert_eq!(message.source_unchecked(), &invoked_method);
212 /// assert_eq!(message.sink_unchecked(), &reply_to_address);
213 /// assert_eq!(message.request_id_unchecked(), &request_id);
214 /// # Ok(())
215 /// # }
216 /// ```
217 pub fn response(
218 reply_to_address: UUri,
219 request_id: UUID,
220 invoked_method: UUri,
221 ) -> UMessageBuilder {
222 UMessageBuilder {
223 validator: Box::new(ResponseValidator),
224 // [impl->dsn~up-attributes-response-type~1]
225 message_type: UMessageType::UMESSAGE_TYPE_RESPONSE,
226 source: Some(invoked_method),
227 sink: Some(reply_to_address),
228 request_id: Some(request_id),
229 priority: UPriority::UPRIORITY_CS4,
230 ..Default::default()
231 }
232 }
233
234 /// Gets a builder for creating RPC *response* messages in reply to a *request*.
235 ///
236 /// A response message is used to send the outcome of processing a request message
237 /// to the original sender of the request message.
238 ///
239 /// The builder will be initialized with values from the given request attributes.
240 ///
241 /// # Arguments
242 ///
243 /// * `request_attributes` - The attributes from the request message. The response message
244 /// builder will be initialized with the corresponding attribute values.
245 ///
246 /// # Examples
247 ///
248 /// ```rust
249 /// use up_rust::{UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UUID, UUri};
250 ///
251 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
252 /// let method_to_invoke = UUri::try_from("//my-vehicle/4210/5/64AB")?;
253 /// let reply_to_address = UUri::try_from("//my-cloud/BA4C/1/0")?;
254 /// let request_message_id = UUID::build();
255 /// let request_message = UMessageBuilder::request(method_to_invoke.clone(), reply_to_address.clone(), 5000)
256 /// .with_message_id(request_message_id.clone()) // normally not needed, used only for asserts below
257 /// .build_with_payload("lock", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?;
258 ///
259 /// let response_message = UMessageBuilder::response_for_request(&request_message.attributes)
260 /// .with_priority(UPriority::UPRIORITY_CS5)
261 /// .build()?;
262 /// assert_eq!(response_message.type_unchecked(), UMessageType::UMESSAGE_TYPE_RESPONSE);
263 /// assert_eq!(response_message.priority_unchecked(), UPriority::UPRIORITY_CS5);
264 /// assert_eq!(response_message.source_unchecked(), &method_to_invoke);
265 /// assert_eq!(response_message.sink_unchecked(), &reply_to_address);
266 /// assert_eq!(response_message.request_id_unchecked(), &request_message_id);
267 /// assert_eq!(response_message.ttl_unchecked(), 5000);
268 /// # Ok(())
269 /// # }
270 /// ```
271 pub fn response_for_request(request_attributes: &UAttributes) -> UMessageBuilder {
272 UMessageBuilder {
273 validator: Box::new(ResponseValidator),
274 // [impl->dsn~up-attributes-response-type~1]
275 message_type: UMessageType::UMESSAGE_TYPE_RESPONSE,
276 source: request_attributes.sink.as_ref().cloned(),
277 sink: request_attributes.source.as_ref().cloned(),
278 request_id: request_attributes.id.as_ref().cloned(),
279 priority: request_attributes
280 .priority
281 .enum_value_or(UPriority::UPRIORITY_CS4),
282 ttl: request_attributes.ttl,
283 ..Default::default()
284 }
285 }
286
287 /// Sets the message's identifier.
288 ///
289 /// Every message must have an identifier. If this function is not used, an identifier will be
290 /// generated and set on the message when one of the `build` functions is called on the
291 /// `UMessageBuilder`.
292 ///
293 /// It's more typical to _not_ use this function, but could have edge case uses.
294 ///
295 /// # Arguments
296 ///
297 /// * `message_id` - The identifier to use.
298 ///
299 /// # Returns
300 ///
301 /// The builder.
302 ///
303 /// # Panics
304 ///
305 /// Panics if the given UUID is not a [valid uProtocol UUID](`UUID::is_uprotocol_uuid`).
306 ///
307 /// # Examples
308 ///
309 /// ```rust
310 /// use up_rust::{UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UUID, UUri};
311 ///
312 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
313 /// let topic = UUri::try_from("//my-vehicle/4210/1/B24D")?;
314 /// let mut builder = UMessageBuilder::publish(topic);
315 /// builder.with_priority(UPriority::UPRIORITY_CS2);
316 /// let message_one = builder
317 /// .with_message_id(UUID::build())
318 /// .build_with_payload("closed", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?;
319 /// let message_two = builder
320 /// // use new message ID but retain all other attributes
321 /// .with_message_id(UUID::build())
322 /// .build_with_payload("open", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?;
323 /// assert_ne!(message_one.id_unchecked(), message_two.id_unchecked());
324 /// assert_eq!(message_one.source_unchecked(), message_two.source_unchecked());
325 /// assert_eq!(message_one.priority_unchecked(), UPriority::UPRIORITY_CS2);
326 /// assert_eq!(message_two.priority_unchecked(), UPriority::UPRIORITY_CS2);
327 /// # Ok(())
328 /// # }
329 /// ```
330 pub fn with_message_id(&mut self, message_id: UUID) -> &mut UMessageBuilder {
331 assert!(
332 message_id.is_uprotocol_uuid(),
333 "Message ID must be a valid uProtocol UUID"
334 );
335 self.message_id = Some(message_id);
336 self
337 }
338
339 /// Sets the message's priority.
340 ///
341 /// If not set explicitly, the default priority as defined in the
342 /// [uProtocol specification](https://github.com/eclipse-uprotocol/up-spec/blob/main/basics/qos.adoc)
343 /// is used.
344 ///
345 /// # Arguments
346 ///
347 /// * `priority` - The priority to be used for sending the message.
348 ///
349 /// # Returns
350 ///
351 /// The builder.
352 ///
353 /// # Panics
354 ///
355 /// if the builder is used for creating an RPC message but the given priority is less than CS4.
356 ///
357 /// # Examples
358 ///
359 /// ```rust
360 /// use up_rust::{UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UUri};
361 ///
362 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
363 /// let topic = UUri::try_from("//my-vehicle/4210/1/B24D")?;
364 /// let message = UMessageBuilder::publish(topic)
365 /// .with_priority(UPriority::UPRIORITY_CS5)
366 /// .build_with_payload("closed", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?;
367 /// assert_eq!(message.priority_unchecked(), UPriority::UPRIORITY_CS5);
368 /// # Ok(())
369 /// # }
370 /// ```
371 pub fn with_priority(&mut self, priority: UPriority) -> &mut UMessageBuilder {
372 // [impl->dsn~up-attributes-request-priority~1]
373 if self.message_type == UMessageType::UMESSAGE_TYPE_REQUEST
374 || self.message_type == UMessageType::UMESSAGE_TYPE_RESPONSE
375 {
376 assert!(priority.value() >= UPriority::UPRIORITY_CS4.value())
377 }
378 if !UAttributes::is_default_priority(priority) {
379 // only set priority explicitly if it differs from the default priority
380 self.priority = priority;
381 } else {
382 // in all other cases set to UNSPECIFIED which will result in the
383 // priority not being included in the serialized protobuf
384 self.priority = UPriority::UPRIORITY_UNSPECIFIED;
385 }
386 self
387 }
388
389 /// Sets the message's time-to-live.
390 ///
391 /// # Arguments
392 ///
393 /// * `ttl` - The time-to-live in milliseconds. The value is capped at [`i32::MAX`].
394 ///
395 /// # Returns
396 ///
397 /// The builder.
398 ///
399 /// # Examples
400 ///
401 /// ```rust
402 /// use up_rust::{UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UUID, UUri};
403 ///
404 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
405 /// let invoked_method = UUri::try_from("//my-vehicle/4210/5/64AB")?;
406 /// let reply_to_address = UUri::try_from("//my-cloud/BA4C/1/0")?;
407 /// let request_msg_id = UUID::build();
408 /// // a service implementation would normally use
409 /// // `UMessageBuilder::response_for_request(&request_message.attributes)` instead
410 /// let message = UMessageBuilder::response(reply_to_address, request_msg_id, invoked_method)
411 /// .with_ttl(2000)
412 /// .build()?;
413 /// assert_eq!(message.ttl_unchecked(), 2000);
414 /// # Ok(())
415 /// # }
416 /// ```
417 pub fn with_ttl(&mut self, ttl: u32) -> &mut UMessageBuilder {
418 self.ttl = Some(ttl);
419 self
420 }
421
422 /// Sets the message's authorization token used for TAP.
423 ///
424 /// # Arguments
425 ///
426 /// * `token` - The token.
427 ///
428 /// # Returns
429 ///
430 /// The builder.
431 ///
432 /// # Panics
433 ///
434 /// * if the message is not an RPC request message
435 ///
436 /// # Examples
437 ///
438 /// ```rust
439 /// use up_rust::{UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UUri};
440 ///
441 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
442 /// let method_to_invoke = UUri::try_from("//my-vehicle/4210/5/64AB")?;
443 /// let reply_to_address = UUri::try_from("//my-cloud/BA4C/1/0")?;
444 /// let token = String::from("this-is-my-token");
445 /// let message = UMessageBuilder::request(method_to_invoke, reply_to_address, 5000)
446 /// .with_token(token.clone())
447 /// .build_with_payload("lock", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?;
448 /// assert_eq!(message.token(), Some(&token));
449 /// # Ok(())
450 /// # }
451 /// ```
452 pub fn with_token<T: Into<String>>(&mut self, token: T) -> &mut UMessageBuilder {
453 // [impl->dsn~up-attributes-request-token~1]
454 assert!(self.message_type == UMessageType::UMESSAGE_TYPE_REQUEST);
455 self.token = Some(token.into());
456 self
457 }
458
459 /// Sets the message's permission level.
460 ///
461 /// # Arguments
462 ///
463 /// * `level` - The level.
464 ///
465 /// # Returns
466 ///
467 /// The builder.
468 ///
469 /// # Panics
470 ///
471 /// * if the given level is greater than [`i32::MAX`]
472 /// * if the message is not an RPC request message
473 ///
474 /// # Examples
475 ///
476 /// ```rust
477 /// use up_rust::{UCode, UMessageBuilder, UPayloadFormat, UPriority, UUri};
478 ///
479 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
480 /// let method_to_invoke = UUri::try_from("//my-vehicle/4210/5/64AB")?;
481 /// let reply_to_address = UUri::try_from("//my-cloud/BA4C/1/0")?;
482 /// let message = UMessageBuilder::request(method_to_invoke, reply_to_address, 5000)
483 /// .with_permission_level(12)
484 /// .build_with_payload("lock", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?;
485 /// assert_eq!(message.permission_level(), Some(12));
486 /// # Ok(())
487 /// # }
488 /// ```
489 pub fn with_permission_level(&mut self, level: u32) -> &mut UMessageBuilder {
490 // [impl->dsn~up-attributes-permission-level~1]
491 assert!(self.message_type == UMessageType::UMESSAGE_TYPE_REQUEST);
492 self.permission_level = Some(level);
493 self
494 }
495
496 /// Sets the message's communication status.
497 ///
498 /// # Arguments
499 ///
500 /// * `comm_status` - The status.
501 ///
502 /// # Returns
503 ///
504 /// The builder.
505 ///
506 /// # Panics
507 ///
508 /// * if the message is not an RPC response message
509 ///
510 /// # Examples
511 ///
512 /// ```rust
513 /// use up_rust::{UCode, UMessageBuilder, UPriority, UUID, UUri};
514 ///
515 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
516 /// let invoked_method = UUri::try_from("//my-vehicle/4210/5/64AB")?;
517 /// let reply_to_address = UUri::try_from("//my-cloud/BA4C/1/0")?;
518 /// let request_msg_id = UUID::build();
519 /// // a service implementation would normally use
520 /// // `UMessageBuilder::response_for_request(&request_message.attributes)` instead
521 /// let message = UMessageBuilder::response(reply_to_address, request_msg_id, invoked_method)
522 /// .with_comm_status(UCode::OK)
523 /// .build()?;
524 /// assert_eq!(message.commstatus_unchecked(), UCode::OK);
525 /// # Ok(())
526 /// # }
527 /// ```
528 pub fn with_comm_status(&mut self, comm_status: UCode) -> &mut UMessageBuilder {
529 assert!(self.message_type == UMessageType::UMESSAGE_TYPE_RESPONSE);
530 self.comm_status = Some(comm_status.into());
531 self
532 }
533
534 /// Sets the identifier of the W3C Trace Context to convey in the message.
535 ///
536 /// # Arguments
537 ///
538 /// * `traceparent` - The identifier.
539 ///
540 /// # Returns
541 ///
542 /// The builder.
543 ///
544 /// # Examples
545 ///
546 /// ```rust
547 /// use up_rust::{UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UUri};
548 ///
549 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
550 /// let topic = UUri::try_from("//my-vehicle/4210/1/B24D")?;
551 /// let traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01".to_string();
552 /// let message = UMessageBuilder::publish(topic.clone())
553 /// .with_traceparent(&traceparent)
554 /// .build_with_payload("closed", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?;
555 /// assert_eq!(message.traceparent(), Some(&traceparent));
556 /// # Ok(())
557 /// # }
558 pub fn with_traceparent<T: Into<String>>(&mut self, traceparent: T) -> &mut UMessageBuilder {
559 // [impl->dsn~up-attributes-traceparent~1]
560 self.traceparent = Some(traceparent.into());
561 self
562 }
563
564 /// Creates the message based on the builder's state.
565 ///
566 /// # Returns
567 ///
568 /// A message ready to be sent using [`crate::UTransport::send`].
569 ///
570 /// # Errors
571 ///
572 /// If the properties set on the builder do not represent a consistent set of [`UAttributes`],
573 /// a [`UMessageError::AttributesValidationError`] is returned.
574 ///
575 /// # Examples
576 ///
577 /// ## Not setting `id` explicitly with [`UMessageBuilder::with_message_id()']
578 ///
579 /// The recommended way to use the `UMessageBuilder`.
580 ///
581 /// ```rust
582 /// use up_rust::{UAttributes, UAttributesValidators, UMessageBuilder, UMessageError, UMessageType, UPriority, UUID, UUri};
583 ///
584 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
585 /// let invoked_method = UUri::try_from("//my-vehicle/4210/5/64AB")?;
586 /// let reply_to_address = UUri::try_from("//my-cloud/BA4C/1/0")?;
587 /// // a service implementation would normally use
588 /// // `UMessageBuilder::response_for_request(&request_message.attributes)` instead
589 /// let result = UMessageBuilder::response(reply_to_address, UUID::build(), invoked_method)
590 /// .build();
591 /// assert!(result.is_ok());
592 /// # Ok(())
593 /// # }
594 /// ```
595 ///
596 /// ## Setting `id` explicitly with [`UMessageBuilder::with_message_id()']
597 ///
598 /// Note that explicitly using [`UMessageBuilder::with_message_id()'] is not required as shown
599 /// above.
600 ///
601 /// ```rust
602 /// use up_rust::{UMessageBuilder, UMessageType, UPriority, UUID, UUri};
603 ///
604 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
605 /// let invoked_method = UUri::try_from("//my-vehicle/4210/5/64AB")?;
606 /// let reply_to_address = UUri::try_from("//my-cloud/BA4C/1/0")?;
607 /// let message_id = UUID::build();
608 /// // a service implementation would normally use
609 /// // `UMessageBuilder::response_for_request(&request_message.attributes)` instead
610 /// let message = UMessageBuilder::response(reply_to_address, UUID::build(), invoked_method)
611 /// .with_message_id(message_id.clone())
612 /// .build()?;
613 /// assert_eq!(message.id_unchecked(), &message_id);
614 /// # Ok(())
615 /// # }
616 /// ```
617 pub fn build(&self) -> Result<UMessage, UMessageError> {
618 // [impl->dsn~up-attributes-id~1]
619 let message_id = self
620 .message_id
621 .clone()
622 .map_or_else(|| Some(UUID::build()), Some);
623 let attributes = UAttributes {
624 commstatus: self.comm_status,
625 id: message_id.into(),
626 payload_format: self.payload_format.into(),
627 permission_level: self.permission_level,
628 priority: self.priority.into(),
629 reqid: self.request_id.clone().into(),
630 sink: self.sink.clone().into(),
631 source: self.source.clone().into(),
632 token: self.token.clone(),
633 traceparent: self.traceparent.clone(),
634 ttl: self.ttl,
635 type_: self.message_type.into(),
636 ..Default::default()
637 };
638 self.validator
639 .validate(&attributes)
640 .map_err(UMessageError::from)
641 .map(|_| UMessage {
642 attributes: Some(attributes).into(),
643 payload: self.payload.to_owned(),
644 ..Default::default()
645 })
646 }
647
648 /// Creates the message based on the builder's state and some payload.
649 ///
650 /// # Arguments
651 ///
652 /// * `payload` - The data to set as payload.
653 /// * `format` - The payload format.
654 ///
655 /// # Returns
656 ///
657 /// A message ready to be sent using [`crate::UTransport::send`].
658 ///
659 /// # Errors
660 ///
661 /// If the properties set on the builder do not represent a consistent set of [`UAttributes`],
662 /// a [`UMessageError::AttributesValidationError`] is returned.
663 ///
664 /// # Examples
665 ///
666 /// ```rust
667 /// use up_rust::{UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UUri};
668 ///
669 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
670 /// let topic = UUri::try_from("//my-vehicle/4210/1/B24D")?;
671 /// let message = UMessageBuilder::publish(topic)
672 /// .build_with_payload("locked", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?;
673 /// assert!(message.payload.is_some());
674 /// # Ok(())
675 /// # }
676 /// ```
677 pub fn build_with_payload<T: Into<Bytes>>(
678 &mut self,
679 payload: T,
680 format: UPayloadFormat,
681 ) -> Result<UMessage, UMessageError> {
682 self.payload = Some(payload.into());
683 // [impl->dsn~up-attributes-payload-format~1]
684 self.payload_format = format;
685
686 self.build()
687 }
688
689 /// Creates the message based on the builder's state and some payload.
690 ///
691 /// # Arguments
692 ///
693 /// * `payload` - The data to set as payload.
694 ///
695 /// # Returns
696 ///
697 /// A message ready to be sent using [`crate::UTransport::send`]. The message will have
698 /// [`UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF`] set as its payload format.
699 ///
700 /// # Errors
701 ///
702 /// If the given payload cannot be serialized into a protobuf byte array, a [`UMessageError::DataSerializationError`] is returned.
703 /// If the properties set on the builder do not represent a consistent set of [`UAttributes`],
704 /// a [`UMessageError::AttributesValidationError`] is returned.
705 ///
706 /// # Examples
707 ///
708 /// ```rust
709 /// use up_rust::{UCode, UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UStatus, UUID, UUri};
710 ///
711 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
712 /// let invoked_method = UUri::try_from("//my-vehicle/4210/5/64AB")?;
713 /// let reply_to_address = UUri::try_from("//my-cloud/BA4C/1/0")?;
714 /// let request_id = UUID::build();
715 /// // a service implementation would normally use
716 /// // `UMessageBuilder::response_for_request(&request_message.attributes)` instead
717 /// let message = UMessageBuilder::response(reply_to_address, request_id, invoked_method)
718 /// .with_comm_status(UCode::INVALID_ARGUMENT)
719 /// .build_with_protobuf_payload(&UStatus::fail("failed to parse request"))?;
720 /// assert!(message.payload.is_some());
721 /// assert_eq!(message.payload_format_unchecked(), UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF);
722 /// # Ok(())
723 /// # }
724 /// ```
725 pub fn build_with_protobuf_payload<T: Message>(
726 &mut self,
727 payload: &T,
728 ) -> Result<UMessage, UMessageError> {
729 payload
730 .write_to_bytes()
731 .map_err(UMessageError::from)
732 .and_then(|serialized_payload| {
733 self.build_with_payload(
734 serialized_payload,
735 UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF,
736 )
737 })
738 }
739
740 /// Creates the message based on the builder's state and some payload.
741 ///
742 /// # Arguments
743 ///
744 /// * `payload` - The data to set as payload.
745 ///
746 /// # Returns
747 ///
748 /// A message ready to be sent using [`crate::UTransport::send`]. The message will have
749 /// [`UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY`] set as its payload format.
750 ///
751 /// # Errors
752 ///
753 /// If the given payload cannot be serialized into a protobuf byte array, a [`UMessageError::DataSerializationError`] is returned.
754 /// If the properties set on the builder do not represent a consistent set of [`UAttributes`],
755 /// a [`UMessageError::AttributesValidationError`] is returned.
756 ///
757 /// # Examples
758 ///
759 /// ```rust
760 /// use up_rust::{UCode, UMessageBuilder, UMessageType, UPayloadFormat, UPriority, UStatus, UUID, UUri};
761 ///
762 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
763 /// let invoked_method = UUri::try_from("//my-vehicle/4210/5/64AB")?;
764 /// let reply_to_address = UUri::try_from("//my-cloud/BA4C/1/0")?;
765 /// let request_id = UUID::build();
766 /// // a service implementation would normally use
767 /// // `UMessageBuilder::response_for_request(&request_message.attributes)` instead
768 /// let message = UMessageBuilder::response(reply_to_address, request_id, invoked_method)
769 /// .with_comm_status(UCode::INVALID_ARGUMENT)
770 /// .build_with_wrapped_protobuf_payload(&UStatus::fail("failed to parse request"))?;
771 /// assert!(message.payload.is_some());
772 /// assert_eq!(message.payload_format_unchecked(), UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY);
773 /// # Ok(())
774 /// # }
775 /// ```
776 pub fn build_with_wrapped_protobuf_payload<T: MessageFull>(
777 &mut self,
778 payload: &T,
779 ) -> Result<UMessage, UMessageError> {
780 Any::pack(payload)
781 .map_err(UMessageError::DataSerializationError)
782 .and_then(|any| any.write_to_bytes().map_err(UMessageError::from))
783 .and_then(|serialized_payload| {
784 self.build_with_payload(
785 serialized_payload,
786 UPayloadFormat::UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY,
787 )
788 })
789 }
790}
791
792#[cfg(test)]
793mod tests {
794 use crate::UCode;
795
796 use super::*;
797
798 use protobuf::Message;
799 use test_case::test_case;
800
801 const METHOD_TO_INVOKE: &str = "//my-vehicle/4D123/2/6FA3";
802 const REPLY_TO_ADDRESS: &str = "//my-cloud/9CB3/1/0";
803 const TOPIC: &str = "//my-vehicle/4210/1/B24D";
804
805 #[test]
806 #[should_panic]
807 fn test_with_message_id_panics_for_invalid_uuid() {
808 let invalid_message_id = UUID {
809 msb: 0x00000000000000ab_u64,
810 lsb: 0x0000000000018000_u64,
811 ..Default::default()
812 };
813 let topic = UUri::try_from(TOPIC).expect("should have been able to create UUri");
814 UMessageBuilder::publish(topic).with_message_id(invalid_message_id);
815 }
816
817 // [utest->dsn~up-attributes-permission-level~1]
818 #[test_case(Some(5), None, None; "with permission level")]
819 #[test_case(None, Some(UCode::NOT_FOUND), None; "with commstatus")]
820 // [utest->dsn~up-attributes-request-token~1]
821 #[test_case(None, None, Some(String::from("my-token")); "with token")]
822 #[should_panic]
823 fn test_publish_message_builder_panics(
824 perm_level: Option<u32>,
825 comm_status: Option<UCode>,
826 token: Option<String>,
827 ) {
828 let topic = UUri::try_from(TOPIC).expect("should have been able to create UUri");
829 let mut builder = UMessageBuilder::publish(topic);
830 if let Some(level) = perm_level {
831 builder.with_permission_level(level);
832 } else if let Some(status_code) = comm_status {
833 builder.with_comm_status(status_code);
834 } else if let Some(t) = token {
835 builder.with_token(t);
836 }
837 }
838
839 // [utest->dsn~up-attributes-permission-level~1]
840 #[test_case(Some(5), None, None; "with permission level")]
841 #[test_case(None, Some(UCode::NOT_FOUND), None; "with commstatus")]
842 // [utest->dsn~up-attributes-request-token~1]
843 #[test_case(None, None, Some(String::from("my-token")); "with token")]
844 #[should_panic]
845 fn test_notification_message_builder_panics(
846 perm_level: Option<u32>,
847 comm_status: Option<UCode>,
848 token: Option<String>,
849 ) {
850 let origin = UUri::try_from(TOPIC).expect("should have been able to create UUri");
851 let destination =
852 UUri::try_from(REPLY_TO_ADDRESS).expect("should have been able to create UUri");
853 let mut builder = UMessageBuilder::notification(origin, destination);
854 if let Some(level) = perm_level {
855 builder.with_permission_level(level);
856 } else if let Some(status_code) = comm_status {
857 builder.with_comm_status(status_code);
858 } else if let Some(t) = token {
859 builder.with_token(t);
860 }
861 }
862
863 // [utest->dsn~up-attributes-permission-level~1]
864 #[test_case(Some(5), None, None; "with permission level")]
865 // [utest->dsn~up-attributes-request-token~1]
866 #[test_case(None, Some(String::from("my-token")), None; "with token")]
867 // [utest->dsn~up-attributes-request-priority~1]
868 #[test_case(None, None, Some(UPriority::UPRIORITY_UNSPECIFIED); "with priority UNSPECIFIED")]
869 // [utest->dsn~up-attributes-request-priority~1]
870 #[test_case(None, None, Some(UPriority::UPRIORITY_CS0); "with priority CS0")]
871 // [utest->dsn~up-attributes-request-priority~1]
872 #[test_case(None, None, Some(UPriority::UPRIORITY_CS1); "with priority CS1")]
873 // [utest->dsn~up-attributes-request-priority~1]
874 #[test_case(None, None, Some(UPriority::UPRIORITY_CS2); "with priority CS2")]
875 // [utest->dsn~up-attributes-request-priority~1]
876 #[test_case(None, None, Some(UPriority::UPRIORITY_CS3); "with priority CS3")]
877 #[should_panic]
878 fn test_response_message_builder_panics(
879 perm_level: Option<u32>,
880 token: Option<String>,
881 priority: Option<UPriority>,
882 ) {
883 let request_id = UUID::build();
884 let method_to_invoke = UUri::try_from(METHOD_TO_INVOKE)
885 .expect("should have been able to create destination UUri");
886 let reply_to_address = UUri::try_from(REPLY_TO_ADDRESS)
887 .expect("should have been able to create reply-to UUri");
888 let mut builder = UMessageBuilder::response(reply_to_address, request_id, method_to_invoke);
889
890 if let Some(level) = perm_level {
891 builder.with_permission_level(level);
892 } else if let Some(t) = token {
893 builder.with_token(t);
894 } else if let Some(prio) = priority {
895 builder.with_priority(prio);
896 }
897 }
898
899 #[test_case(Some(UCode::NOT_FOUND), None; "for comm status")]
900 // [utest->dsn~up-attributes-request-priority~1]
901 #[test_case(None, Some(UPriority::UPRIORITY_UNSPECIFIED); "for priority class unspecified")]
902 // [utest->dsn~up-attributes-request-priority~1]
903 #[test_case(None, Some(UPriority::UPRIORITY_CS0); "for priority class CS0")]
904 // [utest->dsn~up-attributes-request-priority~1]
905 #[test_case(None, Some(UPriority::UPRIORITY_CS1); "for priority class CS1")]
906 // [utest->dsn~up-attributes-request-priority~1]
907 #[test_case(None, Some(UPriority::UPRIORITY_CS2); "for priority class CS2")]
908 // [utest->dsn~up-attributes-request-priority~1]
909 #[test_case(None, Some(UPriority::UPRIORITY_CS3); "for priority class CS3")]
910 #[should_panic]
911 fn test_request_message_builder_panics(
912 comm_status: Option<UCode>,
913 priority: Option<UPriority>,
914 ) {
915 let method_to_invoke = UUri::try_from(METHOD_TO_INVOKE)
916 .expect("should have been able to create destination UUri");
917 let reply_to_address = UUri::try_from(REPLY_TO_ADDRESS)
918 .expect("should have been able to create reply-to UUri");
919 let mut builder = UMessageBuilder::request(method_to_invoke, reply_to_address, 5000);
920
921 if let Some(status) = comm_status {
922 builder.with_comm_status(status);
923 } else if let Some(prio) = priority {
924 builder.with_priority(prio);
925 }
926 }
927
928 #[test]
929 fn test_build_supports_repeated_invocation() {
930 let topic = UUri::try_from(TOPIC).expect("should have been able to create UUri");
931 let mut builder = UMessageBuilder::publish(topic);
932 let message_one = builder
933 .with_message_id(UUID::build())
934 .build_with_payload("locked", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)
935 .expect("should have been able to create message");
936 let message_two = builder
937 .with_message_id(UUID::build())
938 .build_with_payload("unlocked", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)
939 .expect("should have been able to create message");
940 assert_eq!(message_one.attributes.type_, message_two.attributes.type_);
941 assert_ne!(message_one.attributes.id, message_two.attributes.id);
942 assert_eq!(message_one.attributes.source, message_two.attributes.source);
943 assert_ne!(message_one.payload, message_two.payload);
944 }
945
946 #[test]
947 // [utest->req~uattributes-data-model-impl~1]
948 // [utest->req~umessage-data-model-impl~1]
949 fn test_build_retains_all_publish_attributes() {
950 let message_id = UUID::build();
951 let traceparent = String::from("traceparent");
952 let topic = UUri::try_from(TOPIC).expect("should have been able to create UUri");
953 let message = UMessageBuilder::publish(topic.clone())
954 .with_message_id(message_id.clone())
955 .with_ttl(5000)
956 .with_traceparent(&traceparent)
957 .build_with_payload("locked", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)
958 .expect("should have been able to create message");
959
960 // [utest->dsn~up-attributes-id~1]
961 assert_eq!(message.id_unchecked(), &message_id);
962 assert!(UAttributes::is_default_priority(
963 message.priority_unchecked()
964 ));
965 assert_eq!(message.source_unchecked(), &topic);
966 // [utest->dsn~up-attributes-publish-sink~1]
967 assert!(message.sink().is_none());
968 assert_eq!(message.ttl_unchecked(), 5000);
969 // [utest->dsn~up-attributes-traceparent~1]
970 assert_eq!(message.traceparent(), Some(&traceparent));
971 // [utest->dsn~up-attributes-publish-type~1]
972 assert_eq!(
973 message.type_unchecked(),
974 UMessageType::UMESSAGE_TYPE_PUBLISH
975 );
976 // [utest->dsn~up-attributes-payload-format~1]
977 assert_eq!(
978 message.payload_format_unchecked(),
979 UPayloadFormat::UPAYLOAD_FORMAT_TEXT
980 );
981
982 // [utest->req~uattributes-data-model-proto~1]
983 // [utest->req~umessage-data-model-proto~1]
984 let proto = message
985 .write_to_bytes()
986 .expect("failed to serialize to protobuf");
987 let deserialized_message =
988 UMessage::parse_from_bytes(proto.as_slice()).expect("failed to deserialize protobuf");
989 assert_eq!(message, deserialized_message);
990 }
991
992 #[test]
993 // [utest->req~uattributes-data-model-impl~1]
994 // [utest->req~umessage-data-model-impl~1]
995 fn test_build_retains_all_notification_attributes() {
996 let message_id = UUID::build();
997 let traceparent = String::from("traceparent");
998 let origin = UUri::try_from(TOPIC).expect("should have been able to create UUri");
999 let destination =
1000 UUri::try_from(REPLY_TO_ADDRESS).expect("should have been able to create UUri");
1001 let message = UMessageBuilder::notification(origin.clone(), destination.clone())
1002 .with_message_id(message_id.clone())
1003 .with_priority(UPriority::UPRIORITY_CS2)
1004 .with_ttl(5000)
1005 .with_traceparent(&traceparent)
1006 .build_with_payload("locked", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)
1007 .expect("should have been able to create message");
1008
1009 // [utest->dsn~up-attributes-id~1]
1010 assert_eq!(message.id_unchecked(), &message_id);
1011 assert_eq!(message.priority_unchecked(), UPriority::UPRIORITY_CS2);
1012 assert_eq!(message.source_unchecked(), &origin);
1013 assert_eq!(message.sink_unchecked(), &destination);
1014 assert_eq!(message.ttl_unchecked(), 5000);
1015 // [utest->dsn~up-attributes-traceparent~1]
1016 assert_eq!(message.traceparent(), Some(&traceparent));
1017 // [utest->dsn~up-attributes-notification-type~1]
1018 assert_eq!(
1019 message.type_unchecked(),
1020 UMessageType::UMESSAGE_TYPE_NOTIFICATION
1021 );
1022 // [utest->dsn~up-attributes-payload-format~1]
1023 assert_eq!(
1024 message.payload_format_unchecked(),
1025 UPayloadFormat::UPAYLOAD_FORMAT_TEXT
1026 );
1027
1028 // [utest->req~uattributes-data-model-proto~1]
1029 // [utest->req~umessage-data-model-proto~1]
1030 let proto = message
1031 .write_to_bytes()
1032 .expect("failed to serialize to protobuf");
1033 let deserialized_message =
1034 UMessage::parse_from_bytes(proto.as_slice()).expect("failed to deserialize protobuf");
1035 assert_eq!(message, deserialized_message);
1036 }
1037
1038 #[test]
1039 // [utest->req~uattributes-data-model-impl~1]
1040 // [utest->req~umessage-data-model-impl~1]
1041 fn test_build_retains_all_request_attributes() {
1042 let message_id = UUID::build();
1043 let token = String::from("token");
1044 let traceparent = String::from("traceparent");
1045 let method_to_invoke = UUri::try_from(METHOD_TO_INVOKE)
1046 .expect("should have been able to create destination UUri");
1047 let reply_to_address = UUri::try_from(REPLY_TO_ADDRESS)
1048 .expect("should have been able to create reply-to UUri");
1049 let message =
1050 UMessageBuilder::request(method_to_invoke.clone(), reply_to_address.clone(), 5000)
1051 .with_message_id(message_id.clone())
1052 .with_permission_level(5)
1053 .with_priority(UPriority::UPRIORITY_CS4)
1054 .with_token(&token)
1055 .with_traceparent(&traceparent)
1056 .build_with_payload("unlock", UPayloadFormat::UPAYLOAD_FORMAT_TEXT)
1057 .expect("should have been able to create message");
1058
1059 // [utest->dsn~up-attributes-id~1]
1060 assert_eq!(message.id_unchecked(), &message_id);
1061 // [utest->dsn~up-attributes-permission-level~1]
1062 assert_eq!(message.permission_level(), Some(5));
1063 assert_eq!(message.priority_unchecked(), UPriority::UPRIORITY_CS4);
1064 assert_eq!(message.sink_unchecked(), &method_to_invoke);
1065 assert_eq!(message.source_unchecked(), &reply_to_address);
1066 // [utest->dsn~up-attributes-request-token~1]
1067 assert_eq!(message.token(), Some(&token));
1068 assert_eq!(message.ttl_unchecked(), 5000);
1069 // [utest->dsn~up-attributes-traceparent~1]
1070 assert_eq!(message.traceparent(), Some(&traceparent));
1071 // [utest->dsn~up-attributes-request-type~1]
1072 assert_eq!(
1073 message.type_unchecked(),
1074 UMessageType::UMESSAGE_TYPE_REQUEST
1075 );
1076 // [utest->dsn~up-attributes-payload-format~1]
1077 assert_eq!(
1078 message.payload_format_unchecked(),
1079 UPayloadFormat::UPAYLOAD_FORMAT_TEXT
1080 );
1081
1082 // [utest->req~uattributes-data-model-proto~1]
1083 // [utest->req~umessage-data-model-proto~1]
1084 let proto = message
1085 .write_to_bytes()
1086 .expect("failed to serialize to protobuf");
1087 let deserialized_message =
1088 UMessage::parse_from_bytes(proto.as_slice()).expect("failed to deserialize protobuf");
1089 assert_eq!(message, deserialized_message);
1090 }
1091
1092 #[test]
1093 fn test_builder_copies_request_attributes() {
1094 let request_message_id = UUID::build();
1095 let response_message_id = UUID::build();
1096 let method_to_invoke = UUri::try_from(METHOD_TO_INVOKE)
1097 .expect("should have been able to create destination UUri");
1098 let reply_to_address = UUri::try_from(REPLY_TO_ADDRESS)
1099 .expect("should have been able to create reply-to UUri");
1100 let request_message =
1101 UMessageBuilder::request(method_to_invoke.clone(), reply_to_address.clone(), 5000)
1102 .with_message_id(request_message_id.clone())
1103 .with_priority(UPriority::UPRIORITY_CS5)
1104 .build()
1105 .expect("should have been able to create message");
1106 let message = UMessageBuilder::response_for_request(&request_message.attributes)
1107 .with_message_id(response_message_id.clone())
1108 .with_comm_status(UCode::DEADLINE_EXCEEDED)
1109 .build()
1110 .expect("should have been able to create message");
1111 // [utest->dsn~up-attributes-id~1]
1112 assert_eq!(message.id_unchecked(), &response_message_id);
1113 assert_eq!(message.commstatus_unchecked(), UCode::DEADLINE_EXCEEDED);
1114 assert_eq!(message.priority_unchecked(), UPriority::UPRIORITY_CS5);
1115 assert_eq!(message.request_id_unchecked(), &request_message_id);
1116 assert_eq!(message.sink_unchecked(), &reply_to_address);
1117 assert_eq!(message.source_unchecked(), &method_to_invoke);
1118 assert_eq!(message.ttl_unchecked(), 5000);
1119 // [utest->dsn~up-attributes-response-type~1]
1120 assert_eq!(
1121 message.type_unchecked(),
1122 UMessageType::UMESSAGE_TYPE_RESPONSE
1123 );
1124 assert_eq!(
1125 message.payload_format_unchecked(),
1126 UPayloadFormat::UPAYLOAD_FORMAT_UNSPECIFIED
1127 );
1128 assert!(message.payload.is_none());
1129 }
1130
1131 #[test]
1132 // [utest->req~uattributes-data-model-impl~1]
1133 // [utest->req~umessage-data-model-impl~1]
1134 fn test_build_retains_all_response_attributes() {
1135 let message_id = UUID::build();
1136 let request_id = UUID::build();
1137 let traceparent = String::from("traceparent");
1138 let method_to_invoke = UUri::try_from(METHOD_TO_INVOKE)
1139 .expect("should have been able to create destination UUri");
1140 let reply_to_address = UUri::try_from(REPLY_TO_ADDRESS)
1141 .expect("should have been able to create reply-to UUri");
1142 let message = UMessageBuilder::response(
1143 reply_to_address.clone(),
1144 request_id.clone(),
1145 method_to_invoke.clone(),
1146 )
1147 .with_message_id(message_id.clone())
1148 .with_comm_status(UCode::DEADLINE_EXCEEDED)
1149 .with_priority(UPriority::UPRIORITY_CS5)
1150 .with_ttl(4000)
1151 .with_traceparent(&traceparent)
1152 .build()
1153 .expect("should have been able to create message");
1154
1155 // [utest->dsn~up-attributes-id~1]
1156 assert_eq!(message.id_unchecked(), &message_id);
1157 assert_eq!(message.commstatus_unchecked(), UCode::DEADLINE_EXCEEDED);
1158 assert_eq!(message.priority_unchecked(), UPriority::UPRIORITY_CS5);
1159 assert_eq!(message.request_id_unchecked(), &request_id);
1160 assert_eq!(message.sink_unchecked(), &reply_to_address);
1161 assert_eq!(message.source_unchecked(), &method_to_invoke);
1162 assert_eq!(message.ttl_unchecked(), 4000);
1163 // [utest->dsn~up-attributes-traceparent~1]
1164 assert_eq!(message.traceparent(), Some(&traceparent));
1165 // [utest->dsn~up-attributes-response-type~1]
1166 assert_eq!(
1167 message.type_unchecked(),
1168 UMessageType::UMESSAGE_TYPE_RESPONSE
1169 );
1170 assert_eq!(
1171 message.payload_format_unchecked(),
1172 UPayloadFormat::UPAYLOAD_FORMAT_UNSPECIFIED
1173 );
1174 assert!(message.payload.is_none());
1175
1176 // [utest->req~uattributes-data-model-proto~1]
1177 // [utest->req~umessage-data-model-proto~1]
1178 let proto = message
1179 .write_to_bytes()
1180 .expect("failed to serialize to protobuf");
1181 let deserialized_message =
1182 UMessage::parse_from_bytes(proto.as_slice()).expect("failed to deserialize protobuf");
1183 assert_eq!(message, deserialized_message);
1184 }
1185}