Skip to main content

eure_schema/
resolver.rs

1//! Schema import graph types.
2//!
3//! Query-layer code is responsible for loading files and building a
4//! [`LoadedSchemaSet`]. The converter only consumes that already-loaded graph.
5
6use std::path::PathBuf;
7
8use eure_document::document::EureDocument;
9use eure_document::identifier::Identifier;
10use indexmap::IndexMap;
11use thiserror::Error;
12
13/// Stable identity of a schema document, used for cycle detection and diagnostics.
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum ResolvedSchemaUri {
16    /// A lexically normalized local path. This is intentionally not
17    /// canonicalized; query code must not require filesystem access.
18    Local(PathBuf),
19    /// An opaque inline name (used for tests and bundled schemas).
20    Inline(String),
21}
22
23impl std::fmt::Display for ResolvedSchemaUri {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            Self::Local(p) => write!(f, "{}", p.display()),
27            Self::Inline(s) => write!(f, "<inline:{}>", s),
28        }
29    }
30}
31
32/// Errors raised while resolving a `$import` entry.
33#[derive(Debug, Error, Clone, PartialEq, Eq)]
34pub enum ResolverError {
35    #[error("imported path {resolved} escapes workspace root {workspace_root}")]
36    EscapesWorkspaceRoot {
37        resolved: PathBuf,
38        workspace_root: PathBuf,
39    },
40
41    #[error("absolute schema import paths are not supported in v1: {raw_path}")]
42    AbsolutePathUnsupported { raw_path: String },
43
44    #[error("cannot import {scheme} URLs (not yet supported)")]
45    UnsupportedScheme { scheme: String },
46
47    #[error("invalid schema import URL {raw_url}: {reason}")]
48    InvalidUrl { raw_url: String, reason: String },
49
50    #[error("schema import `{raw_path}` has no local filesystem base: {base}")]
51    NonLocalBase {
52        raw_path: String,
53        base: ResolvedSchemaUri,
54    },
55}
56
57/// A complete set of schema documents that has already been loaded by the
58/// caller. Import edges are keyed by the importing document URI and alias.
59#[derive(Debug, Clone, PartialEq)]
60pub struct LoadedSchemaSet {
61    pub root_uri: ResolvedSchemaUri,
62    pub documents: IndexMap<ResolvedSchemaUri, EureDocument>,
63    pub imports: IndexMap<ResolvedSchemaUri, IndexMap<Identifier, ResolvedSchemaUri>>,
64}
65
66impl LoadedSchemaSet {
67    pub fn new(root_uri: ResolvedSchemaUri, root_doc: EureDocument) -> Self {
68        let mut documents = IndexMap::new();
69        documents.insert(root_uri.clone(), root_doc);
70        Self {
71            root_uri,
72            documents,
73            imports: IndexMap::new(),
74        }
75    }
76
77    pub fn insert_document(&mut self, uri: ResolvedSchemaUri, doc: EureDocument) {
78        self.documents.insert(uri, doc);
79    }
80
81    pub fn insert_import(
82        &mut self,
83        base: ResolvedSchemaUri,
84        alias: Identifier,
85        target: ResolvedSchemaUri,
86    ) {
87        self.imports.entry(base).or_default().insert(alias, target);
88    }
89
90    pub fn document(&self, uri: &ResolvedSchemaUri) -> Option<&EureDocument> {
91        self.documents.get(uri)
92    }
93
94    pub fn import_target(
95        &self,
96        base: &ResolvedSchemaUri,
97        alias: &Identifier,
98    ) -> Option<&ResolvedSchemaUri> {
99        self.imports.get(base)?.get(alias)
100    }
101}