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}