mant_query/scope_query/
input.rs1use mant_ir::ResolvedContent;
3use mant_protocol::{
4 MAX_SCOPE_DEPTH, MAX_SCOPE_DOCUMENT_LIMIT, ResolvedDocumentScope, ScopedDocument,
5};
6use std::{collections::BTreeSet, error::Error, fmt};
7
8#[derive(Debug, Clone, Copy)]
14pub struct QueryScopeView<'a> {
15 graph: &'a ResolvedDocumentScope,
16 documents: &'a [ResolvedContent],
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum ScopeInputError {
22 LengthMismatch,
24 TooManyDocuments,
26 AddressMismatch {
28 index: usize,
30 },
31 DuplicateAddress {
33 index: usize,
35 },
36 InvalidSource {
38 index: usize,
40 },
41 UnknownGraphAddress,
43}
44
45impl fmt::Display for ScopeInputError {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 Self::LengthMismatch => f.write_str("scope graph and content lengths do not match"),
49 Self::TooManyDocuments => f.write_str("scope content exceeds the document ceiling"),
50 Self::AddressMismatch { index } => {
51 write!(f, "scope content address does not match graph slot {index}")
52 }
53 Self::DuplicateAddress { index } => {
54 write!(f, "scope graph repeats an address at slot {index}")
55 }
56 Self::InvalidSource { index } => write!(
57 f,
58 "scope source order or coordinates are invalid at slot {index}"
59 ),
60 Self::UnknownGraphAddress => {
61 f.write_str("scope graph refers to a document outside the loaded set")
62 }
63 }
64 }
65}
66impl Error for ScopeInputError {}
67
68impl<'a> QueryScopeView<'a> {
69 pub fn new(
75 graph: &'a ResolvedDocumentScope,
76 documents: &'a [ResolvedContent],
77 ) -> Result<Self, ScopeInputError> {
78 if graph.documents.len() != documents.len() {
79 return Err(ScopeInputError::LengthMismatch);
80 }
81 if documents.len() > MAX_SCOPE_DOCUMENT_LIMIT as usize {
82 return Err(ScopeInputError::TooManyDocuments);
83 }
84 let mut addresses = BTreeSet::new();
85 let mut depth = 0;
86 for (index, (source, content)) in graph.documents.iter().zip(documents).enumerate() {
87 if content.address.as_ref() != Some(&source.address) {
88 return Err(ScopeInputError::AddressMismatch { index });
89 }
90 if !addresses.insert(&source.address) {
91 return Err(ScopeInputError::DuplicateAddress { index });
92 }
93 if source.depth < depth
94 || source.depth > MAX_SCOPE_DEPTH
95 || source
96 .root_indices
97 .iter()
98 .any(|root| usize::from(*root) >= graph.query.documents.len())
99 || (source.depth != 0 && !source.root_indices.is_empty())
100 {
101 return Err(ScopeInputError::InvalidSource { index });
102 }
103 depth = source.depth;
104 }
105 let known = |address: &mant_ir::DocumentAddress| addresses.contains(address);
106 if graph
107 .edges
108 .iter()
109 .any(|edge| !known(&edge.from) || !known(&edge.to))
110 || graph
111 .documents
112 .iter()
113 .any(|source| source.reached_from.iter().any(|from| !known(from)))
114 || graph.frontier.iter().any(|item| !known(&item.from))
115 || graph
116 .unresolved
117 .iter()
118 .any(|item| item.from.as_ref().is_some_and(|from| !known(from)))
119 || graph
120 .reference_limits
121 .iter()
122 .any(|item| !known(&item.document))
123 {
124 return Err(ScopeInputError::UnknownGraphAddress);
125 }
126 Ok(Self { graph, documents })
127 }
128
129 #[must_use]
131 pub const fn graph(self) -> &'a ResolvedDocumentScope {
132 self.graph
133 }
134
135 #[must_use]
137 pub const fn documents(self) -> &'a [ResolvedContent] {
138 self.documents
139 }
140
141 #[must_use]
143 pub fn iter(self) -> impl ExactSizeIterator<Item = (&'a ScopedDocument, &'a ResolvedContent)> {
144 self.graph.documents.iter().zip(self.documents)
145 }
146}