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