Skip to main content

cxx_qt_lib/core/
quuid.rs

1// SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Joshua Booth <joshua.n.booth@gmail.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5use crate::{QByteArray, QString};
6use cxx::{type_id, ExternType};
7use std::{fmt, mem};
8#[cfg(feature = "uuid")]
9use uuid::Uuid;
10
11#[cxx::bridge]
12mod ffi {
13    /// This enum defines the values used in the variant field of the UUID. The value in the variant field determines the layout of the 128-bit value.
14    #[namespace = "rust::cxxqtlib1"]
15    #[repr(i32)]
16    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
17    enum QUuidVariant {
18        /// Variant is unknown
19        VarUnknown = -1,
20        /// Reserved for NCS (Network Computing System) backward compatibility
21        NCS = 0,
22        /// Distributed Computing Environment, the scheme used by [`QUuid`](super::QUuid)
23        DCE = 2,
24        /// Reserved for Microsoft backward compatibility (GUID)
25        Microsoft = 6,
26        /// Reserved for future definition
27        Reserved = 7,
28    }
29
30    /// This enum defines the values used in the version field of the UUID. The version field is meaningful only if the value in the variant field is [`QUuidVariant::DCE`].
31    #[namespace = "rust::cxxqtlib1"]
32    #[repr(i32)]
33    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
34    enum QUuidVersion {
35        /// Version is unknown
36        VerUnknown = -1,
37        /// Time-based, by using timestamp, clock sequence, and MAC network card address (if
38        /// available) for the node sections
39        Time = 1,
40        /// DCE Security version, with embedded POSIX UUIDs
41        EmbeddedPOSIX = 2,
42        /// Name-based, by using values from a name for all sections
43        Md5 = 3,
44        /// Random-based, by using random numbers for all sections
45        Random = 4,
46        Sha1 = 5,
47    }
48
49    extern "C++" {
50        include!("cxx-qt-lib/qbytearray.h");
51        type QByteArray = crate::QByteArray;
52        include!("cxx-qt-lib/qstring.h");
53        type QString = crate::QString;
54    }
55
56    #[namespace = "rust::cxxqtlib1"]
57    extern "C++" {
58        include!("cxx-qt-lib/quuid.h");
59        type QUuidVariant;
60        type QUuidVersion;
61    }
62
63    unsafe extern "C++" {
64        type QUuid = super::QUuid;
65
66        /// On any platform other than Windows, this function returns a new UUID with variant
67        /// [`QUuidVariant::DCE`] and version [`QUuidVersion::Random`]. On Windows, a GUID is generated using
68        /// the Windows API and will be of the type that the API decides to create.
69        #[Self = "QUuid"]
70        #[rust_name = "create_uuid"]
71        fn createUuid() -> QUuid;
72
73        /// Returns the binary representation of this UUID. The byte array is in big endian format,
74        /// and formatted according to RFC 4122, section 4.1.2 - "Layout and byte order".
75        #[rust_name = "to_rfc_4122"]
76        fn toRfc4122(self: &QUuid) -> QByteArray;
77    }
78
79    #[namespace = "rust::cxxqtlib1"]
80    unsafe extern "C++" {
81        #[doc(hidden)]
82        #[rust_name = "quuid_create_uuid_v3"]
83        fn quuidCreateUuidV3(ns: &QUuid, data: &[u8]) -> QUuid;
84        #[doc(hidden)]
85        #[rust_name = "quuid_create_uuid_v5"]
86        fn quuidCreateUuidV5(ns: &QUuid, data: &[u8]) -> QUuid;
87        #[doc(hidden)]
88        #[rust_name = "quuid_to_string"]
89        fn quuidToString(uuid: &QUuid) -> QString;
90        #[doc(hidden)]
91        #[rust_name = "quuid_from_string"]
92        fn quuidFromString(string: &QString) -> QUuid;
93        #[doc(hidden)]
94        #[rust_name = "quuid_from_str"]
95        fn quuidFromStr(string: &str) -> QUuid;
96        #[doc(hidden)]
97        #[rust_name = "quuid_from_rfc_4122"]
98        fn quuidFromRfc4122(bytes: &QByteArray) -> QUuid;
99
100        // Starting with Qt 6.9, Qt added an overload for QUuid::variant and QUuid::version.
101        // This breaks the signature expected by CXX.
102        // We therefore need to use these wrapper functions.
103        #[doc(hidden)]
104        #[rust_name = "quuid_variant"]
105        fn quuidVariant(quuid: &QUuid) -> QUuidVariant;
106        #[doc(hidden)]
107        #[rust_name = "quuid_version"]
108        fn quuidVersion(quuid: &QUuid) -> QUuidVersion;
109
110    }
111}
112
113pub use ffi::{QUuidVariant, QUuidVersion};
114
115/// The `QUuid` class stores a Universally Unique Identifier (UUID).
116///
117/// Qt Documentation: [QUuid](https://doc.qt.io/qt/quuid.html#details)
118#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
119#[repr(C)]
120pub struct QUuid {
121    data1: u32,
122    data2: u16,
123    data3: u16,
124    data4: [u8; 8],
125}
126
127impl Default for QUuid {
128    /// Creates the null UUID. `to_string()` will output the null UUID as
129    /// "{00000000-0000-0000-0000-000000000000}".
130    fn default() -> Self {
131        Self::null()
132    }
133}
134
135impl fmt::Display for QUuid {
136    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137        ffi::quuid_to_string(self).fmt(f)
138    }
139}
140
141impl fmt::Debug for QUuid {
142    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143        ffi::quuid_to_string(self).fmt(f)
144    }
145}
146
147impl QUuid {
148    /// Creates the null UUID. `to_string()` will output the null UUID as
149    /// "{00000000-0000-0000-0000-000000000000}".
150    pub const fn null() -> Self {
151        Self {
152            data1: 0,
153            data2: 0,
154            data3: 0,
155            data4: [0; 8],
156        }
157    }
158
159    /// Returns `true` if this is the null UUID {00000000-0000-0000-0000-000000000000};
160    /// otherwise returns `false`.
161    pub const fn is_null(&self) -> bool {
162        (unsafe { std::mem::transmute::<QUuid, u128>(*self) }) == 0
163    }
164
165    /// This function returns a new UUID with variant [`QUuidVariant::DCE`] and version
166    /// [`QUuidVersion::Md5`]. `namespace` is the namespace and `data` is the basic data as described
167    /// by RFC 4122.
168    pub fn create_uuid_v3(namespace: &Self, data: &[u8]) -> Self {
169        ffi::quuid_create_uuid_v3(namespace, data)
170    }
171
172    /// This function returns a new UUID with variant [`QUuidVariant::DCE`] and version
173    /// [`QUuidVersion::Sha1`]. `namespace` is the namespace and `data` is the basic data as described
174    /// by RFC 4122.
175    pub fn create_uuid_v5(namespace: &Self, data: &[u8]) -> Self {
176        ffi::quuid_create_uuid_v5(namespace, data)
177    }
178
179    /// Creates a `QUuid` object from the binary representation of the UUID.
180    /// The byte array is in big endian format, and formatted according to RFC 4122, section 4.1.2 -
181    /// "Layout and byte order".
182    ///
183    /// The byte array accepted is NOT a human readable format.
184    ///
185    /// If the conversion fails, a null UUID is created.
186    pub fn from_rfc_4122(bytes: &QByteArray) -> Self {
187        ffi::quuid_from_rfc_4122(bytes)
188    }
189
190    /// Creates a UUID with the value specified by the parameters.
191    pub const fn from_fields(data1: u32, data2: u16, data3: u16, data4: [u8; 8]) -> Self {
192        Self {
193            data1,
194            data2,
195            data3,
196            data4,
197        }
198    }
199
200    pub const fn as_fields(&self) -> (u32, u16, u16, &[u8; 8]) {
201        (self.data1, self.data2, self.data3, &self.data4)
202    }
203
204    /// Creates a UUID from its representation as a byte array in big endian.
205    pub const fn from_bytes(bytes: [u8; 16]) -> Self {
206        // On big endian targets, this is a no-op.
207        // On little endian targets, it swaps the bytes of each integer field (data1, data2, data3).
208        unsafe { mem::transmute::<[u8; 16], Self>(bytes) }.to_be()
209    }
210
211    /// Returns the memory representation of this UUID as a byte array in big-endian byte order.
212    pub const fn to_bytes(self) -> [u8; 16] {
213        // On big endian targets, this is a no-op.
214        // On little endian targets, it swaps the bytes of each integer field (data1, data2, data3).
215        unsafe { mem::transmute::<Self, [u8; 16]>(self.to_be()) }
216    }
217
218    /// Creates a UUID from its representation as a 128-bit integer.
219    pub const fn from_u128(data: u128) -> Self {
220        Self::from_bytes(data.to_be_bytes())
221    }
222
223    /// Returns the memory representation of this UUID as a 128-bit integer.
224    pub const fn to_u128(&self) -> u128 {
225        u128::from_be_bytes(self.to_bytes())
226    }
227
228    /// Converts self to big endian from the target’s endianness.
229    /// This function is analogous to [`u8::to_be`](https://doc.rust-lang.org/src/core/num/uint_macros.rs.html#399-431).
230    ///
231    /// On big endian this is a no-op. On little endian the bytes are swapped.
232    ///
233    /// This is useful for converting between QUuids and byte arrays because byte array
234    /// representations of UUIDs are always in big endian mode.
235    #[must_use = "this returns the result of the operation, without modifying the original"]
236    const fn to_be(self) -> Self {
237        #[cfg(target_endian = "big")]
238        {
239            self
240        }
241        #[cfg(target_endian = "little")]
242        {
243            Self {
244                data1: self.data1.swap_bytes(),
245                data2: self.data2.swap_bytes(),
246                data3: self.data3.swap_bytes(),
247                data4: self.data4,
248            }
249        }
250    }
251
252    /// Returns the value in the variant field of the UUID. If the return value is
253    /// [`QUuidVariant::DCE`], call [`version`](Self::version) to see which layout it uses. The null UUID is
254    /// considered to be of an unknown variant.
255    pub fn variant(&self) -> QUuidVariant {
256        ffi::quuid_variant(self)
257    }
258
259    /// Returns the version field of the UUID, if the UUID's variant field is [`QUuidVariant::DCE`].
260    /// Otherwise it returns [`QUuidVersion::VerUnknown`].
261    pub fn version(&self) -> QUuidVersion {
262        ffi::quuid_version(self)
263    }
264}
265
266unsafe impl ExternType for QUuid {
267    type Id = type_id!("QUuid");
268    type Kind = cxx::kind::Trivial;
269}
270
271impl From<QUuid> for QString {
272    fn from(value: QUuid) -> Self {
273        ffi::quuid_to_string(&value)
274    }
275}
276
277impl From<u128> for QUuid {
278    fn from(value: u128) -> Self {
279        Self::from_u128(value)
280    }
281}
282
283impl From<QUuid> for u128 {
284    fn from(value: QUuid) -> Self {
285        value.to_u128()
286    }
287}
288
289impl From<&QString> for QUuid {
290    /// Creates a `QUuid` object from the string text, which must be formatted as five hex fields
291    /// separated by '-', e.g., "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" where each 'x' is a hex
292    /// digit. The curly braces shown here are optional, but it is normal to include them.
293    ///
294    /// If the conversion fails, a null UUID is returned.
295    fn from(value: &QString) -> Self {
296        ffi::quuid_from_string(value)
297    }
298}
299
300impl From<&str> for QUuid {
301    /// Creates a `QUuid` object from the string text, which must be formatted as five hex fields
302    /// separated by '-', e.g., "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" where each 'x' is a hex
303    /// digit. The curly braces shown here are optional, but it is normal to include them.
304    ///
305    /// If the conversion fails, a null UUID is returned.
306    fn from(value: &str) -> Self {
307        ffi::quuid_from_str(value)
308    }
309}
310
311impl From<&String> for QUuid {
312    /// Creates a `QUuid` object from the string text, which must be formatted as five hex fields
313    /// separated by '-', e.g., "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" where each 'x' is a hex
314    /// digit. The curly braces shown here are optional, but it is normal to include them.
315    ///
316    /// If the conversion fails, a null UUID is returned.
317    fn from(value: &String) -> Self {
318        ffi::quuid_from_str(value)
319    }
320}
321
322impl From<&QByteArray> for QUuid {
323    /// See [`QUuid::from_rfc_4122`].
324    fn from(value: &QByteArray) -> Self {
325        ffi::quuid_from_rfc_4122(value)
326    }
327}
328
329impl From<QUuid> for QByteArray {
330    /// See [`QUuid::to_rfc_4122`].
331    fn from(value: QUuid) -> Self {
332        value.to_rfc_4122()
333    }
334}
335
336#[cfg(feature = "uuid")]
337impl From<Uuid> for QUuid {
338    fn from(value: Uuid) -> Self {
339        let (data1, data2, data3, &data4) = value.as_fields();
340        Self {
341            data1,
342            data2,
343            data3,
344            data4,
345        }
346    }
347}
348
349#[cfg(feature = "uuid")]
350impl From<QUuid> for Uuid {
351    fn from(value: QUuid) -> Self {
352        Self::from_fields(value.data1, value.data2, value.data3, &value.data4)
353    }
354}
355
356#[cfg(test)]
357mod test {
358    use super::*;
359
360    const NAMESPACE_DNS: &QUuid = &QUuid::from_u128(0x6ba7b8109dad11d180b400c04fd430c);
361
362    #[test]
363    fn quuid_is_null() {
364        assert!(QUuid::null().is_null())
365    }
366
367    #[test]
368    fn quuid_is_not_null() {
369        assert!(!QUuid::create_uuid().is_null())
370    }
371
372    #[test]
373    fn quuid_variant() {
374        assert_eq!(
375            [QUuid::null().variant(), QUuid::create_uuid().variant()],
376            [QUuidVariant::VarUnknown, QUuidVariant::DCE]
377        );
378    }
379
380    #[test]
381    fn quuid_version() {
382        assert_eq!(
383            [
384                QUuid::null().version(),
385                QUuid::create_uuid_v3(NAMESPACE_DNS, &[]).version(),
386                QUuid::create_uuid().version(),
387                QUuid::create_uuid_v5(NAMESPACE_DNS, &[]).version(),
388            ],
389            [
390                QUuidVersion::VerUnknown,
391                QUuidVersion::Md5,
392                QUuidVersion::Random,
393                QUuidVersion::Sha1
394            ]
395        )
396    }
397
398    #[test]
399    fn quuid_to_rfc_4122() {
400        let bytes = <[u8; 16]>::try_from("random test data".as_bytes()).unwrap();
401        assert_eq!(Vec::from(&QUuid::from_bytes(bytes).to_rfc_4122()), bytes)
402    }
403
404    #[test]
405    fn quuid_null() {
406        assert_eq!(QUuid::null(), QUuid::from_u128(0));
407    }
408
409    #[test]
410    fn quuid_new_v3() {
411        assert_eq!(
412            QUuid::create_uuid_v3(NAMESPACE_DNS, "testdata".as_bytes()),
413            QUuid::from_u128(0x5157facac7e1345c927671c2c6d41e7a)
414        );
415    }
416
417    #[test]
418    fn quuid_new_v4() {
419        assert_ne!(QUuid::create_uuid(), QUuid::create_uuid());
420    }
421
422    #[test]
423    fn quuid_new_v5() {
424        assert_eq!(
425            QUuid::create_uuid_v5(NAMESPACE_DNS, "testdata".as_bytes()),
426            QUuid::from_u128(0x7e95e361a22c51c18c297ac24cb61e83)
427        );
428    }
429
430    #[test]
431    fn quuid_to_string() {
432        assert_eq!(
433            QUuid::from_u128(0x7e95e361a22c51c18c297ac24cb61e83).to_string(),
434            "{7e95e361-a22c-51c1-8c29-7ac24cb61e83}"
435        )
436    }
437
438    #[test]
439    fn quuid_qstring_round_trip() {
440        let uuid = QUuid::create_uuid();
441        let roundtrip = QUuid::from(&QString::from(&uuid.to_string()));
442        assert_eq!(uuid, roundtrip)
443    }
444
445    #[test]
446    fn quuid_str_round_trip() {
447        let uuid = QUuid::create_uuid();
448        let roundtrip = QUuid::from(&uuid.to_string());
449        assert_eq!(uuid, roundtrip)
450    }
451
452    #[test]
453    fn quuid_fields_round_trip() {
454        let uuid = QUuid::create_uuid();
455        let (d1, d2, d3, &d4) = uuid.as_fields();
456        let roundtrip = QUuid::from_fields(d1, d2, d3, d4);
457        assert_eq!(uuid, roundtrip)
458    }
459
460    #[test]
461    fn quuid_bytes_round_trip() {
462        let uuid = QUuid::create_uuid();
463        let roundtrip = QUuid::from_bytes(uuid.to_bytes());
464        assert_eq!(uuid, roundtrip)
465    }
466
467    #[test]
468    fn quuid_qbytearray_round_trip() {
469        let uuid = QUuid::create_uuid();
470        let roundtrip = QUuid::from_rfc_4122(&uuid.to_rfc_4122());
471        assert_eq!(uuid, roundtrip)
472    }
473
474    #[test]
475    fn quuid_u128_round_trip() {
476        let uuid = QUuid::create_uuid();
477        let roundtrip = QUuid::from_u128(uuid.to_u128());
478        assert_eq!(uuid, roundtrip)
479    }
480}