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    /// Verifies that the given authority name complies with the UUri specification.
461    ///
462    /// # Arguments
463    ///
464    /// * `authority` - The authority name to verify.
465    ///
466    /// # Errors
467    ///
468    /// Returns an error if the authority name is invalid.
469    ///
470    /// # Examples
471    ///
472    /// ```rust
473    /// use up_rust::UUri;
474    ///
475    /// assert!(UUri::verify_authority("my.vin").is_ok());
476    /// ```
477    // [impl->dsn~uri-authority-name-length~1]
478    // [impl->dsn~uri-host-only~2]
479    pub fn verify_authority(authority: &str) -> Result<String, UUriError> {
480        Authority::try_from(authority)
481            .map_err(|e| UUriError::validation_error(format!("invalid authority: {e}")))
482            .and_then(|auth| Self::verify_parsed_authority(&auth))
483    }
484
485    // [impl->dsn~uri-authority-name-length~1]
486    // [impl->dsn~uri-host-only~2]
487    pub(crate) fn verify_parsed_authority(auth: &Authority) -> Result<String, UUriError> {
488        if auth.has_port() {
489            Err(UUriError::validation_error(
490                "uProtocol URI's authority must not contain port",
491            ))
492        } else if auth.has_username() || auth.has_password() {
493            Err(UUriError::validation_error(
494                "uProtocol URI's authority must not contain userinfo",
495            ))
496        } else {
497            match auth.host() {
498                uriparse::Host::IPv4Address(_) | uriparse::Host::IPv6Address(_) => {
499                    Ok(auth.host().to_string())
500                }
501                uriparse::Host::RegisteredName(name) => {
502                    if !WILDCARD_AUTHORITY.eq(name.as_str())
503                        && !AUTHORITY_NAME_PATTERN.is_match(name.as_str())
504                    {
505                        return Err(UUriError::validation_error(
506                            "uProtocol URI's authority contains invalid characters",
507                        ));
508                    }
509                    Ok(name.to_string())
510                }
511            }
512        }
513    }
514
515    fn verify_major_version(major_version: u32) -> Result<u8, UUriError> {
516        u8::try_from(major_version).map_err(|_e| {
517            UUriError::ValidationError(
518                "uProtocol URI's major version must be an 8 bit unsigned integer".to_string(),
519            )
520        })
521    }
522
523    fn verify_resource_id(resource_id: u32) -> Result<u16, UUriError> {
524        u16::try_from(resource_id).map_err(|_e| {
525            UUriError::ValidationError(
526                "uProtocol URI's resource ID must be a 16 bit unsigned integer".to_string(),
527            )
528        })
529    }
530
531    /// Verifies that this UUri is indeed a valid uProtocol URI.
532    ///
533    /// This check is not necessary, if any of UUri's constructors functions has been used
534    /// to create the URI. However, if the origin of a UUri is unknown, e.g. when it has
535    /// been deserialized from a protobuf, then this function can be used to check if all
536    /// properties are compliant with the uProtocol specification.
537    ///
538    /// # Errors
539    ///
540    /// Returns an error if this UUri is not a valid uProtocol URI. The returned error may
541    /// contain details regarding the cause of the validation to have failed.
542    ///
543    /// # Examples
544    ///
545    /// ```rust
546    /// use up_rust::UUri;
547    ///
548    /// let uuri = UUri {
549    ///   authority_name: "valid_name".into(),
550    ///   ue_id: 0x1000,
551    ///   ue_version_major: 0x01,
552    ///   resource_id: 0x8100,
553    ///   ..Default::default()
554    /// };
555    /// assert!(uuri.check_validity().is_ok());
556    /// ```
557    pub fn check_validity(&self) -> Result<(), UUriError> {
558        Self::verify_authority(self.authority_name.as_str())?;
559        Self::verify_major_version(self.ue_version_major)?;
560        Self::verify_resource_id(self.resource_id)?;
561        Ok(())
562    }
563
564    /// Checks if this URI is empty.
565    ///
566    /// # Returns
567    ///
568    /// 'true' if this URI is equal to `UUri::default()`, `false` otherwise.
569    ///
570    /// # Examples
571    ///
572    /// ```rust
573    /// use up_rust::UUri;
574    ///
575    /// let uuri = UUri::try_from_parts("myvin", 0xa13b, 0x01, 0x7f4e).unwrap();
576    /// assert!(!uuri.is_empty());
577    /// assert!(UUri::default().is_empty());
578    /// ```
579    pub fn is_empty(&self) -> bool {
580        self.eq(&UUri::default())
581    }
582
583    /// Check if an `UUri` is remote, by comparing authority fields.
584    /// UUris with empty authority are considered to be local.
585    ///
586    /// # Returns
587    ///
588    /// 'true' if other_uri has a different authority than `Self`, `false` otherwise.
589    ///
590    /// # Examples
591    ///
592    /// ```rust
593    /// use std::str::FromStr;
594    /// use up_rust::UUri;
595    ///
596    /// let authority_a = UUri::from_str("up://authority.a/100A/1/0").unwrap();
597    /// let authority_b = UUri::from_str("up://authority.b/200B/2/20").unwrap();
598    /// assert!(authority_a.is_remote(&authority_b));
599    ///
600    /// let authority_local = UUri::from_str("up:///100A/1/0").unwrap();
601    /// assert!(!authority_local.is_remote(&authority_a));
602    ///
603    /// let authority_wildcard = UUri::from_str("up://*/100A/1/0").unwrap();
604    /// assert!(!authority_wildcard.is_remote(&authority_a));
605    /// assert!(!authority_a.is_remote(&authority_wildcard));
606    /// assert!(!authority_wildcard.is_remote(&authority_wildcard));
607    /// ````
608    pub fn is_remote(&self, other_uri: &UUri) -> bool {
609        self.is_remote_authority(&other_uri.authority_name)
610    }
611
612    /// Check if an authority is remote compared to the authority field of the UUri.
613    /// Empty authorities are considered to be local.
614    ///
615    /// # Returns
616    ///
617    /// 'true' if authority is a different than `Self.authority_name`, `false` otherwise.
618    ///
619    /// # Examples
620    ///
621    /// ```rust
622    /// use std::str::FromStr;
623    /// use up_rust::UUri;
624    ///
625    /// let authority_a = UUri::from_str("up://authority.a/100A/1/0").unwrap();
626    /// let authority_b = "authority.b".to_string();
627    /// assert!(authority_a.is_remote_authority(&authority_b));
628    ///
629    /// let authority_local = "".to_string();
630    /// assert!(!authority_a.is_remote_authority(&authority_local));
631    ///
632    /// let authority_wildcard = "*".to_string();
633    /// assert!(!authority_a.is_remote_authority(&authority_wildcard));
634    /// ```
635    pub fn is_remote_authority(&self, authority: &String) -> bool {
636        !authority.is_empty()
637            && !self.authority_name.is_empty()
638            && !self.has_wildcard_authority()
639            && authority != WILDCARD_AUTHORITY
640            && self.authority_name != *authority
641    }
642
643    /// Checks if this UUri has an empty authority name.
644    ///
645    /// # Examples
646    ///
647    /// ```rust
648    /// use up_rust::UUri;
649    ///
650    /// let uuri = UUri::try_from_parts("", 0x9b3a, 0x01, 0x145b).unwrap();
651    /// assert!(uuri.has_empty_authority());
652    /// ```
653    pub fn has_empty_authority(&self) -> bool {
654        self.authority_name.is_empty()
655    }
656
657    /// Checks if this UUri has a wildcard authority name.
658    ///
659    /// # Examples
660    ///
661    /// ```rust
662    /// use up_rust::UUri;
663    ///
664    /// let uuri = UUri::try_from_parts("*", 0x9b3a, 0x01, 0x145b).unwrap();
665    /// assert!(uuri.has_wildcard_authority());
666    /// ```
667    pub fn has_wildcard_authority(&self) -> bool {
668        self.authority_name == WILDCARD_AUTHORITY
669    }
670
671    /// Checks if this UUri has an entity identifier matching any instance.
672    ///
673    /// # Examples
674    ///
675    /// ```rust
676    /// use up_rust::UUri;
677    ///
678    /// let uuri = UUri::try_from_parts("vin", 0xFFFF_0123, 0x01, 0x145b).unwrap();
679    /// assert!(uuri.has_wildcard_entity_instance());
680    /// ```
681    pub fn has_wildcard_entity_instance(&self) -> bool {
682        self.ue_id & WILDCARD_ENTITY_INSTANCE == WILDCARD_ENTITY_INSTANCE
683    }
684
685    /// Checks if this UUri has an entity identifier matching any type.
686    ///
687    /// # Examples
688    ///
689    /// ```rust
690    /// use up_rust::UUri;
691    ///
692    /// let uuri = UUri::try_from_parts("vin", 0x00C0_FFFF, 0x01, 0x145b).unwrap();
693    /// assert!(uuri.has_wildcard_entity_type());
694    /// ```
695    pub fn has_wildcard_entity_type(&self) -> bool {
696        self.ue_id & WILDCARD_ENTITY_TYPE == WILDCARD_ENTITY_TYPE
697    }
698
699    /// Checks if this UUri has a wildcard major version.
700    ///
701    /// # Examples
702    ///
703    /// ```rust
704    /// use up_rust::UUri;
705    ///
706    /// let uuri = UUri::try_from_parts("vin", 0x9b3a, 0xFF, 0x145b).unwrap();
707    /// assert!(uuri.has_wildcard_version());
708    /// ```
709    pub fn has_wildcard_version(&self) -> bool {
710        self.ue_version_major == WILDCARD_ENTITY_VERSION
711    }
712
713    /// Checks if this UUri has a wildcard entity identifier.
714    ///
715    /// # Examples
716    ///
717    /// ```rust
718    /// use up_rust::UUri;
719    ///
720    /// let uuri = UUri::try_from_parts("vin", 0x9b3a, 0x01, 0xFFFF).unwrap();
721    /// assert!(uuri.has_wildcard_resource_id());
722    /// ```
723    pub fn has_wildcard_resource_id(&self) -> bool {
724        self.resource_id == WILDCARD_RESOURCE_ID
725    }
726
727    /// Verifies that this UUri does not contain any wildcards.
728    ///
729    /// # Errors
730    ///
731    /// Returns an error if any of this UUri's properties contain a wildcard value.
732    ///
733    /// # Examples
734    ///
735    /// ```rust
736    /// use up_rust::UUri;
737    ///
738    /// let uri = UUri {
739    ///     authority_name: String::from("VIN.vehicles"),
740    ///     ue_id: 0x0000_2310,
741    ///     ue_version_major: 0x03,
742    ///     resource_id: 0xa000,
743    ///     ..Default::default()
744    /// };
745    /// assert!(uri.verify_no_wildcards().is_ok());
746    /// ```
747    pub fn verify_no_wildcards(&self) -> Result<(), UUriError> {
748        if self.has_wildcard_authority() {
749            Err(UUriError::validation_error(format!(
750                "Authority must not contain wildcard character [{WILDCARD_AUTHORITY}]"
751            )))
752        } else if self.has_wildcard_entity_instance() {
753            Err(UUriError::validation_error(format!(
754                "Entity instance ID must not be set to wildcard value [{WILDCARD_ENTITY_INSTANCE:#X}]")))
755        } else if self.has_wildcard_entity_type() {
756            Err(UUriError::validation_error(format!(
757                "Entity type ID must not be set to wildcard value [{WILDCARD_ENTITY_TYPE:#X}]"
758            )))
759        } else if self.has_wildcard_version() {
760            Err(UUriError::validation_error(format!(
761                "Entity version must not be set to wildcard value [{WILDCARD_ENTITY_VERSION:#X}]"
762            )))
763        } else if self.has_wildcard_resource_id() {
764            Err(UUriError::validation_error(format!(
765                "Resource ID must not be set to wildcard value [{WILDCARD_RESOURCE_ID:#X}]"
766            )))
767        } else {
768            Ok(())
769        }
770    }
771
772    /// Checks if this UUri refers to a service method.
773    ///
774    /// Returns `true` if 0 < resource ID < 0x8000.
775    ///
776    /// # Examples
777    ///
778    /// ```rust
779    /// use up_rust::UUri;
780    ///
781    /// let uri = UUri {
782    ///     resource_id: 0x7FFF,
783    ///     ..Default::default()
784    /// };
785    /// assert!(uri.is_rpc_method());
786    /// ```
787    pub fn is_rpc_method(&self) -> bool {
788        self.resource_id > RESOURCE_ID_RESPONSE && self.resource_id < RESOURCE_ID_MIN_EVENT
789    }
790
791    /// Verifies that this UUri refers to a service method.
792    ///
793    /// # Errors
794    ///
795    /// Returns an error if [`Self::is_rpc_method`] fails or
796    /// the UUri [contains any wildcards](Self::verify_no_wildcards).
797    ///
798    /// # Examples
799    ///
800    /// ```rust
801    /// use up_rust::UUri;
802    ///
803    /// let uri = UUri {
804    ///     resource_id: 0x8000,
805    ///     ..Default::default()
806    /// };
807    /// assert!(uri.verify_rpc_method().is_err());
808    ///
809    /// let uri = UUri {
810    ///     resource_id: 0x0,
811    ///     ..Default::default()
812    /// };
813    /// assert!(uri.verify_rpc_method().is_err());
814    /// ```
815    pub fn verify_rpc_method(&self) -> Result<(), UUriError> {
816        if !self.is_rpc_method() {
817            Err(UUriError::validation_error(format!(
818                "Resource ID must be a value from ]{RESOURCE_ID_RESPONSE:#X}, {RESOURCE_ID_MIN_EVENT:#X}[")))
819        } else {
820            self.verify_no_wildcards()
821        }
822    }
823
824    /// Checks if this UUri represents a destination for a Notification.
825    ///
826    /// Returns `true` if resource ID is 0.
827    ///
828    /// # Examples
829    ///
830    /// ```rust
831    /// use up_rust::UUri;
832    ///
833    /// let uri = UUri {
834    ///     resource_id: 0,
835    ///     ..Default::default()
836    /// };
837    /// assert!(uri.is_notification_destination());
838    /// ```
839    pub fn is_notification_destination(&self) -> bool {
840        self.resource_id == RESOURCE_ID_RESPONSE
841    }
842
843    /// Checks if this UUri represents an RPC response address.
844    ///
845    /// Returns `true` if resource ID is 0.
846    ///
847    /// # Examples
848    ///
849    /// ```rust
850    /// use up_rust::UUri;
851    ///
852    /// let uri = UUri {
853    ///     resource_id: 0,
854    ///     ..Default::default()
855    /// };
856    /// assert!(uri.is_rpc_response());
857    /// ```
858    pub fn is_rpc_response(&self) -> bool {
859        self.resource_id == RESOURCE_ID_RESPONSE
860    }
861
862    /// Verifies that this UUri represents an RPC response address.
863    ///
864    /// # Errors
865    ///
866    /// Returns an error if [`Self::is_rpc_response`] fails or
867    /// the UUri [contains any wildcards](Self::verify_no_wildcards).
868    ///
869    /// # Examples
870    ///
871    /// ```rust
872    /// use up_rust::UUri;
873    ///
874    /// let uri = UUri {
875    ///     resource_id: 0x4001,
876    ///     ..Default::default()
877    /// };
878    /// assert!(uri.verify_rpc_response().is_err());
879    /// ```
880    pub fn verify_rpc_response(&self) -> Result<(), UUriError> {
881        if !self.is_rpc_response() {
882            Err(UUriError::validation_error(format!(
883                "Resource ID must be {RESOURCE_ID_RESPONSE:#X}"
884            )))
885        } else {
886            self.verify_no_wildcards()
887        }
888    }
889
890    /// Checks if this UUri can be used as the source of an event.
891    ///
892    /// Returns `true` if resource ID >= 0x8000.
893    ///
894    /// # Examples
895    ///
896    /// ```rust
897    /// use up_rust::UUri;
898    ///
899    /// let uri = UUri {
900    ///     resource_id: 0x8000,
901    ///     ..Default::default()
902    /// };
903    /// assert!(uri.is_event());
904    /// ```
905    pub fn is_event(&self) -> bool {
906        self.resource_id >= RESOURCE_ID_MIN_EVENT
907    }
908
909    /// Verifies that this UUri can be used as the source of an event.
910    ///
911    /// # Errors
912    ///
913    /// Returns an error if [`Self::is_event`] fails or
914    /// the UUri [contains any wildcards](Self::verify_no_wildcards).
915    ///
916    /// # Examples
917    ///
918    /// ```rust
919    /// use up_rust::UUri;
920    ///
921    /// let uri = UUri {
922    ///     resource_id: 0x7FFF,
923    ///     ..Default::default()
924    /// };
925    /// assert!(uri.verify_event().is_err());
926    /// ```
927    pub fn verify_event(&self) -> Result<(), UUriError> {
928        if !self.is_event() {
929            Err(UUriError::validation_error(format!(
930                "Resource ID must be >= {RESOURCE_ID_MIN_EVENT:#X}"
931            )))
932        } else {
933            self.verify_no_wildcards()
934        }
935    }
936
937    fn matches_authority(&self, candidate: &UUri) -> bool {
938        self.has_wildcard_authority() || self.authority_name == candidate.authority_name
939    }
940
941    fn matches_entity_type(&self, candidate: &UUri) -> bool {
942        self.has_wildcard_entity_type() || self.uentity_type_id() == candidate.uentity_type_id()
943    }
944
945    fn matches_entity_instance(&self, candidate: &UUri) -> bool {
946        self.has_wildcard_entity_instance()
947            || self.uentity_instance_id() == candidate.uentity_instance_id()
948    }
949
950    fn matches_entity_version(&self, candidate: &UUri) -> bool {
951        self.has_wildcard_version()
952            || self.uentity_major_version() == candidate.uentity_major_version()
953    }
954
955    fn matches_entity(&self, candidate: &UUri) -> bool {
956        self.matches_entity_type(candidate)
957            && self.matches_entity_instance(candidate)
958            && self.matches_entity_version(candidate)
959    }
960
961    fn matches_resource(&self, candidate: &UUri) -> bool {
962        self.has_wildcard_resource_id() || self.resource_id == candidate.resource_id
963    }
964
965    /// Checks if a given candidate URI matches a pattern.
966    ///
967    /// # Returns
968    ///
969    /// `true` if the candiadate matches the pattern represented by this UUri.
970    ///
971    /// # Examples
972    ///
973    /// ```rust
974    /// use up_rust::UUri;
975    ///
976    /// let pattern = UUri::try_from("//vin/A14F/3/FFFF").unwrap();
977    /// let candidate = UUri::try_from("//vin/A14F/3/B1D4").unwrap();
978    /// assert!(pattern.matches(&candidate));
979    /// ```
980    // [impl->dsn~uri-pattern-matching~2]
981    pub fn matches(&self, candidate: &UUri) -> bool {
982        self.matches_authority(candidate)
983            && self.matches_entity(candidate)
984            && self.matches_resource(candidate)
985    }
986}
987
988#[cfg(test)]
989mod tests {
990    use super::*;
991    use test_case::test_case;
992
993    // [utest->dsn~uri-authority-name-length~1]
994    // [utest->dsn~uri-host-only~2]
995    #[test_case(UUri {
996            authority_name: "invalid:5671".into(),
997            ue_id: 0x0000_8000,
998            ue_version_major: 0x01,
999            resource_id: 0x0002,
1000            ..Default::default()
1001        };
1002        "for authority including port")]
1003    #[test_case(UUri {
1004            authority_name: ['a'; 129].iter().collect::<String>(),
1005            ue_id: 0x0000_8000,
1006            ue_version_major: 0x01,
1007            resource_id: 0x0002,
1008            ..Default::default()
1009        };
1010        "for authority exceeding max length")]
1011    // additional test cases covering all sorts of invalid authority are
1012    // included in [`test_from_string_fails`]
1013    #[test_case(UUri {
1014            authority_name: "valid".into(),
1015            ue_id: 0x0000_8000,
1016            ue_version_major: 0x0101,
1017            resource_id: 0x0002,
1018            ..Default::default()
1019        };
1020        "for invalid major version")]
1021    #[test_case(UUri {
1022            authority_name: "valid".into(),
1023            ue_id: 0x0000_8000,
1024            ue_version_major: 0x01,
1025            resource_id: 0x10002,
1026            ..Default::default()
1027        };
1028        "for invalid resource ID")]
1029    fn test_check_validity_fails(uuri: UUri) {
1030        assert!(uuri.check_validity().is_err());
1031    }
1032
1033    #[test_case("//*/A100/1/1"; "for any authority")]
1034    #[test_case("//vin/FFFF/1/1"; "for any entity type")]
1035    #[test_case("//vin/FFFF0ABC/1/1"; "for any entity instance")]
1036    #[test_case("//vin/A100/FF/1"; "for any version")]
1037    #[test_case("//vin/A100/1/FFFF"; "for any resource")]
1038    fn test_verify_no_wildcards_fails(uri: &str) {
1039        let uuri = UUri::try_from(uri).expect("should have been able to deserialize URI");
1040        assert!(uuri.verify_no_wildcards().is_err());
1041    }
1042
1043    // [utest->dsn~uri-authority-name-length~1]
1044    #[test]
1045    fn test_from_str_fails_for_authority_exceeding_max_length() {
1046        let host_name = "a".repeat(129);
1047        let uri = format!("//{}/A100/1/6501", host_name);
1048        assert!(UUri::from_str(&uri).is_err());
1049    }
1050
1051    // [utest->dsn~uri-path-mapping~2]
1052    #[test]
1053    fn test_from_str_accepts_lowercase_hex_encoding() {
1054        let result = UUri::try_from("up://vin/ffff0abc/a1/bcd1");
1055        assert!(result.is_ok_and(|uuri| {
1056            uuri.authority_name == "vin"
1057                && uuri.ue_id == 0xFFFF0ABC
1058                && uuri.ue_version_major == 0xA1
1059                && uuri.resource_id == 0xBCD1
1060        }));
1061    }
1062}