mod bytes;
mod collections;
mod float;
mod internet;
mod numeric;
mod regex;
mod special;
mod text;
use crate::cbor_utils::map_get;
use crate::native::bignum::{BigInt, Sign, ToPrimitive};
use crate::native::core::state::MAX_DEPTH;
use crate::native::core::{EngineError, ManyState, NativeTestCase, Span, Status};
use ciborium::Value;
pub(super) fn require<'a>(schema: &'a Value, field: &str) -> Result<&'a Value, EngineError> {
map_get(schema, field).ok_or_else(|| {
EngineError::InvalidArgument(format!("schema is missing required \"{field}\" field"))
})
}
pub(crate) fn interpret_schema(
ntc: &mut NativeTestCase,
schema: &Value,
) -> Result<Value, EngineError> {
use crate::cbor_utils::as_text;
let schema_type = map_get(schema, "type").and_then(as_text).ok_or_else(|| {
EngineError::InvalidArgument("schema is missing a string \"type\" field".to_string())
})?;
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),
"domain" => internet::interpret_domain(ntc, schema),
"email" => internet::interpret_email(ntc),
"url" => internet::interpret_url(ntc),
other => Err(EngineError::InvalidArgument(format!(
"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, EngineError> {
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<(), EngineError> {
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(EngineError::InvalidTestCase);
} else {
state.force_stop = true;
}
}
Ok(())
}
pub(super) fn cbor_to_bigint(value: &Value) -> Result<BigInt, EngineError> {
match value {
Value::Integer(i) => Ok(BigInt::from(i128::from(*i))),
Value::Tag(2, inner) => {
let Value::Bytes(bytes) = inner.as_ref() else {
return Err(EngineError::InvalidArgument(format!(
"expected bytes inside bignum tag 2, got {inner:?}"
)));
};
Ok(BigInt::from_bytes_be(Sign::Plus, bytes))
}
Value::Tag(3, inner) => {
let Value::Bytes(bytes) = inner.as_ref() else {
return Err(EngineError::InvalidArgument(format!(
"expected bytes inside bignum tag 3, got {inner:?}"
)));
};
Ok(-BigInt::from_bytes_be(Sign::Plus, bytes) - 1)
}
_ => Err(EngineError::InvalidArgument(format!(
"expected CBOR integer, got {value:?}"
))),
}
}
pub(super) fn bigint_to_cbor(v: &BigInt) -> Value {
if let Some(n) = v.to_i64() {
return Value::Integer(n.into());
}
if let Some(n) = v.to_u64() {
return Value::Integer(n.into());
}
if v.sign() == Sign::Minus {
let n = (-v) - BigInt::from(1);
let (_sign, bytes) = n.to_bytes_be();
Value::Tag(3, Box::new(Value::Bytes(bytes)))
} else {
let (_sign, bytes) = v.to_bytes_be();
Value::Tag(2, Box::new(Value::Bytes(bytes)))
}
}
#[cfg(test)]
#[path = "../../../tests/embedded/native/schema/mod_tests.rs"]
mod tests;