mod classification;
mod locations;
mod matches;
mod support;
use crate::{OutlineTrail, Producer};
pub use classification::*;
pub use locations::ExplanationTextRoot;
use mant_ir::{
Diagnostic, DocumentAddress, EntryKind, Inline, NameCase, NodeId, SourceSpan, ValueDomain,
};
pub use matches::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub use support::*;
pub const MAX_EXPLANATION_RESULTS: u32 = 256;
pub const MAX_EXPLANATION_CANDIDATES: usize = 10_000;
pub const MAX_EXPLANATION_CONTENT_BYTES: u32 = 4 * 1024 * 1024;
pub const MAX_EXPLANATION_RELATIONS: usize = 4096;
pub const MAX_EXPLANATION_RELATION_DEPTH: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ExplanationOptions {
#[serde(default = "default_explanation_limit")]
#[schemars(range(min = 1, max = 256))]
pub limit: u32,
#[serde(default)]
pub offset: u32,
#[serde(default = "default_explanation_content_bytes")]
#[schemars(range(min = 1, max = 4_194_304))]
pub content_bytes: u32,
}
impl Default for ExplanationOptions {
fn default() -> Self {
Self {
limit: default_explanation_limit(),
offset: 0,
content_bytes: default_explanation_content_bytes(),
}
}
}
#[must_use]
pub const fn default_explanation_limit() -> u32 {
50
}
#[must_use]
pub const fn default_explanation_content_bytes() -> u32 {
1024 * 1024
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ExplanationQuery {
#[schemars(length(min = 1, max = 512))]
pub entry: String,
#[serde(default)]
pub options: ExplanationOptions,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum ExplanationSchema {
#[serde(rename = "mant.explanation/v0.11")]
V0Dot11,
}
impl ExplanationSchema {
pub const ID: &'static str = "mant.explanation/v0.11";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum ExplanationOutcome {
Evidence,
NoEvidence,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
#[serde(
tag = "kind",
rename_all = "kebab-case",
rename_all_fields = "camelCase",
deny_unknown_fields
)]
pub enum EvidenceBasis {
Name {
matches: Vec<ExplanationNameMatch>,
},
Form {
matches: Vec<ExplanationFormMatch>,
},
Literal,
Identity {
fields: Vec<ExplanationIdentityField>,
},
AliasGroup {
members: Vec<String>,
},
Related {
from: NodeId,
declarations: Vec<NodeId>,
},
}
#[derive(Deserialize)]
#[serde(
tag = "kind",
rename_all = "kebab-case",
rename_all_fields = "camelCase",
deny_unknown_fields
)]
enum ClosedEvidenceBasis {
Name {
matches: Vec<ExplanationNameMatch>,
},
Form {
matches: Vec<ExplanationFormMatch>,
},
Literal {},
Identity {
fields: Vec<ExplanationIdentityField>,
},
AliasGroup {
members: Vec<String>,
},
Related {
from: NodeId,
declarations: Vec<NodeId>,
},
}
impl<'de> Deserialize<'de> for EvidenceBasis {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(match ClosedEvidenceBasis::deserialize(deserializer)? {
ClosedEvidenceBasis::Name { matches } => Self::Name { matches },
ClosedEvidenceBasis::Form { matches } => Self::Form { matches },
ClosedEvidenceBasis::Literal {} => Self::Literal,
ClosedEvidenceBasis::Identity { fields } => Self::Identity { fields },
ClosedEvidenceBasis::AliasGroup { members } => Self::AliasGroup { members },
ClosedEvidenceBasis::Related { from, declarations } => {
Self::Related { from, declarations }
}
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ExplanationEntry {
pub kind: EntryKind,
pub case: NameCase,
pub names: Vec<String>,
pub forms: Vec<Vec<Inline>>,
pub name_bindings: Vec<ExplanationNameBinding>,
pub alias_groups: Vec<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub alias_of: Option<NodeId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value_domain: Option<ValueDomain>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[allow(clippy::struct_excessive_bools)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ExplanationEvidence {
#[serde(skip_serializing_if = "Option::is_none")]
pub support: Option<usize>,
pub support_omitted: bool,
pub class: EvidenceClass,
pub ordinal: u32,
pub outline: OutlineTrail,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<SourceSpan>,
pub bases: Vec<EvidenceBasis>,
pub previews: Vec<ExplanationPreview>,
pub previews_omitted: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub entry: Option<ExplanationEntry>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<ExplanationContent>,
pub details_omitted: bool,
pub match_details_omitted: bool,
pub name_bindings_omitted: bool,
pub content_omitted: bool,
}
impl ExplanationEvidence {
#[must_use]
pub const fn has_omitted_content(&self) -> bool {
self.content_omitted
|| self.support_omitted
|| self.details_omitted
|| self.previews_omitted
|| self.match_details_omitted
|| self.name_bindings_omitted
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind", rename_all = "kebab-case", deny_unknown_fields)]
pub enum ExplanationContent {
SharedEntry {
support: usize,
path: Vec<ExplanationBlockStep>,
#[serde(rename = "itemIndex")]
item_index: usize,
},
DeclarationMember {
support: usize,
#[serde(rename = "itemIndex")]
item_index: usize,
},
Entry {
block: mant_ir::Block,
},
Block {
block: mant_ir::Block,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[schemars(extend("$id" = "urn:mant:explanation:v0.11"))]
pub struct QueryExplanation {
pub supports: Vec<ExplanationSupport>,
pub order: EvidenceOrder,
pub counts: EvidenceCounts,
pub schema: ExplanationSchema,
pub query: ExplanationQuery,
pub label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<DocumentAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
pub producer: Option<Producer>,
pub outcome: ExplanationOutcome,
pub total: u32,
pub returned: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_offset: Option<u32>,
pub truncation: ExplanationTruncation,
pub semantics_complete: bool,
pub diagnostics: Vec<Diagnostic>,
pub evidence: Vec<ExplanationEvidence>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ExplanationTruncation {
pub candidates: bool,
pub relations: bool,
pub content: bool,
}
#[derive(Deserialize)]
#[serde(
remote = "QueryExplanation",
rename_all = "camelCase",
deny_unknown_fields
)]
struct QueryExplanationWire {
pub supports: Vec<ExplanationSupport>,
pub order: EvidenceOrder,
pub counts: EvidenceCounts,
pub schema: ExplanationSchema,
pub query: ExplanationQuery,
pub label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<DocumentAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
pub producer: Option<Producer>,
pub outcome: ExplanationOutcome,
pub total: u32,
pub returned: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_offset: Option<u32>,
pub truncation: ExplanationTruncation,
pub semantics_complete: bool,
pub diagnostics: Vec<Diagnostic>,
pub evidence: Vec<ExplanationEvidence>,
}
impl<'de> Deserialize<'de> for QueryExplanation {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = QueryExplanationWire::deserialize(deserializer)?;
value
.validate_references()
.map_err(serde::de::Error::custom)?;
Ok(value)
}
}