Fix up is binary & sync_lib docs & tests

This commit is contained in:
Andras Schmelczer 2025-01-03 22:30:30 +00:00
commit 44cb7a5b7c
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
7 changed files with 79 additions and 41 deletions

View file

@ -43,6 +43,8 @@ jobs:
run: | run: |
cd backend cd backend
cargo test --verbose cargo test --verbose
cd sync_lib
wasm-pack test --node
- name: Lint frontend - name: Lint frontend
run: | run: |

1
backend/Cargo.lock generated
View file

@ -2102,6 +2102,7 @@ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"console_error_panic_hook", "console_error_panic_hook",
"getrandom", "getrandom",
"insta",
"reconcile", "reconcile",
"thiserror", "thiserror",
"wasm-bindgen", "wasm-bindgen",

View file

@ -22,6 +22,7 @@ console_error_panic_hook = { version = "0.1.7", optional = true }
[dev-dependencies] [dev-dependencies]
wasm-bindgen-test = "0.3.34" wasm-bindgen-test = "0.3.34"
insta = "1.41.1"
[features] [features]
default = ["console_error_panic_hook"] default = ["console_error_panic_hook"]

View file

@ -1,5 +1,3 @@
use std::str::Utf8Error;
use base64::DecodeError; use base64::DecodeError;
use thiserror::Error; use thiserror::Error;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
@ -8,9 +6,6 @@ use wasm_bindgen::JsValue;
pub enum SyncLibError { pub enum SyncLibError {
#[error("Base64 decoding error because of {}", .reason)] #[error("Base64 decoding error because of {}", .reason)]
Base64DecodingError { reason: String }, Base64DecodingError { reason: String },
#[error("Bytes cannot be decoded as UTF-8 string because of {}", .reason)]
StringDecodingError { reason: String },
} }
impl From<DecodeError> for SyncLibError { impl From<DecodeError> for SyncLibError {
@ -21,14 +16,6 @@ impl From<DecodeError> for SyncLibError {
} }
} }
impl From<Utf8Error> for SyncLibError {
fn from(e: Utf8Error) -> Self {
SyncLibError::StringDecodingError {
reason: e.to_string(),
}
}
}
impl From<std::string::FromUtf8Error> for SyncLibError { impl From<std::string::FromUtf8Error> for SyncLibError {
fn from(e: std::string::FromUtf8Error) -> Self { fn from(e: std::string::FromUtf8Error) -> Self {
SyncLibError::Base64DecodingError { SyncLibError::Base64DecodingError {

View file

@ -6,53 +6,57 @@ use wasm_bindgen::prelude::*;
pub mod errors; pub mod errors;
/// Encode binary data for easy transport over HTTP. Inverse of
/// `base64_to_bytes`.
#[wasm_bindgen(js_name = bytesToBase64)] #[wasm_bindgen(js_name = bytesToBase64)]
pub fn bytes_to_base64(input: &[u8]) -> String { STANDARD_NO_PAD.encode(input) } pub fn bytes_to_base64(input: &[u8]) -> String { STANDARD_NO_PAD.encode(input) }
#[wasm_bindgen(js_name = stringToBase64)] /// Inverse of `bytes_to_base64`.
pub fn string_to_base64(input: &str) -> String { bytes_to_base64(input.as_bytes()) }
#[wasm_bindgen(js_name = base64ToBytes)] #[wasm_bindgen(js_name = base64ToBytes)]
pub fn base64_to_bytes(input: &str) -> Result<Vec<u8>, SyncLibError> { pub fn base64_to_bytes(input: &str) -> Result<Vec<u8>, SyncLibError> {
STANDARD_NO_PAD.decode(input).map_err(SyncLibError::from) STANDARD_NO_PAD.decode(input).map_err(SyncLibError::from)
} }
#[wasm_bindgen(js_name = base64ToString)] /// Merge two documents with a common parent. Relies on `reconcile::reconcile`
pub fn base64_to_string(input: &str) -> Result<String, SyncLibError> { /// for texts and returns the right document as-is if either of the updated
let bytes = base64_to_bytes(input)?; /// documents is binary.
String::from_utf8(bytes).map_err(SyncLibError::from)
}
#[wasm_bindgen] #[wasm_bindgen]
pub fn merge(parent: &[u8], left: &[u8], right: &[u8]) -> Result<Vec<u8>, SyncLibError> { pub fn merge(parent: &[u8], left: &[u8], right: &[u8]) -> Vec<u8> {
Ok(if is_binary(right) { if is_binary(parent) || is_binary(left) || is_binary(right) {
right.to_vec() right.to_vec()
} else { } else {
reconcile::reconcile( reconcile::reconcile(
str::from_utf8(parent).map_err(SyncLibError::from)?, str::from_utf8(parent).expect("parent must be valid UTF-8 because it's not binary"),
str::from_utf8(left).map_err(SyncLibError::from)?, str::from_utf8(left).expect("left must be valid UTF-8 because it's not binary"),
str::from_utf8(right).map_err(SyncLibError::from)?, str::from_utf8(right).expect("right must be valid UTF-8 because it's not binary"),
) )
.into_bytes() .into_bytes()
}) }
} }
/// WASM wrapper around `reconcile::reconcile` for text merging.
#[wasm_bindgen(js_name = mergeText)] #[wasm_bindgen(js_name = mergeText)]
pub fn merge_text(parent: &str, left: &str, right: &str) -> String { pub fn merge_text(parent: &str, left: &str, right: &str) -> String {
reconcile::reconcile(parent, left, right) reconcile::reconcile(parent, left, right)
} }
/// Heuristically determine if the given data is a binary or a text file's
/// content.
#[wasm_bindgen(js_name = isBinary)] #[wasm_bindgen(js_name = isBinary)]
pub fn is_binary(data: &[u8]) -> bool { std::str::from_utf8(data).is_ok() } pub fn is_binary(data: &[u8]) -> bool {
if data.iter().any(|&b| b == 0) {
// Even though the NUL character is valid in UTF-8, it's highly suspicious in
// human-readable text.
return true;
}
std::str::from_utf8(data).is_err()
}
/// Set up panic hook for better error messages in the browser console.
#[cfg(feature = "console_error_panic_hook")] #[cfg(feature = "console_error_panic_hook")]
#[wasm_bindgen(js_name = setPanicHook)] #[wasm_bindgen(js_name = setPanicHook)]
pub fn set_panic_hook() { pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme // https://github.com/rustwasm/console_error_panic_hook#readme
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
} }

View file

@ -0,0 +1,10 @@
---
source: sync_lib/tests/web.rs
expression: base64_to_bytes(input)
snapshot_kind: text
---
Err(
Base64DecodingError {
reason: "Invalid symbol 61, offset 0.",
},
)

View file

@ -1,13 +1,46 @@
//! Test suite for the Web and headless browsers. //! Test suite for the Web and headless browsers.
#![cfg(target_arch = "wasm32")] use insta::assert_debug_snapshot;
use sync_lib::*;
extern crate wasm_bindgen_test;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test(unsupported = test)]
fn test_bytes_to_base64() {
#[wasm_bindgen_test] let input = b"hello";
fn pass() { let expected = "aGVsbG8";
assert_eq!(1 + 1, 2); assert_eq!(bytes_to_base64(input), expected);
}
#[wasm_bindgen_test(unsupported = test)]
fn test_base64_to_bytes() {
let input = "aGVsbG8";
let expected = b"hello".to_vec();
assert_eq!(base64_to_bytes(input).unwrap(), expected);
}
#[test] // insta doesn't support wasm-bindgen-test
fn test_base64_to_bytes_error() {
let input = "===";
assert_debug_snapshot!(base64_to_bytes(input));
}
#[wasm_bindgen_test(unsupported = test)]
fn merge_text() {
let left = b"hello ";
let right = b"world";
assert_eq!(merge(b"", left, right), b"hello world".to_vec());
}
#[wasm_bindgen_test(unsupported = test)]
fn merge_binary() {
let left = [0, 1, 2];
let right = [3, 4, 5];
assert_eq!(merge(b"", &left, &right), right);
}
#[wasm_bindgen_test(unsupported = test)]
fn test_is_binary() {
assert!(is_binary(&[0, 159, 146, 150]));
assert!(is_binary(&[0, 12]));
assert!(!is_binary(b"hello"));
} }