use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
DocumentAddress, QueryExcerpt, QuerySearch, SearchCase, SearchQuery, SearchScope, SearchSyntax,
default_search_limit,
};
pub const MAX_SCOPE_DOCUMENTS: usize = 16;
pub const DEFAULT_SCOPE_DEPTH: u16 = 8;
pub const MAX_SCOPE_DEPTH: u16 = 32;
pub const DEFAULT_SCOPE_DOCUMENT_LIMIT: u32 = 64;
pub const MAX_SCOPE_DOCUMENT_LIMIT: u32 = 256;
pub const MAX_SCOPE_CONTENT_BYTES: u64 = 64 * 1024 * 1024;
pub const MAX_DOCUMENT_SELECTOR_BYTES: usize = 1024;
pub const MAX_SEMANTIC_ENTRY_BYTES: usize = 512;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScopeTextError {
Empty,
ControlCharacter,
TooLong {
maximum: usize,
},
}
pub fn validate_scope_text(value: &str, maximum: usize) -> Result<(), ScopeTextError> {
if value.trim().is_empty() {
return Err(ScopeTextError::Empty);
}
if value.chars().any(char::is_control) {
return Err(ScopeTextError::ControlCharacter);
}
if value.len() > maximum {
return Err(ScopeTextError::TooLong { maximum });
}
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct DocumentSelector {
#[schemars(length(min = 1, max = MAX_DOCUMENT_SELECTOR_BYTES))]
pub selector: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manual_section: Option<String>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct DocumentTraversal {
#[serde(default)]
pub follow_links: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(range(max = 32))]
pub max_depth: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 1, max = 256))]
pub max_documents: Option<u32>,
}
impl DocumentTraversal {
#[must_use]
pub fn effective_max_depth(self) -> u16 {
self.max_depth.unwrap_or(DEFAULT_SCOPE_DEPTH)
}
#[must_use]
pub fn effective_max_documents(self) -> u32 {
self.max_documents.unwrap_or(DEFAULT_SCOPE_DOCUMENT_LIMIT)
}
}
#[must_use]
pub const fn default_scope_depth() -> u16 {
DEFAULT_SCOPE_DEPTH
}
#[must_use]
pub const fn default_scope_document_limit() -> u32 {
DEFAULT_SCOPE_DOCUMENT_LIMIT
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct DocumentScope {
#[schemars(length(min = 1, max = 16))]
pub documents: Vec<DocumentSelector>,
#[serde(default)]
pub traversal: DocumentTraversal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum ScopeRequestSchema {
#[serde(rename = "mant.scope-request/v0.9")]
V0Dot9,
}
impl ScopeRequestSchema {
pub const ID: &'static str = "mant.scope-request/v0.9";
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(
tag = "kind",
rename_all = "kebab-case",
rename_all_fields = "camelCase",
deny_unknown_fields
)]
pub enum ScopeQueryView {
Explain {
#[schemars(length(min = 1, max = MAX_SEMANTIC_ENTRY_BYTES))]
entry: String,
},
Search {
#[schemars(length(min = 1, max = 4096))]
pattern: String,
#[serde(default)]
syntax: SearchSyntax,
#[serde(default)]
case: SearchCase,
#[serde(default)]
scope: SearchScope,
#[serde(default)]
word: bool,
#[serde(default)]
#[schemars(range(max = 100))]
context_lines: u16,
#[serde(default = "default_search_limit")]
#[schemars(range(min = 1, max = 10000))]
limit: u32,
#[serde(default)]
offset: u32,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[schemars(extend("$id" = "urn:mant:scope-request:v0.9"))]
pub struct ScopeQueryRequest {
pub schema: ScopeRequestSchema,
pub scope: DocumentScope,
pub view: ScopeQueryView,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum ScopeQuerySchema {
#[serde(rename = "mant.scope-query/v0.9")]
V0Dot9,
}
impl ScopeQuerySchema {
pub const ID: &'static str = "mant.scope-query/v0.9";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum DocumentEdgeKind {
Document,
Manual,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum TraversalLimit {
MaxDepth,
MaxDocuments,
MaxContentBytes,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct DocumentFrontier {
pub from: DocumentAddress,
pub target: DocumentSelector,
pub kind: DocumentEdgeKind,
pub limit: TraversalLimit,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct DocumentEdge {
pub from: DocumentAddress,
pub to: DocumentAddress,
pub kind: DocumentEdgeKind,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ScopedDocument {
pub address: DocumentAddress,
pub depth: u16,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub root_indices: Vec<u16>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reached_from: Vec<DocumentAddress>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct UnresolvedDocument {
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<DocumentAddress>,
pub selector: DocumentSelector,
pub reason: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ResolvedDocumentScope {
pub query: DocumentScope,
pub documents: Vec<ScopedDocument>,
pub edges: Vec<DocumentEdge>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub frontier: Vec<DocumentFrontier>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub unresolved: Vec<UnresolvedDocument>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ScopedSearchDocument {
pub address: DocumentAddress,
pub depth: u16,
pub search: QuerySearch,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ScopeSearch {
pub query: SearchQuery,
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<ScopedSearchDocument>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ScopedExplanation {
pub address: DocumentAddress,
pub depth: u16,
pub excerpt: QueryExcerpt,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ScopedQueryFailure {
pub address: DocumentAddress,
pub reason: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(
tag = "kind",
rename_all = "kebab-case",
rename_all_fields = "camelCase"
)]
pub enum ScopeQueryResult {
Explain {
entry: String,
matches: Vec<ScopedExplanation>,
missed: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
failures: Vec<ScopedQueryFailure>,
},
Search {
search: ScopeSearch,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("$id" = "urn:mant:scope-query:v0.9"))]
pub struct ScopeQueryResponse {
pub schema: ScopeQuerySchema,
pub scope: ResolvedDocumentScope,
pub result: ScopeQueryResult,
}