use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
DocumentAddress, SearchCase, SearchHit, SearchQuery, SearchRender, 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_CHARS: usize = 1024;
pub const MAX_SEMANTIC_ENTRY_CHARS: usize = 512;
pub const MAX_SEARCH_PATTERN_CHARS: usize = 4096;
pub const MAX_SOURCE_SELECTOR_CHARS: usize = 128;
pub const MAX_MANUAL_SECTION_CHARS: usize = 32;
#[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.chars().count() > 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_CHARS))]
pub selector: String,
#[schemars(length(min = 1, max = MAX_SOURCE_SELECTOR_CHARS))]
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[schemars(length(min = 1, max = MAX_MANUAL_SECTION_CHARS))]
#[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.11")]
V0Dot11,
}
impl ScopeRequestSchema {
pub const ID: &'static str = "mant.scope-request/v0.11";
}
#[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_CHARS))]
entry: String,
#[serde(default)]
options: crate::ExplanationOptions,
},
Search {
#[schemars(length(min = 1, max = MAX_SEARCH_PATTERN_CHARS))]
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.11"))]
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.11")]
V0Dot11,
}
impl ScopeQuerySchema {
pub const ID: &'static str = "mant.scope-query/v0.11";
}
#[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>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reference_limits: Vec<ScopeReferenceLimit>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ScopeReferenceLimit {
pub document: DocumentAddress,
pub coverage: crate::ReferenceCoverage,
#[serde(skip_serializing_if = "Option::is_none")]
pub retention_limit: Option<crate::ReferencePageLimit>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ScopedSearchDocument {
pub address: DocumentAddress,
pub depth: u16,
pub render: SearchRender,
pub matches: Vec<SearchHit>,
}
#[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", deny_unknown_fields)]
pub struct ScopedExplanation {
pub supports: Vec<crate::ExplanationSupport>,
pub address: DocumentAddress,
pub depth: u16,
pub label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub producer: Option<crate::Producer>,
pub diagnostics: Vec<mant_ir::Diagnostic>,
pub semantics_complete: bool,
pub outcome: crate::ExplanationOutcome,
pub total: u32,
pub returned: u32,
pub counts: crate::EvidenceCounts,
pub truncation: crate::ExplanationTruncation,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ScopedExplanationEvidence {
pub document_index: usize,
pub evidence: crate::ExplanationEvidence,
}
#[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 {
explanation: ScopeExplanation,
},
Search {
search: ScopeSearch,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ScopeExplanation {
pub order: crate::EvidenceOrder,
pub counts: crate::EvidenceCounts,
pub query: crate::ExplanationQuery,
pub outcome: crate::ExplanationOutcome,
pub total: u32,
pub returned: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_offset: Option<u32>,
pub truncation: crate::ExplanationTruncation,
pub documents: Vec<ScopedExplanation>,
pub evidence: Vec<ScopedExplanationEvidence>,
pub failures: Vec<ScopedQueryFailure>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("$id" = "urn:mant:scope-query:v0.11"))]
pub struct ScopeQueryResponse {
pub schema: ScopeQuerySchema,
pub scope: ResolvedDocumentScope,
pub result: ScopeQueryResult,
}
#[derive(Deserialize)]
#[serde(
remote = "ScopeExplanation",
rename_all = "camelCase",
deny_unknown_fields
)]
struct ScopeExplanationWire {
pub order: crate::EvidenceOrder,
pub counts: crate::EvidenceCounts,
pub query: crate::ExplanationQuery,
pub outcome: crate::ExplanationOutcome,
pub total: u32,
pub returned: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_offset: Option<u32>,
pub truncation: crate::ExplanationTruncation,
pub documents: Vec<ScopedExplanation>,
pub evidence: Vec<ScopedExplanationEvidence>,
pub failures: Vec<ScopedQueryFailure>,
}
impl<'de> Deserialize<'de> for ScopeExplanation {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = ScopeExplanationWire::deserialize(deserializer)?;
value
.validate_references()
.map_err(serde::de::Error::custom)?;
Ok(value)
}
}