use std::collections::HashMap;
use std::hash::BuildHasher;
use panproto_gat::Theory;
use panproto_schema::{EdgeRule, Protocol, Schema, SchemaBuilder};
use crate::emit::{children_by_edge, find_roots, vertex_constraints};
use crate::error::ProtocolError;
use crate::theories;
#[must_use]
pub fn protocol() -> Protocol {
Protocol {
name: "fhir".into(),
schema_theory: "ThFhirSchema".into(),
instance_theory: "ThFhirInstance".into(),
edge_rules: edge_rules(),
obj_kinds: vec![
"resource".into(),
"element".into(),
"backbone-element".into(),
"extension".into(),
"string".into(),
"integer".into(),
"decimal".into(),
"boolean".into(),
"uri".into(),
"url".into(),
"canonical".into(),
"date".into(),
"dateTime".into(),
"instant".into(),
"time".into(),
"code".into(),
"id".into(),
"oid".into(),
"uuid".into(),
"markdown".into(),
"base64Binary".into(),
"coding".into(),
"codeable-concept".into(),
"reference".into(),
"identifier".into(),
"period".into(),
"quantity".into(),
],
constraint_sorts: vec!["min".into(), "max".into(), "binding".into()],
has_order: true,
has_coproducts: true,
has_recursion: true,
nominal_identity: true,
..Protocol::default()
}
}
pub fn register_theories<S: BuildHasher>(registry: &mut HashMap<String, Theory, S>) {
theories::register_constrained_multigraph_wtype(registry, "ThFhirSchema", "ThFhirInstance");
}
pub fn parse_fhir_schema(json: &serde_json::Value) -> Result<Schema, ProtocolError> {
let proto = protocol();
let mut builder = SchemaBuilder::new(&proto);
let resource_name = json
.get("name")
.and_then(serde_json::Value::as_str)
.ok_or_else(|| ProtocolError::MissingField("name".into()))?;
builder = builder.vertex(resource_name, "resource", None)?;
if let Some(elements) = json.get("elements").and_then(serde_json::Value::as_object) {
for (elem_name, elem_def) in elements {
let elem_id = format!("{resource_name}.{elem_name}");
let kind = elem_def
.get("type")
.and_then(serde_json::Value::as_str)
.map_or("element", fhir_type_to_kind);
builder = builder.vertex(&elem_id, kind, None)?;
builder = builder.edge(resource_name, &elem_id, "prop", Some(elem_name))?;
if let Some(min) = elem_def.get("min").and_then(serde_json::Value::as_str) {
builder = builder.constraint(&elem_id, "min", min);
}
if let Some(max) = elem_def.get("max").and_then(serde_json::Value::as_str) {
builder = builder.constraint(&elem_id, "max", max);
}
}
}
let schema = builder.build()?;
Ok(schema)
}
fn fhir_type_to_kind(type_str: &str) -> &'static str {
match type_str {
"string" => "string",
"integer" | "positiveInt" | "unsignedInt" => "integer",
"decimal" => "decimal",
"boolean" => "boolean",
"uri" => "uri",
"url" => "url",
"canonical" => "canonical",
"date" => "date",
"dateTime" => "dateTime",
"instant" => "instant",
"time" => "time",
"code" => "code",
"id" => "id",
"oid" => "oid",
"uuid" => "uuid",
"markdown" => "markdown",
"base64Binary" => "base64Binary",
"Coding" => "coding",
"CodeableConcept" => "codeable-concept",
"Reference" => "reference",
"Identifier" => "identifier",
"Period" => "period",
"Quantity" => "quantity",
"BackboneElement" => "backbone-element",
"Extension" => "extension",
_ => "element",
}
}
pub fn emit_fhir_schema(schema: &Schema) -> Result<serde_json::Value, ProtocolError> {
let structural = &["prop"];
let roots = find_roots(schema, structural);
let resource = roots
.first()
.ok_or_else(|| ProtocolError::Emit("no root resource".into()))?;
let mut elements = serde_json::Map::new();
for (edge, child) in children_by_edge(schema, &resource.id, "prop") {
let name = edge.name.as_deref().unwrap_or(&child.id);
let mut elem = serde_json::Map::new();
elem.insert("type".into(), serde_json::json!(child.kind));
for c in vertex_constraints(schema, &child.id) {
elem.insert(c.sort.to_string(), serde_json::json!(c.value));
}
elements.insert(name.to_string(), serde_json::Value::Object(elem));
}
Ok(serde_json::json!({
"name": resource.id,
"elements": elements
}))
}
fn edge_rules() -> Vec<EdgeRule> {
vec![EdgeRule {
edge_kind: "prop".into(),
src_kinds: vec!["resource".into(), "backbone-element".into()],
tgt_kinds: vec![],
}]
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn protocol_def() {
let p = protocol();
assert_eq!(p.name, "fhir");
}
#[test]
fn register_theories_works() {
let mut registry = HashMap::new();
register_theories(&mut registry);
assert!(registry.contains_key("ThFhirSchema"));
}
#[test]
fn parse_and_emit() {
let json = serde_json::json!({
"name": "Patient",
"elements": {
"name": {"type": "string", "min": "1", "max": "*"},
"birthDate": {"type": "date"}
}
});
let schema = parse_fhir_schema(&json).expect("should parse");
assert!(schema.has_vertex("Patient"));
assert!(schema.has_vertex("Patient.name"));
let emitted = emit_fhir_schema(&schema).expect("emit");
let s2 = parse_fhir_schema(&emitted).expect("re-parse");
assert_eq!(schema.vertex_count(), s2.vertex_count());
}
}