strixonomy_obo/
obofoundry.rs1use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8use thiserror::Error;
9
10#[derive(Debug, Error)]
11pub enum OboFoundryError {
12 #[error("invalid OBO Foundry registry JSON: {0}")]
13 Json(#[from] serde_json::Error),
14}
15
16pub type Result<T> = std::result::Result<T, OboFoundryError>;
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct OboFoundryContact {
20 #[serde(default)]
21 pub email: Option<String>,
22 #[serde(default)]
23 pub github: Option<String>,
24 #[serde(default)]
25 pub label: Option<String>,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct OboFoundryLicense {
30 #[serde(default)]
31 pub label: Option<String>,
32 #[serde(default)]
33 pub url: Option<String>,
34 #[serde(default)]
35 pub logo: Option<String>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct OboFoundryEntry {
40 pub id: String,
41 #[serde(default)]
42 pub title: Option<String>,
43 #[serde(default)]
44 pub description: Option<String>,
45 #[serde(default)]
46 pub activity_status: Option<String>,
47 #[serde(default)]
48 pub ontology_purl: Option<String>,
49 #[serde(default)]
50 pub homepage: Option<String>,
51 #[serde(default)]
52 pub contact: Option<OboFoundryContact>,
53 #[serde(default)]
54 pub license: Option<OboFoundryLicense>,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58struct RegistryFile {
59 #[serde(default)]
60 ontologies: Vec<OboFoundryEntry>,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct OboFoundryRegistry {
65 ontologies: Vec<OboFoundryEntry>,
66 by_id: BTreeMap<String, usize>,
67}
68
69impl OboFoundryRegistry {
70 pub fn empty() -> Self {
71 Self { ontologies: Vec::new(), by_id: BTreeMap::new() }
72 }
73
74 pub fn from_entries(ontologies: Vec<OboFoundryEntry>) -> Self {
75 let mut by_id = BTreeMap::new();
76 for (i, e) in ontologies.iter().enumerate() {
77 by_id.insert(e.id.clone(), i);
78 }
79 Self { ontologies, by_id }
80 }
81
82 pub fn ontologies(&self) -> &[OboFoundryEntry] {
83 &self.ontologies
84 }
85
86 pub fn get(&self, id: &str) -> Option<&OboFoundryEntry> {
87 self.by_id.get(id).map(|&i| &self.ontologies[i])
88 }
89
90 pub fn len(&self) -> usize {
91 self.ontologies.len()
92 }
93
94 pub fn is_empty(&self) -> bool {
95 self.ontologies.is_empty()
96 }
97}
98
99pub fn parse_registry_json(bytes: &[u8]) -> Result<OboFoundryRegistry> {
101 let file: RegistryFile = serde_json::from_slice(bytes)?;
102 Ok(OboFoundryRegistry::from_entries(file.ontologies))
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn parse_minimal_registry() {
111 let json = br#"{
112 "ontologies": [
113 {
114 "id": "bfo",
115 "title": "Basic Formal Ontology",
116 "activity_status": "active",
117 "ontology_purl": "http://purl.obolibrary.org/obo/bfo.owl",
118 "contact": {"email": "a@b.c", "label": "Alice"},
119 "license": {"label": "CC-BY", "url": "http://creativecommons.org/licenses/by/4.0/"}
120 }
121 ]
122 }"#;
123 let reg = parse_registry_json(json).expect("parse");
124 assert_eq!(reg.len(), 1);
125 let e = reg.get("bfo").expect("bfo");
126 assert_eq!(e.title.as_deref(), Some("Basic Formal Ontology"));
127 assert_eq!(e.contact.as_ref().unwrap().label.as_deref(), Some("Alice"));
128 assert_eq!(e.license.as_ref().unwrap().label.as_deref(), Some("CC-BY"));
129 }
130
131 #[test]
132 fn empty_registry() {
133 let reg = parse_registry_json(br#"{"ontologies":[]}"#).unwrap();
134 assert!(reg.is_empty());
135 assert!(reg.get("go").is_none());
136 }
137}