first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
@@ -0,0 +1,74 @@
use warp_graphql::scalars::time::ServerTimestamp;
use super::{
CloudObjectEventEntrypoint, GenericStringObjectFormat, GenericStringObjectUniqueKey, Owner,
RevisionAndLastEditor, SerializedModel, ServerPermissions,
};
use crate::ids::{ClientId, FolderId, ServerIdAndType};
/// Helper struct that contains all the info needed to create an object on the server.
pub struct CreateObjectRequest {
pub serialized_model: Option<SerializedModel>,
pub title: Option<String>,
pub owner: Owner,
pub client_id: ClientId,
pub initial_folder_id: Option<FolderId>,
pub entrypoint: CloudObjectEventEntrypoint,
}
#[derive(PartialEq, Eq, Debug)]
pub struct BulkCreateGenericStringObjectsRequest {
pub id: ClientId,
pub format: GenericStringObjectFormat,
pub uniqueness_key: Option<GenericStringObjectUniqueKey>,
pub serialized_model: SerializedModel,
pub initial_folder_id: Option<FolderId>,
pub entrypoint: CloudObjectEventEntrypoint,
}
/// The data returned by the server when an object is created, generic to any object type.
#[derive(Debug)]
pub struct CreatedCloudObject {
pub client_id: ClientId,
pub revision_and_editor: RevisionAndLastEditor,
pub metadata_ts: ServerTimestamp,
pub server_id_and_type: ServerIdAndType,
pub creator_uid: Option<String>,
pub permissions: ServerPermissions,
}
/// Result of attempting to create a cloud object.
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum CreateCloudObjectResult {
/// The object creation was successful.
Success {
created_cloud_object: CreatedCloudObject,
},
/// The object creation was denied due to an expected user error.
UserFacingError(String),
/// The object creation was rejected because the generic string object had
/// already been created by another client.
GenericStringObjectUniqueKeyConflict,
}
/// Result of attempting to bulk create a cloud object.
#[derive(Debug)]
pub enum BulkCreateCloudObjectResult {
/// The bulk object creation was successful.
Success {
created_cloud_objects: Vec<CreatedCloudObject>,
},
/// The bulk object creation was rejected because at least one generic string object had
/// already been created by another client.
GenericStringObjectUniqueKeyConflict,
}
/// The creation-specific data returned by the server, which is inserted into CloudModel and persisted
/// just once.
#[derive(Debug, PartialEq, Clone)]
pub struct ServerCreationInfo {
pub server_id_and_type: ServerIdAndType,
pub creator_uid: Option<String>,
pub permissions: ServerPermissions,
}
@@ -0,0 +1,208 @@
use std::sync::Arc;
use super::{
CloudObjectMetadata, CloudObjectPermissions, CloudObjectStatuses, CloudObjectSyncStatus,
ConflictStatus, GenericServerObject, NumInFlightRequests, ObjectType, Owner,
};
use crate::ids::{ClientId, SyncId};
/// A portable payload for persisting or otherwise upserting a cloud object without app-local event types.
#[derive(Clone, Debug)]
pub struct CloudObjectUpsertParams<M> {
pub id: SyncId,
pub object_type: ObjectType,
pub metadata: CloudObjectMetadata,
pub permissions: CloudObjectPermissions,
pub model: M,
}
/// A generic implementation of cloud objects that can be used for any model and id types.
///
/// For instance, rather than directly implementing the CloudObject trait, CloudObjects can
/// implement GenericCloudObject<K, M> where K is their id type and M is their model type.
///
/// For example, CloudNotebook becomes:
///
/// pub type CloudNotebook = GenericCloudObject<NotebookId, CloudNotebookModel>
///
/// The advantage of using the generic model is you get common implementations
/// of CloudObject methods like ```versions``` for free.
///
/// See the comments for CloudObject to understand the relationship between
/// this trait, CloudObject and CloudModelType. They are tightly coupled.
#[derive(Clone, Debug)]
pub struct GenericCloudObject<K, M> {
pub id: SyncId,
pub metadata: CloudObjectMetadata,
pub permissions: CloudObjectPermissions,
/// Tracks whether this object has a conflict with the server version.
/// This is runtime state (not persisted) - conflicts are always NoConflicts when loaded from SQLite.
pub conflict_status: ConflictStatus<GenericServerObject<K, M>>,
// Intentionally not public to prevent users of this class from holding
// onto references to the model outside of this struct.
//
// This is an Arc in order to support clone-on-write semantics for the model.
// By wrapping the model in an Arc, clones become cheap, and we can avoid
// doing deep clones of the model whenever the containing object is cloned.
//
// Callers who want to update the model need to call set_model to update the
// entire model atomically.
model: Arc<M>,
}
impl<K, M> PartialEq for GenericCloudObject<K, M>
where
M: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.model() == other.model()
}
}
impl<K, M> GenericCloudObject<K, M> {
/// Gets a reference to the model held by the object.
pub fn model(&self) -> &M {
&self.model
}
/// Returns a shared handle to the model.
pub fn shared_model(&self) -> Arc<M> {
self.model.clone()
}
/// Sets a new version of the model on the object, replacing the old version.
pub fn set_model(&mut self, model: M) {
self.model = model.into();
}
/// Constructs a new instance of this model with the given id, model, metadata and permissions.
pub fn new(
id: SyncId,
model: M,
metadata: CloudObjectMetadata,
permissions: CloudObjectPermissions,
) -> Self {
Self {
id,
model: model.into(),
metadata,
permissions,
conflict_status: ConflictStatus::NoConflicts,
}
}
/// Creates a new GenericCloudObject with the given model, owner, and initial folder id.
/// This is for the local creation flow, as opposed to creating from a server update.
pub fn new_local(
model: M,
owner: Owner,
initial_folder_id: Option<SyncId>,
client_id: ClientId,
) -> Self {
Self {
id: SyncId::ClientId(client_id),
model: model.into(),
metadata: CloudObjectMetadata {
pending_changes_statuses: CloudObjectStatuses {
content_sync_status: CloudObjectSyncStatus::InFlight(NumInFlightRequests(1)),
has_pending_metadata_change: false,
has_pending_permissions_change: false,
pending_untrash: false,
pending_delete: false,
},
folder_id: initial_folder_id,
revision: Default::default(),
metadata_last_updated_ts: Default::default(),
current_editor_uid: Default::default(),
trashed_ts: Default::default(),
// Objects created from the client are never welcome objects.
is_welcome_object: false,
creator_uid: None,
last_editor_uid: None,
last_task_run_ts: None,
},
permissions: CloudObjectPermissions {
owner,
anyone_with_link: None,
guests: Default::default(),
permissions_last_updated_ts: None,
},
conflict_status: ConflictStatus::NoConflicts,
}
}
/// Creates a new [`GenericCloudObject`] from a [`GenericServerObject`].
pub fn new_from_server(server_object: GenericServerObject<K, M>) -> Self {
Self {
id: server_object.id,
model: server_object.model.into(),
metadata: CloudObjectMetadata::new_from_server(server_object.metadata),
permissions: CloudObjectPermissions::new_from_server(server_object.permissions),
conflict_status: ConflictStatus::NoConflicts,
}
}
/// Marks this object as being in conflict with the provided object.
pub fn set_conflicting_object(&mut self, object: Arc<GenericServerObject<K, M>>) {
self.conflict_status = ConflictStatus::ConflictingChanges { object };
}
pub fn update_from_server_object(&mut self, server_object: GenericServerObject<K, M>) {
// Check if we should create a conflict or apply the update.
if self.metadata.has_pending_content_changes() || self.conflict_status.has_conflicts() {
// There are pending changes, so this creates a conflict.
self.conflict_status = ConflictStatus::ConflictingChanges {
object: Arc::new(server_object),
};
} else {
// No pending changes, apply the server update.
self.metadata
.update_revision_from_server(&server_object.metadata);
self.model = server_object.model.into();
self.conflict_status = ConflictStatus::NoConflicts;
}
}
/// Returns portable upsert parameters for this object.
pub fn upsert_params(&self, object_type: ObjectType) -> CloudObjectUpsertParams<M>
where
M: Clone,
{
CloudObjectUpsertParams {
id: self.id,
object_type,
metadata: self.metadata.clone(),
permissions: self.permissions.clone(),
model: self.model().clone(),
}
}
/// Converts this object into portable upsert parameters.
pub fn into_upsert_params(self, object_type: ObjectType) -> CloudObjectUpsertParams<M>
where
M: Clone,
{
let Self {
id,
metadata,
permissions,
model,
conflict_status: _,
} = self;
let model = Arc::try_unwrap(model).unwrap_or_else(|model| (*model).clone());
CloudObjectUpsertParams {
id,
object_type,
metadata,
permissions,
model,
}
}
}
impl<K, M> From<CloudObjectUpsertParams<M>> for GenericCloudObject<K, M> {
fn from(params: CloudObjectUpsertParams<M>) -> Self {
Self::new(params.id, params.model, params.metadata, params.permissions)
}
}
@@ -0,0 +1,56 @@
use std::fmt::Debug;
use std::marker::PhantomData;
use anyhow::Result;
use super::{GenericStringObjectFormat, ObjectType, SerializedModel, ServerObjectModel};
/// A serializer goes from a model to a string and back.
pub trait Serializer<M>: Debug + Clone + 'static {
fn model_format() -> GenericStringObjectFormat;
fn serialize(model: &M) -> SerializedModel;
fn deserialize_owned(serialized: &str) -> Result<M>
where
Self: Sized;
}
/// A `GenericStringModel` is a generic implementation of model types that can serialize to/from string.
/// given a particular serializer.
#[derive(Clone, Debug, PartialEq, Default)]
pub struct GenericStringModel<M, S>
where
S: Serializer<M>,
{
pub string_model: M,
_serializer: PhantomData<fn() -> S>,
}
impl<M, S> GenericStringModel<M, S>
where
S: Serializer<M>,
{
pub fn deserialize_owned(serialized: &str) -> Result<Self> {
S::deserialize_owned(serialized).map(Self::new)
}
pub fn new(model: M) -> Self {
Self {
string_model: model,
_serializer: PhantomData,
}
}
pub fn json_model(&self) -> &M {
&self.string_model
}
}
impl<M, S> ServerObjectModel for GenericStringModel<M, S>
where
M: Debug + Clone + Send + Sync + 'static,
S: Serializer<M>,
{
fn object_type(&self) -> ObjectType {
ObjectType::GenericStringObject(S::model_format())
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
@@ -0,0 +1,155 @@
use std::any::Any;
use std::fmt::{self, Debug};
use std::marker::PhantomData;
use std::sync::Arc;
use super::{ObjectType, ServerMetadata, ServerPermissions};
use crate::ids::SyncId;
#[derive(Clone, Debug, Default)]
pub enum ConflictStatus<T> {
#[default]
NoConflicts,
ConflictingChanges {
object: Arc<T>,
},
}
impl<T> ConflictStatus<T> {
/// Utility function that allows for a more ergonomic way of figuring out whether there is a
/// conflict (for cases where we don't care about the conflict details).
pub fn has_conflicts(&self) -> bool {
matches!(self, ConflictStatus::ConflictingChanges { .. })
}
}
/// Common behavior that server-backed models expose to generic server objects.
pub trait ServerObjectModel: Debug + Clone + Send + Sync + 'static {
/// Returns the object type for this model.
fn object_type(&self) -> ObjectType;
}
/// Common trait for server objects that allows us to use them as trait objects
/// and downcast to concrete types when needed.
pub trait ServerObject: Debug + Send + Sync {
/// Returns the object type of this server object
fn object_type(&self) -> ObjectType;
/// Returns this object as a ref to the Any type. Needed for typecasts.
fn as_any(&self) -> &dyn Any;
/// Returns the trait object as a concrete type reference by downcasting it.
/// Returns None if the downcast fails.
fn as_concrete_type<K, M>(
server_object: &dyn ServerObject,
) -> Option<&GenericServerObject<K, M>>
where
Self: Sized,
K: 'static,
M: 'static,
{
server_object
.as_any()
.downcast_ref::<GenericServerObject<K, M>>()
}
/// Returns a cloned boxed version of this server object.
/// Note that we can't force the ServerObject trait to derive from Cloned
/// directly because that would make the trait not object safe. This
/// is a workaround.
fn clone_box(&self) -> Box<dyn ServerObject>;
}
/// An object that maps directly to the data returned from the server
/// for a given model and id type.
pub struct GenericServerObject<K, M> {
pub id: SyncId,
pub model: M,
pub metadata: ServerMetadata,
pub permissions: ServerPermissions,
_marker: PhantomData<fn() -> K>,
}
impl<K, M> Clone for GenericServerObject<K, M>
where
M: Clone,
{
fn clone(&self) -> Self {
Self::new(
self.id,
self.model.clone(),
self.metadata.clone(),
self.permissions.clone(),
)
}
}
impl<K, M> Debug for GenericServerObject<K, M>
where
M: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GenericServerObject")
.field("id", &self.id)
.field("model", &self.model)
.field("metadata", &self.metadata)
.field("permissions", &self.permissions)
.finish()
}
}
impl<K, M> GenericServerObject<K, M> {
/// Constructs a server object from its server-provided parts.
pub fn new(
id: SyncId,
model: M,
metadata: ServerMetadata,
permissions: ServerPermissions,
) -> Self {
Self {
id,
model,
metadata,
permissions,
_marker: PhantomData,
}
}
}
impl<'a, K, M> From<&'a dyn ServerObject> for Option<&'a GenericServerObject<K, M>>
where
K: 'static,
M: 'static,
{
fn from(value: &'a dyn ServerObject) -> Self {
value.as_any().downcast_ref::<GenericServerObject<K, M>>()
}
}
impl<'a, K, M> From<&'a Box<dyn ServerObject>> for Option<&'a GenericServerObject<K, M>>
where
K: 'static,
M: 'static,
{
fn from(value: &'a Box<dyn ServerObject>) -> Self {
value.as_ref().into()
}
}
impl<K, M> ServerObject for GenericServerObject<K, M>
where
K: 'static,
M: ServerObjectModel,
{
fn object_type(&self) -> ObjectType {
self.model.object_type()
}
fn as_any(&self) -> &dyn Any {
self
}
fn clone_box(&self) -> Box<dyn ServerObject> {
Box::new(self.clone())
}
}
@@ -0,0 +1,24 @@
use warp_graphql::queries::get_updated_cloud_objects::UpdatedObjectInput;
use super::RevisionAndLastEditor;
/// Result of attempting to update a cloud object.
#[derive(Debug)]
pub enum UpdateCloudObjectResult<T> {
/// The update was successful and the object now has the specified revision.
Success {
revision_and_editor: RevisionAndLastEditor,
},
/// The update was rejected because the update was not sent from the current revision in
/// storage. The object and revision in storage are returned.
Rejected { object: T },
}
/// Helper struct that contains all the info needed to fetch changed objects from the server.
#[derive(Default, Clone)]
pub struct ObjectsToUpdate {
pub notebooks: Vec<UpdatedObjectInput>,
pub workflows: Vec<UpdatedObjectInput>,
pub folders: Vec<UpdatedObjectInput>,
pub generic_string_objects: Vec<UpdatedObjectInput>,
}