1use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7 DocumentAddress, SearchCase, SearchHit, SearchQuery, SearchRender, SearchScope, SearchSyntax,
8 default_search_limit,
9};
10
11pub const MAX_SCOPE_DOCUMENTS: usize = 16;
13pub const DEFAULT_SCOPE_DEPTH: u16 = 8;
15pub const MAX_SCOPE_DEPTH: u16 = 32;
17pub const DEFAULT_SCOPE_DOCUMENT_LIMIT: u32 = 64;
19pub const MAX_SCOPE_DOCUMENT_LIMIT: u32 = 256;
21pub const MAX_SCOPE_CONTENT_BYTES: u64 = 64 * 1024 * 1024;
28pub const MAX_DOCUMENT_SELECTOR_CHARS: usize = 1024;
30pub const MAX_SEMANTIC_ENTRY_CHARS: usize = 512;
32pub const MAX_SEARCH_PATTERN_CHARS: usize = 4096;
34pub const MAX_SOURCE_SELECTOR_CHARS: usize = 128;
36pub const MAX_MANUAL_SECTION_CHARS: usize = 32;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum ScopeTextError {
42 Empty,
44 ControlCharacter,
46 TooLong {
48 maximum: usize,
50 },
51}
52
53pub fn validate_scope_text(value: &str, maximum: usize) -> Result<(), ScopeTextError> {
63 if value.trim().is_empty() {
64 return Err(ScopeTextError::Empty);
65 }
66 if value.chars().any(char::is_control) {
67 return Err(ScopeTextError::ControlCharacter);
68 }
69 if value.chars().count() > maximum {
70 return Err(ScopeTextError::TooLong { maximum });
71 }
72 Ok(())
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
77#[serde(rename_all = "camelCase", deny_unknown_fields)]
78pub struct DocumentSelector {
79 #[schemars(length(min = 1, max = MAX_DOCUMENT_SELECTOR_CHARS))]
81 pub selector: String,
82 #[schemars(length(min = 1, max = MAX_SOURCE_SELECTOR_CHARS))]
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub source: Option<String>,
86 #[schemars(length(min = 1, max = MAX_MANUAL_SECTION_CHARS))]
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub manual_section: Option<String>,
90}
91
92#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
94#[serde(rename_all = "camelCase", deny_unknown_fields)]
95pub struct DocumentTraversal {
96 #[serde(default)]
98 pub follow_links: bool,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
104 #[schemars(range(max = 32))]
105 pub max_depth: Option<u16>,
106 #[serde(default, skip_serializing_if = "Option::is_none")]
112 #[schemars(range(min = 1, max = 256))]
113 pub max_documents: Option<u32>,
114}
115
116impl DocumentTraversal {
117 #[must_use]
119 pub fn effective_max_depth(self) -> u16 {
120 self.max_depth.unwrap_or(DEFAULT_SCOPE_DEPTH)
121 }
122
123 #[must_use]
125 pub fn effective_max_documents(self) -> u32 {
126 self.max_documents.unwrap_or(DEFAULT_SCOPE_DOCUMENT_LIMIT)
127 }
128}
129
130#[must_use]
132pub const fn default_scope_depth() -> u16 {
133 DEFAULT_SCOPE_DEPTH
134}
135
136#[must_use]
138pub const fn default_scope_document_limit() -> u32 {
139 DEFAULT_SCOPE_DOCUMENT_LIMIT
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
144#[serde(rename_all = "camelCase", deny_unknown_fields)]
145pub struct DocumentScope {
146 #[schemars(length(min = 1, max = 16))]
148 pub documents: Vec<DocumentSelector>,
149 #[serde(default)]
151 pub traversal: DocumentTraversal,
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
156pub enum ScopeRequestSchema {
157 #[serde(rename = "mant.scope-request/v0.11")]
159 V0Dot11,
160}
161
162impl ScopeRequestSchema {
163 pub const ID: &'static str = "mant.scope-request/v0.11";
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
169#[serde(
170 tag = "kind",
171 rename_all = "kebab-case",
172 rename_all_fields = "camelCase",
173 deny_unknown_fields
174)]
175pub enum ScopeQueryView {
176 Explain {
178 #[schemars(length(min = 1, max = MAX_SEMANTIC_ENTRY_CHARS))]
180 entry: String,
181 #[serde(default)]
183 options: crate::ExplanationOptions,
184 },
185 Search {
187 #[schemars(length(min = 1, max = MAX_SEARCH_PATTERN_CHARS))]
189 pattern: String,
190 #[serde(default)]
192 syntax: SearchSyntax,
193 #[serde(default)]
195 case: SearchCase,
196 #[serde(default)]
198 scope: SearchScope,
199 #[serde(default)]
201 word: bool,
202 #[serde(default)]
204 #[schemars(range(max = 100))]
205 context_lines: u16,
206 #[serde(default = "default_search_limit")]
208 #[schemars(range(min = 1, max = 10000))]
209 limit: u32,
210 #[serde(default)]
212 offset: u32,
213 },
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
218#[serde(rename_all = "camelCase", deny_unknown_fields)]
219#[schemars(extend("$id" = "urn:mant:scope-request:v0.11"))]
220pub struct ScopeQueryRequest {
221 pub schema: ScopeRequestSchema,
223 pub scope: DocumentScope,
225 pub view: ScopeQueryView,
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
231pub enum ScopeQuerySchema {
232 #[serde(rename = "mant.scope-query/v0.11")]
234 V0Dot11,
235}
236
237impl ScopeQuerySchema {
238 pub const ID: &'static str = "mant.scope-query/v0.11";
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
244#[serde(rename_all = "kebab-case")]
245pub enum DocumentEdgeKind {
246 Document,
248 Manual,
250}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
254#[serde(rename_all = "kebab-case")]
255pub enum TraversalLimit {
256 MaxDepth,
258 MaxDocuments,
260 MaxContentBytes,
263}
264
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
270#[serde(rename_all = "camelCase")]
271pub struct DocumentFrontier {
272 pub from: DocumentAddress,
274 pub target: DocumentSelector,
276 pub kind: DocumentEdgeKind,
278 pub limit: TraversalLimit,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
284#[serde(rename_all = "camelCase")]
285pub struct DocumentEdge {
286 pub from: DocumentAddress,
288 pub to: DocumentAddress,
290 pub kind: DocumentEdgeKind,
292}
293
294#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
296#[serde(rename_all = "camelCase")]
297pub struct ScopedDocument {
298 pub address: DocumentAddress,
300 pub depth: u16,
302 #[serde(default, skip_serializing_if = "Vec::is_empty")]
304 pub root_indices: Vec<u16>,
305 #[serde(default, skip_serializing_if = "Vec::is_empty")]
307 pub reached_from: Vec<DocumentAddress>,
308}
309
310#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
312#[serde(rename_all = "camelCase")]
313pub struct UnresolvedDocument {
314 #[serde(skip_serializing_if = "Option::is_none")]
316 pub from: Option<DocumentAddress>,
317 pub selector: DocumentSelector,
319 pub reason: String,
321}
322
323#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
325#[serde(rename_all = "camelCase")]
326pub struct ResolvedDocumentScope {
327 pub query: DocumentScope,
329 pub documents: Vec<ScopedDocument>,
331 pub edges: Vec<DocumentEdge>,
333 #[serde(default, skip_serializing_if = "Vec::is_empty")]
335 pub frontier: Vec<DocumentFrontier>,
336 #[serde(default, skip_serializing_if = "Vec::is_empty")]
338 pub unresolved: Vec<UnresolvedDocument>,
339 #[serde(default, skip_serializing_if = "Vec::is_empty")]
342 pub reference_limits: Vec<ScopeReferenceLimit>,
343}
344
345#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
347#[serde(rename_all = "camelCase", deny_unknown_fields)]
348pub struct ScopeReferenceLimit {
349 pub document: DocumentAddress,
351 pub coverage: crate::ReferenceCoverage,
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub retention_limit: Option<crate::ReferencePageLimit>,
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
360#[serde(rename_all = "camelCase")]
361pub struct ScopedSearchDocument {
362 pub address: DocumentAddress,
364 pub depth: u16,
366 pub render: SearchRender,
368 pub matches: Vec<SearchHit>,
371}
372
373#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
375#[serde(rename_all = "camelCase")]
376pub struct ScopeSearch {
377 pub query: SearchQuery,
379 pub total: u32,
381 pub returned: u32,
383 pub offset: u32,
385 pub truncated: bool,
387 #[serde(skip_serializing_if = "Option::is_none")]
389 pub next_offset: Option<u32>,
390 pub documents: Vec<ScopedSearchDocument>,
392}
393
394#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
396#[serde(rename_all = "camelCase", deny_unknown_fields)]
397pub struct ScopedExplanation {
398 pub supports: Vec<crate::ExplanationSupport>,
400 pub address: DocumentAddress,
402 pub depth: u16,
404 pub label: String,
406 #[serde(skip_serializing_if = "Option::is_none")]
408 pub producer: Option<crate::Producer>,
409 pub diagnostics: Vec<mant_ir::Diagnostic>,
411 pub semantics_complete: bool,
413 pub outcome: crate::ExplanationOutcome,
415 pub total: u32,
417 pub returned: u32,
419 pub counts: crate::EvidenceCounts,
421 pub truncation: crate::ExplanationTruncation,
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
427#[serde(rename_all = "camelCase", deny_unknown_fields)]
428pub struct ScopedExplanationEvidence {
429 pub document_index: usize,
431 pub evidence: crate::ExplanationEvidence,
433}
434
435#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
437#[serde(rename_all = "camelCase")]
438pub struct ScopedQueryFailure {
439 pub address: DocumentAddress,
441 pub reason: String,
443}
444
445#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
447#[serde(
448 tag = "kind",
449 rename_all = "kebab-case",
450 rename_all_fields = "camelCase"
451)]
452pub enum ScopeQueryResult {
453 Explain {
455 explanation: ScopeExplanation,
457 },
458 Search {
460 search: ScopeSearch,
462 },
463}
464
465#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
468#[serde(rename_all = "camelCase", deny_unknown_fields)]
469pub struct ScopeExplanation {
470 pub order: crate::EvidenceOrder,
472 pub counts: crate::EvidenceCounts,
474 pub query: crate::ExplanationQuery,
476 pub outcome: crate::ExplanationOutcome,
478 pub total: u32,
480 pub returned: u32,
482 #[serde(skip_serializing_if = "Option::is_none")]
484 pub next_offset: Option<u32>,
485 pub truncation: crate::ExplanationTruncation,
487 pub documents: Vec<ScopedExplanation>,
489 pub evidence: Vec<ScopedExplanationEvidence>,
491 pub failures: Vec<ScopedQueryFailure>,
493}
494
495#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
497#[serde(rename_all = "camelCase")]
498#[schemars(extend("$id" = "urn:mant:scope-query:v0.11"))]
499pub struct ScopeQueryResponse {
500 pub schema: ScopeQuerySchema,
502 pub scope: ResolvedDocumentScope,
504 pub result: ScopeQueryResult,
506}
507
508#[derive(Deserialize)]
511#[serde(
512 remote = "ScopeExplanation",
513 rename_all = "camelCase",
514 deny_unknown_fields
515)]
516struct ScopeExplanationWire {
517 pub order: crate::EvidenceOrder,
518 pub counts: crate::EvidenceCounts,
519 pub query: crate::ExplanationQuery,
520 pub outcome: crate::ExplanationOutcome,
521 pub total: u32,
522 pub returned: u32,
523 #[serde(skip_serializing_if = "Option::is_none")]
524 pub next_offset: Option<u32>,
525 pub truncation: crate::ExplanationTruncation,
526 pub documents: Vec<ScopedExplanation>,
527 pub evidence: Vec<ScopedExplanationEvidence>,
528 pub failures: Vec<ScopedQueryFailure>,
529}
530impl<'de> Deserialize<'de> for ScopeExplanation {
531 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
532 let value = ScopeExplanationWire::deserialize(deserializer)?;
533 value
534 .validate_references()
535 .map_err(serde::de::Error::custom)?;
536 Ok(value)
537 }
538}