#![allow(clippy::float_cmp, clippy::cast_sign_loss)]
use crate::LazyInstance;
use std::borrow::Cow;
use serde_json::{Map, Value};
use crate::{compiler, paths::Location, types::JsonType, Json, ValidationError};
pub(crate) fn size_limit<F: Json>(ctx: &compiler::Context<F>, schema: &Value) -> Option<u64> {
if let Some(limit) = schema.as_u64() {
return Some(limit);
}
if is_integer_past_u64(schema) {
return Some(u64::MAX);
}
if ctx.supports_integer_valued_numbers() {
#[allow(clippy::cast_possible_truncation)]
if let Some(limit) = schema
.as_f64()
.filter(|limit| *limit >= 0.0 && limit.trunc() == *limit)
{
return Some(limit as u64);
}
}
None
}
#[cfg(feature = "arbitrary-precision")]
fn is_integer_past_u64(schema: &Value) -> bool {
match schema {
Value::Number(number) => number.as_str().bytes().all(|byte| byte.is_ascii_digit()),
_ => false,
}
}
#[cfg(not(feature = "arbitrary-precision"))]
fn is_integer_past_u64(schema: &Value) -> bool {
schema
.as_f64()
.is_some_and(|value| value >= crate::canonical::json::U64_UPPER_EXCLUSIVE_F64)
}
#[inline]
pub(crate) fn map_get_u64<'a, F: Json>(
m: &'a Map<String, Value>,
ctx: &compiler::Context<F>,
keyword: &str,
) -> Option<Result<u64, ValidationError<'a>>> {
let schema_value = m.get(keyword)?;
Some(match size_limit(ctx, schema_value) {
Some(limit) => Ok(limit),
None => Err(fail_on_non_positive_integer(
schema_value,
ctx.location().join(keyword),
)),
})
}
pub(crate) fn fail_on_non_positive_integer(
schema_value: &Value,
schema_path: Location,
) -> ValidationError<'_> {
if schema_value.is_i64() {
ValidationError::minimum(
schema_path.clone(),
schema_path,
Location::new(),
LazyInstance::Ready(Cow::Borrowed(schema_value)),
0.into(),
)
} else {
ValidationError::single_type_error(
schema_path.clone(),
schema_path,
Location::new(),
LazyInstance::Ready(Cow::Borrowed(schema_value)),
JsonType::Integer,
)
}
}
#[cfg(test)]
mod tests {
use crate::tests_util;
use serde_json::{json, Value};
use test_case::test_case;
#[test_case(&json!({"maxItems": -1}), "-1 is less than the minimum of 0", "/maxItems"; "max items negative")]
#[test_case(&json!({"maxItems": "a"}), "\"a\" is not of type \"integer\"", "/maxItems"; "max items not integer")]
#[test_case(&json!({"minItems": -1}), "-1 is less than the minimum of 0", "/minItems"; "min items negative")]
#[test_case(&json!({"maxLength": -1}), "-1 is less than the minimum of 0", "/maxLength"; "max length negative")]
#[test_case(&json!({"minLength": "a"}), "\"a\" is not of type \"integer\"", "/minLength"; "min length not integer")]
#[test_case(&json!({"maxProperties": -1}), "-1 is less than the minimum of 0", "/maxProperties"; "max properties negative")]
#[test_case(&json!({"minProperties": 1.5}), "1.5 is not of type \"integer\"", "/minProperties"; "min properties fractional")]
#[test_case(&json!({"contains": {}, "minContains": -1}), "-1 is less than the minimum of 0", "/minContains"; "min contains negative")]
#[test_case(&json!({"contains": {}, "maxContains": "a"}), "\"a\" is not of type \"integer\"", "/maxContains"; "max contains not integer")]
fn malformed_size_limit(schema: &Value, message: &str, location: &str) {
tests_util::assert_compile_error(schema, message, location);
}
}