use std::collections::HashMap;
use panproto_gat::Name;
use panproto_schema::{Edge, Schema};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Migration {
pub vertex_map: HashMap<Name, Name>,
#[serde(with = "panproto_schema::serde_helpers::map_as_vec")]
pub edge_map: HashMap<Edge, Edge>,
pub hyper_edge_map: HashMap<Name, Name>,
#[serde(with = "panproto_schema::serde_helpers::map_as_vec")]
pub label_map: HashMap<(Name, Name), Name>,
#[serde(with = "panproto_schema::serde_helpers::map_as_vec")]
pub resolver: HashMap<(Name, Name), Edge>,
#[allow(clippy::type_complexity)]
#[serde(with = "panproto_schema::serde_helpers::map_as_vec")]
pub hyper_resolver: HashMap<(Name, Vec<Name>), (Name, HashMap<Name, Name>)>,
#[serde(default, with = "panproto_schema::serde_helpers::map_as_vec_default")]
pub expr_resolvers: HashMap<(Name, Name), panproto_expr::Expr>,
#[serde(default)]
pub coercions: HashMap<Name, panproto_schema::CoercionSpec>,
#[serde(default)]
pub domain: Option<Name>,
#[serde(default)]
pub codomain: Option<Name>,
}
impl Migration {
#[must_use]
pub fn identity(vertices: &[Name], edges: &[Edge]) -> Self {
let vertex_map: HashMap<Name, Name> =
vertices.iter().map(|v| (v.clone(), v.clone())).collect();
let edge_map: HashMap<Edge, Edge> = edges.iter().map(|e| (e.clone(), e.clone())).collect();
Self {
vertex_map,
edge_map,
hyper_edge_map: HashMap::new(),
label_map: HashMap::new(),
resolver: HashMap::new(),
hyper_resolver: HashMap::new(),
expr_resolvers: HashMap::new(),
coercions: HashMap::new(),
domain: None,
codomain: None,
}
}
#[must_use]
pub fn identity_for(vertices: &[Name], edges: &[Edge], id: Name) -> Self {
let mut mig = Self::identity(vertices, edges);
mig.domain = Some(id.clone());
mig.codomain = Some(id);
mig
}
#[must_use]
pub fn empty() -> Self {
Self {
vertex_map: HashMap::new(),
edge_map: HashMap::new(),
hyper_edge_map: HashMap::new(),
label_map: HashMap::new(),
resolver: HashMap::new(),
hyper_resolver: HashMap::new(),
expr_resolvers: HashMap::new(),
coercions: HashMap::new(),
domain: None,
codomain: None,
}
}
#[must_use]
pub fn with_endpoints(mut self, domain: Option<Name>, codomain: Option<Name>) -> Self {
self.domain = domain;
self.codomain = codomain;
self
}
#[must_use]
pub fn with_coercions(mut self, src: &Schema, tgt: &Schema) -> Self {
self.coercions.clear();
for (src_v, tgt_v) in &self.vertex_map {
let (Some(sv), Some(tv)) = (src.vertex(src_v), tgt.vertex(tgt_v)) else {
continue;
};
if sv.kind == tv.kind {
continue;
}
if let Some(spec) = tgt.coercions.get(&(sv.kind.clone(), tv.kind.clone())) {
self.coercions.insert(src_v.clone(), spec.clone());
}
}
self
}
}
pub const COERCION_INPUT: &str = "__value__";
#[must_use]
pub fn compose_coercions(
first: &panproto_schema::CoercionSpec,
second: &panproto_schema::CoercionSpec,
) -> panproto_schema::CoercionSpec {
let forward = panproto_expr::substitute(&second.forward, COERCION_INPUT, &first.forward);
let inverse = match (&first.inverse, &second.inverse) {
(Some(first_inv), Some(second_inv)) => Some(panproto_expr::substitute(
first_inv,
COERCION_INPUT,
second_inv,
)),
_ => None,
};
panproto_schema::CoercionSpec {
forward,
inverse,
class: first.class.compose(second.class),
}
}
#[must_use]
pub fn invert_coercion(
spec: &panproto_schema::CoercionSpec,
) -> Option<panproto_schema::CoercionSpec> {
use panproto_gat::CoercionClass;
let forward = spec.inverse.clone()?;
let class = match spec.class {
CoercionClass::Iso => CoercionClass::Iso,
CoercionClass::Retraction => CoercionClass::Projection,
_ => CoercionClass::Opaque,
};
Some(panproto_schema::CoercionSpec {
forward,
inverse: Some(spec.forward.clone()),
class,
})
}