mod collections;
mod float;
mod numeric;
use crate::cbor_utils::map_get;
use crate::native::core::{ManyState, NativeTestCase, 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 is_leaf = matches!(
schema_type,
"integer" | "boolean" | "float" | "sampled_from"
);
let span_start = if is_leaf { ntc.nodes.len() } else { 0 };
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),
"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),
"string" | "binary" | "regex" | "email" | "url" | "domain" | "ip_address" | "uuid"
| "ipv4" | "ipv6" | "date" | "time" | "datetime" => {
todo!("schema {:?} not yet supported in native mode", schema_type)
}
other => panic!("Unknown schema type: {}", other),
};
if is_leaf && result.is_ok() {
ntc.record_span(span_start, ntc.nodes.len(), schema_type.to_string());
}
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;