oversync-core 0.6.1

Core types, traits, and config for the oversync data sync engine.
Documentation
#[derive(Debug, thiserror::Error)]
pub enum OversyncError {
	#[error("surrealdb: {0}")]
	SurrealDb(String),

	#[error("connector: {0}")]
	Connector(String),

	#[error("sink: {0}")]
	Sink(String),

	#[error("config: {0}")]
	Config(String),

	#[error("migration: {0}")]
	Migration(String),

	#[error("plugin: {0}")]
	Plugin(String),

	#[error("fail-safe: {deleted_count}/{previous_count} rows deleted (>{threshold_pct:.0}%)")]
	FailSafe {
		deleted_count: usize,
		previous_count: usize,
		threshold_pct: f64,
	},

	#[error("internal: {0}")]
	Internal(String),
}

impl OversyncError {
	pub fn is_fail_safe(&self) -> bool {
		matches!(self, Self::FailSafe { .. })
	}
}

impl From<serde_json::Error> for OversyncError {
	fn from(e: serde_json::Error) -> Self {
		Self::Internal(e.to_string())
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn display_all_variants() {
		let cases: Vec<(OversyncError, &str)> = vec![
			(OversyncError::SurrealDb("conn".into()), "surrealdb: conn"),
			(
				OversyncError::Connector("timeout".into()),
				"connector: timeout",
			),
			(OversyncError::Sink("full".into()), "sink: full"),
			(OversyncError::Config("bad".into()), "config: bad"),
			(OversyncError::Migration("v1".into()), "migration: v1"),
			(OversyncError::Plugin("missing".into()), "plugin: missing"),
			(
				OversyncError::FailSafe {
					deleted_count: 8,
					previous_count: 10,
					threshold_pct: 30.0,
				},
				"fail-safe: 8/10 rows deleted (>30%)",
			),
			(OversyncError::Internal("oops".into()), "internal: oops"),
		];
		for (err, expected) in cases {
			assert_eq!(err.to_string(), expected);
		}
	}

	#[test]
	fn serde_json_error_converts() {
		let bad_json = serde_json::from_str::<serde_json::Value>("not json");
		let oversync_err: OversyncError = bad_json.unwrap_err().into();
		assert!(matches!(oversync_err, OversyncError::Internal(_)));
	}

	#[test]
	fn fail_safe_helper_detects_variant() {
		let err = OversyncError::FailSafe {
			deleted_count: 4,
			previous_count: 5,
			threshold_pct: 30.0,
		};
		assert!(err.is_fail_safe());
		assert!(!OversyncError::Internal("oops".into()).is_fail_safe());
	}
}