#![allow(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::float_cmp
)]
#![allow(clippy::incompatible_msrv)]
mod container;
mod fraction;
mod pool;
mod scalar;
use hegel::{generators as gs, TestCase};
use jsonschema::{
canonical::{CanonicalSchema, CanonicalView},
JsonType,
};
use serde_json::{Number, Value};
#[allow(unused_imports)]
pub(crate) use pool::{
aliased_number, arbitrary_instance, arbitrary_scalar, draw_keys, finite_float, small_int,
wide_number,
};
pub(crate) const MAX_SIZE: u64 = 8;
pub(crate) const MAX_ATTEMPTS: usize = 8;
pub(crate) fn size_floor(bound: Option<&Number>) -> u64 {
bound.map_or(0, |bound| bound.as_u64().unwrap_or(u64::MAX))
}
pub(crate) fn size_ceiling(bound: Option<&Number>) -> u64 {
bound.map_or(u64::MAX, |bound| bound.as_u64().unwrap_or(u64::MAX))
}
fn value_has_type(value: &Value, ty: JsonType) -> bool {
match ty {
JsonType::Null => value.is_null(),
JsonType::Boolean => value.is_boolean(),
JsonType::Integer => value.is_i64() || value.is_u64(),
JsonType::Number => value.is_number(),
JsonType::String => value.is_string(),
JsonType::Array => value.is_array(),
JsonType::Object => value.is_object(),
}
}
#[derive(Clone, Copy)]
pub(crate) struct Sampler<'a> {
pub(crate) tc: &'a TestCase,
pub(crate) root: &'a CanonicalSchema,
pub(crate) depth: u8,
}
impl Sampler<'_> {
pub(crate) fn descend(&self, schema: &CanonicalSchema) -> Option<Value> {
Sampler {
depth: self.depth.saturating_sub(1),
..*self
}
.draw(schema)
}
pub(crate) fn draw(&self, schema: &CanonicalSchema) -> Option<Value> {
if self.depth == 0 {
return None;
}
match schema.view() {
CanonicalView::False => None,
CanonicalView::True | CanonicalView::Raw(_) | CanonicalView::Not(_) => {
Some(self.tc.draw(arbitrary_instance()))
}
CanonicalView::Const(value) => Some(value),
CanonicalView::Enum(values) => {
let index = self.tc.draw(
gs::integers::<usize>()
.min_value(0)
.max_value(values.len() - 1),
);
values.into_iter().nth(index)
}
CanonicalView::MultiType(set) => {
let types: Vec<JsonType> = set.iter().collect();
let ty = self.tc.draw(gs::sampled_from(types));
Some(pool::draw_unconstrained(self.tc, ty))
}
CanonicalView::TypedGroup(group) => {
let value = self.descend(&group.body)?;
value_has_type(&value, group.ty).then_some(value)
}
CanonicalView::String(view) => scalar::draw_string(
self.tc,
view.min_length.as_ref(),
view.max_length.as_ref(),
&view.patterns,
&view.formats,
&view.excluded,
&view.content_media_types,
&view.content_encodings,
),
CanonicalView::Integer(view) => scalar::draw_integer(
self.tc,
view.minimum.as_ref(),
view.maximum.as_ref(),
&view.multiple_of,
&view.not_multiple_of,
),
CanonicalView::Number(view) => scalar::draw_number(
self.tc,
view.minimum.as_ref(),
view.exclusive_minimum,
view.maximum.as_ref(),
view.exclusive_maximum,
&view.multiple_of,
&view.not_multiple_of,
view.excludes_integers,
),
CanonicalView::Array(view) => container::draw_array(
self,
container::ArrayFacets {
min_items: view.min_items,
max_items: view.max_items,
distinctness: view.distinctness,
prefix_items: view.prefix_items,
items: view.items,
contains: view.contains,
},
),
CanonicalView::Object(view) => container::draw_object(
self,
&container::ObjectFacets {
draft: schema.draft(),
min_properties: view.min_properties,
max_properties: view.max_properties,
required: view.required,
property_names: view.property_names,
properties: view.properties,
pattern_properties: view.pattern_properties,
additional_properties: view.additional_properties,
violations: view.violations,
},
),
CanonicalView::AllOf(branches) => {
let mut resolved = Vec::new();
for branch in &branches {
match branch.view() {
CanonicalView::Reference(uri) => {
let target = if uri == "#" {
self.root.clone()
} else {
branch.definition(&uri)?
};
resolved.push(target);
}
_ => resolved.push(branch.clone()),
}
}
let mut folded = resolved.first()?.clone();
let mut fold_failed = false;
for other in &resolved[1..] {
if let Ok(next) = folded.intersect(other) {
folded = next;
} else {
fold_failed = true;
break;
}
}
if !fold_failed && !matches!(folded.view(), CanonicalView::AllOf(_)) {
return self.descend(&folded);
}
let index = self.tc.draw(
gs::integers::<usize>()
.min_value(0)
.max_value(branches.len() - 1),
);
self.descend(&branches[index])
}
CanonicalView::AnyOf(branches) | CanonicalView::OneOf(branches) => {
let index = self.tc.draw(
gs::integers::<usize>()
.min_value(0)
.max_value(branches.len() - 1),
);
self.descend(&branches[index])
}
CanonicalView::Reference(uri) => {
let target = if uri == "#" {
Some(self.root.clone())
} else {
schema.definition(&uri)
};
self.descend(&target?)
}
}
}
}
pub(crate) fn draw_valid_instance(
tc: &TestCase,
canonical: &CanonicalSchema,
validator: &jsonschema::Validator,
) -> Option<Value> {
let sampler = Sampler {
tc,
root: canonical,
depth: 5,
};
for _ in 0..MAX_ATTEMPTS {
let Some(candidate) = sampler.draw(canonical) else {
continue;
};
if validator.is_valid(&candidate) {
return Some(candidate);
}
}
None
}