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}