use qubit_budget::json::JsonResource;
use qubit_budget::json::JsonValueBudget;
use qubit_budget::json::JsonValueLimits;
use serde_json::Number;
use crate::decode::JsonDecodeNumberModel;
use crate::encode::JsonIntegerSignedness;
use crate::encode::JsonSerializationError;
use crate::encode::JsonSerializationErrorKind;
use crate::lexical::JsonLexicalError;
use crate::lexical::JsonLexicalErrorReason;
use crate::lexical::JsonLexicalScanner;
use crate::lexical::JsonNumberModel;
pub(crate) enum StrictJsonNumber {}
impl StrictJsonNumber {
pub(crate) fn parse(text: &str) -> Result<Number, JsonSerializationError> {
let bytes = text.as_bytes();
if bytes.first().is_none_or(|byte| !matches!(byte, b'-' | b'0'..=b'9'))
|| bytes.last().is_some_and(u8::is_ascii_whitespace)
{
return Err(invalid_number());
}
let limits = JsonValueLimits::<JsonResource, usize>::default();
let mut budget = JsonValueBudget::new(limits);
let mut transaction = budget.transaction();
let mut scanner =
JsonLexicalScanner::new(&mut transaction, false).with_number_model(JsonNumberModel::Integer64);
match scanner.scan(bytes) {
Ok(()) => Self::from_admitted_lexeme(text),
Err(JsonLexicalError::Syntax(failure)) => match failure.reason {
JsonLexicalErrorReason::IntegerOutOfRange => Err(integer_out_of_range(bytes)),
JsonLexicalErrorReason::FloatOutOfRange => {
Err(number_error(JsonSerializationErrorKind::NonFiniteFloat))
}
_ => Err(invalid_number()),
},
Err(JsonLexicalError::Budget(_)) => Err(invalid_number()),
}
}
pub(crate) fn from_admitted_lexeme(text: &str) -> Result<Number, JsonSerializationError> {
if text == "-0" {
return Number::from_f64(-0.0).ok_or_else(invalid_number);
}
if text.bytes().any(|byte| matches!(byte, b'.' | b'e' | b'E')) {
let value = text.parse::<f64>().map_err(|_| invalid_number())?;
if !value.is_finite() {
return Err(number_error(JsonSerializationErrorKind::NonFiniteFloat));
}
return Number::from_f64(value).ok_or_else(invalid_number);
}
if text.starts_with('-') {
let value = text
.parse::<i64>()
.map_err(|_| integer_error(JsonIntegerSignedness::Signed))?;
Ok(Number::from(value))
} else {
let value = text
.parse::<u64>()
.map_err(|_| integer_error(JsonIntegerSignedness::Unsigned))?;
Ok(Number::from(value))
}
}
pub(crate) fn from_admitted_lexeme_with_model(
text: &str,
model: JsonDecodeNumberModel,
) -> Result<Number, JsonSerializationError> {
match Self::from_admitted_lexeme(text) {
Ok(number) => Ok(number),
Err(error)
if model == JsonDecodeNumberModel::Integer64WithFiniteFallback
&& matches!(error.kind(), JsonSerializationErrorKind::IntegerOutOfRange { .. })
&& !text.bytes().any(|byte| matches!(byte, b'.' | b'e' | b'E')) =>
{
let value = text.parse::<f64>().map_err(|_| error)?;
if !value.is_finite() {
return Err(number_error(JsonSerializationErrorKind::NonFiniteFloat));
}
Number::from_f64(value).ok_or_else(|| number_error(JsonSerializationErrorKind::NonFiniteFloat))
}
Err(error) => Err(error),
}
}
pub(crate) fn from_i128(value: i128) -> Result<Number, JsonSerializationError> {
if let Ok(value) = i64::try_from(value) {
return Ok(Number::from(value));
}
if let Ok(value) = u64::try_from(value) {
return Ok(Number::from(value));
}
Err(integer_error(JsonIntegerSignedness::Signed))
}
pub(crate) fn from_u128(value: u128) -> Result<Number, JsonSerializationError> {
let value = u64::try_from(value).map_err(|_| integer_error(JsonIntegerSignedness::Unsigned))?;
Ok(Number::from(value))
}
}
fn invalid_number() -> JsonSerializationError {
number_error(JsonSerializationErrorKind::InvalidNumberRepresentation)
}
fn integer_out_of_range(bytes: &[u8]) -> JsonSerializationError {
let signedness = if bytes.first() == Some(&b'-') {
JsonIntegerSignedness::Signed
} else {
JsonIntegerSignedness::Unsigned
};
integer_error(signedness)
}
fn integer_error(signedness: JsonIntegerSignedness) -> JsonSerializationError {
number_error(JsonSerializationErrorKind::IntegerOutOfRange { signedness })
}
fn number_error(kind: JsonSerializationErrorKind) -> JsonSerializationError {
JsonSerializationError::new(kind)
}