knowledge-base-crud 0.3.0

Filesystem CRUD operations for a file-based knowledge base
Documentation
use super::{Entities, query::load_entities};
use crate::Error;
use knowledge_base_models::Entity;
use serde::Serialize;

#[derive(Clone, Debug, Serialize)]
pub struct EntitySearchPage {
    pub query: String,
    pub offset: usize,
    pub limit: usize,
    pub total: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub next_offset: Option<usize>,
    pub entities: Vec<Entity>,
}

impl Entities<'_> {
    pub fn search(&self, query: &str, limit: usize, offset: usize) -> Result<EntitySearchPage, Error> {
        let query = normalize(query);
        if query.is_empty() {
            return Err(Error::InvalidRequest("entity search query must not be empty".to_owned()));
        }
        if limit == 0 {
            return Err(Error::InvalidRequest("entity search limit must be greater than zero".to_owned()));
        }

        let mut entities = load_entities(self.repository.root())?
            .into_iter()
            .filter(|entity| entity.labels.values().any(|label| normalize(&label.text).contains(&query)))
            .collect::<Vec<_>>();
        entities.sort_by(|left, right| {
            has_exact_label(left, &query)
                .cmp(&has_exact_label(right, &query))
                .reverse()
                .then_with(|| left.id.number().cmp(&right.id.number()))
        });

        let total = entities.len();
        let entities = entities.into_iter().skip(offset).take(limit).collect::<Vec<_>>();
        let returned_end = offset.saturating_add(entities.len());
        let next_offset = (returned_end < total).then_some(returned_end);

        Ok(EntitySearchPage {
            query,
            offset,
            limit,
            total,
            next_offset,
            entities,
        })
    }
}

fn normalize(value: &str) -> String {
    value.trim().to_lowercase()
}

fn has_exact_label(entity: &Entity, query: &str) -> bool {
    entity.labels.values().any(|label| normalize(&label.text) == query)
}