use std::{collections::BTreeMap, error::Error, fmt, sync::Arc};
use crate::schema::{NonEmpty, Schema};
pub type LoadSchemaResult = Result<LoadedSchema, InvalidSchema>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JsonSchemaResourceInput {
pub uri: String,
pub label: Option<SourceLabel>,
pub contents: JsonSchemaResourceContents,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JsonSchemaResourceContents {
Loaded(Arc<str>),
ReadFailure(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LinkedJsonSchemaInput {
pub root_uri: String,
pub resources: Vec<JsonSchemaResourceInput>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JsonSchemaExternalReference {
pub physical_uri: String,
pub logical_uri: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoadedSchema {
pub schema: Schema,
pub sources: SchemaSources,
pub locations: SchemaLocations,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidSchema {
pub sources: SchemaSources,
pub errors: NonEmpty<SchemaError>,
}
impl fmt::Display for InvalidSchema {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let additional = self.errors.rest.len();
write!(formatter, "{}", self.errors.first)?;
if additional > 0 {
write!(formatter, " (and {additional} more schema error")?;
if additional != 1 {
formatter.write_str("s")?;
}
formatter.write_str(")")?;
}
Ok(())
}
}
impl Error for InvalidSchema {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SchemaSource {
pub label: Option<SourceLabel>,
pub text: Arc<str>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SchemaSources {
pub primary: SourceId,
pub documents: BTreeMap<SourceId, SchemaSource>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct SourceId(pub u32);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct SourceLabel(pub String);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SchemaLocations {
pub document: SourceRange,
pub nodes: BTreeMap<SchemaNode, SourceRange>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SchemaNode {
Title,
Frontmatter,
FrontmatterSchemaDeclaration,
FrontmatterSchemaDocument,
Rule(RulePath),
Constraint(ConstraintPath),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RulePath {
pub scope: ScopePath,
pub index: RuleIndex,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ConstraintPath {
pub scope: ScopePath,
pub index: ConstraintIndex,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ScopePath(pub Vec<RuleIndex>);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct RuleIndex(pub usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ConstraintIndex(pub usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TextRange {
pub start: ByteOffset,
pub end: ByteOffset,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ByteOffset(pub usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SourceRange {
pub source: SourceId,
pub range: TextRange,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SchemaError {
pub kind: SchemaErrorKind,
pub range: SourceRange,
pub related: Vec<RelatedLocation>,
pub message: String,
}
impl fmt::Display for SchemaError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}: {}", self.kind, self.message)
}
}
impl Error for SchemaError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RelatedLocation {
pub range: SourceRange,
pub message: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SchemaErrorKind {
Syntax,
InvalidDocumentShape,
UnsupportedVersion,
DuplicateId,
UnresolvedRef,
ForbiddenRef,
DuplicateRef,
ReservedId,
InvalidMatcher,
InvalidRepeat,
OrderedScopeMismatch,
ConflictingCardinality,
ConflictingFrontmatter,
ConflictingOutline,
InvalidFrontmatterSchema,
}
impl SchemaErrorKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::Syntax => "syntax",
Self::InvalidDocumentShape => "invalid-document-shape",
Self::UnsupportedVersion => "unsupported-version",
Self::DuplicateId => "duplicate-id",
Self::UnresolvedRef => "unresolved-ref",
Self::ForbiddenRef => "forbidden-ref",
Self::DuplicateRef => "duplicate-ref",
Self::ReservedId => "reserved-id",
Self::InvalidMatcher => "invalid-matcher",
Self::InvalidRepeat => "invalid-repeat",
Self::OrderedScopeMismatch => "ordered-scope-mismatch",
Self::ConflictingCardinality => "conflicting-cardinality",
Self::ConflictingFrontmatter => "conflicting-frontmatter",
Self::ConflictingOutline => "conflicting-outline",
Self::InvalidFrontmatterSchema => "invalid-frontmatter-schema",
}
}
}
impl fmt::Display for SchemaErrorKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::SchemaErrorKind;
#[test]
fn schema_error_ids_use_the_public_spellings() {
let expected = [
(SchemaErrorKind::Syntax, "syntax"),
(
SchemaErrorKind::InvalidDocumentShape,
"invalid-document-shape",
),
(SchemaErrorKind::UnsupportedVersion, "unsupported-version"),
(SchemaErrorKind::DuplicateId, "duplicate-id"),
(SchemaErrorKind::UnresolvedRef, "unresolved-ref"),
(SchemaErrorKind::ForbiddenRef, "forbidden-ref"),
(SchemaErrorKind::DuplicateRef, "duplicate-ref"),
(SchemaErrorKind::ReservedId, "reserved-id"),
(SchemaErrorKind::InvalidMatcher, "invalid-matcher"),
(SchemaErrorKind::InvalidRepeat, "invalid-repeat"),
(
SchemaErrorKind::OrderedScopeMismatch,
"ordered-scope-mismatch",
),
(
SchemaErrorKind::ConflictingCardinality,
"conflicting-cardinality",
),
(
SchemaErrorKind::ConflictingFrontmatter,
"conflicting-frontmatter",
),
(SchemaErrorKind::ConflictingOutline, "conflicting-outline"),
(
SchemaErrorKind::InvalidFrontmatterSchema,
"invalid-frontmatter-schema",
),
];
for (kind, spelling) in expected {
assert_eq!(kind.as_str(), spelling);
}
}
}