use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SemanticLoss {
None,
Safe,
PotentiallyLossy,
Lossy,
Unsupported,
}
impl SemanticLoss {
pub fn blocks_default_migration(self) -> bool {
matches!(self, Self::Lossy | Self::Unsupported)
}
pub fn requires_allow_lossy(self) -> bool {
matches!(self, Self::Lossy)
}
pub fn worsen(self, other: Self) -> Self {
self.max(other)
}
}
impl fmt::Display for SemanticLoss {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "NONE"),
Self::Safe => write!(f, "SAFE"),
Self::PotentiallyLossy => write!(f, "POTENTIALLY_LOSSY"),
Self::Lossy => write!(f, "LOSSY"),
Self::Unsupported => write!(f, "UNSUPPORTED"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConversionPolicy {
pub allow_lossy: bool,
pub allow_potentially_lossy: bool,
pub prefer_bigint: bool,
pub reject_unsupported: bool,
}
impl Default for ConversionPolicy {
fn default() -> Self {
Self {
allow_lossy: false,
allow_potentially_lossy: true,
prefer_bigint: true,
reject_unsupported: false,
}
}
}
impl ConversionPolicy {
pub fn strict() -> Self {
Self {
allow_lossy: false,
allow_potentially_lossy: false,
prefer_bigint: true,
reject_unsupported: true,
}
}
pub fn permissive() -> Self {
Self {
allow_lossy: true,
allow_potentially_lossy: true,
prefer_bigint: true,
reject_unsupported: false,
}
}
pub fn allows(&self, loss: SemanticLoss) -> bool {
match loss {
SemanticLoss::None | SemanticLoss::Safe => true,
SemanticLoss::PotentiallyLossy => self.allow_potentially_lossy,
SemanticLoss::Lossy => self.allow_lossy,
SemanticLoss::Unsupported => !self.reject_unsupported,
}
}
}
pub const JS_MAX_SAFE_INTEGER: i64 = 9_007_199_254_740_991;
pub const JS_MIN_SAFE_INTEGER: i64 = -9_007_199_254_740_991;
pub fn integer_to_js_number_loss(value: i128) -> SemanticLoss {
if value >= i128::from(JS_MIN_SAFE_INTEGER) && value <= i128::from(JS_MAX_SAFE_INTEGER) {
SemanticLoss::None
} else {
SemanticLoss::Lossy
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn safe_integer_is_none() {
assert_eq!(integer_to_js_number_loss(42), SemanticLoss::None);
}
#[test]
fn unsafe_integer_is_lossy() {
assert_eq!(
integer_to_js_number_loss(9_007_199_254_740_993),
SemanticLoss::Lossy
);
}
#[test]
fn default_policy_rejects_lossy() {
assert!(!ConversionPolicy::default().allows(SemanticLoss::Lossy));
}
}