Skip to content

Commit a078115

Browse files
authored
Merge pull request #3 from Kapersyx/fix/inlined-subclasses-with-designates_type
Fix polymorphism when loading the contents of a multivalued inlined_as_dict slot #85
2 parents d120fb1 + 4f147b4 commit a078115

4 files changed

Lines changed: 118 additions & 7 deletions

File tree

src/runtime/src/lib.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -291,16 +291,18 @@ impl LinkMLValue {
291291
})?;
292292
let mut values = HashMap::new();
293293
for (k, v) in map.into_iter() {
294-
let chosen = sv
294+
let base = sv
295295
.get_class(&Identifier::new(range_cv.name()), conv)
296296
.ok()
297297
.flatten()
298298
.unwrap_or_else(|| range_cv.clone());
299299
let child = match v {
300300
JsonValue::Object(m) => {
301+
// Select the most specific subclass using any type designator in the map
302+
let selected = Self::select_class(&m, &base, sv, conv);
301303
let mut child_values = HashMap::new();
302304
for (ck, cv) in m.into_iter() {
303-
let slot_tmp = chosen
305+
let slot_tmp = selected
304306
.slots()
305307
.iter()
306308
.find(|s| slot_matches_key(s, &ck))
@@ -315,7 +317,7 @@ impl LinkMLValue {
315317
key_name,
316318
Self::from_json_internal(
317319
cv,
318-
chosen.clone(),
320+
selected.clone(),
319321
slot_tmp,
320322
sv,
321323
conv,
@@ -326,14 +328,14 @@ impl LinkMLValue {
326328
}
327329
LinkMLValue::Object {
328330
values: child_values,
329-
class: chosen.clone(),
331+
class: selected,
330332
sv: sv.clone(),
331333
}
332334
}
333335
other => {
334336
// Scalar mapping value: attach it to a chosen scalar slot if any
335337
let scalar_slot = Self::find_scalar_slot_for_inlined_map(
336-
&chosen,
338+
&base,
337339
range_cv
338340
.key_or_identifier_slot()
339341
.map(|s| s.name.as_str())
@@ -351,13 +353,13 @@ impl LinkMLValue {
351353
LinkMLValue::Scalar {
352354
value: other,
353355
slot: scalar_slot.clone(),
354-
class: Some(chosen.clone()),
356+
class: Some(base.clone()),
355357
sv: sv.clone(),
356358
},
357359
);
358360
LinkMLValue::Object {
359361
values: child_values,
360-
class: chosen.clone(),
362+
class: base.clone(),
361363
sv: sv.clone(),
362364
}
363365
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"things": {
3+
"alpha": {
4+
"typeURI": "ThingA",
5+
"a_only": "foo",
6+
"common": "shared"
7+
},
8+
"beta": {
9+
"typeURI": "ThingB",
10+
"b_only": true,
11+
"common": "shared"
12+
}
13+
}
14+
}
15+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
id: https://example.org/mapping
2+
name: mapping
3+
prefixes:
4+
ex: https://example.org/
5+
6+
default_prefix: ex
7+
8+
classes:
9+
Bag:
10+
attributes:
11+
things:
12+
range: Thing
13+
multivalued: true
14+
inlined: true
15+
inlined_as_list: false
16+
17+
Thing:
18+
attributes:
19+
key:
20+
range: string
21+
key: true
22+
typeURI:
23+
range: string
24+
designates_type: true
25+
common:
26+
range: string
27+
28+
ThingA:
29+
is_a: Thing
30+
attributes:
31+
a_only:
32+
range: string
33+
34+
ThingB:
35+
is_a: Thing
36+
attributes:
37+
b_only:
38+
range: boolean
39+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use linkml_runtime::{load_json_file, validate, LinkMLValue};
2+
use linkml_schemaview::identifier::{converter_from_schema, Identifier};
3+
use linkml_schemaview::io::from_yaml;
4+
use linkml_schemaview::schemaview::SchemaView;
5+
use std::path::{Path, PathBuf};
6+
7+
fn data_path(name: &str) -> PathBuf {
8+
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
9+
p.push("tests");
10+
p.push("data");
11+
p.push(name);
12+
p
13+
}
14+
15+
#[test]
16+
fn inlined_mapping_selects_subclass_by_typeuri() {
17+
// Load simple schema
18+
let schema = from_yaml(Path::new(&data_path("mapping_schema.yaml"))).unwrap();
19+
let mut sv = SchemaView::new();
20+
sv.add_schema(schema.clone()).unwrap();
21+
let conv = converter_from_schema(&schema);
22+
23+
// Base container class
24+
let bag = sv
25+
.get_class(&Identifier::new("Bag"), &conv)
26+
.unwrap()
27+
.expect("class not found");
28+
29+
// Load JSON data
30+
let v = load_json_file(Path::new(&data_path("mapping_data.json")), &sv, &bag, &conv).unwrap();
31+
32+
// Instance should validate
33+
assert!(validate(&v).is_ok());
34+
35+
// Ensure inlined mapping children select subclasses based on typeURI
36+
match v {
37+
LinkMLValue::Object { values, .. } => {
38+
let things = values.get("things").expect("things slot missing");
39+
match things {
40+
LinkMLValue::Mapping { values, .. } => {
41+
match values.get("alpha").expect("alpha missing") {
42+
LinkMLValue::Object { class, .. } => assert_eq!(class.name(), "ThingA"),
43+
_ => panic!("alpha should be an object"),
44+
}
45+
match values.get("beta").expect("beta missing") {
46+
LinkMLValue::Object { class, .. } => assert_eq!(class.name(), "ThingB"),
47+
_ => panic!("beta should be an object"),
48+
}
49+
}
50+
_ => panic!("things should be a mapping"),
51+
}
52+
}
53+
_ => panic!("expected top-level object"),
54+
}
55+
}

0 commit comments

Comments
 (0)