use mockall::predicate::eq;
use treaty::{
any::{BorrowedStatic, BorrowedStaticHrt, OwnedStatic, TypeNameId},
effect::{blocking::Blocking, Effect, Effective, ErasedEffective},
protocol::{
visitor::{tags, SequenceProto, TagConst, TagProto, ValueProto, VisitResult},
DynVisitor,
},
walkers::core::r#struct::{StaticType, StructTypeInfo, StructWalker},
Builder, DefaultMode, Flow, Walker,
};
use crate::common::{
builder::MockBuilder,
protocol::{
sequence::{MockSequenceVisitor, SequenceScopeFactory},
tag::MockTagVisitor,
visitor::MockValueVisitor,
},
};
mod common;
struct X {
a: bool,
b: i32,
}
struct Info;
// This gives the struct walker enough information to walk the X struct.
impl<'ctx, M> StructTypeInfo<'ctx, M> for Info {
const NAME: &'static str = "X";
const FIELDS: &'static [&'static str] = &["a", "b"];
type FieldError = ();
type S = StaticType;
type T = X;
fn walk_field<'a, E: Effect>(
index: usize,
value: &'ctx Self::T,
mut visitor: DynVisitor<'a, 'ctx>,
) -> ErasedEffective<'a, Result<Flow, Self::FieldError>, E> {
E::from_future(async move {
match index {
// A real impl would be expected to tag these values with the field name.
0 => {
// Basic visit for the field value.
let obj = visitor
.upcast_mut::<ValueProto<OwnedStatic<bool>, E>>()
.unwrap();
// Emit the field value.
assert_eq!(
obj.visit(OwnedStatic(value.a)).into_future().await,
Flow::Done.into()
);
// There are more fields.
Ok(Flow::Continue)
}
1 => {
// Basic visit for the field value.
let obj = visitor
.upcast_mut::<ValueProto<OwnedStatic<i32>, E>>()
.unwrap();
// Emit the field value.
assert_eq!(
obj.visit(OwnedStatic(value.b)).into_future().await,
Flow::Done.into()
);
// There are no more fields.
Ok(Flow::Done)
}
_ => Ok(Flow::Done),
}
})
.into_erased()
}
}
/// Tests that a struct walker in its most basic form just emits a sequence of field values.
///
/// This makes structs behave like tuples (lists) if the builder doesn't know about any of the
/// extra tags.
#[test]
fn sequence_of_field_values() {
// The struct value to walk.
let value = X { a: true, b: 42 };
// The struct walker using the info we provided about the struct.
let walker = StructWalker::<Info, _, DefaultMode, Blocking>::new(&value);
let mut builder = MockBuilder::<(), (), ()>::new();
// Expect a visit on the sequence protocol for the struct fields.
builder
.expect_traits_mut()
.once()
.with(eq(TypeNameId::of::<SequenceProto<Blocking>>()))
.returning(|_| {
let mut visitor = MockSequenceVisitor::<Blocking>::new();
// Expect the sequence visitor to be used.
visitor.expect_visit().once().return_const(
(|_, scope| {
// The struct should have exactly 2 fields.
assert_eq!(scope.size_hint().value(), (2, Some(2)));
// Get the first field value.
{
let mut visitor = MockBuilder::<(), (), ()>::new();
// Expect a bool value for the field.
visitor
.expect_traits_mut()
.once()
.with(eq(
TypeNameId::of::<ValueProto<OwnedStatic<bool>, Blocking>>(),
))
.returning(|_| {
let mut visitor =
MockValueVisitor::<OwnedStatic<bool>, Blocking>::new();
// Expect true.
visitor
.expect_visit()
.once()
.with(eq(OwnedStatic(true)))
.return_const(Flow::Done);
Some(Box::new(visitor))
});
assert_eq!(
scope
.next(Builder::<Blocking>::as_visitor(&mut visitor))
.value(),
Flow::Continue
);
}
// Get the second field value.
{
let mut visitor = MockBuilder::<(), (), ()>::new();
// Expect a i32 value.
visitor
.expect_traits_mut()
.once()
.with(eq(
TypeNameId::of::<ValueProto<OwnedStatic<i32>, Blocking>>(),
))
.returning(|_| {
let mut visitor =
MockValueVisitor::<OwnedStatic<i32>, Blocking>::new();
// Expect a 42.
visitor
.expect_visit()
.once()
.with(eq(OwnedStatic(42)))
.return_const(Flow::Done);
Some(Box::new(visitor))
});
assert_eq!(
scope
.next(Builder::<Blocking>::as_visitor(&mut visitor))
.value(),
Flow::Done
);
}
// We are done with the sequence of fields.
VisitResult::Control(Flow::Done)
}) as SequenceScopeFactory<Blocking>,
);
Some(Box::new(visitor))
});
// All other protocols are not used for this test.
builder.expect_traits_mut().return_var(None);
// Walk the struct.
assert_eq!(
walker
.walk(Builder::<Blocking>::as_visitor(&mut builder))
.value(),
Ok(())
);
}
#[test]
fn has_struct_tag() {
// The struct value to walk.
let value = X { a: true, b: 42 };
// The struct walker using the info we provided about the struct.
let walker = StructWalker::<Info, _, DefaultMode, Blocking>::new(&value);
let mut builder = MockBuilder::<(), (), ()>::new();
let mut seq = mockall::Sequence::new();
// Skip the request hint, borrowed value, and type id.
builder
.expect_traits_mut()
.times(4)
.in_sequence(&mut seq)
.return_var(None);
// Expect that the tag for a struct is emmited.
builder
.expect_traits_mut()
.once()
.in_sequence(&mut seq)
.with(eq(TypeNameId::of::<TagProto<tags::Struct, Blocking>>()))
.returning(|_| {
let mut visitor = MockTagVisitor::<tags::Struct, Blocking>::new();
visitor.expect_visit().once().returning(|TagConst, walker| {
let mut visitor = MockBuilder::<(), (), ()>::new();
// Walk the noop walker so there isn't an error.
assert_eq!(walker.walk(DynVisitor(&mut visitor)).value(), Flow::Done);
// We are done, the walker should now stop early.
VisitResult::Control(Flow::Done)
});
Some(Box::new(visitor))
});
// Walk the struct.
assert_eq!(
walker
.walk(Builder::<Blocking>::as_visitor(&mut builder))
.value(),
Ok(())
);
}
#[test]
fn has_map_backup_tag() {
// The struct value to walk.
let value = X { a: true, b: 42 };
// The struct walker using the info we provided about the struct.
let walker = StructWalker::<Info, _, DefaultMode, Blocking>::new(&value);
let mut builder = MockBuilder::<(), (), ()>::new();
let mut seq = mockall::Sequence::new();
// Skip the request hint, borrowed value type id, and struct tag.
builder
.expect_traits_mut()
.times(6)
.in_sequence(&mut seq)
.return_var(None);
// Expect that the tag for a map is emmited.
//
// This only happens if the struct tag isn't supported by the visitor.
builder
.expect_traits_mut()
.once()
.in_sequence(&mut seq)
.with(eq(TypeNameId::of::<TagProto<tags::Map, Blocking>>()))
.returning(|_| {
let mut visitor = MockTagVisitor::<tags::Map, Blocking>::new();
visitor.expect_visit().once().returning(|TagConst, walker| {
let mut visitor = MockBuilder::<(), (), ()>::new();
// Walk the noop walker so there isn't an error.
assert_eq!(walker.walk(DynVisitor(&mut visitor)).value(), Flow::Done);
// We are done, the walker should now stop early.
VisitResult::Control(Flow::Done)
});
Some(Box::new(visitor))
});
// Walk the struct.
assert_eq!(
walker
.walk(Builder::<Blocking>::as_visitor(&mut builder))
.value(),
Ok(())
);
}
#[test]
fn borrowed_value_directly() {
// The struct value to walk.
let value = X { a: true, b: 42 };
// The struct walker using the info we provided about the struct.
let walker = StructWalker::<Info, _, DefaultMode, Blocking>::new(&value);
let mut builder = MockBuilder::<(), (), ()>::new();
let mut seq = mockall::Sequence::new();
// Skip the request hint.
builder
.expect_traits_mut()
.once()
.in_sequence(&mut seq)
.return_var(None);
// Get the struct value directly from the walker.
builder
.expect_traits_mut()
.once()
.in_sequence(&mut seq)
.with(eq(TypeNameId::of::<
ValueProto<BorrowedStaticHrt<X>, Blocking>,
>()))
.returning(|_| {
let mut visitor = MockValueVisitor::<BorrowedStaticHrt<X>, Blocking>::new();
// Expect the struct value.
visitor
.expect_visit()
.once()
.returning(|BorrowedStatic(value)| {
// Check the value to be the same.
assert!(value.a);
assert_eq!(value.b, 42);
// We are done, the walker should now stop early.
VisitResult::Control(Flow::Done)
});
Some(Box::new(visitor))
});
// Walk the struct.
assert_eq!(
walker
.walk(Builder::<Blocking>::as_visitor(&mut builder))
.value(),
Ok(())
);
}