use std::{collections::BTreeMap, error::Error, path::PathBuf};
use crate::{
attribute::Attribute,
markdown::frontmatter::FrontMatter,
object::{Enumeration, Object},
option::AttrOption,
prelude::DataModel,
};
use super::schema::{AttributeDefinition, ClassDefinition, EnumDefinition, LinkML};
pub fn deserialize_linkml(path: &PathBuf) -> Result<DataModel, Box<dyn Error>> {
let yaml = std::fs::read_to_string(path)?;
let linkml: LinkML = serde_yaml::from_str(&yaml)?;
Ok(DataModel::from(linkml))
}
impl From<LinkML> for DataModel {
fn from(linkml: LinkML) -> Self {
let config = FrontMatter {
prefix: linkml.id,
prefixes: Some(linkml.prefixes.into_iter().collect()),
..Default::default()
};
let mut objects = Vec::new();
for (name, class) in linkml.classes {
let mut obj = Object::from(class.clone());
obj.name = name;
for slot_name in class.slots {
if let Some(slot_def) = linkml.slots.get(&slot_name) {
let mut attr = Attribute::from(slot_def.clone());
attr.name = slot_name;
obj.attributes.push(attr);
}
}
objects.push(obj);
}
let enums = linkml
.enums
.into_iter()
.map(|(name, def)| {
let mut enum_ = Enumeration::from(def);
enum_.name = name;
enum_
})
.collect();
DataModel {
name: Some(linkml.name),
config: Some(config),
objects,
enums,
}
}
}
impl From<ClassDefinition> for Object {
fn from(class: ClassDefinition) -> Self {
let mut attributes = Vec::new();
if let Some(attrs) = class.attributes {
for (name, def) in attrs {
let mut attr = Attribute::from(def);
attr.name = name;
attributes.push(attr);
}
}
if let Some(slot_usage) = class.slot_usage {
for (name, usage) in slot_usage {
if let Some(pattern) = usage.pattern {
if let Some(attr) = attributes.iter_mut().find(|a| a.name == name) {
attr.options.push(AttrOption::Pattern(pattern));
}
}
}
}
Object {
name: class.is_a.unwrap_or_default(),
docstring: class.description.unwrap_or_default(),
term: class.class_uri,
attributes,
mixins: Vec::new(),
position: None,
}
}
}
impl From<AttributeDefinition> for Attribute {
fn from(attr: AttributeDefinition) -> Self {
Attribute {
name: String::new(), docstring: attr.description.unwrap_or_default(),
dtypes: vec![attr.range.unwrap_or_else(|| "string".to_string())],
term: attr.slot_uri,
is_array: attr.multivalued.unwrap_or(false),
is_id: attr.identifier.unwrap_or(false),
required: attr.required.unwrap_or(false),
options: Vec::new(), default: None,
is_enum: false,
position: None,
xml: None,
import_prefix: None,
}
}
}
impl From<EnumDefinition> for Enumeration {
fn from(enum_def: EnumDefinition) -> Self {
let mappings = enum_def
.permissible_values
.into_iter()
.map(|(key, value)| (key, value.meaning.unwrap_or_default()))
.collect::<BTreeMap<String, String>>();
Enumeration {
name: String::new(), docstring: enum_def.description.unwrap_or_default(),
mappings,
position: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn deserialize_linkml_test() {
let model = deserialize_linkml(&PathBuf::from("tests/data/expected_linkml.yml")).unwrap();
let expected_model =
DataModel::from_markdown(&PathBuf::from("tests/data/model.md")).unwrap();
assert_eq!(
model.objects.len(),
expected_model.objects.len(),
"Objects length mismatch"
);
assert_eq!(
model.enums.len(),
expected_model.enums.len(),
"Enums length mismatch"
);
for obj in model.objects.iter() {
let other_obj = expected_model
.objects
.iter()
.find(|o| o.name == obj.name)
.unwrap_or_else(|| panic!("Object {} not found", obj.name));
assert_eq!(obj.name, other_obj.name, "Object name mismatch");
assert_eq!(
obj.docstring, other_obj.docstring,
"Object docstring mismatch"
);
assert_eq!(obj.term, other_obj.term, "Object term mismatch");
assert_eq!(
obj.attributes.len(),
other_obj.attributes.len(),
"Attributes length mismatch"
);
for attr in obj.attributes.iter() {
let other_attr = other_obj
.attributes
.iter()
.find(|a| a.name == attr.name)
.unwrap_or_else(|| panic!("Attribute {} not found", attr.name));
assert_eq!(attr.name, other_attr.name, "Attribute name mismatch");
}
}
for enum_ in model.enums.iter() {
let other_enum = expected_model
.enums
.iter()
.find(|e| e.name == enum_.name)
.unwrap_or_else(|| panic!("Enum {} not found", enum_.name));
assert_eq!(enum_.name, other_enum.name, "Enum name mismatch");
assert_eq!(
enum_.docstring, other_enum.docstring,
"Enum docstring mismatch"
);
assert_eq!(
enum_.mappings, other_enum.mappings,
"Enum mappings mismatch"
);
}
}
}