Preview of the proposed up-rust native-frame-model branch (up-rust b6b99c6d, up-spec f0e9b17) — not released documentation. branch · write-up

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