use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub use mant_ir::{DocumentAddress, MarkdownOrigin};
use crate::{SearchCase, SearchSyntax};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum CatalogSchema {
#[serde(rename = "mant.catalog/v7")]
V7,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum CatalogDocumentKind {
Markdown,
Manual,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct CatalogQuery {
#[serde(skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
#[serde(default)]
pub syntax: SearchSyntax,
#[serde(default)]
pub case: SearchCase,
#[serde(skip_serializing_if = "Option::is_none")]
pub kind: Option<CatalogDocumentKind>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manual_section: Option<String>,
#[serde(default = "default_catalog_limit")]
#[schemars(range(min = 1, max = 10000))]
pub limit: u32,
#[serde(default)]
pub offset: u32,
}
impl Default for CatalogQuery {
fn default() -> Self {
Self {
pattern: None,
syntax: SearchSyntax::Literal,
case: SearchCase::Insensitive,
kind: None,
source: None,
manual_section: None,
limit: default_catalog_limit(),
offset: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct DocumentSummary {
pub address: DocumentAddress,
pub catalog_path: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("$id" = "urn:mant:catalog:v7"))]
pub struct DocumentCatalog {
pub schema: CatalogSchema,
pub total: u32,
pub returned: u32,
pub offset: u32,
pub truncated: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_offset: Option<u32>,
pub documents: Vec<DocumentSummary>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CatalogMatchRank {
Exact,
ComponentSuffix,
Prefix,
Substring,
Unranked,
}
#[must_use]
pub fn catalog_literal_match_rank(
name: &str,
pattern: Option<&str>,
case: SearchCase,
) -> CatalogMatchRank {
let Some(pattern) = pattern else {
return CatalogMatchRank::Unranked;
};
let insensitive = case == SearchCase::Insensitive
|| case == SearchCase::Smart && !pattern.chars().any(char::is_uppercase);
let (name, pattern) = if insensitive {
(name.to_lowercase(), pattern.to_lowercase())
} else {
(name.to_owned(), pattern.to_owned())
};
if name == pattern {
CatalogMatchRank::Exact
} else if name.ends_with(&format!("/{pattern}")) {
CatalogMatchRank::ComponentSuffix
} else if name.starts_with(&pattern) {
CatalogMatchRank::Prefix
} else {
CatalogMatchRank::Substring
}
}
#[must_use]
pub const fn default_catalog_limit() -> u32 {
100
}