mant_loader/
scope_load.rs1use crate::{DocumentLoader, LoadError, LoadPolicy, LoadSpec};
3use mant_ir::{DocumentAddress, DocumentReference, ResolvedContent};
4use mant_protocol::{
5 DocumentEdge, DocumentEdgeKind, DocumentFrontier, DocumentScope, DocumentSelector,
6 MAX_DOCUMENT_SELECTOR_CHARS, MAX_SCOPE_CONTENT_BYTES, MAX_SCOPE_DEPTH,
7 MAX_SCOPE_DOCUMENT_LIMIT, MAX_SCOPE_DOCUMENTS, ResolvedDocumentScope, ScopeTextError,
8 ScopedDocument, TraversalLimit, UnresolvedDocument, validate_scope_text,
9};
10use std::collections::{BTreeMap, BTreeSet, VecDeque};
11use std::{error::Error, fmt, io::Write};
12
13mod references;
14mod resolve;
15#[cfg(test)]
16mod tests;
17
18#[derive(Debug, Clone)]
20pub struct LoadedDocumentScope {
21 scope: ResolvedDocumentScope,
23 documents: Vec<ResolvedContent>,
25}
26
27impl LoadedDocumentScope {
28 #[must_use]
30 pub const fn scope(&self) -> &ResolvedDocumentScope {
31 &self.scope
32 }
33 #[must_use]
35 pub fn documents(&self) -> &[ResolvedContent] {
36 &self.documents
37 }
38 #[must_use]
40 pub fn into_parts(self) -> (ResolvedDocumentScope, Vec<ResolvedContent>) {
41 (self.scope, self.documents)
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum ScopeLoadError {
48 EmptyScope,
50 TooManyDocuments,
52 DepthLimit,
54 DocumentLimit,
56 TraversalLimitsRequireLinks,
58 DocumentSelector(ScopeTextError),
60 NoResolvedDocuments {
62 reasons: Vec<String>,
64 },
65}
66impl fmt::Display for ScopeLoadError {
67 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 Self::EmptyScope => formatter.write_str("at least one document is required"),
70 Self::TooManyDocuments => write!(
71 formatter,
72 "at most {MAX_SCOPE_DOCUMENTS} initial documents are allowed"
73 ),
74 Self::DepthLimit => write!(
75 formatter,
76 "maximum link depth must not exceed {MAX_SCOPE_DEPTH}"
77 ),
78 Self::DocumentLimit => write!(
79 formatter,
80 "document limit must include every initial document and not exceed {MAX_SCOPE_DOCUMENT_LIMIT}"
81 ),
82 Self::TraversalLimitsRequireLinks => {
83 formatter.write_str("maxDepth and maxDocuments require followLinks=true")
84 }
85 Self::DocumentSelector(error) => write!(
86 formatter,
87 "document selector {}",
88 scope_text_error_message(*error)
89 ),
90 Self::NoResolvedDocuments { reasons } => {
91 formatter.write_str("none of the initial documents could be resolved")?;
92 if !reasons.is_empty() {
93 write!(formatter, ": {}", reasons.join("; "))?;
94 }
95 Ok(())
96 }
97 }
98 }
99}
100impl Error for ScopeLoadError {}
101
102pub fn validate_document_scope(scope: &DocumentScope) -> Result<(), ScopeLoadError> {
108 if scope.documents.is_empty() {
109 return Err(ScopeLoadError::EmptyScope);
110 }
111 if scope.documents.len() > MAX_SCOPE_DOCUMENTS {
112 return Err(ScopeLoadError::TooManyDocuments);
113 }
114 for selector in &scope.documents {
115 validate_scope_text(&selector.selector, MAX_DOCUMENT_SELECTOR_CHARS)
116 .map_err(ScopeLoadError::DocumentSelector)?;
117 }
118 if !scope.traversal.follow_links
119 && (scope.traversal.max_depth.is_some() || scope.traversal.max_documents.is_some())
120 {
121 return Err(ScopeLoadError::TraversalLimitsRequireLinks);
122 }
123 if scope.traversal.effective_max_depth() > MAX_SCOPE_DEPTH {
124 return Err(ScopeLoadError::DepthLimit);
125 }
126 let root_count = u32::try_from(scope.documents.len()).unwrap_or(u32::MAX);
127 if scope.traversal.effective_max_documents() < root_count
128 || scope.traversal.effective_max_documents() > MAX_SCOPE_DOCUMENT_LIMIT
129 {
130 return Err(ScopeLoadError::DocumentLimit);
131 }
132 Ok(())
133}
134
135fn scope_text_error_message(error: ScopeTextError) -> String {
136 match error {
137 ScopeTextError::Empty => "must not be empty".to_owned(),
138 ScopeTextError::ControlCharacter => "must not contain control characters".to_owned(),
139 ScopeTextError::TooLong { maximum } => {
140 format!("must not exceed {maximum} Unicode scalar values")
141 }
142 }
143}