Skip to main content

ontocore_core/
model.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4
5/// Wire format (LSP JSON) uses snake_case via serde; SQL CLI uses [`OntologyFormat::as_str`].
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum OntologyFormat {
9    Turtle,
10    RdfXml,
11    Owl,
12    JsonLd,
13    NTriples,
14    NQuads,
15    TriG,
16    Obo,
17    Unknown,
18}
19
20impl OntologyFormat {
21    pub fn from_extension(ext: &str) -> Self {
22        match ext.to_ascii_lowercase().as_str() {
23            "ttl" => Self::Turtle,
24            "rdf" => Self::RdfXml,
25            "owl" => Self::Owl,
26            "jsonld" | "json-ld" => Self::JsonLd,
27            "nt" => Self::NTriples,
28            "nq" => Self::NQuads,
29            "trig" => Self::TriG,
30            "obo" => Self::Obo,
31            _ => Self::Unknown,
32        }
33    }
34
35    pub fn as_str(&self) -> &'static str {
36        match self {
37            Self::Turtle => "turtle",
38            Self::RdfXml => "rdf_xml",
39            Self::Owl => "owl",
40            Self::JsonLd => "json_ld",
41            Self::NTriples => "n_triples",
42            Self::NQuads => "n_quads",
43            Self::TriG => "trig",
44            Self::Obo => "obo",
45            Self::Unknown => "unknown",
46        }
47    }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
51#[serde(rename_all = "snake_case")]
52pub enum ParseStatus {
53    Ok,
54    Warning,
55    Error,
56}
57
58impl ParseStatus {
59    pub fn as_str(&self) -> &'static str {
60        match self {
61            Self::Ok => "ok",
62            Self::Warning => "warning",
63            Self::Error => "error",
64        }
65    }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
69#[serde(rename_all = "snake_case")]
70pub enum EntityKind {
71    Class,
72    ObjectProperty,
73    DataProperty,
74    AnnotationProperty,
75    Individual,
76    Ontology,
77    Other,
78}
79
80impl EntityKind {
81    pub fn as_str(&self) -> &'static str {
82        match self {
83            Self::Class => "class",
84            Self::ObjectProperty => "object_property",
85            Self::DataProperty => "data_property",
86            Self::AnnotationProperty => "annotation_property",
87            Self::Individual => "individual",
88            Self::Ontology => "ontology",
89            Self::Other => "other",
90        }
91    }
92}
93
94#[derive(Debug, Clone, Default, Serialize, Deserialize)]
95pub struct SourceLocation {
96    pub line: Option<u64>,
97    pub column: Option<u64>,
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub start_byte: Option<u64>,
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub end_byte: Option<u64>,
102}
103
104impl SourceLocation {
105    pub fn is_empty(&self) -> bool {
106        self.line.is_none()
107            && self.column.is_none()
108            && self.start_byte.is_none()
109            && self.end_byte.is_none()
110    }
111
112    pub fn at_line_col(line: u64, column: u64) -> Self {
113        Self { line: Some(line), column: Some(column), start_byte: None, end_byte: None }
114    }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
118#[serde(rename_all = "snake_case")]
119pub enum DiagnosticSeverity {
120    Error,
121    Warning,
122    Info,
123}
124
125impl DiagnosticSeverity {
126    pub fn as_str(&self) -> &'static str {
127        match self {
128            Self::Error => "error",
129            Self::Warning => "warning",
130            Self::Info => "info",
131        }
132    }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
136#[serde(rename_all = "snake_case")]
137pub enum DiagnosticCode {
138    ParseError,
139    BrokenImport,
140    UndefinedPrefix,
141    DuplicateLabel,
142    MissingLabel,
143    OrphanClass,
144    OwlBridgeFailed,
145    IoReadError,
146}
147
148impl DiagnosticCode {
149    pub fn as_str(&self) -> &'static str {
150        match self {
151            Self::ParseError => "parse_error",
152            Self::BrokenImport => "broken_import",
153            Self::UndefinedPrefix => "undefined_prefix",
154            Self::DuplicateLabel => "duplicate_label",
155            Self::MissingLabel => "missing_label",
156            Self::OrphanClass => "orphan_class",
157            Self::OwlBridgeFailed => "owl_bridge_failed",
158            Self::IoReadError => "io_read_error",
159        }
160    }
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct Diagnostic {
165    pub code: DiagnosticCode,
166    pub severity: DiagnosticSeverity,
167    pub message: String,
168    pub file: PathBuf,
169    pub range: SourceLocation,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub entity_iri: Option<String>,
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub quick_fix: Option<String>,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct OntologyDocument {
178    pub id: String,
179    pub path: PathBuf,
180    pub format: OntologyFormat,
181    pub base_iri: Option<String>,
182    pub imports: Vec<String>,
183    pub namespaces: BTreeMap<String, String>,
184    pub parse_status: ParseStatus,
185    pub content_hash: String,
186    pub modified_time: u64,
187    pub parse_message: Option<String>,
188    pub parse_error_location: Option<SourceLocation>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct Entity {
193    pub iri: String,
194    pub short_name: String,
195    pub kind: EntityKind,
196    pub ontology_id: String,
197    pub source_location: SourceLocation,
198    pub labels: Vec<String>,
199    pub comments: Vec<String>,
200    pub deprecated: bool,
201    #[serde(default, skip_serializing_if = "Option::is_none")]
202    pub obo_id: Option<String>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct Annotation {
207    pub subject: String,
208    pub predicate: String,
209    pub object: String,
210    pub ontology_id: String,
211    #[serde(default, skip_serializing_if = "SourceLocation::is_empty")]
212    pub source_location: SourceLocation,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct Axiom {
217    pub id: String,
218    pub ontology_id: String,
219    pub subject: String,
220    pub predicate: String,
221    pub object: String,
222    pub axiom_kind: String,
223    #[serde(default, skip_serializing_if = "SourceLocation::is_empty")]
224    pub source_location: SourceLocation,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct Namespace {
229    pub prefix: String,
230    pub iri: String,
231    pub ontology_id: String,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct Import {
236    pub ontology_id: String,
237    pub import_iri: String,
238}
239
240/// Snake_case axiom kind stored in [`Axiom::axiom_kind`] and SQL `axioms.axiom_kind`.
241pub const AXIOM_KIND_SUB_CLASS_OF: &str = "sub_class_of";
242pub const AXIOM_KIND_EQUIVALENT_CLASS: &str = "equivalent_class";
243pub const AXIOM_KIND_DISJOINT_CLASS: &str = "disjoint_class";
244pub const AXIOM_KIND_DOMAIN: &str = "domain";
245pub const AXIOM_KIND_RANGE: &str = "range";
246pub const AXIOM_KIND_PROPERTY_CHAIN: &str = "property_chain";
247
248#[cfg(test)]
249mod tests {
250    use super::ParseStatus;
251
252    #[test]
253    fn parse_status_as_str_matches_serde() {
254        assert_eq!(ParseStatus::Ok.as_str(), "ok");
255        assert_eq!(ParseStatus::Warning.as_str(), "warning");
256        assert_eq!(ParseStatus::Error.as_str(), "error");
257    }
258}