diff --git a/backend/sync_lib/src/errors.rs b/backend/sync_lib/src/errors.rs index d8809ee..68bc71c 100644 --- a/backend/sync_lib/src/errors.rs +++ b/backend/sync_lib/src/errors.rs @@ -1,16 +1,29 @@ +use std::str::Utf8Error; + use base64::DecodeError; use thiserror::Error; use wasm_bindgen::JsValue; #[derive(Error, Debug)] pub enum SyncLibError { - #[error("Base64 decoding error: {}", .reason)] - DecodingError { reason: String }, + #[error("Base64 decoding error because of {}", .reason)] + Base64DecodingError { reason: String }, + + #[error("Bytes cannot be decoded as UTF-8 string because of {}", .reason)] + StringDecodingError { reason: String }, } impl From for SyncLibError { fn from(e: DecodeError) -> Self { - SyncLibError::DecodingError { + SyncLibError::Base64DecodingError { + reason: e.to_string(), + } + } +} + +impl From for SyncLibError { + fn from(e: Utf8Error) -> Self { + SyncLibError::StringDecodingError { reason: e.to_string(), } } @@ -18,7 +31,7 @@ impl From for SyncLibError { impl From for SyncLibError { fn from(e: std::string::FromUtf8Error) -> Self { - SyncLibError::DecodingError { + SyncLibError::Base64DecodingError { reason: e.to_string(), } } diff --git a/backend/sync_lib/src/lib.rs b/backend/sync_lib/src/lib.rs index 7a913c8..ff374ac 100644 --- a/backend/sync_lib/src/lib.rs +++ b/backend/sync_lib/src/lib.rs @@ -1,3 +1,5 @@ +use core::str; + use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine as _}; use errors::SyncLibError; use wasm_bindgen::prelude::*; @@ -27,6 +29,20 @@ pub fn base64_to_string(input: &str) -> Result { String::from_utf8(bytes).map_err(SyncLibError::from) } +#[wasm_bindgen] +pub fn merge(parent: &[u8], left: &[u8], right: &[u8]) -> Result, SyncLibError> { + Ok(if is_binary(right) { + right.to_vec() + } else { + reconcile::reconcile( + str::from_utf8(parent).map_err(SyncLibError::from)?, + str::from_utf8(left).map_err(SyncLibError::from)?, + str::from_utf8(right).map_err(SyncLibError::from)?, + ) + .into_bytes() + }) +} + #[wasm_bindgen] pub fn is_binary(data: &[u8]) -> bool { data.iter().any(|&b| b == 0) } diff --git a/backend/sync_server/src/database.rs b/backend/sync_server/src/database.rs index 5373f56..a5ae9e9 100644 --- a/backend/sync_server/src/database.rs +++ b/backend/sync_server/src/database.rs @@ -70,7 +70,6 @@ impl Database { created_date as "created_date: chrono::DateTime", updated_date as "updated_date: chrono::DateTime", relative_path, - is_binary, is_deleted from latest_documents where is_deleted = false and vault_id = ? @@ -103,7 +102,6 @@ impl Database { updated_date as "updated_date: chrono::DateTime", relative_path, content, - is_binary, is_deleted from latest_documents where vault_id = ? and document_id = ? @@ -137,7 +135,6 @@ impl Database { updated_date as "updated_date: chrono::DateTime", relative_path, content, - is_binary, is_deleted from latest_documents where vault_id = ? and relative_path = ? and is_deleted = false @@ -172,7 +169,6 @@ impl Database { updated_date as "updated_date: chrono::DateTime", relative_path, content, - is_binary, is_deleted from documents where vault_id = ? and document_id = ? and version_id = ?"#, @@ -204,10 +200,9 @@ impl Database { updated_date, relative_path, content, - is_binary, is_deleted ) - values (?, ?, ?, ?, ?, ?, ?, ?, ?) + values (?, ?, ?, ?, ?, ?, ?, ?) "#, version.vault_id, version.document_id, @@ -216,7 +211,6 @@ impl Database { version.updated_date, version.relative_path, version.content, - version.is_binary, version.is_deleted ); diff --git a/backend/sync_server/src/database/models.rs b/backend/sync_server/src/database/models.rs index 71e431e..32dc169 100644 --- a/backend/sync_server/src/database/models.rs +++ b/backend/sync_server/src/database/models.rs @@ -16,14 +16,9 @@ pub struct StoredDocumentVersion { pub updated_date: DateTime, pub relative_path: String, pub content: Vec, - pub is_binary: bool, pub is_deleted: bool, } -impl StoredDocumentVersion { - pub fn content_as_string(&self) -> String { String::from_utf8_lossy(&self.content).to_string() } -} - #[derive(Debug, Clone, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct DocumentVersionWithoutContent { @@ -33,7 +28,6 @@ pub struct DocumentVersionWithoutContent { pub created_date: DateTime, pub updated_date: DateTime, pub relative_path: String, - pub is_binary: bool, pub is_deleted: bool, } @@ -46,7 +40,6 @@ impl From for DocumentVersionWithoutContent { created_date: value.created_date, updated_date: value.updated_date, relative_path: value.relative_path, - is_binary: value.is_binary, is_deleted: value.is_deleted, } } @@ -69,7 +62,6 @@ pub struct DocumentVersion { pub updated_date: DateTime, pub relative_path: String, pub content_base64: String, - pub is_binary: bool, pub is_deleted: bool, } @@ -83,7 +75,6 @@ impl From for DocumentVersion { updated_date: value.updated_date, relative_path: value.relative_path, content_base64: bytes_to_base64(&value.content), - is_binary: value.is_binary, is_deleted: value.is_deleted, } } diff --git a/backend/sync_server/src/server/create_document.rs b/backend/sync_server/src/server/create_document.rs index 479969f..4268adb 100644 --- a/backend/sync_server/src/server/create_document.rs +++ b/backend/sync_server/src/server/create_document.rs @@ -9,7 +9,7 @@ use axum_extra::{ }; use schemars::JsonSchema; use serde::Deserialize; -use sync_lib::{base64_to_bytes, base64_to_string}; +use sync_lib::{base64_to_bytes, merge}; use super::{auth::auth, requests::CreateDocumentVersion}; use crate::{ @@ -53,20 +53,17 @@ pub async fn create_document( .map_err(server_error)?; let new_version = if let Some(existing_version) = maybe_existing_version { - let merged_content = if request.is_binary { - base64_to_bytes(&request.content_base64) - .context("Failed to decode base64 content in request") - .map_err(client_error)? - } else { - reconcile::reconcile( - "", // the empty string is the first common parent of the two documents - &existing_version.content_as_string(), - &base64_to_string(&request.content_base64) - .context("Failed to decode base64 content in request") - .map_err(client_error)?, - ) - .into_bytes() - }; + let content_bytes = base64_to_bytes(&request.content_base64) + .context("Failed to decode base64 content in request") + .map_err(client_error)?; + + let merged_content = merge( + &[], // the empty string is the first common parent of the two documents, + &existing_version.content, + &content_bytes, + ) + .context("Failed to decode bytes as UTF-8") + .map_err(client_error)?; StoredDocumentVersion { vault_id, @@ -76,7 +73,6 @@ pub async fn create_document( created_date: request.created_date, relative_path: request.relative_path, updated_date: chrono::Utc::now(), - is_binary: request.is_binary, is_deleted: false, } } else { @@ -90,7 +86,6 @@ pub async fn create_document( created_date: request.created_date, relative_path: request.relative_path, updated_date: chrono::Utc::now(), - is_binary: request.is_binary, is_deleted: false, } }; diff --git a/backend/sync_server/src/server/delete_document.rs b/backend/sync_server/src/server/delete_document.rs index 54922f0..a1fafa1 100644 --- a/backend/sync_server/src/server/delete_document.rs +++ b/backend/sync_server/src/server/delete_document.rs @@ -63,7 +63,6 @@ pub async fn delete_document( created_date: request.created_date, updated_date: chrono::Utc::now(), relative_path: latest_version.relative_path, - is_binary: latest_version.is_binary, is_deleted: true, }; diff --git a/backend/sync_server/src/server/ping.rs b/backend/sync_server/src/server/ping.rs index bf6c843..30f5c74 100644 --- a/backend/sync_server/src/server/ping.rs +++ b/backend/sync_server/src/server/ping.rs @@ -4,7 +4,7 @@ use axum_extra::{ TypedHeader, }; -use super::auth::{self, auth}; +use super::auth::auth; use crate::{app_state::AppState, database::models::PingResponse, errors::SyncServerError}; #[axum::debug_handler] diff --git a/backend/sync_server/src/server/requests.rs b/backend/sync_server/src/server/requests.rs index 57edb3d..8f04910 100644 --- a/backend/sync_server/src/server/requests.rs +++ b/backend/sync_server/src/server/requests.rs @@ -10,7 +10,6 @@ pub struct CreateDocumentVersion { pub created_date: DateTime, pub relative_path: String, pub content_base64: String, - pub is_binary: bool, } #[derive(Debug, Deserialize, JsonSchema)] @@ -20,7 +19,6 @@ pub struct UpdateDocumentVersion { pub created_date: DateTime, pub relative_path: String, pub content_base64: String, - pub is_binary: bool, } #[derive(Debug, Deserialize, JsonSchema)] diff --git a/backend/sync_server/src/server/update_document.rs b/backend/sync_server/src/server/update_document.rs index 21b96bb..0627a45 100644 --- a/backend/sync_server/src/server/update_document.rs +++ b/backend/sync_server/src/server/update_document.rs @@ -9,7 +9,7 @@ use axum_extra::{ }; use schemars::JsonSchema; use serde::Deserialize; -use sync_lib::{base64_to_bytes, base64_to_string}; +use sync_lib::{base64_to_bytes, merge}; use super::{auth::auth, requests::UpdateDocumentVersion}; use crate::{ @@ -76,20 +76,13 @@ pub async fn update_document( ))); } - let merged_content = if request.is_binary { - base64_to_bytes(&request.content_base64) - .context("Failed to decode base64 content in request") - .map_err(client_error)? - } else { - reconcile::reconcile( - &parent.content_as_string(), - &latest_version.content_as_string(), - &base64_to_string(&request.content_base64) - .context("Failed to decode base64 content in request") - .map_err(client_error)?, - ) - .into_bytes() - }; + let content_bytes = base64_to_bytes(&request.content_base64) + .context("Failed to decode base64 content in request") + .map_err(client_error)?; + + let merged_content = merge(&parent.content, &latest_version.content, &content_bytes) + .context("Failed to decode bytes as UTF-8") + .map_err(client_error)?; let new_version = StoredDocumentVersion { vault_id, @@ -99,7 +92,6 @@ pub async fn update_document( created_date: request.created_date, relative_path: request.relative_path, updated_date: chrono::Utc::now(), - is_binary: request.is_binary, is_deleted: false, };