Add diff applying error & improve CI (#32)

* Use stable rust

* Add From impls

* Revert to nightly

* Improve dev env & CI setup

* Update lock

* Add thiserror

* Add diff error

* Fix tests

* Lint

* Rename NumberOrString

* Format

* Fix lint script
This commit is contained in:
Andras Schmelczer 2025-12-06 21:54:08 +00:00 • committed by GitHub
commit 88d48afce3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

105
src/types/number_or_text.rs Normal file
View file

@ -0,0 +1,105 @@
use std::{borrow::Cow, fmt::Debug};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
#[cfg(feature = "wasm")]
#[allow(clippy::cast_precision_loss)]
const INTEGRAL_LIMIT: f64 = (1u64 << 53) as f64;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
#[derive(Debug, Clone, PartialEq)]
pub enum NumberOrText {
Number(i64),
Text(String),
}
#[cfg(feature = "wasm")]
impl TryFrom<JsValue> for NumberOrText {
type Error = DeserialisationError;
fn try_from(value: JsValue) -> Result<Self, Self::Error> {
if let Ok(num) = value.clone().try_into() {
return Ok(NumberOrText::Number(num));
}
if let Some(num) = value.clone().as_f64() {
if num.abs() > INTEGRAL_LIMIT {
return Err(DeserialisationError::new(
"Floating-point number exceeds safe integer limit, use BigInt instead",
));
}
#[allow(clippy::cast_possible_truncation)]
return Ok(NumberOrText::Number(num.round() as i64));
}
if let Ok(text) = value.try_into() {
return Ok(NumberOrText::Text(text));
}
Err(DeserialisationError::new(
"Could not parse JsValue as either number or string",
))
}
}
#[cfg(feature = "wasm")]
impl From<NumberOrText> for JsValue {
fn from(value: NumberOrText) -> Self {
match value {
NumberOrText::Number(num) => JsValue::from(num),
NumberOrText::Text(text) => JsValue::from(text),
}
}
}
impl From<i64> for NumberOrText {
fn from(value: i64) -> Self { NumberOrText::Number(value) }
}
impl From<String> for NumberOrText {
fn from(value: String) -> Self { NumberOrText::Text(value) }
}
impl From<&str> for NumberOrText {
fn from(value: &str) -> Self { NumberOrText::Text(value.to_owned()) }
}
impl<'a> From<Cow<'a, str>> for NumberOrText {
fn from(value: Cow<'a, str>) -> Self { NumberOrText::Text(value.into_owned()) }
}
/// Error type for deserialisation failures
#[cfg(feature = "wasm")]
#[derive(Debug, Clone)]
pub struct DeserialisationError {
pub message: String,
}
#[cfg(feature = "wasm")]
impl DeserialisationError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
#[cfg(feature = "wasm")]
impl std::fmt::Display for DeserialisationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Deserialisation error: {}", self.message)
}
}
#[cfg(feature = "wasm")]
impl std::error::Error for DeserialisationError {}
#[cfg(feature = "wasm")]
impl From<DeserialisationError> for JsValue {
fn from(error: DeserialisationError) -> Self { JsValue::from_str(&error.message) }
}