use std::collections::BTreeMap;
use serde::Deserialize;
use serde_json::Value;
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncScenarioFixture {
pub actors: SyncScenarioActors,
pub subscription: SyncScenarioSubscription,
pub owner_conflict: OwnerConflictScenario,
pub revoked_subscription: RevokedSubscriptionScenario,
pub retry_backoff: RetryBackoffScenario,
pub snapshot_chunk: SnapshotChunkScenario,
pub repeated_pull: RepeatedPullScenario,
pub duplicate_push: DuplicatePushScenario,
pub conflict_keep_local: ConflictKeepLocalScenario,
pub realtime: RealtimeScenario,
pub live_query: LiveQueryScenario,
pub worker_auth: WorkerAuthScenario,
pub auth_refresh: AuthRefreshScenario,
pub revoked_session: RevokedSessionScenario,
pub schema_version: SchemaVersionScenario,
pub e2ee: E2eeScenario,
pub blob: BlobScenario,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SyncScenarioActors {
#[serde(rename = "ownerA")]
pub owner_a: SyncScenarioActor,
#[serde(rename = "ownerB")]
pub owner_b: SyncScenarioActor,
pub rust: RustSyncScenarioActor,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncScenarioActor {
pub actor_id: String,
pub token: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RustSyncScenarioActor {
pub actor_id: String,
pub project_id: String,
pub token: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SyncScenarioSubscription {
pub id: String,
pub table: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OwnerConflictScenario {
pub client_id: String,
pub first_file_name: String,
pub second_file_name: String,
pub expected_error_pattern: String,
pub expected_refresh_count: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RevokedSubscriptionScenario {
pub client_id: String,
pub revoked_actor_id: String,
pub seed_task: SyncScenarioVersionedTask,
pub expected_status: String,
pub expected_scopes: BTreeMap<String, Value>,
pub expected_cursor_sequence: Vec<i64>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RetryBackoffScenario {
pub client_id: String,
pub local_row: SyncScenarioTaskRow,
pub expected_sync_post_counts: Vec<i64>,
pub expected_pending_pushes: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SnapshotChunkScenario {
pub client_id: String,
pub failure_client_id: String,
pub chunk_id: String,
pub byte_length: i64,
pub sha256: String,
pub encoding: String,
pub compression: String,
pub expected_error_pattern: String,
pub server_task: SyncScenarioVersionedTask,
pub browser_server_task: SyncScenarioTaskInput,
pub local_row: SyncScenarioTaskRow,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RepeatedPullScenario {
pub client_id: String,
pub task: SyncScenarioVersionedTask,
pub expected_cursor: i64,
pub expected_browser_cursor: i64,
pub expected_row_count: i64,
pub expected_pull_count: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DuplicatePushScenario {
pub client_id: String,
pub task: SyncScenarioTaskRow,
pub expected_first_push_commits: i64,
pub expected_second_push_commits: i64,
pub expected_server_row_count: i64,
pub expected_outbox_status: String,
pub expected_conflict_count: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConflictKeepLocalScenario {
pub client_id: String,
pub keep_server_client_id: String,
pub dismiss_client_id: String,
pub row_id: String,
pub local_title: String,
pub server_title: String,
pub stale_base_version: i64,
pub server_version: i64,
pub conflict_code: String,
pub conflict_message: String,
pub browser_conflict_message: String,
pub keep_server_resolution: String,
pub dismiss_resolution: String,
pub expected_initial_conflict_count: i64,
pub expected_after_resolve_conflict_count: i64,
pub expected_after_retry_conflict_count: i64,
pub expected_retry_push_commits: i64,
pub retry_base_version: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RealtimeScenario {
pub client_a_id: String,
pub client_b_id: String,
pub auth_refresh_client_id: String,
pub websocket_token: String,
pub refreshed_websocket_token: String,
pub expected_auth_tokens: Vec<String>,
pub expected_connection_count: i64,
pub presence_event: String,
pub expected_event_debug: Vec<String>,
pub task: SyncScenarioVersionedTask,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LiveQueryScenario {
pub client_a_id: String,
pub client_b_id: String,
pub query_sql: String,
pub tables: Vec<String>,
pub expected_initial_rows: i64,
pub expected_events_before_unsubscribe: i64,
pub expected_events_after_unsubscribe: i64,
pub first_task: SyncScenarioTaskInput,
pub second_task: SyncScenarioTaskInput,
pub third_task: SyncScenarioTaskInput,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkerAuthScenario {
pub client_id: String,
pub authorization: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthRefreshScenario {
pub client_id: String,
pub initial_authorization: String,
pub refreshed_authorization: String,
pub expected_refresh_count: i64,
pub expected_auth_headers: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RevokedSessionScenario {
pub client_id: String,
pub authorization: String,
pub expected_status: u16,
pub expected_refresh_count: i64,
pub expected_retry_count: i64,
pub expected_error_pattern: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SchemaVersionScenario {
pub required_future_client_id: String,
pub latest_future_client_id: String,
pub invalid_outbox_client_id: String,
pub future_version_offset: i32,
pub expected_required_error_pattern: String,
pub expected_invalid_outbox_error_pattern: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct E2eeScenario {
pub client_id: String,
pub pull_client_id: String,
pub key_base64: String,
pub envelope_prefix: String,
pub rule: E2eeRuleScenario,
pub task: SyncScenarioTaskInput,
pub conflict: E2eeConflictScenario,
pub chunk: E2eeChunkScenario,
pub server_version: i64,
pub expected_decrypted_row_count: i64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct E2eeRuleScenario {
pub scope: String,
pub table: String,
pub fields: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct E2eeConflictScenario {
pub seed_client_id: String,
pub client_id: String,
pub row_id: String,
pub server_title: String,
pub local_title: String,
pub stale_base_version: i64,
pub expected_conflict_count: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct E2eeChunkScenario {
pub seed_client_id: String,
pub client_id: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlobScenario {
pub client_id: String,
pub browser_client_id: String,
pub streaming_client_id: String,
pub dedupe_client_id: String,
pub auth_failure_client_id: String,
pub interrupted_upload_client_id: String,
pub missing_client_id: String,
pub cache_prune_client_id: String,
pub actor_id: String,
pub browser_actor_id: String,
pub authorization: String,
pub stale_authorization: String,
pub mime_type: String,
pub text_mime_type: String,
pub bytes: Vec<u8>,
pub browser_text: String,
pub reference_sync: BlobReferenceSyncScenario,
pub dedupe_text: String,
pub auth_failure_text: String,
pub interrupted_upload_text: String,
pub cache_prune_old_text: String,
pub cache_prune_new_text: String,
pub streaming_byte_count: usize,
pub upload_token: String,
pub upload_path: String,
pub download_path: String,
pub expected_upload_queue_before: BlobQueueStats,
pub expected_upload_queue_after: BlobQueueStats,
pub expected_failed_queue: BlobQueueStats,
pub cache_prune_max_bytes: i64,
pub expected_cache_before_prune: BlobCacheStats,
pub expected_cache_pruned_bytes: i64,
pub expected_cache_after_prune: BlobCacheStats,
pub expected_process_uploaded: BlobProcessResult,
pub expected_process_retryable_failure: BlobProcessResult,
pub expected_process_permanent_failure: BlobProcessResult,
pub expected_auth_header_count: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlobReferenceSyncScenario {
pub source_client_id: String,
pub reader_client_id: String,
pub task: SyncScenarioTaskInput,
pub image: SyncScenarioBlobRef,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncScenarioBlobRef {
pub hash: String,
pub size: i64,
pub mime_type: String,
#[serde(default)]
pub encrypted: Option<bool>,
#[serde(default)]
pub key_id: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlobQueueStats {
pub pending: i64,
pub uploading: i64,
pub failed: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlobCacheStats {
pub count: i64,
pub total_bytes: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlobProcessResult {
pub uploaded: i32,
pub failed: i32,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncScenarioVersionedTask {
pub id: String,
pub title: String,
pub server_version: i64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SyncScenarioTaskInput {
pub id: String,
pub title: String,
#[serde(default)]
pub description: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SyncScenarioTaskRow {
pub id: String,
pub title: String,
pub completed: i64,
#[serde(default)]
pub user_id: Option<String>,
pub project_id: Option<String>,
pub server_version: i64,
pub image: Option<String>,
pub title_yjs_state: Option<String>,
}
pub fn sync_conformance_fixture() -> SyncScenarioFixture {
serde_json::from_str(include_str!("../conformance/sync-scenarios.json"))
.expect("typed sync conformance JSON")
}
pub fn sync_conformance() -> Value {
serde_json::from_str(include_str!("../conformance/sync-scenarios.json"))
.expect("sync conformance JSON")
}
pub fn sync_conformance_str(path: &[&str]) -> String {
sync_conformance_value(path)
.as_str()
.unwrap_or_else(|| panic!("sync conformance path {path:?} must be a string"))
.to_string()
}
pub fn sync_conformance_i64(path: &[&str]) -> i64 {
sync_conformance_value(path)
.as_i64()
.unwrap_or_else(|| panic!("sync conformance path {path:?} must be an integer"))
}
pub fn sync_conformance_i32(path: &[&str]) -> i32 {
sync_conformance_i64(path)
.try_into()
.unwrap_or_else(|_| panic!("sync conformance path {path:?} must fit in i32"))
}
pub fn sync_conformance_usize(path: &[&str]) -> usize {
sync_conformance_i64(path)
.try_into()
.unwrap_or_else(|_| panic!("sync conformance path {path:?} must fit in usize"))
}
pub fn sync_conformance_bytes(path: &[&str]) -> Vec<u8> {
sync_conformance_value(path)
.as_array()
.unwrap_or_else(|| panic!("sync conformance path {path:?} must be an array"))
.iter()
.map(|value| {
value
.as_u64()
.and_then(|byte| byte.try_into().ok())
.unwrap_or_else(|| panic!("sync conformance path {path:?} must contain bytes"))
})
.collect()
}
pub fn sync_conformance_value(path: &[&str]) -> Value {
let mut value = sync_conformance();
for segment in path {
value = value
.get(segment)
.unwrap_or_else(|| panic!("missing sync conformance path {path:?}"))
.clone();
}
value
}