mod bytes;
mod collections;
mod float;
mod numeric;
mod regex;
mod special;
mod text;
use crate::cbor_utils::map_get;
use crate::native::core::state::MAX_DEPTH;
use crate::native::core::{ManyState, NativeTestCase, Span, Status, StopTest};
use ciborium::Value;
pub(crate) fn interpret_schema(
ntc: &mut NativeTestCase,
schema: &Value,
) -> Result<Value, StopTest> {
use crate::cbor_utils::as_text;
let schema_type = map_get(schema, "type")
.and_then(as_text)
.expect("Schema must have a \"type\" field");
let span_idx = ntc.spans.len();
let span_start = ntc.nodes.len();
let depth = ntc.span_stack.len() as u32;
let parent = ntc.span_stack.last().copied();
ntc.spans.push(Span {
start: span_start,
end: span_start,
label: schema_type.to_string(),
depth,
parent,
discarded: false,
});
ntc.span_stack.push(span_idx);
if depth + 1 > MAX_DEPTH && ntc.status.is_none() {
ntc.status = Some(Status::Invalid);
ntc.freeze();
}
let result = match schema_type {
"integer" => numeric::interpret_integer(ntc, schema),
"boolean" => numeric::interpret_boolean(ntc),
"constant" => numeric::interpret_constant(schema),
"null" => Ok(Value::Null),
"float" => float::interpret_float(ntc, schema),
"binary" => bytes::interpret_binary(ntc, schema),
"string" => text::interpret_string(ntc, schema),
"regex" => regex::interpret_regex(ntc, schema),
"tuple" => collections::interpret_tuple(ntc, schema),
"one_of" => collections::interpret_one_of(ntc, schema),
"sampled_from" => collections::interpret_sampled_from(ntc, schema),
"list" => collections::interpret_list(ntc, schema),
"dict" => collections::interpret_dict(ntc, schema),
"date" => special::interpret_date(ntc),
"time" => special::interpret_time(ntc),
"datetime" => special::interpret_datetime(ntc),
"ip_address" => special::interpret_ip_address(ntc, schema),
"uuid" => special::interpret_uuid(ntc, schema),
"email" | "url" | "domain" => {
todo!("schema {:?} not yet supported in native mode", schema_type)
}
other => panic!("Unknown schema type: {}", other),
};
ntc.span_stack.pop();
if let Some(span) = ntc.spans.get_mut(span_idx) {
span.end = ntc.nodes.len();
}
result
}
pub(crate) fn many_more(ntc: &mut NativeTestCase, state: &mut ManyState) -> Result<bool, StopTest> {
let should_continue = if state.min_size as f64 == state.max_size {
state.count < state.min_size
} else {
let forced = if state.force_stop {
Some(false)
} else if state.count < state.min_size {
Some(true)
} else if state.count as f64 >= state.max_size {
Some(false)
} else {
None
};
ntc.weighted(state.p_continue, forced)?
};
if should_continue {
state.count += 1;
}
Ok(should_continue)
}
pub(crate) fn many_reject(ntc: &mut NativeTestCase, state: &mut ManyState) -> Result<(), StopTest> {
assert!(state.count > 0);
state.count -= 1;
state.rejections += 1;
if state.rejections > std::cmp::max(3, 2 * state.count) {
if state.count < state.min_size {
ntc.status = Some(Status::Invalid);
return Err(StopTest);
} else {
state.force_stop = true;
}
}
Ok(())
}
pub(super) fn cbor_to_i128(value: &Value) -> i128 {
match value {
Value::Integer(i) => (*i).into(),
Value::Tag(2, inner) => {
let Value::Bytes(bytes) = inner.as_ref() else {
panic!("Expected Bytes inside bignum tag 2, got {:?}", inner)
};
let mut n = 0u128;
for b in bytes {
n = (n << 8) | (*b as u128);
}
i128::try_from(n).unwrap_or(i128::MAX)
}
Value::Tag(3, inner) => {
let Value::Bytes(bytes) = inner.as_ref() else {
panic!("Expected Bytes inside bignum tag 3, got {:?}", inner)
};
let mut n = 0u128;
for b in bytes {
n = (n << 8) | (*b as u128);
}
-1i128 - i128::try_from(n).unwrap_or(i128::MAX)
}
_ => panic!("Expected CBOR integer, got {:?}", value),
}
}
fn bignum_overflows_i128(value: &Value) -> bool {
match value {
Value::Tag(2, inner) => {
let Value::Bytes(bytes) = inner.as_ref() else {
return false;
};
if bytes.len() > 16 {
return true;
}
if bytes.len() == 16 && bytes[0] >= 0x80 {
return true;
}
let mut n = 0u128;
for b in bytes {
n = (n << 8) | (*b as u128);
}
n > i128::MAX as u128
}
_ => false,
}
}
fn u128_to_cbor(v: u128) -> Value {
if let Ok(n) = u64::try_from(v) {
return Value::Integer(n.into());
}
let bytes = v.to_be_bytes();
let first_nonzero = bytes
.iter()
.position(|&b| b != 0)
.unwrap_or(bytes.len() - 1);
Value::Tag(2, Box::new(Value::Bytes(bytes[first_nonzero..].to_vec())))
}
fn i128_to_cbor(v: i128) -> Value {
if let Ok(n) = i64::try_from(v) {
Value::Integer(n.into())
} else if let Ok(n) = u64::try_from(v) {
Value::Integer(n.into())
} else {
crate::cbor_utils::cbor_serialize(&v)
}
}
#[cfg(test)]
#[path = "../../../tests/embedded/native/schema/mod_tests.rs"]
mod tests;