Skip to main content

coven_foundation/
uuid_text.rs

1//! Coven's ids at the SQL value boundary, without database access: UUIDs as
2//! canonical text; store, key and stored objects' ids as their sixteen bytes;
3//! and device ids as the eight big-endian bytes coven stores.
4
5use crate::id_source::{CircleId, DeviceId, FileId, KeyId, MemberId, ObjectId, StoreId};
6use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, Value, ValueRef};
7use rusqlite::ToSql;
8use uuid::Uuid;
9
10/// Bind or read a UUID as canonical lowercase hyphenated SQL text.
11/// Rusqlite's `uuid` feature binds a bare UUID as a BLOB instead.
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub struct UuidText(
14    /// The UUID to bind or read as text.
15    pub Uuid,
16);
17
18impl From<UuidText> for Value {
19    fn from(value: UuidText) -> Self {
20        Self::Text(value.0.to_string())
21    }
22}
23
24impl ToSql for UuidText {
25    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
26        Ok(ToSqlOutput::Owned((*self).into()))
27    }
28}
29
30impl FromSql for UuidText {
31    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
32        crate::id_source::canonical_uuid(value.as_str()?)
33            .map(Self)
34            .map_err(|error| FromSqlError::Other(Box::new(error)))
35    }
36}
37
38impl ToSql for CircleId {
39    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
40        Ok(ToSqlOutput::Owned(UuidText(self.0).into()))
41    }
42}
43
44impl FromSql for CircleId {
45    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
46        UuidText::column_result(value).map(|id| Self(id.0))
47    }
48}
49
50/// A member id as canonical UUID text, as coven's member columns and the
51/// `coven_members` view hold it.
52impl ToSql for MemberId {
53    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
54        Ok(ToSqlOutput::Owned(UuidText(self.0).into()))
55    }
56}
57
58impl FromSql for MemberId {
59    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
60        UuidText::column_result(value).map(|id| Self(id.0))
61    }
62}
63
64/// A file id as canonical UUID text, as `coven_files` and the app's file
65/// columns hold it.
66impl ToSql for FileId {
67    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
68        Ok(ToSqlOutput::Owned(UuidText(self.0).into()))
69    }
70}
71
72impl FromSql for FileId {
73    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
74        UuidText::column_result(value).map(|id| Self(id.0))
75    }
76}
77
78/// A UUID's sixteen bytes, as a BLOB.
79fn uuid_blob(id: &Uuid) -> ToSqlOutput<'_> {
80    ToSqlOutput::Borrowed(ValueRef::Blob(id.as_bytes()))
81}
82
83/// A UUID from a BLOB of exactly sixteen bytes.
84fn uuid_from_blob(value: ValueRef<'_>) -> FromSqlResult<Uuid> {
85    let bytes = value.as_blob()?;
86    let bytes = <[u8; 16]>::try_from(bytes).map_err(|_wrong_size| {
87        FromSqlError::InvalidBlobSize { expected_size: 16, blob_size: bytes.len() }
88    })?;
89    Ok(Uuid::from_bytes(bytes))
90}
91
92/// A stored object's id as its sixteen bytes, as coven's object columns hold
93/// it.
94impl ToSql for ObjectId {
95    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
96        Ok(uuid_blob(&self.0))
97    }
98}
99
100impl FromSql for ObjectId {
101    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
102        uuid_from_blob(value).map(Self)
103    }
104}
105
106/// A store id as its sixteen bytes, as `_coven_store` holds it.
107impl ToSql for StoreId {
108    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
109        Ok(uuid_blob(&self.0))
110    }
111}
112
113impl FromSql for StoreId {
114    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
115        uuid_from_blob(value).map(Self)
116    }
117}
118
119/// A store or circle key's id as its sixteen bytes, as coven's key columns
120/// hold it.
121impl ToSql for KeyId {
122    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
123        Ok(uuid_blob(&self.0))
124    }
125}
126
127impl FromSql for KeyId {
128    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
129        uuid_from_blob(value).map(Self)
130    }
131}
132
133/// A device id as its eight big-endian bytes, as `coven_devices` holds it:
134/// the whole 64-bit id, which an SQL integer would hold only as a signed
135/// number.
136impl ToSql for DeviceId {
137    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
138        Ok(ToSqlOutput::Owned(Value::Blob(self.0.to_be_bytes().to_vec())))
139    }
140}
141
142impl FromSql for DeviceId {
143    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
144        let bytes = value.as_blob()?;
145        let bytes = <[u8; 8]>::try_from(bytes).map_err(|_wrong_size| {
146            FromSqlError::InvalidBlobSize { expected_size: 8, blob_size: bytes.len() }
147        })?;
148        Ok(Self(u64::from_be_bytes(bytes)))
149    }
150}
151
152#[cfg(test)]
153#[path = "uuid_text_tests.rs"]
154mod tests;