use crate::domain::model::entity_ref::EntityRef;
use crate::domain::model::record_kind::RecordKind;
use crate::domain::model::title::Title;
#[derive(Debug, Clone, serde::Serialize)]
pub struct SearchHit {
pub id: EntityRef,
pub kind: RecordKind,
pub title: Title,
pub excerpt: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct SearchFilter {
pub kind: Option<RecordKind>,
pub limit: Option<usize>,
}
impl SearchFilter {
pub fn new() -> Self {
Self {
kind: None,
limit: None,
}
}
pub fn kind(mut self, kind: &str) -> Self {
self.kind = RecordKind::new(kind).ok();
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
}
#[cfg(test)]
pub mod strategy {
use super::{SearchFilter, SearchHit};
use crate::domain::model::entity_ref::strategy::entity_ref;
use crate::domain::model::record_kind::strategy::record_kind;
use crate::domain::model::title::strategy::arb_title;
use proptest::prelude::*;
pub fn search_hit() -> impl Strategy<Value = SearchHit> {
(
entity_ref(),
record_kind(),
arb_title(),
proptest::option::of(any::<String>()),
)
.prop_map(|(id, kind, title, excerpt)| SearchHit {
id,
kind,
title,
excerpt,
})
}
pub fn search_filter() -> impl Strategy<Value = SearchFilter> {
(
proptest::option::of(record_kind()),
proptest::option::of(0usize..1024),
)
.prop_map(|(kind, limit)| SearchFilter { kind, limit })
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn search_filter_limit_is_some_when_built_explicitly(n in 0usize..1024) {
let f = SearchFilter::new().limit(n);
prop_assert_eq!(f.limit, Some(n));
}
}
fn make_hit(id: &str, kind: &str, title: &str) -> SearchHit {
SearchHit {
id: EntityRef::new(id).unwrap(),
kind: RecordKind::new(kind).unwrap(),
title: Title::new(title).unwrap(),
excerpt: None,
}
}
#[test]
fn search_hit_fields_are_accessible() {
let hit = make_hit("ISSUE-0001", "issue", "Add login");
assert_eq!(hit.id.to_string(), "ISSUE-0001");
assert_eq!(hit.kind.as_str(), "issue");
assert_eq!(hit.title.as_str(), "Add login");
assert!(hit.excerpt.is_none());
}
#[test]
fn search_filter_defaults_have_no_limit() {
let f = SearchFilter::new();
assert!(f.kind.is_none());
assert!(
f.limit.is_none(),
"default limit should be None (unlimited)"
);
}
#[test]
fn search_filter_builder_sets_fields() {
let f = SearchFilter::new().kind("adr").limit(5);
assert_eq!(f.kind.as_ref().map(|k| k.as_str()), Some("adr"));
assert_eq!(f.limit, Some(5));
}
#[test]
fn search_filter_invalid_kind_is_ignored() {
let f = SearchFilter::new().kind("INVALID KIND");
assert!(f.kind.is_none());
}
}