Skip to main content

provenant/models/
package_uid.rs

1use std::borrow::Borrow;
2use std::fmt;
3use std::ops::Deref;
4
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
9#[serde(transparent)]
10pub struct PackageUid(String);
11
12impl PackageUid {
13    /// Creates a new `PackageUid` by appending a UUID to the given purl.
14    pub fn new(purl: &str) -> Self {
15        let uuid = Uuid::new_v4();
16        if purl.contains('?') {
17            PackageUid(format!("{}&uuid={}", purl, uuid))
18        } else {
19            PackageUid(format!("{}?uuid={}", purl, uuid))
20        }
21    }
22
23    /// Wraps an existing UID string without validation or UUID generation.
24    ///
25    /// Use this for deserialization boundaries and round-trip conversions
26    /// where the UID string is already well-formed.
27    pub fn from_raw(s: String) -> Self {
28        PackageUid(s)
29    }
30
31    /// Returns the empty-string sentinel representing "no purl".
32    pub fn empty() -> Self {
33        PackageUid(String::new())
34    }
35
36    /// Returns the purl portion by stripping the UUID suffix.
37    pub fn stable_key(&self) -> &str {
38        self.0
39            .split_once("?uuid=")
40            .map(|(prefix, _)| prefix)
41            .or_else(|| self.0.split_once("&uuid=").map(|(prefix, _)| prefix))
42            .unwrap_or(&self.0)
43    }
44
45    /// Returns a new `PackageUid` with the purl base replaced, preserving the UUID suffix.
46    pub fn replace_base(&self, new_purl: &str) -> Self {
47        if let Some((_, suffix)) = self.0.split_once("?uuid=") {
48            return PackageUid(format!("{}?uuid={}", new_purl, suffix));
49        }
50        if let Some((_, suffix)) = self.0.split_once("&uuid=") {
51            let separator = if new_purl.contains('?') { '&' } else { '?' };
52            return PackageUid(format!("{}{separator}uuid={suffix}", new_purl));
53        }
54        PackageUid(self.0.clone())
55    }
56
57    /// Returns the inner string slice.
58    pub fn as_str(&self) -> &str {
59        &self.0
60    }
61
62    /// Returns `true` if this is the empty-string sentinel.
63    pub fn is_empty(&self) -> bool {
64        self.0.is_empty()
65    }
66}
67
68impl fmt::Display for PackageUid {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        self.0.fmt(f)
71    }
72}
73
74impl AsRef<str> for PackageUid {
75    fn as_ref(&self) -> &str {
76        &self.0
77    }
78}
79
80impl Borrow<str> for PackageUid {
81    fn borrow(&self) -> &str {
82        &self.0
83    }
84}
85
86impl Deref for PackageUid {
87    type Target = str;
88
89    fn deref(&self) -> &Self::Target {
90        &self.0
91    }
92}