use std::collections::BTreeMap;
use panproto_gat::SiteRename;
use panproto_mig::Migration;
use panproto_schema::Schema;
use serde::{Deserialize, Serialize};
use crate::ObjectId;
#[non_exhaustive]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Object {
Migration {
src: ObjectId,
tgt: ObjectId,
mapping: Migration,
},
Commit(CommitObject),
Tag(TagObject),
DataSet(DataSetObject),
Complement(ComplementObject),
Protocol(Box<panproto_schema::Protocol>),
Expr(Box<panproto_expr::Expr>),
EditLog(EditLogObject),
Theory(Box<panproto_gat::Theory>),
TheoryMorphism(Box<panproto_gat::TheoryMorphism>),
CstComplement(CstComplementObject),
FileSchema(Box<FileSchemaObject>),
FlatSchema(Box<panproto_schema::Schema>),
SchemaTree(Box<SchemaTreeObject>),
}
impl Object {
#[must_use]
pub const fn type_name(&self) -> &'static str {
match self {
Self::Migration { .. } => "migration",
Self::Commit(_) => "commit",
Self::Tag(_) => "tag",
Self::DataSet(_) => "dataset",
Self::Complement(_) => "complement",
Self::Protocol(_) => "protocol",
Self::Expr(_) => "expr",
Self::EditLog(_) => "editlog",
Self::Theory(_) => "theory",
Self::TheoryMorphism(_) => "theory_morphism",
Self::CstComplement(_) => "cst_complement",
Self::FileSchema(_) => "file_schema",
Self::SchemaTree(_) => "schema_tree",
Self::FlatSchema(_) => "flat_schema",
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FileSchemaObject {
pub path: String,
pub protocol: String,
pub schema: Schema,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub cross_file_edges: Vec<panproto_schema::Edge>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SchemaTreeEntry {
File(ObjectId),
Tree(ObjectId),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SchemaTreeObject {
SingleLeaf {
file_schema_id: ObjectId,
},
Directory {
entries: Vec<(String, SchemaTreeEntry)>,
},
}
impl SchemaTreeObject {
#[must_use]
pub fn sorted_entries(&self) -> Vec<(&str, &SchemaTreeEntry)> {
match self {
Self::SingleLeaf { .. } => Vec::new(),
Self::Directory { entries } => {
let mut out: Vec<(&str, &SchemaTreeEntry)> =
entries.iter().map(|(n, e)| (n.as_str(), e)).collect();
out.sort_by(|a, b| a.0.cmp(b.0));
out
}
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CommitObject {
pub schema_id: ObjectId,
pub parents: Vec<ObjectId>,
pub migration_id: Option<ObjectId>,
pub protocol: String,
pub author: String,
pub timestamp: u64,
pub message: String,
pub renames: Vec<SiteRename>,
pub protocol_id: Option<ObjectId>,
pub data_ids: Vec<ObjectId>,
pub complement_ids: Vec<ObjectId>,
pub edit_log_ids: Vec<ObjectId>,
pub theory_ids: BTreeMap<String, ObjectId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub cst_complement_ids: Vec<ObjectId>,
}
impl CommitObject {
#[must_use]
pub fn builder(
schema_id: ObjectId,
protocol: impl Into<String>,
author: impl Into<String>,
message: impl Into<String>,
) -> CommitObjectBuilder {
CommitObjectBuilder {
schema_id,
parents: Vec::new(),
migration_id: None,
protocol: protocol.into(),
author: author.into(),
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
message: message.into(),
renames: Vec::new(),
protocol_id: None,
data_ids: Vec::new(),
complement_ids: Vec::new(),
edit_log_ids: Vec::new(),
theory_ids: BTreeMap::new(),
cst_complement_ids: Vec::new(),
}
}
}
pub struct CommitObjectBuilder {
schema_id: ObjectId,
parents: Vec<ObjectId>,
migration_id: Option<ObjectId>,
protocol: String,
author: String,
timestamp: u64,
message: String,
renames: Vec<SiteRename>,
protocol_id: Option<ObjectId>,
data_ids: Vec<ObjectId>,
complement_ids: Vec<ObjectId>,
edit_log_ids: Vec<ObjectId>,
theory_ids: BTreeMap<String, ObjectId>,
cst_complement_ids: Vec<ObjectId>,
}
impl CommitObjectBuilder {
#[must_use]
pub fn parents(mut self, parents: Vec<ObjectId>) -> Self {
self.parents = parents;
self
}
#[must_use]
pub const fn migration_id(mut self, id: ObjectId) -> Self {
self.migration_id = Some(id);
self
}
#[must_use]
pub const fn timestamp(mut self, ts: u64) -> Self {
self.timestamp = ts;
self
}
#[must_use]
pub fn renames(mut self, renames: Vec<SiteRename>) -> Self {
self.renames = renames;
self
}
#[must_use]
pub const fn protocol_id(mut self, id: ObjectId) -> Self {
self.protocol_id = Some(id);
self
}
#[must_use]
pub fn data_ids(mut self, ids: Vec<ObjectId>) -> Self {
self.data_ids = ids;
self
}
#[must_use]
pub fn complement_ids(mut self, ids: Vec<ObjectId>) -> Self {
self.complement_ids = ids;
self
}
#[must_use]
pub fn edit_log_ids(mut self, ids: Vec<ObjectId>) -> Self {
self.edit_log_ids = ids;
self
}
#[must_use]
pub fn theory_ids(mut self, ids: BTreeMap<String, ObjectId>) -> Self {
self.theory_ids = ids;
self
}
#[must_use]
pub fn build(self) -> CommitObject {
CommitObject {
schema_id: self.schema_id,
parents: self.parents,
migration_id: self.migration_id,
protocol: self.protocol,
author: self.author,
timestamp: self.timestamp,
message: self.message,
renames: self.renames,
protocol_id: self.protocol_id,
data_ids: self.data_ids,
complement_ids: self.complement_ids,
edit_log_ids: self.edit_log_ids,
theory_ids: self.theory_ids,
cst_complement_ids: self.cst_complement_ids,
}
}
#[must_use]
pub fn cst_complement_ids(mut self, ids: Vec<ObjectId>) -> Self {
self.cst_complement_ids = ids;
self
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DataSetObject {
pub schema_id: ObjectId,
pub data: Vec<u8>,
pub record_count: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ComplementObject {
pub migration_id: ObjectId,
pub data_id: ObjectId,
pub complement: Vec<u8>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CstComplementObject {
pub data_id: ObjectId,
pub cst_complement: Vec<u8>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EditLogObject {
pub schema_id: ObjectId,
pub data_id: ObjectId,
pub edits: Vec<u8>,
pub edit_count: u64,
pub final_complement: ObjectId,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TagObject {
pub target: ObjectId,
pub tagger: String,
pub timestamp: u64,
pub message: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dataset_round_trip() -> Result<(), Box<dyn std::error::Error>> {
let ds = DataSetObject {
schema_id: ObjectId::ZERO,
data: vec![1, 2, 3, 4],
record_count: 42,
};
let bytes = rmp_serde::to_vec(&ds)?;
let ds2: DataSetObject = rmp_serde::from_slice(&bytes)?;
assert_eq!(ds.schema_id, ds2.schema_id);
assert_eq!(ds.data, ds2.data);
assert_eq!(ds.record_count, ds2.record_count);
Ok(())
}
#[test]
fn complement_round_trip() -> Result<(), Box<dyn std::error::Error>> {
let comp = ComplementObject {
migration_id: ObjectId::from_bytes([1; 32]),
data_id: ObjectId::from_bytes([2; 32]),
complement: vec![10, 20, 30],
};
let bytes = rmp_serde::to_vec(&comp)?;
let comp2: ComplementObject = rmp_serde::from_slice(&bytes)?;
assert_eq!(comp.migration_id, comp2.migration_id);
assert_eq!(comp.data_id, comp2.data_id);
assert_eq!(comp.complement, comp2.complement);
Ok(())
}
#[test]
fn edit_log_round_trip() -> Result<(), Box<dyn std::error::Error>> {
let el = EditLogObject {
schema_id: ObjectId::from_bytes([1; 32]),
data_id: ObjectId::from_bytes([2; 32]),
edits: vec![42, 43, 44],
edit_count: 3,
final_complement: ObjectId::from_bytes([3; 32]),
};
let bytes = rmp_serde::to_vec(&el)?;
let el2: EditLogObject = rmp_serde::from_slice(&bytes)?;
assert_eq!(el.schema_id, el2.schema_id);
assert_eq!(el.data_id, el2.data_id);
assert_eq!(el.edits, el2.edits);
assert_eq!(el.edit_count, el2.edit_count);
assert_eq!(el.final_complement, el2.final_complement);
Ok(())
}
#[test]
fn commit_with_edit_logs() -> Result<(), Box<dyn std::error::Error>> {
let commit = CommitObject::builder(ObjectId::ZERO, "test", "test", "test")
.timestamp(0)
.edit_log_ids(vec![
ObjectId::from_bytes([10; 32]),
ObjectId::from_bytes([11; 32]),
])
.build();
let bytes = rmp_serde::to_vec(&commit)?;
let commit2: CommitObject = rmp_serde::from_slice(&bytes)?;
assert_eq!(commit.edit_log_ids, commit2.edit_log_ids);
Ok(())
}
#[test]
fn commit_with_theory_ids() -> Result<(), Box<dyn std::error::Error>> {
let mut theories = BTreeMap::new();
theories.insert("ThGraph".to_owned(), ObjectId::from_bytes([5; 32]));
let commit = CommitObject::builder(ObjectId::ZERO, "test", "test", "test")
.timestamp(0)
.theory_ids(theories)
.build();
let bytes = rmp_serde::to_vec(&commit)?;
let commit2: CommitObject = rmp_serde::from_slice(&bytes)?;
assert_eq!(commit.theory_ids, commit2.theory_ids);
assert_eq!(
commit2.theory_ids.get("ThGraph"),
Some(&ObjectId::from_bytes([5; 32]))
);
Ok(())
}
#[test]
fn flat_schema_round_trip_through_serde() -> Result<(), Box<dyn std::error::Error>> {
use panproto_gat::Name;
use panproto_schema::{Schema, Vertex};
use std::collections::HashMap;
let mut vertices = HashMap::new();
vertices.insert(
Name::from("root"),
Vertex {
id: Name::from("root"),
kind: Name::from("object"),
nsid: None,
},
);
let schema = Schema {
protocol: "flat-proto".into(),
vertices,
edges: HashMap::new(),
hyper_edges: HashMap::new(),
constraints: HashMap::new(),
required: HashMap::new(),
nsids: HashMap::new(),
entries: Vec::new(),
variants: HashMap::new(),
orderings: HashMap::new(),
recursion_points: HashMap::new(),
spans: HashMap::new(),
usage_modes: HashMap::new(),
nominal: HashMap::new(),
coercions: HashMap::new(),
mergers: HashMap::new(),
defaults: HashMap::new(),
policies: HashMap::new(),
outgoing: HashMap::new(),
incoming: HashMap::new(),
between: HashMap::new(),
};
let obj = Object::FlatSchema(Box::new(schema.clone()));
let bytes = rmp_serde::to_vec(&obj)?;
let obj2: Object = rmp_serde::from_slice(&bytes)?;
match obj2 {
Object::FlatSchema(s) => {
assert_eq!(s.protocol, schema.protocol);
assert_eq!(s.vertices.len(), 1);
}
other => panic!("expected FlatSchema, got {}", other.type_name()),
}
Ok(())
}
#[test]
fn single_leaf_and_directory_hash_distinctly() -> Result<(), Box<dyn std::error::Error>> {
use crate::hash::hash_schema_tree;
let leaf_id = ObjectId::from_bytes([9; 32]);
let single = SchemaTreeObject::SingleLeaf {
file_schema_id: leaf_id,
};
let dir = SchemaTreeObject::Directory {
entries: vec![("only".to_owned(), SchemaTreeEntry::File(leaf_id))],
};
let h_single = hash_schema_tree(&single)?;
let h_dir = hash_schema_tree(&dir)?;
assert_ne!(
h_single, h_dir,
"SingleLeaf and Directory must hash to distinct ObjectIds"
);
Ok(())
}
#[test]
fn commit_backward_compat_no_theory_ids() -> Result<(), Box<dyn std::error::Error>> {
let commit_old = CommitObject::builder(ObjectId::ZERO, "test", "test", "test")
.timestamp(0)
.build();
let bytes = rmp_serde::to_vec(&commit_old)?;
let commit2: CommitObject = rmp_serde::from_slice(&bytes)?;
assert!(commit2.theory_ids.is_empty());
Ok(())
}
}