1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3use std::path::PathBuf;
4
5#[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 OwlXml,
13 JsonLd,
14 NTriples,
15 NQuads,
16 TriG,
17 Obo,
18 Unknown,
19}
20
21impl OntologyFormat {
22 pub fn from_extension(ext: &str) -> Self {
23 match ext.to_ascii_lowercase().as_str() {
24 "ttl" => Self::Turtle,
25 "rdf" => Self::RdfXml,
26 "owl" => Self::Owl,
27 "owx" => Self::OwlXml,
28 "jsonld" | "json-ld" => Self::JsonLd,
29 "nt" => Self::NTriples,
30 "nq" => Self::NQuads,
31 "trig" => Self::TriG,
32 "obo" => Self::Obo,
33 _ => Self::Unknown,
34 }
35 }
36
37 pub fn as_str(&self) -> &'static str {
38 match self {
39 Self::Turtle => "turtle",
40 Self::RdfXml => "rdf_xml",
41 Self::Owl => "owl",
42 Self::OwlXml => "owl_xml",
43 Self::JsonLd => "json_ld",
44 Self::NTriples => "n_triples",
45 Self::NQuads => "n_quads",
46 Self::TriG => "trig",
47 Self::Obo => "obo",
48 Self::Unknown => "unknown",
49 }
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
54#[serde(rename_all = "snake_case")]
55pub enum ParseStatus {
56 Ok,
57 Warning,
58 Error,
59}
60
61impl ParseStatus {
62 pub fn as_str(&self) -> &'static str {
63 match self {
64 Self::Ok => "ok",
65 Self::Warning => "warning",
66 Self::Error => "error",
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
72#[serde(rename_all = "snake_case")]
73pub enum EntityKind {
74 Class,
75 ObjectProperty,
76 DataProperty,
77 AnnotationProperty,
78 Individual,
79 Ontology,
80 Other,
81}
82
83impl EntityKind {
84 pub fn as_str(&self) -> &'static str {
85 match self {
86 Self::Class => "class",
87 Self::ObjectProperty => "object_property",
88 Self::DataProperty => "data_property",
89 Self::AnnotationProperty => "annotation_property",
90 Self::Individual => "individual",
91 Self::Ontology => "ontology",
92 Self::Other => "other",
93 }
94 }
95}
96
97#[derive(Debug, Clone, Default, Serialize, Deserialize)]
98pub struct SourceLocation {
99 pub line: Option<u64>,
100 pub column: Option<u64>,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub start_byte: Option<u64>,
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub end_byte: Option<u64>,
105}
106
107impl SourceLocation {
108 pub fn is_empty(&self) -> bool {
109 self.line.is_none()
110 && self.column.is_none()
111 && self.start_byte.is_none()
112 && self.end_byte.is_none()
113 }
114
115 pub fn at_line_col(line: u64, column: u64) -> Self {
116 Self { line: Some(line), column: Some(column), start_byte: None, end_byte: None }
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
121#[serde(rename_all = "snake_case")]
122pub enum DiagnosticSeverity {
123 Error,
124 Warning,
125 Info,
126}
127
128impl DiagnosticSeverity {
129 pub fn as_str(&self) -> &'static str {
130 match self {
131 Self::Error => "error",
132 Self::Warning => "warning",
133 Self::Info => "info",
134 }
135 }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
139#[serde(rename_all = "snake_case")]
140pub enum DiagnosticCode {
141 ParseError,
142 BrokenImport,
143 UndefinedPrefix,
144 DuplicateLabel,
145 MissingLabel,
146 OrphanClass,
147 OwlBridgeFailed,
148 IoReadError,
149 PluginViolation,
150}
151
152impl DiagnosticCode {
153 pub fn as_str(&self) -> &'static str {
154 match self {
155 Self::ParseError => "parse_error",
156 Self::BrokenImport => "broken_import",
157 Self::UndefinedPrefix => "undefined_prefix",
158 Self::DuplicateLabel => "duplicate_label",
159 Self::MissingLabel => "missing_label",
160 Self::OrphanClass => "orphan_class",
161 Self::OwlBridgeFailed => "owl_bridge_failed",
162 Self::IoReadError => "io_read_error",
163 Self::PluginViolation => "plugin_violation",
164 }
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct Diagnostic {
170 pub code: DiagnosticCode,
171 pub severity: DiagnosticSeverity,
172 pub message: String,
173 pub file: PathBuf,
174 pub range: SourceLocation,
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub entity_iri: Option<String>,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub quick_fix: Option<String>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub plugin_id: Option<String>,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub plugin_code: Option<String>,
183}
184
185impl Diagnostic {
186 pub fn display_code(&self) -> String {
187 if let (Some(plugin_id), Some(code)) = (&self.plugin_id, &self.plugin_code) {
188 format!("plugin:{plugin_id}:{code}")
189 } else {
190 self.code.as_str().to_string()
191 }
192 }
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct OntologyDocument {
197 pub id: String,
198 pub path: PathBuf,
199 pub format: OntologyFormat,
200 pub base_iri: Option<String>,
201 pub imports: Vec<String>,
202 pub namespaces: BTreeMap<String, String>,
203 pub parse_status: ParseStatus,
204 pub content_hash: String,
205 pub modified_time: u64,
206 pub parse_message: Option<String>,
207 pub parse_error_location: Option<SourceLocation>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct Entity {
212 pub iri: String,
213 pub short_name: String,
214 pub kind: EntityKind,
215 pub ontology_id: String,
216 pub source_location: SourceLocation,
217 pub labels: Vec<String>,
218 pub comments: Vec<String>,
219 pub deprecated: bool,
220 #[serde(default, skip_serializing_if = "Option::is_none")]
221 pub obo_id: Option<String>,
222 #[serde(default, skip_serializing_if = "PropertyCharacteristics::is_empty")]
223 pub characteristics: PropertyCharacteristics,
224}
225
226#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
227pub struct PropertyCharacteristics {
228 pub functional: bool,
229 pub inverse_functional: bool,
230 pub transitive: bool,
231 pub symmetric: bool,
232 pub asymmetric: bool,
233 pub reflexive: bool,
234 pub irreflexive: bool,
235}
236
237impl PropertyCharacteristics {
238 pub fn is_empty(&self) -> bool {
239 !self.functional
240 && !self.inverse_functional
241 && !self.transitive
242 && !self.symmetric
243 && !self.asymmetric
244 && !self.reflexive
245 && !self.irreflexive
246 }
247}
248
249impl Default for Entity {
250 fn default() -> Self {
251 Self {
252 iri: String::new(),
253 short_name: String::new(),
254 kind: EntityKind::Class,
255 ontology_id: String::new(),
256 source_location: SourceLocation::default(),
257 labels: Vec::new(),
258 comments: Vec::new(),
259 deprecated: false,
260 obo_id: None,
261 characteristics: PropertyCharacteristics::default(),
262 }
263 }
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct Annotation {
268 pub subject: String,
269 pub predicate: String,
270 pub object: String,
271 pub ontology_id: String,
272 #[serde(default, skip_serializing_if = "SourceLocation::is_empty")]
273 pub source_location: SourceLocation,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct Axiom {
278 pub id: String,
279 pub ontology_id: String,
280 pub subject: String,
281 pub predicate: String,
282 pub object: String,
283 pub axiom_kind: String,
284 #[serde(default, skip_serializing_if = "SourceLocation::is_empty")]
285 pub source_location: SourceLocation,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct Namespace {
290 pub prefix: String,
291 pub iri: String,
292 pub ontology_id: String,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct Import {
297 pub ontology_id: String,
298 pub import_iri: String,
299}
300
301pub const AXIOM_KIND_SUB_CLASS_OF: &str = "sub_class_of";
303pub const AXIOM_KIND_EQUIVALENT_CLASS: &str = "equivalent_class";
304pub const AXIOM_KIND_DISJOINT_CLASS: &str = "disjoint_class";
305pub const AXIOM_KIND_DOMAIN: &str = "domain";
306pub const AXIOM_KIND_RANGE: &str = "range";
307pub const AXIOM_KIND_PROPERTY_CHAIN: &str = "property_chain";
308pub const AXIOM_KIND_CLASS_ASSERTION: &str = "class_assertion";
309pub const AXIOM_KIND_OBJECT_PROPERTY_ASSERTION: &str = "object_property_assertion";
310pub const AXIOM_KIND_DATA_PROPERTY_ASSERTION: &str = "data_property_assertion";
311
312#[cfg(test)]
313mod tests {
314 use super::ParseStatus;
315
316 #[test]
317 fn parse_status_as_str_matches_serde() {
318 assert_eq!(ParseStatus::Ok.as_str(), "ok");
319 assert_eq!(ParseStatus::Warning.as_str(), "warning");
320 assert_eq!(ParseStatus::Error.as_str(), "error");
321 }
322}