Skip to main content

knowledge_base_crud/read/entities/
search.rs

1use super::{Entities, query::load_entities};
2use crate::Error;
3use knowledge_base_models::Entity;
4use serde::Serialize;
5
6#[derive(Clone, Debug, Serialize)]
7pub struct EntitySearchPage {
8    pub query: String,
9    pub offset: usize,
10    pub limit: usize,
11    pub total: usize,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub next_offset: Option<usize>,
14    pub entities: Vec<Entity>,
15}
16
17impl Entities<'_> {
18    pub fn search(&self, query: &str, limit: usize, offset: usize) -> Result<EntitySearchPage, Error> {
19        let query = normalize(query);
20        if query.is_empty() {
21            return Err(Error::InvalidRequest("entity search query must not be empty".to_owned()));
22        }
23        if limit == 0 {
24            return Err(Error::InvalidRequest("entity search limit must be greater than zero".to_owned()));
25        }
26
27        let mut entities = load_entities(self.repository.root())?
28            .into_iter()
29            .filter(|entity| entity.labels.values().any(|label| normalize(&label.text).contains(&query)))
30            .collect::<Vec<_>>();
31        entities.sort_by(|left, right| {
32            has_exact_label(left, &query)
33                .cmp(&has_exact_label(right, &query))
34                .reverse()
35                .then_with(|| left.id.number().cmp(&right.id.number()))
36        });
37
38        let total = entities.len();
39        let entities = entities.into_iter().skip(offset).take(limit).collect::<Vec<_>>();
40        let returned_end = offset.saturating_add(entities.len());
41        let next_offset = (returned_end < total).then_some(returned_end);
42
43        Ok(EntitySearchPage {
44            query,
45            offset,
46            limit,
47            total,
48            next_offset,
49            entities,
50        })
51    }
52}
53
54fn normalize(value: &str) -> String {
55    value.trim().to_lowercase()
56}
57
58fn has_exact_label(entity: &Entity, query: &str) -> bool {
59    entity.labels.values().any(|label| normalize(&label.text) == query)
60}