1use crate::{ManualIndex, discover_manual_roots};
4use mant_protocol::{CatalogQuery, DocumentCatalog, MAX_CATALOG_PATTERN_CHARS};
5use mant_sources::{SourceConfigError, list_registered_documents};
6use std::{error::Error, fmt, path::PathBuf};
7
8mod inventory;
9mod selection;
10#[cfg(test)]
11mod tests;
12
13pub(crate) use inventory::list_available_documents_from;
14pub use selection::{PreparedCatalogQuery, query_available_documents};
15
16#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
18pub enum AvailableDocumentKind {
19 Markdown,
21 Manual,
23}
24
25#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
27pub enum AvailableDocumentOrigin {
28 Documents,
30 Source(String),
32 ManualPath,
34}
35
36#[derive(Clone, Debug, Eq, PartialEq)]
38pub struct AvailableDocument {
39 pub name: String,
41 pub logical_path: String,
43 pub kind: AvailableDocumentKind,
45 pub manual_section: Option<String>,
47 pub path: PathBuf,
49 pub origin: AvailableDocumentOrigin,
51 pub source_priority: Option<i32>,
53}
54
55#[derive(Clone, Debug, Eq, PartialEq)]
57pub enum CatalogError {
58 EmptyPattern,
60 PatternTooLong,
62 InvalidLimit,
64 ConflictingSelectors,
66 InvalidPattern(String),
68}
69
70impl fmt::Display for CatalogError {
71 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
72 match self {
73 Self::EmptyPattern => formatter.write_str("catalog pattern must not be empty"),
74 Self::PatternTooLong => {
75 write!(
76 formatter,
77 "catalog pattern exceeds the {MAX_CATALOG_PATTERN_CHARS}-character limit"
78 )
79 }
80 Self::InvalidLimit => formatter.write_str("catalog limit must be between 1 and 10000"),
81 Self::ConflictingSelectors => {
82 formatter.write_str("catalog source and manual-section filters cannot be combined")
83 }
84 Self::InvalidPattern(message) => {
85 write!(formatter, "invalid catalog pattern: {message}")
86 }
87 }
88 }
89}
90
91impl Error for CatalogError {}
92
93pub fn list_available_documents() -> Result<Vec<AvailableDocument>, SourceConfigError> {
100 let manuals = ManualIndex::from_roots(discover_manual_roots());
101 Ok(list_available_documents_from(
102 list_registered_documents()?,
103 manuals.pages(),
104 ))
105}
106
107pub fn discover_documents(query: &CatalogQuery) -> Result<DocumentCatalog, String> {
114 discover_with(query, list_available_documents)
115}
116
117fn discover_with(
118 query: &CatalogQuery,
119 inventory: impl FnOnce() -> Result<Vec<AvailableDocument>, SourceConfigError>,
120) -> Result<DocumentCatalog, String> {
121 let plan = PreparedCatalogQuery::new(query).map_err(|error| error.to_string())?;
122 let documents = inventory().map_err(|error| error.to_string())?;
123 Ok(plan.apply(&documents))
124}