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 Datatype,
80 Ontology,
81 Other,
82}
83
84impl EntityKind {
85 pub fn as_str(&self) -> &'static str {
86 match self {
87 Self::Class => "class",
88 Self::ObjectProperty => "object_property",
89 Self::DataProperty => "data_property",
90 Self::AnnotationProperty => "annotation_property",
91 Self::Individual => "individual",
92 Self::Datatype => "datatype",
93 Self::Ontology => "ontology",
94 Self::Other => "other",
95 }
96 }
97}
98
99#[derive(Debug, Clone, Default, Serialize, Deserialize)]
100pub struct SourceLocation {
101 pub line: Option<u64>,
102 pub column: Option<u64>,
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub start_byte: Option<u64>,
105 #[serde(skip_serializing_if = "Option::is_none")]
106 pub end_byte: Option<u64>,
107}
108
109impl SourceLocation {
110 pub fn is_empty(&self) -> bool {
111 self.line.is_none()
112 && self.column.is_none()
113 && self.start_byte.is_none()
114 && self.end_byte.is_none()
115 }
116
117 pub fn at_line_col(line: u64, column: u64) -> Self {
118 Self { line: Some(line), column: Some(column), start_byte: None, end_byte: None }
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
123#[serde(rename_all = "snake_case")]
124pub enum DiagnosticSeverity {
125 Error,
126 Warning,
127 Info,
128}
129
130impl DiagnosticSeverity {
131 pub fn as_str(&self) -> &'static str {
132 match self {
133 Self::Error => "error",
134 Self::Warning => "warning",
135 Self::Info => "info",
136 }
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
141#[serde(rename_all = "snake_case")]
142pub enum DiagnosticCode {
143 ParseError,
144 BrokenImport,
145 UndefinedPrefix,
146 DuplicateLabel,
147 MissingLabel,
148 OrphanClass,
149 OwlBridgeFailed,
150 IoReadError,
151 PluginViolation,
152}
153
154impl DiagnosticCode {
155 pub fn as_str(&self) -> &'static str {
156 match self {
157 Self::ParseError => "parse_error",
158 Self::BrokenImport => "broken_import",
159 Self::UndefinedPrefix => "undefined_prefix",
160 Self::DuplicateLabel => "duplicate_label",
161 Self::MissingLabel => "missing_label",
162 Self::OrphanClass => "orphan_class",
163 Self::OwlBridgeFailed => "owl_bridge_failed",
164 Self::IoReadError => "io_read_error",
165 Self::PluginViolation => "plugin_violation",
166 }
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct Diagnostic {
172 pub code: DiagnosticCode,
173 pub severity: DiagnosticSeverity,
174 pub message: String,
175 pub file: PathBuf,
176 pub range: SourceLocation,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub entity_iri: Option<String>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub quick_fix: Option<String>,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub plugin_id: Option<String>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub plugin_code: Option<String>,
185}
186
187impl Diagnostic {
188 pub fn display_code(&self) -> String {
189 if let (Some(plugin_id), Some(code)) = (&self.plugin_id, &self.plugin_code) {
190 format!("plugin:{plugin_id}:{code}")
191 } else {
192 self.code.as_str().to_string()
193 }
194 }
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct OntologyDocument {
199 pub id: String,
200 pub path: PathBuf,
201 pub format: OntologyFormat,
202 pub base_iri: Option<String>,
203 #[serde(default)]
205 pub version_iri: Option<String>,
206 pub imports: Vec<String>,
207 pub namespaces: BTreeMap<String, String>,
208 pub parse_status: ParseStatus,
209 pub content_hash: String,
210 pub modified_time: u64,
211 pub parse_message: Option<String>,
212 pub parse_error_location: Option<SourceLocation>,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct Entity {
217 pub iri: String,
218 pub short_name: String,
219 pub kind: EntityKind,
220 pub ontology_id: String,
221 pub source_location: SourceLocation,
222 pub labels: Vec<String>,
223 pub comments: Vec<String>,
224 pub deprecated: bool,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub obo_id: Option<String>,
227 #[serde(default, skip_serializing_if = "PropertyCharacteristics::is_empty")]
228 pub characteristics: PropertyCharacteristics,
229}
230
231#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
232pub struct PropertyCharacteristics {
233 pub functional: bool,
234 pub inverse_functional: bool,
235 pub transitive: bool,
236 pub symmetric: bool,
237 pub asymmetric: bool,
238 pub reflexive: bool,
239 pub irreflexive: bool,
240}
241
242impl PropertyCharacteristics {
243 pub fn is_empty(&self) -> bool {
244 !self.functional
245 && !self.inverse_functional
246 && !self.transitive
247 && !self.symmetric
248 && !self.asymmetric
249 && !self.reflexive
250 && !self.irreflexive
251 }
252}
253
254impl Default for Entity {
255 fn default() -> Self {
256 Self {
257 iri: String::new(),
258 short_name: String::new(),
259 kind: EntityKind::Class,
260 ontology_id: String::new(),
261 source_location: SourceLocation::default(),
262 labels: Vec::new(),
263 comments: Vec::new(),
264 deprecated: false,
265 obo_id: None,
266 characteristics: PropertyCharacteristics::default(),
267 }
268 }
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct Annotation {
273 pub subject: String,
274 pub predicate: String,
275 pub object: String,
276 pub ontology_id: String,
277 #[serde(default, skip_serializing_if = "SourceLocation::is_empty")]
278 pub source_location: SourceLocation,
279}
280
281#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
283pub struct AxiomAnnotation {
284 pub predicate: String,
285 pub value: String,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct Axiom {
290 pub id: String,
291 pub ontology_id: String,
292 pub subject: String,
293 pub predicate: String,
294 pub object: String,
295 pub axiom_kind: String,
296 #[serde(default, skip_serializing_if = "SourceLocation::is_empty")]
297 pub source_location: SourceLocation,
298 #[serde(default, skip_serializing_if = "Vec::is_empty")]
300 pub annotations: Vec<AxiomAnnotation>,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct Namespace {
305 pub prefix: String,
306 pub iri: String,
307 pub ontology_id: String,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct Import {
312 pub ontology_id: String,
313 pub import_iri: String,
314}
315
316pub const AXIOM_KIND_SUB_CLASS_OF: &str = "sub_class_of";
318pub const AXIOM_KIND_EQUIVALENT_CLASS: &str = "equivalent_class";
319pub const AXIOM_KIND_DISJOINT_CLASS: &str = "disjoint_class";
320pub const AXIOM_KIND_DOMAIN: &str = "domain";
321pub const AXIOM_KIND_RANGE: &str = "range";
322pub const AXIOM_KIND_PROPERTY_CHAIN: &str = "property_chain";
323pub const AXIOM_KIND_CLASS_ASSERTION: &str = "class_assertion";
324pub const AXIOM_KIND_OBJECT_PROPERTY_ASSERTION: &str = "object_property_assertion";
325pub const AXIOM_KIND_DATA_PROPERTY_ASSERTION: &str = "data_property_assertion";
326pub const AXIOM_KIND_HAS_KEY: &str = "has_key";
327pub const AXIOM_KIND_DISJOINT_UNION: &str = "disjoint_union";
328pub const AXIOM_KIND_INVERSE_OBJECT_PROPERTIES: &str = "inverse_object_properties";
329pub const AXIOM_KIND_EQUIVALENT_OBJECT_PROPERTIES: &str = "equivalent_object_properties";
330pub const AXIOM_KIND_DISJOINT_OBJECT_PROPERTIES: &str = "disjoint_object_properties";
331pub const AXIOM_KIND_EQUIVALENT_DATA_PROPERTIES: &str = "equivalent_data_properties";
332pub const AXIOM_KIND_DISJOINT_DATA_PROPERTIES: &str = "disjoint_data_properties";
333pub const AXIOM_KIND_SUB_OBJECT_PROPERTY_OF: &str = "sub_object_property_of";
334pub const AXIOM_KIND_SUB_DATA_PROPERTY_OF: &str = "sub_data_property_of";
335pub const AXIOM_KIND_NEGATIVE_OBJECT_PROPERTY_ASSERTION: &str =
336 "negative_object_property_assertion";
337pub const AXIOM_KIND_NEGATIVE_DATA_PROPERTY_ASSERTION: &str = "negative_data_property_assertion";
338pub const AXIOM_KIND_SAME_INDIVIDUAL: &str = "same_individual";
339pub const AXIOM_KIND_DIFFERENT_INDIVIDUALS: &str = "different_individuals";
340pub const AXIOM_KIND_DATATYPE_DEFINITION: &str = "datatype_definition";
341
342#[cfg(test)]
343mod tests {
344 use super::ParseStatus;
345
346 #[test]
347 fn parse_status_as_str_matches_serde() {
348 assert_eq!(ParseStatus::Ok.as_str(), "ok");
349 assert_eq!(ParseStatus::Warning.as_str(), "warning");
350 assert_eq!(ParseStatus::Error.as_str(), "error");
351 }
352}