up_rust/uri.rs
1/********************************************************************************
2 * Copyright (c) 2023 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
14// [impl->dsn~uri-data-model-naming~1]
15// [impl->req~uri-data-model-proto~1]
16
17use std::hash::{Hash, Hasher};
18use std::str::FromStr;
19use std::sync::LazyLock;
20
21use uriparse::{Authority, URIReference};
22
23pub use crate::up_core_api::uri::UUri;
24
25pub(crate) const WILDCARD_AUTHORITY: &str = "*";
26pub(crate) const WILDCARD_ENTITY_INSTANCE: u32 = 0xFFFF_0000;
27pub(crate) const WILDCARD_ENTITY_TYPE: u32 = 0x0000_FFFF;
28pub(crate) const WILDCARD_ENTITY_VERSION: u32 = 0x0000_00FF;
29pub(crate) const WILDCARD_RESOURCE_ID: u32 = 0x0000_FFFF;
30
31pub(crate) const RESOURCE_ID_RESPONSE: u32 = 0;
32pub(crate) const RESOURCE_ID_MIN_EVENT: u32 = 0x8000;
33
34static AUTHORITY_NAME_PATTERN: LazyLock<regex::Regex> =
35 LazyLock::new(|| regex::Regex::new(r"^[a-z0-9\-._~]{0,128}$").unwrap());
36
37#[derive(Debug)]
38pub enum UUriError {
39 SerializationError(String),
40 ValidationError(String),
41}
42
43impl UUriError {
44 pub fn serialization_error<T>(message: T) -> UUriError
45 where
46 T: Into<String>,
47 {
48 Self::SerializationError(message.into())
49 }
50
51 pub fn validation_error<T>(message: T) -> UUriError
52 where
53 T: Into<String>,
54 {
55 Self::ValidationError(message.into())
56 }
57}
58
59impl std::fmt::Display for UUriError {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 match self {
62 Self::SerializationError(e) => f.write_fmt(format_args!("Serialization error: {e}")),
63 Self::ValidationError(e) => f.write_fmt(format_args!("Validation error: {e}")),
64 }
65 }
66}
67
68impl std::error::Error for UUriError {}
69
70// [impl->req~uri-serialization~1]
71impl From<&UUri> for String {
72 /// Serializes a uProtocol URI to a URI string.
73 ///
74 /// # Arguments
75 ///
76 /// * `uri` - The URI to serialize. Note that the given URI is **not** validated before serialization.
77 /// In particular, the URI's version and resource ID length are not checked to be within limits.
78 ///
79 /// # Returns
80 ///
81 /// The output of [`UUri::to_uri`] without including the uProtocol scheme.
82 ///
83 /// # Examples
84 ///
85 /// ```rust
86 /// use up_rust::UUri;
87 ///
88 /// let uuri = UUri {
89 /// authority_name: String::from("vin.vehicles"),
90 /// ue_id: 0x0000_800A,
91 /// ue_version_major: 0x02,
92 /// resource_id: 0x0000_1a50,
93 /// ..Default::default()
94 /// };
95 ///
96 /// let uri_string = String::from(&uuri);
97 /// assert_eq!(uri_string, "//vin.vehicles/800A/2/1A50");
98 /// ````
99 fn from(uri: &UUri) -> Self {
100 UUri::to_uri(uri, false)
101 }
102}
103
104impl FromStr for UUri {
105 type Err = UUriError;
106
107 /// Attempts to parse a `String` into a `UUri`.
108 ///
109 /// As part of the parsing, the _authority_ of the URI is getting normalized. This means that all characters
110 /// are converted to lowercase, no bytes that are in the unreserved character set remain percent-encoded,
111 /// and all alphabetical characters in percent-encodings are converted to uppercase.
112 ///
113 /// # Arguments
114 ///
115 /// * `uri` - The `String` to be converted into a `UUri`.
116 ///
117 /// # Returns
118 ///
119 /// A `Result` containing either the `UUri` representation of the URI or a `SerializationError`.
120 ///
121 /// # Examples
122 ///
123 /// ```rust
124 /// use std::str::FromStr;
125 /// use up_rust::UUri;
126 ///
127 /// let uri = UUri {
128 /// authority_name: "vin.vehicles".to_string(),
129 /// ue_id: 0x000A_8000,
130 /// ue_version_major: 0x02,
131 /// resource_id: 0x0000_1a50,
132 /// ..Default::default()
133 /// };
134 ///
135 /// let uri_from = UUri::from_str("//vin.vehicles/A8000/2/1A50").unwrap();
136 /// assert_eq!(uri, uri_from);
137 /// ````
138 // [impl->dsn~uri-authority-name-length~1]
139 // [impl->dsn~uri-scheme~1]
140 // [impl->dsn~uri-host-only~2]
141 // [impl->dsn~uri-authority-mapping~1]
142 // [impl->dsn~uri-path-mapping~2]
143 // [impl->req~uri-serialization~1]
144 fn from_str(uri: &str) -> Result<Self, Self::Err> {
145 if uri.is_empty() {
146 return Err(UUriError::serialization_error("URI is empty"));
147 }
148 let parsed_uri = URIReference::try_from(uri)
149 .map_err(|e| UUriError::serialization_error(e.to_string()))?;
150
151 if let Some(scheme) = parsed_uri.scheme() {
152 if scheme.ne("up") {
153 return Err(UUriError::serialization_error(
154 "uProtocol URI must use 'up' scheme",
155 ));
156 }
157 }
158 if parsed_uri.has_query() {
159 return Err(UUriError::serialization_error(
160 "uProtocol URI must not contain query",
161 ));
162 }
163 if parsed_uri.has_fragment() {
164 return Err(UUriError::serialization_error(
165 "uProtocol URI must not contain fragment",
166 ));
167 }
168 let authority_name = parsed_uri
169 .authority()
170 .map_or(Ok(String::default()), Self::verify_parsed_authority)?;
171
172 let path_segments = parsed_uri.path().segments();
173 if path_segments.len() != 3 {
174 return Err(UUriError::serialization_error(
175 "uProtocol URI must contain entity ID, entity version and resource ID",
176 ));
177 }
178 let entity = path_segments[0].as_str();
179 if entity.is_empty() {
180 return Err(UUriError::serialization_error(
181 "URI must contain non-empty entity ID",
182 ));
183 }
184 let ue_id = u32::from_str_radix(entity, 16)
185 .map_err(|e| UUriError::serialization_error(format!("Cannot parse entity ID: {e}")))?;
186 let version = path_segments[1].as_str();
187 if version.is_empty() {
188 return Err(UUriError::serialization_error(
189 "URI must contain non-empty entity version",
190 ));
191 }
192 let ue_version_major = u8::from_str_radix(version, 16).map_err(|e| {
193 UUriError::serialization_error(format!("Cannot parse entity version: {e}"))
194 })?;
195 let resource = path_segments[2].as_str();
196 if resource.is_empty() {
197 return Err(UUriError::serialization_error(
198 "URI must contain non-empty resource ID",
199 ));
200 }
201 let resource_id = u16::from_str_radix(resource, 16).map_err(|e| {
202 UUriError::serialization_error(format!("Cannot parse resource ID: {e}"))
203 })?;
204
205 Ok(UUri {
206 authority_name,
207 ue_id,
208 ue_version_major: ue_version_major as u32,
209 resource_id: resource_id as u32,
210 ..Default::default()
211 })
212 }
213}
214
215// [impl->req~uri-serialization~1]
216impl TryFrom<String> for UUri {
217 type Error = UUriError;
218
219 /// Attempts to serialize a `String` into a `UUri`.
220 ///
221 /// # Arguments
222 ///
223 /// * `uri` - The `String` to be converted into a `UUri`.
224 ///
225 /// # Returns
226 ///
227 /// A `Result` containing either the `UUri` representation of the URI or a `SerializationError`.
228 ///
229 /// # Examples
230 ///
231 /// ```rust
232 /// use up_rust::UUri;
233 ///
234 /// let uri = UUri {
235 /// authority_name: "".to_string(),
236 /// ue_id: 0x001A_8000,
237 /// ue_version_major: 0x02,
238 /// resource_id: 0x0000_1a50,
239 /// ..Default::default()
240 /// };
241 ///
242 /// let uri_from = UUri::try_from("/1A8000/2/1A50".to_string()).unwrap();
243 /// assert_eq!(uri, uri_from);
244 /// ````
245 fn try_from(uri: String) -> Result<Self, Self::Error> {
246 UUri::from_str(uri.as_str())
247 }
248}
249
250// [impl->req~uri-serialization~1]
251impl TryFrom<&str> for UUri {
252 type Error = UUriError;
253
254 /// Attempts to serialize a `String` into a `UUri`.
255 ///
256 /// # Arguments
257 ///
258 /// * `uri` - The `String` to be converted into a `UUri`.
259 ///
260 /// # Returns
261 ///
262 /// A `Result` containing either the `UUri` representation of the URI or a `SerializationError`.
263 ///
264 /// # Examples
265 ///
266 /// ```rust
267 /// use up_rust::UUri;
268 ///
269 /// let uri = UUri {
270 /// authority_name: "".to_string(),
271 /// ue_id: 0x001A_8000,
272 /// ue_version_major: 0x02,
273 /// resource_id: 0x0000_1a50,
274 /// ..Default::default()
275 /// };
276 ///
277 /// let uri_from = UUri::try_from("/1A8000/2/1A50").unwrap();
278 /// assert_eq!(uri, uri_from);
279 /// ````
280 fn try_from(uri: &str) -> Result<Self, Self::Error> {
281 UUri::from_str(uri)
282 }
283}
284
285impl Hash for UUri {
286 fn hash<H: Hasher>(&self, state: &mut H) {
287 self.authority_name.hash(state);
288 self.ue_id.hash(state);
289 self.ue_version_major.hash(state);
290 self.resource_id.hash(state);
291 }
292}
293
294impl Eq for UUri {}
295
296impl UUri {
297 /// Serializes this UUri to a URI string.
298 ///
299 /// # Arguments
300 ///
301 /// * `include_scheme` - Indicates whether to include the uProtocol scheme (`up`) in the URI.
302 ///
303 /// # Returns
304 ///
305 /// The URI as defined by the [uProtocol Specification](https://github.com/eclipse-uprotocol/up-spec).
306 ///
307 /// # Examples
308 ///
309 /// ```rust
310 /// use up_rust::UUri;
311 ///
312 /// let uuri = UUri {
313 /// authority_name: String::from("vin.vehicles"),
314 /// ue_id: 0x0000_800A,
315 /// ue_version_major: 0x02,
316 /// resource_id: 0x0000_1a50,
317 /// ..Default::default()
318 /// };
319 ///
320 /// let uri_string = uuri.to_uri(true);
321 /// assert_eq!(uri_string, "up://vin.vehicles/800A/2/1A50");
322 /// ````
323 // [impl->dsn~uri-authority-mapping~1]
324 // [impl->dsn~uri-path-mapping~2]
325 // [impl->req~uri-serialization~1]
326 pub fn to_uri(&self, include_scheme: bool) -> String {
327 let mut output = String::default();
328 if include_scheme {
329 output.push_str("up:");
330 }
331 if !self.authority_name.is_empty() {
332 output.push_str("//");
333 output.push_str(&self.authority_name);
334 }
335 let uri = format!(
336 "/{:X}/{:X}/{:X}",
337 self.ue_id, self.ue_version_major, self.resource_id
338 );
339 output.push_str(&uri);
340 output
341 }
342
343 /// Creates a new UUri from its parts.
344 ///
345 /// # Errors
346 ///
347 /// Returns a [`UUriError::ValidationError`] if the authority does not comply with the UUri specification.
348 ///
349 /// # Examples
350 ///
351 /// ```rust
352 /// use up_rust::UUri;
353 ///
354 /// assert!(UUri::try_from_parts("vin", 0x0000_5a6b, 0x01, 0x0001).is_ok());
355 /// ```
356 // [impl->dsn~uri-authority-name-length~1]
357 // [impl->dsn~uri-host-only~2]
358 pub fn try_from_parts(
359 authority: &str,
360 entity_id: u32,
361 entity_version: u8,
362 resource_id: u16,
363 ) -> Result<Self, UUriError> {
364 let authority_name = Self::verify_authority(authority)?;
365 Ok(UUri {
366 authority_name,
367 ue_id: entity_id,
368 ue_version_major: entity_version as u32,
369 resource_id: resource_id as u32,
370 ..Default::default()
371 })
372 }
373
374 /// Gets a URI that consists of wildcards only and therefore matches any URI.
375 pub fn any() -> Self {
376 Self::any_with_resource_id(WILDCARD_RESOURCE_ID)
377 }
378
379 /// Gets a URI that consists of wildcards and the specific resource ID.
380 pub fn any_with_resource_id(resource_id: u32) -> Self {
381 UUri {
382 authority_name: WILDCARD_AUTHORITY.to_string(),
383 ue_id: WILDCARD_ENTITY_INSTANCE | WILDCARD_ENTITY_TYPE,
384 ue_version_major: WILDCARD_ENTITY_VERSION,
385 resource_id,
386 ..Default::default()
387 }
388 }
389
390 /// Gets the authority name part from this uProtocol URI.
391 ///
392 /// # Examples
393 ///
394 /// ```rust
395 /// use up_rust::UUri;
396 ///
397 /// let uri = UUri::try_from_parts("my-vehicle", 0x10101234, 0x01, 0x9a10).unwrap();
398 /// assert_eq!(uri.authority_name(), *"my-vehicle");
399 /// ```
400 pub fn authority_name(&self) -> String {
401 self.authority_name.to_owned()
402 }
403
404 // Gets the uEntity type identifier part from this uProtocol URI.
405 ///
406 /// # Examples
407 ///
408 /// ```rust
409 /// use up_rust::UUri;
410 ///
411 /// let uri = UUri::try_from_parts("my-vehicle", 0x10101234, 0x01, 0x9a10).unwrap();
412 /// assert_eq!(uri.uentity_type_id(), 0x1234);
413 /// ```
414 pub fn uentity_type_id(&self) -> u16 {
415 (self.ue_id & WILDCARD_ENTITY_TYPE) as u16
416 }
417
418 // Gets the uEntity instance identifier part from this uProtocol URI.
419 ///
420 /// # Examples
421 ///
422 /// ```rust
423 /// use up_rust::UUri;
424 ///
425 /// let uri = UUri::try_from_parts("my-vehicle", 0x10101234, 0x01, 0x9a10).unwrap();
426 /// assert_eq!(uri.uentity_instance_id(), 0x1010);
427 /// ```
428 pub fn uentity_instance_id(&self) -> u16 {
429 ((self.ue_id & WILDCARD_ENTITY_INSTANCE) >> 16) as u16
430 }
431
432 // Gets the major version part from this uProtocol URI.
433 ///
434 /// # Examples
435 ///
436 /// ```rust
437 /// use up_rust::UUri;
438 ///
439 /// let uri = UUri::try_from_parts("my-vehicle", 0x10101234, 0x01, 0x9a10).unwrap();
440 /// assert_eq!(uri.uentity_major_version(), 0x01);
441 /// ```
442 pub fn uentity_major_version(&self) -> u8 {
443 (self.ue_version_major & WILDCARD_ENTITY_VERSION) as u8
444 }
445
446 // Gets the resource identifier part from this uProtocol URI.
447 ///
448 /// # Examples
449 ///
450 /// ```rust
451 /// use up_rust::UUri;
452 ///
453 /// let uri = UUri::try_from_parts("my-vehicle", 0x10101234, 0x01, 0x9a10).unwrap();
454 /// assert_eq!(uri.resource_id(), 0x9a10);
455 /// ```
456 pub fn resource_id(&self) -> u16 {
457 (self.resource_id & WILDCARD_RESOURCE_ID) as u16
458 }
459
460 // [impl->dsn~uri-authority-name-length~1]
461 // [impl->dsn~uri-host-only~2]
462 pub(crate) fn verify_authority(authority: &str) -> Result<String, UUriError> {
463 Authority::try_from(authority)
464 .map_err(|e| UUriError::validation_error(format!("invalid authority: {e}")))
465 .and_then(|auth| Self::verify_parsed_authority(&auth))
466 }
467
468 // [impl->dsn~uri-authority-name-length~1]
469 // [impl->dsn~uri-host-only~2]
470 pub(crate) fn verify_parsed_authority(auth: &Authority) -> Result<String, UUriError> {
471 if auth.has_port() {
472 Err(UUriError::validation_error(
473 "uProtocol URI's authority must not contain port",
474 ))
475 } else if auth.has_username() || auth.has_password() {
476 Err(UUriError::validation_error(
477 "uProtocol URI's authority must not contain userinfo",
478 ))
479 } else {
480 match auth.host() {
481 uriparse::Host::IPv4Address(_) | uriparse::Host::IPv6Address(_) => {
482 Ok(auth.host().to_string())
483 }
484 uriparse::Host::RegisteredName(name) => {
485 if !WILDCARD_AUTHORITY.eq(name.as_str())
486 && !AUTHORITY_NAME_PATTERN.is_match(name.as_str())
487 {
488 return Err(UUriError::validation_error(
489 "uProtocol URI's authority contains invalid characters",
490 ));
491 }
492 Ok(name.to_string())
493 }
494 }
495 }
496 }
497
498 fn verify_major_version(major_version: u32) -> Result<u8, UUriError> {
499 u8::try_from(major_version).map_err(|_e| {
500 UUriError::ValidationError(
501 "uProtocol URI's major version must be an 8 bit unsigned integer".to_string(),
502 )
503 })
504 }
505
506 fn verify_resource_id(resource_id: u32) -> Result<u16, UUriError> {
507 u16::try_from(resource_id).map_err(|_e| {
508 UUriError::ValidationError(
509 "uProtocol URI's resource ID must be a 16 bit unsigned integer".to_string(),
510 )
511 })
512 }
513
514 /// Verifies that this UUri is indeed a valid uProtocol URI.
515 ///
516 /// This check is not necessary, if any of UUri's constructors functions has been used
517 /// to create the URI. However, if the origin of a UUri is unknown, e.g. when it has
518 /// been deserialized from a protobuf, then this function can be used to check if all
519 /// properties are compliant with the uProtocol specification.
520 ///
521 /// # Errors
522 ///
523 /// Returns an error if this UUri is not a valid uProtocol URI. The returned error may
524 /// contain details regarding the cause of the validation to have failed.
525 ///
526 /// # Examples
527 ///
528 /// ```rust
529 /// use up_rust::UUri;
530 ///
531 /// let uuri = UUri {
532 /// authority_name: "valid_name".into(),
533 /// ue_id: 0x1000,
534 /// ue_version_major: 0x01,
535 /// resource_id: 0x8100,
536 /// ..Default::default()
537 /// };
538 /// assert!(uuri.check_validity().is_ok());
539 /// ```
540 pub fn check_validity(&self) -> Result<(), UUriError> {
541 Self::verify_authority(self.authority_name.as_str())?;
542 Self::verify_major_version(self.ue_version_major)?;
543 Self::verify_resource_id(self.resource_id)?;
544 Ok(())
545 }
546
547 /// Checks if this URI is empty.
548 ///
549 /// # Returns
550 ///
551 /// 'true' if this URI is equal to `UUri::default()`, `false` otherwise.
552 ///
553 /// # Examples
554 ///
555 /// ```rust
556 /// use up_rust::UUri;
557 ///
558 /// let uuri = UUri::try_from_parts("myvin", 0xa13b, 0x01, 0x7f4e).unwrap();
559 /// assert!(!uuri.is_empty());
560 /// assert!(UUri::default().is_empty());
561 /// ```
562 pub fn is_empty(&self) -> bool {
563 self.eq(&UUri::default())
564 }
565
566 /// Check if an `UUri` is remote, by comparing authority fields.
567 /// UUris with empty authority are considered to be local.
568 ///
569 /// # Returns
570 ///
571 /// 'true' if other_uri has a different authority than `Self`, `false` otherwise.
572 ///
573 /// # Examples
574 ///
575 /// ```rust
576 /// use std::str::FromStr;
577 /// use up_rust::UUri;
578 ///
579 /// let authority_a = UUri::from_str("up://authority.a/100A/1/0").unwrap();
580 /// let authority_b = UUri::from_str("up://authority.b/200B/2/20").unwrap();
581 /// assert!(authority_a.is_remote(&authority_b));
582 ///
583 /// let authority_local = UUri::from_str("up:///100A/1/0").unwrap();
584 /// assert!(!authority_local.is_remote(&authority_a));
585 ///
586 /// let authority_wildcard = UUri::from_str("up://*/100A/1/0").unwrap();
587 /// assert!(!authority_wildcard.is_remote(&authority_a));
588 /// assert!(!authority_a.is_remote(&authority_wildcard));
589 /// assert!(!authority_wildcard.is_remote(&authority_wildcard));
590 /// ````
591 pub fn is_remote(&self, other_uri: &UUri) -> bool {
592 self.is_remote_authority(&other_uri.authority_name)
593 }
594
595 /// Check if an authority is remote compared to the authority field of the UUri.
596 /// Empty authorities are considered to be local.
597 ///
598 /// # Returns
599 ///
600 /// 'true' if authority is a different than `Self.authority_name`, `false` otherwise.
601 ///
602 /// # Examples
603 ///
604 /// ```rust
605 /// use std::str::FromStr;
606 /// use up_rust::UUri;
607 ///
608 /// let authority_a = UUri::from_str("up://authority.a/100A/1/0").unwrap();
609 /// let authority_b = "authority.b".to_string();
610 /// assert!(authority_a.is_remote_authority(&authority_b));
611 ///
612 /// let authority_local = "".to_string();
613 /// assert!(!authority_a.is_remote_authority(&authority_local));
614 ///
615 /// let authority_wildcard = "*".to_string();
616 /// assert!(!authority_a.is_remote_authority(&authority_wildcard));
617 /// ```
618 pub fn is_remote_authority(&self, authority: &String) -> bool {
619 !authority.is_empty()
620 && !self.authority_name.is_empty()
621 && !self.has_wildcard_authority()
622 && authority != WILDCARD_AUTHORITY
623 && self.authority_name != *authority
624 }
625
626 /// Checks if this UUri has an empty authority name.
627 ///
628 /// # Examples
629 ///
630 /// ```rust
631 /// use up_rust::UUri;
632 ///
633 /// let uuri = UUri::try_from_parts("", 0x9b3a, 0x01, 0x145b).unwrap();
634 /// assert!(uuri.has_empty_authority());
635 /// ```
636 pub fn has_empty_authority(&self) -> bool {
637 self.authority_name.is_empty()
638 }
639
640 /// Checks if this UUri has a wildcard authority name.
641 ///
642 /// # Examples
643 ///
644 /// ```rust
645 /// use up_rust::UUri;
646 ///
647 /// let uuri = UUri::try_from_parts("*", 0x9b3a, 0x01, 0x145b).unwrap();
648 /// assert!(uuri.has_wildcard_authority());
649 /// ```
650 pub fn has_wildcard_authority(&self) -> bool {
651 self.authority_name == WILDCARD_AUTHORITY
652 }
653
654 /// Checks if this UUri has an entity identifier matching any instance.
655 ///
656 /// # Examples
657 ///
658 /// ```rust
659 /// use up_rust::UUri;
660 ///
661 /// let uuri = UUri::try_from_parts("vin", 0xFFFF_0123, 0x01, 0x145b).unwrap();
662 /// assert!(uuri.has_wildcard_entity_instance());
663 /// ```
664 pub fn has_wildcard_entity_instance(&self) -> bool {
665 self.ue_id & WILDCARD_ENTITY_INSTANCE == WILDCARD_ENTITY_INSTANCE
666 }
667
668 /// Checks if this UUri has an entity identifier matching any type.
669 ///
670 /// # Examples
671 ///
672 /// ```rust
673 /// use up_rust::UUri;
674 ///
675 /// let uuri = UUri::try_from_parts("vin", 0x00C0_FFFF, 0x01, 0x145b).unwrap();
676 /// assert!(uuri.has_wildcard_entity_type());
677 /// ```
678 pub fn has_wildcard_entity_type(&self) -> bool {
679 self.ue_id & WILDCARD_ENTITY_TYPE == WILDCARD_ENTITY_TYPE
680 }
681
682 /// Checks if this UUri has a wildcard major version.
683 ///
684 /// # Examples
685 ///
686 /// ```rust
687 /// use up_rust::UUri;
688 ///
689 /// let uuri = UUri::try_from_parts("vin", 0x9b3a, 0xFF, 0x145b).unwrap();
690 /// assert!(uuri.has_wildcard_version());
691 /// ```
692 pub fn has_wildcard_version(&self) -> bool {
693 self.ue_version_major == WILDCARD_ENTITY_VERSION
694 }
695
696 /// Checks if this UUri has a wildcard entity identifier.
697 ///
698 /// # Examples
699 ///
700 /// ```rust
701 /// use up_rust::UUri;
702 ///
703 /// let uuri = UUri::try_from_parts("vin", 0x9b3a, 0x01, 0xFFFF).unwrap();
704 /// assert!(uuri.has_wildcard_resource_id());
705 /// ```
706 pub fn has_wildcard_resource_id(&self) -> bool {
707 self.resource_id == WILDCARD_RESOURCE_ID
708 }
709
710 /// Verifies that this UUri does not contain any wildcards.
711 ///
712 /// # Errors
713 ///
714 /// Returns an error if any of this UUri's properties contain a wildcard value.
715 ///
716 /// # Examples
717 ///
718 /// ```rust
719 /// use up_rust::UUri;
720 ///
721 /// let uri = UUri {
722 /// authority_name: String::from("VIN.vehicles"),
723 /// ue_id: 0x0000_2310,
724 /// ue_version_major: 0x03,
725 /// resource_id: 0xa000,
726 /// ..Default::default()
727 /// };
728 /// assert!(uri.verify_no_wildcards().is_ok());
729 /// ```
730 pub fn verify_no_wildcards(&self) -> Result<(), UUriError> {
731 if self.has_wildcard_authority() {
732 Err(UUriError::validation_error(format!(
733 "Authority must not contain wildcard character [{WILDCARD_AUTHORITY}]"
734 )))
735 } else if self.has_wildcard_entity_instance() {
736 Err(UUriError::validation_error(format!(
737 "Entity instance ID must not be set to wildcard value [{WILDCARD_ENTITY_INSTANCE:#X}]")))
738 } else if self.has_wildcard_entity_type() {
739 Err(UUriError::validation_error(format!(
740 "Entity type ID must not be set to wildcard value [{WILDCARD_ENTITY_TYPE:#X}]"
741 )))
742 } else if self.has_wildcard_version() {
743 Err(UUriError::validation_error(format!(
744 "Entity version must not be set to wildcard value [{WILDCARD_ENTITY_VERSION:#X}]"
745 )))
746 } else if self.has_wildcard_resource_id() {
747 Err(UUriError::validation_error(format!(
748 "Resource ID must not be set to wildcard value [{WILDCARD_RESOURCE_ID:#X}]"
749 )))
750 } else {
751 Ok(())
752 }
753 }
754
755 /// Checks if this UUri refers to a service method.
756 ///
757 /// Returns `true` if 0 < resource ID < 0x8000.
758 ///
759 /// # Examples
760 ///
761 /// ```rust
762 /// use up_rust::UUri;
763 ///
764 /// let uri = UUri {
765 /// resource_id: 0x7FFF,
766 /// ..Default::default()
767 /// };
768 /// assert!(uri.is_rpc_method());
769 /// ```
770 pub fn is_rpc_method(&self) -> bool {
771 self.resource_id > RESOURCE_ID_RESPONSE && self.resource_id < RESOURCE_ID_MIN_EVENT
772 }
773
774 /// Verifies that this UUri refers to a service method.
775 ///
776 /// # Errors
777 ///
778 /// Returns an error if [`Self::is_rpc_method`] fails or
779 /// the UUri [contains any wildcards](Self::verify_no_wildcards).
780 ///
781 /// # Examples
782 ///
783 /// ```rust
784 /// use up_rust::UUri;
785 ///
786 /// let uri = UUri {
787 /// resource_id: 0x8000,
788 /// ..Default::default()
789 /// };
790 /// assert!(uri.verify_rpc_method().is_err());
791 ///
792 /// let uri = UUri {
793 /// resource_id: 0x0,
794 /// ..Default::default()
795 /// };
796 /// assert!(uri.verify_rpc_method().is_err());
797 /// ```
798 pub fn verify_rpc_method(&self) -> Result<(), UUriError> {
799 if !self.is_rpc_method() {
800 Err(UUriError::validation_error(format!(
801 "Resource ID must be a value from ]{RESOURCE_ID_RESPONSE:#X}, {RESOURCE_ID_MIN_EVENT:#X}[")))
802 } else {
803 self.verify_no_wildcards()
804 }
805 }
806
807 /// Checks if this UUri represents a destination for a Notification.
808 ///
809 /// Returns `true` if resource ID is 0.
810 ///
811 /// # Examples
812 ///
813 /// ```rust
814 /// use up_rust::UUri;
815 ///
816 /// let uri = UUri {
817 /// resource_id: 0,
818 /// ..Default::default()
819 /// };
820 /// assert!(uri.is_notification_destination());
821 /// ```
822 pub fn is_notification_destination(&self) -> bool {
823 self.resource_id == RESOURCE_ID_RESPONSE
824 }
825
826 /// Checks if this UUri represents an RPC response address.
827 ///
828 /// Returns `true` if resource ID is 0.
829 ///
830 /// # Examples
831 ///
832 /// ```rust
833 /// use up_rust::UUri;
834 ///
835 /// let uri = UUri {
836 /// resource_id: 0,
837 /// ..Default::default()
838 /// };
839 /// assert!(uri.is_rpc_response());
840 /// ```
841 pub fn is_rpc_response(&self) -> bool {
842 self.resource_id == RESOURCE_ID_RESPONSE
843 }
844
845 /// Verifies that this UUri represents an RPC response address.
846 ///
847 /// # Errors
848 ///
849 /// Returns an error if [`Self::is_rpc_response`] fails or
850 /// the UUri [contains any wildcards](Self::verify_no_wildcards).
851 ///
852 /// # Examples
853 ///
854 /// ```rust
855 /// use up_rust::UUri;
856 ///
857 /// let uri = UUri {
858 /// resource_id: 0x4001,
859 /// ..Default::default()
860 /// };
861 /// assert!(uri.verify_rpc_response().is_err());
862 /// ```
863 pub fn verify_rpc_response(&self) -> Result<(), UUriError> {
864 if !self.is_rpc_response() {
865 Err(UUriError::validation_error(format!(
866 "Resource ID must be {RESOURCE_ID_RESPONSE:#X}"
867 )))
868 } else {
869 self.verify_no_wildcards()
870 }
871 }
872
873 /// Checks if this UUri can be used as the source of an event.
874 ///
875 /// Returns `true` if resource ID >= 0x8000.
876 ///
877 /// # Examples
878 ///
879 /// ```rust
880 /// use up_rust::UUri;
881 ///
882 /// let uri = UUri {
883 /// resource_id: 0x8000,
884 /// ..Default::default()
885 /// };
886 /// assert!(uri.is_event());
887 /// ```
888 pub fn is_event(&self) -> bool {
889 self.resource_id >= RESOURCE_ID_MIN_EVENT
890 }
891
892 /// Verifies that this UUri can be used as the source of an event.
893 ///
894 /// # Errors
895 ///
896 /// Returns an error if [`Self::is_event`] fails or
897 /// the UUri [contains any wildcards](Self::verify_no_wildcards).
898 ///
899 /// # Examples
900 ///
901 /// ```rust
902 /// use up_rust::UUri;
903 ///
904 /// let uri = UUri {
905 /// resource_id: 0x7FFF,
906 /// ..Default::default()
907 /// };
908 /// assert!(uri.verify_event().is_err());
909 /// ```
910 pub fn verify_event(&self) -> Result<(), UUriError> {
911 if !self.is_event() {
912 Err(UUriError::validation_error(format!(
913 "Resource ID must be >= {RESOURCE_ID_MIN_EVENT:#X}"
914 )))
915 } else {
916 self.verify_no_wildcards()
917 }
918 }
919
920 fn matches_authority(&self, candidate: &UUri) -> bool {
921 self.has_wildcard_authority() || self.authority_name == candidate.authority_name
922 }
923
924 fn matches_entity_type(&self, candidate: &UUri) -> bool {
925 self.has_wildcard_entity_type() || self.uentity_type_id() == candidate.uentity_type_id()
926 }
927
928 fn matches_entity_instance(&self, candidate: &UUri) -> bool {
929 self.has_wildcard_entity_instance()
930 || self.uentity_instance_id() == candidate.uentity_instance_id()
931 }
932
933 fn matches_entity_version(&self, candidate: &UUri) -> bool {
934 self.has_wildcard_version()
935 || self.uentity_major_version() == candidate.uentity_major_version()
936 }
937
938 fn matches_entity(&self, candidate: &UUri) -> bool {
939 self.matches_entity_type(candidate)
940 && self.matches_entity_instance(candidate)
941 && self.matches_entity_version(candidate)
942 }
943
944 fn matches_resource(&self, candidate: &UUri) -> bool {
945 self.has_wildcard_resource_id() || self.resource_id == candidate.resource_id
946 }
947
948 /// Checks if a given candidate URI matches a pattern.
949 ///
950 /// # Returns
951 ///
952 /// `true` if the candiadate matches the pattern represented by this UUri.
953 ///
954 /// # Examples
955 ///
956 /// ```rust
957 /// use up_rust::UUri;
958 ///
959 /// let pattern = UUri::try_from("//vin/A14F/3/FFFF").unwrap();
960 /// let candidate = UUri::try_from("//vin/A14F/3/B1D4").unwrap();
961 /// assert!(pattern.matches(&candidate));
962 /// ```
963 // [impl->dsn~uri-pattern-matching~2]
964 pub fn matches(&self, candidate: &UUri) -> bool {
965 self.matches_authority(candidate)
966 && self.matches_entity(candidate)
967 && self.matches_resource(candidate)
968 }
969}
970
971#[cfg(test)]
972mod tests {
973 use super::*;
974 use test_case::test_case;
975
976 // [utest->dsn~uri-authority-name-length~1]
977 // [utest->dsn~uri-host-only~2]
978 #[test_case(UUri {
979 authority_name: "invalid:5671".into(),
980 ue_id: 0x0000_8000,
981 ue_version_major: 0x01,
982 resource_id: 0x0002,
983 ..Default::default()
984 };
985 "for authority including port")]
986 #[test_case(UUri {
987 authority_name: ['a'; 129].iter().collect::<String>(),
988 ue_id: 0x0000_8000,
989 ue_version_major: 0x01,
990 resource_id: 0x0002,
991 ..Default::default()
992 };
993 "for authority exceeding max length")]
994 // additional test cases covering all sorts of invalid authority are
995 // included in [`test_from_string_fails`]
996 #[test_case(UUri {
997 authority_name: "valid".into(),
998 ue_id: 0x0000_8000,
999 ue_version_major: 0x0101,
1000 resource_id: 0x0002,
1001 ..Default::default()
1002 };
1003 "for invalid major version")]
1004 #[test_case(UUri {
1005 authority_name: "valid".into(),
1006 ue_id: 0x0000_8000,
1007 ue_version_major: 0x01,
1008 resource_id: 0x10002,
1009 ..Default::default()
1010 };
1011 "for invalid resource ID")]
1012 fn test_check_validity_fails(uuri: UUri) {
1013 assert!(uuri.check_validity().is_err());
1014 }
1015
1016 #[test_case("//*/A100/1/1"; "for any authority")]
1017 #[test_case("//vin/FFFF/1/1"; "for any entity type")]
1018 #[test_case("//vin/FFFF0ABC/1/1"; "for any entity instance")]
1019 #[test_case("//vin/A100/FF/1"; "for any version")]
1020 #[test_case("//vin/A100/1/FFFF"; "for any resource")]
1021 fn test_verify_no_wildcards_fails(uri: &str) {
1022 let uuri = UUri::try_from(uri).expect("should have been able to deserialize URI");
1023 assert!(uuri.verify_no_wildcards().is_err());
1024 }
1025
1026 // [utest->dsn~uri-authority-name-length~1]
1027 #[test]
1028 fn test_from_str_fails_for_authority_exceeding_max_length() {
1029 let host_name = "a".repeat(129);
1030 let uri = format!("//{}/A100/1/6501", host_name);
1031 assert!(UUri::from_str(&uri).is_err());
1032 }
1033
1034 // [utest->dsn~uri-path-mapping~2]
1035 #[test]
1036 fn test_from_str_accepts_lowercase_hex_encoding() {
1037 let result = UUri::try_from("up://vin/ffff0abc/a1/bcd1");
1038 assert!(result.is_ok_and(|uuri| {
1039 uuri.authority_name == "vin"
1040 && uuri.ue_id == 0xFFFF0ABC
1041 && uuri.ue_version_major == 0xA1
1042 && uuri.resource_id == 0xBCD1
1043 }));
1044 }
1045}