432 lines · 12.9 KB · Rust
RawDownload
use std::fmt; use itertools::Itertools;use serde::{Deserialize, Deserializer, Serialize, Serializer};use uuid::Uuid; use crate::cloud_object::ObjectIdType; /// Convert ID enums into and from a hashed UUID.pub trait HashableId: Sized + Send + Sync {    fn to_hash(&self) -> String;    fn from_hash(hash: &str) -> Option<Self>;} #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, schemars::JsonSchema)]#[schemars(description = "A client-generated unique identifier.")]pub struct ClientId(Uuid); impl HashableId for ClientId {    fn to_hash(&self) -> String {        self.to_string()    }     fn from_hash(hash: &str) -> Option<ClientId> {        hash.strip_prefix("Client-")            .and_then(|s| Uuid::parse_str(s).ok())            .map(ClientId)    }} impl ClientId {    pub fn new() -> ClientId {        Self(Uuid::new_v4())    }     pub fn sqlite_hash(&self) -> String {        self.to_string()    }} impl Default for ClientId {    fn default() -> Self {        ClientId::new()    }} impl fmt::Display for ClientId {    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {        write!(f, "Client-{}", self.0)    }} impl From<String> for ClientId {    fn from(s: String) -> Self {        ClientId::from_hash(&s).unwrap_or_default()    }} /// ID of an object in the sync queue.#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, schemars::JsonSchema)]#[schemars(description = "Identifier for a synced object, either local or server-assigned.")]pub enum SyncId {    /// Item has not been sync-ed yet. Using a client-created UUID.    #[schemars(        description = "A locally-generated identifier for an object not yet synced to the server."    )]    ClientId(ClientId),    /// Item has been sync-ed to the cloud. Using the server ID.    #[schemars(description = "A server-assigned identifier for a synced object.")]    ServerId(ServerId),} impl SyncId {    pub fn from_object_id<K>(id: K) -> Self    where        K: ToServerId,    {        Self::ServerId(id.to_server_id())    }     pub fn uid(&self) -> ObjectUid {        match self {            Self::ClientId(id) => id.to_string(),            Self::ServerId(id) => id.uid(),        }    }     pub fn sqlite_uid_hash(&self, object_id_type: ObjectIdType) -> String {        match self {            SyncId::ClientId(id) => id.sqlite_hash(),            SyncId::ServerId(id) => id.sqlite_type_and_uid_hash(object_id_type),        }    }     /// If this item has been synced to the cloud, extract its server ID.    pub fn into_server(self) -> Option<ServerId> {        match self {            Self::ServerId(id) => Some(id),            Self::ClientId(_) => None,        }    }     pub fn into_client(self) -> Option<ClientId> {        match self {            Self::ServerId(_) => None,            Self::ClientId(id) => Some(id),        }    }} impl settings_value::SettingsValue for SyncId {} impl fmt::Display for SyncId {    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {        match self {            Self::ServerId(id) => id.fmt(f),            Self::ClientId(id) => id.fmt(f),        }    }} impl From<ServerId> for SyncId {    fn from(id: ServerId) -> SyncId {        SyncId::ServerId(id)    }} /// Custom serialize function for SyncIds.impl Serialize for SyncId {    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>    where        S: Serializer,    {        match self {            SyncId::ServerId(server_id) => server_id.serialize(serializer),            SyncId::ClientId(client_id) => client_id.to_hash().serialize(serializer),        }    }} /// Custom deserialize function for SyncIds.impl<'de> Deserialize<'de> for SyncId {    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>    where        D: Deserializer<'de>,    {        let s: String = Deserialize::deserialize(deserializer)?;         // We try to deserialize as a ClientID, which only succeeds if the ID is prefixed with `Client-`.        // If that fails, we assume this is a server id and create a server ID.        if let Some(hashed) = ClientId::from_hash(s.as_str()) {            Ok(SyncId::ClientId(hashed))        } else {            Ok(SyncId::ServerId(ServerId::from_string_lossy(s)))        }    }} /// Length of the ServerId, should be in sync with the length picked for the server.const SERVER_ID_LENGTH: usize = 22; /// ServerId is a representation of a string-based unique ID we generate on the server,/// of length SERVER_ID_LENGTH./// Because it's of fixed length, it can implement the Copy trait/// (in contrast to simply using a String type).#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, schemars::JsonSchema)]#[schemars(description = "A server-assigned unique identifier.")]pub struct ServerId([char; SERVER_ID_LENGTH]); /// For server IDs, this is the value that is stored/// in the database. For client IDs, it is of the form "Client-{id}"./// Used to index into cloud model and in most object read, write, and metadata/// mutation server APIs.pub type ObjectUid = String; /// Corresponds to what is stored for a given object id within the local sqlite/// database. Needed for backwards compatibility of the sqlite db following a refactor/// that stripped the object type away from SyncID.////// Of the format {sqlite_prefix}-{uid}.////// Other than sqlite model events, this id is used for embedded objects within notebooks.pub type HashedSqliteId = String; /// UID for API keys.pub type ApiKeyUid = String; #[derive(Debug, thiserror::Error)]pub enum ParseServerIdError {    #[error("ServerId must be exactly {SERVER_ID_LENGTH} characters, got {len}")]    InvalidLength { len: usize },} /// Removes the prefix from sqlite IDs to extract the UIDs. Should not be used unless there/// is not other way to cleanly do the conversion, i.e., when we don't know the ID type.#[allow(clippy::result_unit_err)]pub fn parse_sqlite_id_to_uid(hashed_sqlite_id: HashedSqliteId) -> Result<ObjectUid, ()> {    let Some(uid) = hashed_sqlite_id.split("-").last() else {        return Err(());    };     Ok(uid.to_owned())} impl ServerId {    /// Convert a string input to a server ID. If the string is not exactly    /// [`SERVER_ID_LENGTH`] characters long, it will be truncated or padded as    /// necessary.    pub fn from_string_lossy(id: impl AsRef<str>) -> Self {        let id = id.as_ref();        Self::try_from(id).unwrap_or_else(|err| {            if cfg!(debug_assertions) {                panic!("{err}");            }            // ServerIds need to be exactly 22 characters, so to prevent a crash, we'll normalize            // the string. Nothing that uses it will work, but it's better than crashing.            let normalized = Self::normalize_id_str(id, 0);            Self::try_from(normalized).expect("id should convert")        })    }     /// Normalizes a string to be exactly 22 characters long.    fn normalize_id_str(input: &str, prefix_length: usize) -> String {        let available_len = SERVER_ID_LENGTH - prefix_length;        let truncated = if input.len() > available_len {            &input[input.len() - available_len..]        } else {            input        };        format!("{truncated:0>available_len$}")    }     pub fn uid(&self) -> ObjectUid {        (*self).into()    }     /// We need this API for backwards compatibility with local sqlite data.    /// In sqlite, objects are stored in object typy, uid pairs of the format    /// {sqlite-prefix}-{uid}. For example, for a workflow this would be    /// "Workflow-{uid}".    pub fn sqlite_type_and_uid_hash(&self, object_id_type: ObjectIdType) -> HashedSqliteId {        format!("{}-{}", object_id_type.sqlite_prefix(), self)    }} impl TryFrom<&str> for ServerId {    type Error = ParseServerIdError;     fn try_from(s: &str) -> Result<Self, Self::Error> {        match s.chars().collect_array() {            Some(chars) => Ok(Self(chars)),            None => Err(ParseServerIdError::InvalidLength {                len: s.chars().count(),            }),        }    }} impl TryFrom<String> for ServerId {    type Error = ParseServerIdError;     fn try_from(id: String) -> Result<Self, Self::Error> {        Self::try_from(id.as_str())    }} /// Creates a conversion between an i64 and a corresponding deterministic ServerId for use in tests./// An i64 like 123 will be converted to "test_uid00000000000123".#[cfg(any(test, feature = "test-util"))]impl From<i64> for ServerId {    fn from(id: i64) -> Self {        let prefix = "test_uid";        let id_str = id.abs().to_string();        let normalized = format!(            "{}{}",            prefix,            Self::normalize_id_str(&id_str, prefix.len())        );        Self::try_from(normalized).expect("normalized string should always be valid")    }} impl From<ServerId> for String {    fn from(id: ServerId) -> String {        String::from_iter(id.0)    }} /// We need our own implementation of serialize, due to ServerId being essentially a char array./// The default serializer in this case would spit a string that looks like an array, instead of a/// nicely formatted string that we want. This implementation would serialize ServerId('a', 'b') to/// "ab" instead.impl Serialize for ServerId {    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>    where        S: Serializer,    {        let s: String = (*self).into();        serializer.serialize_str(&s)    }} impl<'de> Deserialize<'de> for ServerId {    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>    where        D: Deserializer<'de>,    {        let s: String = Deserialize::deserialize(deserializer)?;        ServerId::try_from(s.as_str()).map_err(serde::de::Error::custom)    }} impl std::fmt::Display for ServerId {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {        use std::fmt::Write;        for ch in self.0.iter() {            f.write_char(*ch)?;        }        Ok(())    }} impl std::fmt::Debug for ServerId {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {        write!(f, "ServerId({self})")    }} pub trait ToServerId {    fn to_server_id(&self) -> ServerId;} #[derive(Clone, Debug, PartialEq)]pub struct ServerIdAndType {    pub id: ServerId,    pub id_type: ObjectIdType,} impl ServerIdAndType {    pub fn sqlite_type_and_uid_hash(&self) -> HashedSqliteId {        self.id.sqlite_type_and_uid_hash(self.id_type)    }} /// string_id_traits is a macro used for generating implementations for the type aliases on/// ServerId, implements different To/From and Display, and HashableId traits./// Takes type and desired prefix for HashableId.#[macro_export]macro_rules! server_id_traits {    ($t:ty, $prefix:literal) => {        #[cfg(any(test, feature = "test-util"))]        impl From<i64> for $t {            fn from(id: i64) -> Self {                Self(id.into())            }        }         impl From<String> for $t {            fn from(id: String) -> Self {                Self($crate::ids::ServerId::from_string_lossy(id))            }        }         impl From<$t> for String {            fn from(id: $t) -> String {                id.0.into()            }        }         impl std::fmt::Display for $t {            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {                write!(f, "{}", self.0)            }        }         impl From<$t> for $crate::ids::ServerId {            fn from(id: $t) -> Self {                id.0            }        }         impl $crate::ids::HashableId for $t {            fn to_hash(&self) -> String {                format!("{}-{}", $prefix, self)            }             fn from_hash(hash: &str) -> Option<$t> {                hash.strip_prefix(&format!("{}-", $prefix))                    .map(|s| s.to_string().into())            }        }         impl From<$crate::ids::ServerId> for $t {            fn from(id: $crate::ids::ServerId) -> Self {                Self(id)            }        }         impl $crate::ids::ToServerId for $t {            fn to_server_id(&self) -> $crate::ids::ServerId {                self.0            }        }    };} #[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Default)]pub struct FolderId(ServerId);server_id_traits! { FolderId, "Folder" } impl From<FolderId> for SyncId {    fn from(id: FolderId) -> Self {        Self::ServerId(id.into())    }} /// Object ID type that is common for all generic string objects.#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]pub struct GenericStringObjectId(ServerId);crate::server_id_traits! { GenericStringObjectId, "GenericStringObject" } impl From<GenericStringObjectId> for SyncId {    fn from(id: GenericStringObjectId) -> Self {        Self::ServerId(id.into())    }} impl GenericStringObjectId {    pub fn uid(&self) -> ObjectUid {        self.0.uid()    }}