use std::sync::Arc;
use hyphae::SwitchMapExt;
use crate::prelude::*;
#[myko_item]
pub struct BenchItem {
#[searchable]
pub name: String,
#[searchable]
pub category: String,
pub value: i64,
}
pub use tree::BenchTreeItem;
mod tree {
use std::sync::Arc;
use crate::prelude::*;
#[myko_item]
pub struct BenchTreeItem {
pub name: String,
pub parent_id: Option<Arc<str>>,
pub depth: i64,
}
}
#[myko_query(BenchItem)]
pub struct GetBenchItemsByCategory {
pub category: String,
}
impl QueryHandler for GetBenchItemsByCategory {
fn test_entity(ctx: QueryTestCtx<Self>) -> bool {
ctx.item.category == ctx.query.category.as_str()
}
}
#[myko_report(Vec<String>)]
pub struct SwitchMapReport {
pub category: String,
}
impl ReportHandler for SwitchMapReport {
type Output = Vec<String>;
fn compute(&self, ctx: ReportContext) -> impl MaterializeDefinite<Arc<Self::Output>> {
let category = self.category.clone();
let items = ctx
.query_map(GetBenchItemsByQuery(PartialBenchItem {
category: Some(category),
..Default::default()
}))
.items();
items.switch_map(move |items| {
if items.is_empty() {
return Cell::new(Arc::new(Vec::<String>::new())).lock();
}
let ids: Vec<BenchItemId> = items.iter().map(|item| item.id.clone()).collect();
ctx.query_map(GetBenchItemsByIds { ids })
.items()
.map(|items| {
Arc::new(
items
.iter()
.map(|item| item.name.clone())
.collect::<Vec<_>>(),
)
})
.materialize()
})
}
}
#[cfg(test)]
mod typed_search_tests {
use super::*;
use crate::search::typed::{Score, SearchIndex, SearchOptions};
fn item(id: &str, name: &str, category: &str) -> BenchItem {
BenchItem {
id: id.into(),
name: name.to_string(),
category: category.to_string(),
value: 0,
}
}
#[test]
fn macro_generated_impl_indexes_and_finds() {
let mut index = SearchIndex::<BenchItem>::new();
index.insert(&item("1", "audio mixer", "hardware"));
index.insert(&item("2", "video camera", "hardware"));
index.insert(&item("3", "lighting fixture", "props"));
let mixer = index.search("mixer", SearchOptions::default());
assert_eq!(mixer.len(), 1);
assert_eq!(mixer[0].id.0.as_ref(), "1");
assert_eq!(mixer[0].score, Score::Exact);
let hardware = index.search("hardware", SearchOptions::default());
assert_eq!(hardware.len(), 2);
}
#[test]
fn macro_generated_field_names_match_searchable_attrs() {
use crate::search::typed::Searchable;
assert_eq!(BenchItem::searchable_field_names(), &["name", "category"]);
}
#[test]
fn matched_field_resolves_against_macro_generated_order() {
let mut index = SearchIndex::<BenchItem>::new();
index.insert(&item("1", "alpha", "beta"));
let name_hit = &index.search("alpha", SearchOptions::default())[0];
assert_eq!(name_hit.matched_field, 0, "alpha is the name field");
let cat_hit = &index.search("beta", SearchOptions::default())[0];
assert_eq!(cat_hit.matched_field, 1, "beta is the category field");
}
#[test]
fn build_typed_registry_picks_up_macro_emitted_registration() {
let registry = crate::search::build_typed_registry();
assert!(
registry.entity_types().any(|t| t == "BenchItem"),
"BenchItem should be registered via inventory; got: {:?}",
registry.entity_types().collect::<Vec<_>>()
);
registry.insert(&item("1", "audio mixer", "hardware"));
let hits = registry.search("BenchItem", "mixer", SearchOptions::default());
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].id.as_ref(), "1");
}
#[test]
fn macro_generated_typed_search_report_exists() {
let report = SearchBenchItem {
query: "audio".to_string(),
limit: 25,
};
assert_eq!(report.query, "audio");
assert_eq!(report.limit, 25);
let result = SearchBenchItemResult {
ids: vec![BenchItemId::from(std::sync::Arc::<str>::from("1"))],
};
assert_eq!(result.ids.len(), 1);
}
}