Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/jsonschema-testsuite-codegen/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fn generate_nested_structure(
let is_optional = case_path.iter().any(|segment| segment == "optional");
let should_ignore = xfail.iter().any(|x| full_test_path.starts_with(x));
let ignore_attr = if should_ignore {
quote! { #[ignore] }
quote! { #[ignore = "known failure listed in xfail"] }
} else {
quote! {}
};
Expand All @@ -86,7 +86,7 @@ fn generate_nested_structure(
data: serde_json::from_str(#data).expect("Failed to load JSON"),
valid: #valid,
};
inner_test(test);
inner_test(&test);
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion crates/jsonschema-testsuite-codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ pub fn suite(args: TokenStream, input: TokenStream) -> TokenStream {
use super::#test_func_ident;

#[inline]
fn inner_test(test: Test) {
fn inner_test(test: &Test) {
#test_func_ident(test);
}
#modules
Expand Down
4 changes: 2 additions & 2 deletions crates/jsonschema/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ tempfile = "3.13.0"
test-case = "3"
tokio = { version = "1", features = ["macros", "rt"] }

[lints.clippy]
result_large_err = "allow"
[lints]
workspace = true

[[bench]]
harness = false
Expand Down
15 changes: 10 additions & 5 deletions crates/jsonschema/benches/errors.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
#[cfg(not(target_arch = "wasm32"))]
mod bench {
pub use benchmark::run_error_formatting_benchmarks;
pub use criterion::{criterion_group, BenchmarkId, Criterion};
pub use serde_json::Value;
pub(crate) use benchmark::run_error_formatting_benchmarks;
pub(crate) use criterion::{criterion_group, BenchmarkId, Criterion};
pub(crate) use serde_json::Value;

pub fn bench_error_formatting(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
pub(crate) fn bench_error_formatting(
c: &mut Criterion,
name: &str,
schema: &Value,
instance: &Value,
) {
let validator = jsonschema::validator_for(schema).expect("Valid schema");
let error = validator.validate(instance).unwrap_err();

Expand All @@ -15,7 +20,7 @@ mod bench {
);
}

pub fn run_benchmarks(c: &mut Criterion) {
pub(crate) fn run_benchmarks(c: &mut Criterion) {
run_error_formatting_benchmarks(&mut |name, schema, instance| {
bench_error_formatting(c, name, schema, instance);
});
Expand Down
16 changes: 8 additions & 8 deletions crates/jsonschema/benches/jsonschema.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
#[cfg(not(target_arch = "wasm32"))]
mod bench {
pub use benchmark::Benchmark;
pub use codspeed_criterion_compat::{criterion_group, BenchmarkId, Criterion};
pub use serde_json::Value;
pub(crate) use benchmark::Benchmark;
pub(crate) use codspeed_criterion_compat::{criterion_group, BenchmarkId, Criterion};
pub(crate) use serde_json::Value;

pub fn bench_build(c: &mut Criterion, name: &str, schema: &Value) {
pub(crate) fn bench_build(c: &mut Criterion, name: &str, schema: &Value) {
c.bench_with_input(BenchmarkId::new("build", name), schema, |b, schema| {
b.iter_with_large_drop(|| jsonschema::validator_for(schema).expect("Valid schema"));
});
}

pub fn bench_is_valid(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
pub(crate) fn bench_is_valid(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
let validator = jsonschema::validator_for(schema).expect("Valid schema");
c.bench_with_input(
BenchmarkId::new("is_valid", name),
Expand All @@ -23,7 +23,7 @@ mod bench {
);
}

pub fn bench_validate(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
pub(crate) fn bench_validate(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
let validator = jsonschema::validator_for(schema).expect("Valid schema");
c.bench_with_input(
BenchmarkId::new("validate", name),
Expand All @@ -36,14 +36,14 @@ mod bench {
);
}

pub fn bench_apply(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
pub(crate) fn bench_apply(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
let validator = jsonschema::validator_for(schema).expect("Valid schema");
c.bench_with_input(BenchmarkId::new("apply", name), instance, |b, instance| {
b.iter_with_large_drop(|| validator.apply(instance).basic());
});
}

pub fn run_benchmarks(c: &mut Criterion) {
pub(crate) fn run_benchmarks(c: &mut Criterion) {
for benchmark in Benchmark::iter() {
benchmark.run(&mut |name, schema, instances| {
bench_build(c, name, schema);
Expand Down
26 changes: 18 additions & 8 deletions crates/jsonschema/benches/keywords.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
#[cfg(not(target_arch = "wasm32"))]
mod bench {
pub use benchmark::run_keyword_benchmarks;
pub use criterion::{criterion_group, BenchmarkId, Criterion};
pub use serde_json::Value;
pub(crate) use benchmark::run_keyword_benchmarks;
pub(crate) use criterion::{criterion_group, BenchmarkId, Criterion};
pub(crate) use serde_json::Value;

pub fn validator_for(schema: &Value) -> jsonschema::Validator {
pub(crate) fn validator_for(schema: &Value) -> jsonschema::Validator {
jsonschema::options()
.with_draft(jsonschema::Draft::Draft7)
.build(schema)
.expect("Schema used in benchmarks should compile")
}

pub fn bench_keyword_build(c: &mut Criterion, name: &str, schema: &Value) {
pub(crate) fn bench_keyword_build(c: &mut Criterion, name: &str, schema: &Value) {
c.bench_function(&format!("keyword/{name}/build"), |b| {
b.iter_with_large_drop(|| validator_for(schema));
});
}

pub fn bench_keyword_is_valid(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
pub(crate) fn bench_keyword_is_valid(
c: &mut Criterion,
name: &str,
schema: &Value,
instance: &Value,
) {
let validator = validator_for(schema);
c.bench_with_input(
BenchmarkId::new(format!("keyword/{name}"), "is_valid"),
Expand All @@ -30,7 +35,12 @@ mod bench {
);
}

pub fn bench_keyword_validate(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
pub(crate) fn bench_keyword_validate(
c: &mut Criterion,
name: &str,
schema: &Value,
instance: &Value,
) {
let validator = validator_for(schema);
c.bench_with_input(
BenchmarkId::new(format!("keyword/{name}"), "validate"),
Expand All @@ -43,7 +53,7 @@ mod bench {
);
}

pub fn run_benchmarks(c: &mut Criterion) {
pub(crate) fn run_benchmarks(c: &mut Criterion) {
run_keyword_benchmarks(&mut |name, schema, instances| {
bench_keyword_build(c, name, schema);
for instance in instances {
Expand Down
10 changes: 5 additions & 5 deletions crates/jsonschema/benches/location.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#[cfg(not(target_arch = "wasm32"))]
mod bench {
pub use criterion::{criterion_group, BenchmarkId, Criterion};
pub use jsonschema::paths::{Location, LocationSegment};
pub use std::hint::black_box;
pub(crate) use criterion::{criterion_group, BenchmarkId, Criterion};
pub(crate) use jsonschema::paths::{Location, LocationSegment};
pub(crate) use std::hint::black_box;

pub fn benchmark_into_iterator(c: &mut Criterion) {
pub(crate) fn benchmark_into_iterator(c: &mut Criterion) {
let empty = Location::new();
let small = vec!["a", "b", "c"]
.into_iter()
Expand All @@ -25,7 +25,7 @@ mod bench {
}
}

pub fn benchmark_from_iterator(c: &mut Criterion) {
pub(crate) fn benchmark_from_iterator(c: &mut Criterion) {
let empty = vec![];
let small = vec![
LocationSegment::from("a"),
Expand Down
4 changes: 2 additions & 2 deletions crates/jsonschema/benches/unevaluated_properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod bench {
fn large_schema() -> Value {
let mut properties = serde_json::Map::new();
for i in 0..50 {
properties.insert(format!("prop{}", i), json!({"type": "string"}));
properties.insert(format!("prop{i}"), json!({"type": "string"}));
}

json!({
Expand Down Expand Up @@ -63,7 +63,7 @@ mod bench {
fn large_invalid_instance() -> Value {
let mut obj = serde_json::Map::new();
for i in 0..50 {
obj.insert(format!("prop{}", i), json!("value"));
obj.insert(format!("prop{i}"), json!("value"));
}
obj.insert("unexpected".to_string(), json!("property"));
Value::Object(obj)
Expand Down
11 changes: 6 additions & 5 deletions crates/jsonschema/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ struct PropertyValidatorsPendingKey {
impl PropertyValidatorsPendingKey {
fn new(schema: &Map<String, Value>) -> Self {
Self {
schema_ptr: schema as *const _ as usize,
schema_ptr: std::ptr::from_ref(schema) as usize,
}
}
}
Expand All @@ -61,7 +61,7 @@ struct ItemsValidatorsPendingKey {
impl ItemsValidatorsPendingKey {
fn new(schema: &Map<String, Value>) -> Self {
Self {
schema_ptr: schema as *const _ as usize,
schema_ptr: std::ptr::from_ref(schema) as usize,
}
}
}
Expand Down Expand Up @@ -512,7 +512,7 @@ impl<'a> Context<'a> {
size_limit,
dfa_size_limit,
} => (backtrack_limit, size_limit, dfa_size_limit),
_ => (None, None, None),
PatternEngineOptions::Regex { .. } => (None, None, None),
};

let mut builder = fancy_regex::RegexBuilder::new(translated.as_ref());
Expand Down Expand Up @@ -554,7 +554,7 @@ impl<'a> Context<'a> {
size_limit,
dfa_size_limit,
} => (size_limit, dfa_size_limit),
_ => (None, None),
PatternEngineOptions::FancyRegex { .. } => (None, None),
};

let mut builder = regex::RegexBuilder::new(translated.as_ref());
Expand Down Expand Up @@ -778,6 +778,7 @@ pub(crate) fn compile_with_alias<'a>(
compile_with_internal(ctx, resource, Some(alias))
}

#[allow(clippy::needless_pass_by_value)]
fn compile_with_internal<'a>(
ctx: &Context,
resource: ResourceRef<'a>,
Expand Down Expand Up @@ -898,7 +899,7 @@ fn compile_without_cache<'a>(
validators.push((keyword, validator.map_err(ValidationError::to_owned)?));
} else if !ctx.is_known_keyword(keyword) {
// Treat all non-validation keywords as annotations
annotations.insert(keyword.to_string(), value.clone());
annotations.insert(keyword.clone(), value.clone());
}
}
let annotations = if annotations.is_empty() {
Expand Down
8 changes: 4 additions & 4 deletions crates/jsonschema/src/ext/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,10 @@ mod tests {
#[test_case("1e19", "10000000000000000001", false; "scientific integer mismatch")]
#[test_case("1.5e2", "150", true; "decimal scientific vs integer")]
#[test_case("1.5e2", "150.0", true; "decimal scientific vs decimal")]
#[test_case(r#"[0.1, 0.2, 0.3]"#, r#"[0.1, 0.2, 0.3]"#, true; "array exact match")]
#[test_case(r#"[0.1, 0.2]"#, r#"[0.10, 0.20]"#, true; "array with trailing zeros")]
#[test_case(r#"[18446744073709551616]"#, r#"[18446744073709551616]"#, true; "array with large integer")]
#[test_case(r#"[0.1, 0.2]"#, r#"[0.1, 0.3]"#, false; "array different values")]
#[test_case(r"[0.1, 0.2, 0.3]", r"[0.1, 0.2, 0.3]", true; "array exact match")]
#[test_case(r"[0.1, 0.2]", r"[0.10, 0.20]", true; "array with trailing zeros")]
#[test_case(r"[18446744073709551616]", r"[18446744073709551616]", true; "array with large integer")]
#[test_case(r"[0.1, 0.2]", r"[0.1, 0.3]", false; "array different values")]
#[test_case(r#"{"value": 0.1}"#, r#"{"value": 0.1}"#, true; "object exact match")]
#[test_case(r#"{"value": 0.1}"#, r#"{"value": 0.10}"#, true; "object with trailing zero")]
#[test_case(r#"{"id": 18446744073709551616}"#, r#"{"id": 18446744073709551616}"#, true; "object with large integer")]
Expand Down
24 changes: 16 additions & 8 deletions crates/jsonschema/src/ext/numeric.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::float_cmp
)]

use fraction::{BigFraction, One, Zero};
use serde_json::Number;

Expand Down Expand Up @@ -127,7 +135,7 @@ pub(crate) mod bignum {
/// would otherwise force us to append billions of zeros just to materialize the number,
/// opening the door to denial-of-service attacks. Limiting the exponent adjustment to
/// one million digits keeps conversions deterministic while still covering realistic
/// use-cases (10^1_000_000 is already astronomically large for JSON Schema).
/// use-cases (`10^1_000_000` is already astronomically large for JSON Schema).
const MAX_EXPONENT_ADJUSTMENT: u32 = 1_000_000;

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -255,20 +263,20 @@ pub(crate) mod bignum {
if shift <= 0 {
return false;
}
shift as u64 > MAX_EXPONENT_ADJUSTMENT as u64
shift as u64 > u64::from(MAX_EXPONENT_ADJUSTMENT)
}

fn exponent_reduction_exceeds_limit(exponent: i64) -> bool {
if exponent >= 0 {
return false;
}
match exponent.checked_abs() {
Some(abs) => abs as u64 > MAX_EXPONENT_ADJUSTMENT as u64,
Some(abs) => abs as u64 > u64::from(MAX_EXPONENT_ADJUSTMENT),
None => true,
}
}

/// Try to parse a Number as BigInt if it's outside i64 range or for compile-time
/// Try to parse a Number as `BigInt` if it's outside i64 range or for compile-time
/// schema values that need exact representation
pub(crate) fn try_parse_bigint(num: &Number) -> Option<BigInt> {
let num_str = num.as_str();
Expand Down Expand Up @@ -328,15 +336,15 @@ pub(crate) mod bignum {
Some(value)
}

/// Try to parse a Number as BigFraction for arbitrary precision decimal support
/// Try to parse a Number as `BigFraction` for arbitrary precision decimal support
///
/// Returns Some for numbers requiring exact decimal precision:
/// - Decimals with a decimal point (e.g., `0.1`, `123.456`)
/// - Scientific notation decimals that can't be represented exactly as f64
///
/// Returns None for:
/// - Integers that fit in i64 (handled by standard numeric path)
/// - Large integers including u64 beyond i64::MAX (handled by try_parse_bigint)
/// - Large integers including u64 beyond `i64::MAX` (handled by `try_parse_bigint`)
pub(crate) fn try_parse_bigfraction(num: &Number) -> Option<BigFraction> {
// Skip integers that fit in i64 - they don't need BigFraction
if num.as_i64().is_some() {
Expand Down Expand Up @@ -444,7 +452,7 @@ pub(crate) mod bignum {
f64_ge_bigint, f64_le_bigint, f64_gt_bigint, f64_lt_bigint, f64, BigInt, bigint_ge_f64, bigint_le_f64, bigint_gt_f64, bigint_lt_f64;
);

/// Check if a Number (as BigInt) is a multiple of another BigInt
/// Check if a Number (as `BigInt`) is a multiple of another `BigInt`
pub(crate) fn is_multiple_of_bigint(value: &BigInt, multiple: &BigInt) -> bool {
// Zero is a multiple of any non-zero number
// Mathematically: 0 = k * multiple for k = 0
Expand Down Expand Up @@ -494,7 +502,7 @@ pub(crate) mod bignum {
f64_ge_bigfrac, f64_le_bigfrac, f64_gt_bigfrac, f64_lt_bigfrac, f64, BigFraction, bigfrac_ge_f64, bigfrac_le_f64, bigfrac_gt_f64, bigfrac_lt_f64;
);

/// Check if a BigFraction is a multiple of another value
/// Check if a `BigFraction` is a multiple of another value
pub(crate) fn is_multiple_of_bigfrac(value: &BigFraction, multiple: &BigFraction) -> bool {
// Zero is a multiple of any non-zero number
if value.is_zero() {
Expand Down
10 changes: 6 additions & 4 deletions crates/jsonschema/src/keywords/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,13 @@ fn is_valid_idn_email(email: &str) -> bool {
fn is_valid_hostname(hostname: &str) -> bool {
const VALID_CHARS: [bool; 256] = {
let mut table = [false; 256];
let mut i = 0;
while i < 256 {
table[i] = matches!(i as u8, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-');
i += 1;
let mut byte: u8 = 0;
while byte < 255 {
table[byte as usize] = matches!(byte, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-');
byte += 1;
}
// Handle byte 255 separately to avoid overflow
table[255] = matches!(255u8, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-');
table
};

Expand Down
2 changes: 2 additions & 0 deletions crates/jsonschema/src/keywords/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::float_cmp, clippy::cast_sign_loss)]

use serde_json::{Map, Value};

use crate::{compiler, paths::Location, types::JsonType, ValidationError};
Expand Down
2 changes: 2 additions & 0 deletions crates/jsonschema/src/keywords/max_items.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::float_cmp, clippy::cast_sign_loss)]

use crate::{
compiler,
error::ValidationError,
Expand Down
2 changes: 2 additions & 0 deletions crates/jsonschema/src/keywords/max_length.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::float_cmp, clippy::cast_sign_loss)]

use crate::{
compiler,
error::ValidationError,
Expand Down
Loading
Loading