use crate::codegen::fb_unbounded;
use crate::projection::flatbuffers::{MAX_ENCODABLE, max_size, root_table};
use crate::v2::Package;
use super::{AbsentCause, Ctx, SizeState, UnboundedCause};
pub(crate) fn state<'a>(home: &'a Package, type_name: &str, ctx: &Ctx<'a>) -> SizeState {
let Some((decl, declaring)) = ctx.resolve(home, type_name) else {
return SizeState::Absent(AbsentCause::Unresolved);
};
if root_table(decl).is_none() {
return SizeState::Absent(AbsentCause::Unresolved);
}
let Some(packages) = ctx.packages_for(declaring) else {
return SizeState::Absent(AbsentCause::Unresolved);
};
match max_size(packages, decl) {
Some(bytes) => {
debug_assert!(bytes <= MAX_ENCODABLE);
SizeState::Bounded(
u32::try_from(bytes).expect("max_size refuses a bound above MAX_ENCODABLE"),
)
}
None => SizeState::Unbounded(cause_of(fb_unbounded(declaring, decl).cause)),
}
}
fn cause_of(cause: i32) -> UnboundedCause {
UnboundedCause::try_from(cause).unwrap_or(UnboundedCause::Unspecified)
}
#[cfg(test)]
mod tests {
use super::super::tests_support::*;
use super::super::{
AbsentCause, Ctx, Encoding, PayloadShape, SizeState, UnboundedCause, size_state,
};
use super::*;
use crate::projection::flatbuffers::{Packages, max_size};
use crate::v2::{
Backing, Constraint, Decl, Field, FieldType, Package, PrimitiveType, StructDef,
StructMember, TypeDef, backing, decl, struct_member,
};
#[test]
fn a_bounded_type_advertises_the_projections_number() {
let package = fixture();
let others: [&Package; 0] = [];
let ctx = Ctx::new(&package, &others);
for name in ["Point", "Vin", "Coord", "Bag", "Shape"] {
let (decl, _) = ctx.resolve(&package, name).unwrap();
let expected = max_size(
Packages {
package: &package,
others: &[],
},
decl,
)
.unwrap_or_else(|| panic!("{name} is bounded in the fixture"));
assert_eq!(
state(&package, name, &ctx),
SizeState::Bounded(u32::try_from(expected).unwrap()),
"{name}"
);
}
}
#[test]
fn an_unbounded_type_carries_the_models_cause() {
let package = unbounded_fixture();
let others: [&Package; 0] = [];
let ctx = Ctx::new(&package, &others);
assert_eq!(
state(&package, "Open", &ctx),
SizeState::Unbounded(UnboundedCause::Member)
);
}
#[test]
fn a_name_with_no_root_table_or_no_declaration_is_absent() {
let package = fixture();
let others: [&Package; 0] = [];
let ctx = Ctx::new(&package, &others);
assert_eq!(
state(&package, "Missing", &ctx),
SizeState::Absent(AbsentCause::Unresolved)
);
assert_eq!(
state(&package, "LIMIT", &ctx),
SizeState::Absent(AbsentCause::Unresolved)
);
}
#[test]
fn size_state_routes_the_flatbuffers_column() {
let package = fixture();
let others: [&Package; 0] = [];
let ctx = Ctx::new(&package, &others);
assert_eq!(
size_state(
&package,
&PayloadShape::Named("Point"),
&ctx,
Encoding::FlatBuffers
),
SizeState::Bounded(55)
);
}
#[test]
fn an_imported_declaration_is_sized_in_its_own_package() {
let (root, imported) = two_package_fixture();
let others = [&imported];
let ctx = Ctx::new(&root, &others);
let (thing, _) = ctx.resolve(&root, "q.Thing").expect("q.Thing resolves");
let in_q = max_size(
Packages {
package: &imported,
others: &[&root],
},
thing,
)
.expect("bounded in q");
let in_p = max_size(
Packages {
package: &root,
others: &[&imported],
},
thing,
)
.expect("bounded in p too, with the wider `Inner`");
assert_ne!(in_q, in_p, "the two `Inner`s differ in size");
assert_eq!(
state(&root, "q.Thing", &ctx),
SizeState::Bounded(u32::try_from(in_q).unwrap())
);
assert_eq!(
state(&root, "q.Loose", &ctx),
SizeState::Unbounded(UnboundedCause::Member)
);
}
#[test]
fn every_model_cause_reads_back_as_itself() {
for model in [
UnboundedCause::Member,
UnboundedCause::Untyped,
UnboundedCause::Layout,
UnboundedCause::Aggregate,
UnboundedCause::Exempt,
UnboundedCause::Unspecified,
] {
assert_eq!(cause_of(model as i32), model, "{model:?}");
}
assert_eq!(
cause_of(99),
UnboundedCause::Unspecified,
"a cause tag the model does not define"
);
}
fn one_member_struct(name: &str, members: Vec<StructMember>) -> Decl {
Decl {
name: name.to_owned(),
kind: Some(decl::Kind::StructDef(StructDef {
members,
fixed_layout: false,
})),
..Default::default()
}
}
fn member(name: &str, ordinal: u32, ty: Option<FieldType>) -> StructMember {
StructMember {
member: Some(struct_member::Member::Field(Box::new(Field {
name: name.to_owned(),
ordinal,
r#type: ty,
..Default::default()
}))),
}
}
fn bytes_max(len_max: u64) -> FieldType {
inline(TypeDef {
backing: Some(Backing {
kind: Some(backing::Kind::Primitive(PrimitiveType::Bytes as i32)),
}),
constraint: Some(Constraint {
len_max: Some(len_max),
..Default::default()
}),
..Default::default()
})
}
#[test]
fn each_unbounded_path_carries_its_own_cause() {
let (root, imported) = two_package_fixture();
let mut root = root;
root.decls
.push(one_member_struct("NoType", vec![member("f", 1, None)]));
root.decls.push(one_member_struct(
"Clash",
vec![
member("a", 1, Some(named("Hole"))),
member("b", 1, Some(named("Hole"))),
],
));
root.decls.push(one_member_struct(
"TooBig",
vec![
member("a", 1, Some(bytes_max(1 << 31))),
member("b", 2, Some(bytes_max(1 << 31))),
],
));
root.decls.push(one_member_struct(
"Far",
vec![member("hole", 1, Some(named("q.Hole")))],
));
let others = [&imported];
let ctx = Ctx::new(&root, &others);
assert_eq!(
state(&root, "NoType", &ctx),
SizeState::Unbounded(UnboundedCause::Untyped)
);
assert_eq!(
state(&root, "Clash", &ctx),
SizeState::Unbounded(UnboundedCause::Layout)
);
assert_eq!(
state(&root, "TooBig", &ctx),
SizeState::Unbounded(UnboundedCause::Aggregate)
);
assert_eq!(
state(&root, "Far", &ctx),
SizeState::Unbounded(UnboundedCause::Exempt)
);
}
}