use serde::{Deserialize, Serialize};
use super::{ContentHash, SemanticIndexError};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SemanticEdgeKind {
RefersTo,
Calls,
TypeRef,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ResolvedSemanticEdge {
pub source_occurrence: u32,
pub target_path: String,
pub target_file_node: ContentHash,
pub target_definition: u32,
pub kind: SemanticEdgeKind,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FileBindingDelta {
pub path: String,
pub file_node: Option<ContentHash>,
pub replace_edges: Vec<ResolvedSemanticEdge>,
}
impl FileBindingDelta {
pub fn new(
path: impl Into<String>,
file_node: Option<ContentHash>,
mut replace_edges: Vec<ResolvedSemanticEdge>,
) -> Self {
replace_edges.sort();
replace_edges.dedup();
Self {
path: path.into(),
file_node,
replace_edges,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BindingDelta {
pub format_version: u8,
pub parent: Option<ContentHash>,
pub files: Vec<FileBindingDelta>,
}
impl BindingDelta {
pub const FORMAT_VERSION: u8 = 1;
pub fn new(parent: Option<ContentHash>, mut files: Vec<FileBindingDelta>) -> Self {
files.sort_by(|a, b| a.path.cmp(&b.path));
Self {
format_version: Self::FORMAT_VERSION,
parent,
files,
}
}
pub fn encode(&self) -> Result<Vec<u8>, SemanticIndexError> {
rmp_serde::to_vec_named(self).map_err(|err| SemanticIndexError::Encoding(err.to_string()))
}
pub fn decode(bytes: &[u8]) -> Result<Self, SemanticIndexError> {
let delta: Self = rmp_serde::from_slice(bytes)
.map_err(|err| SemanticIndexError::Encoding(err.to_string()))?;
if delta.format_version != Self::FORMAT_VERSION {
return Err(SemanticIndexError::UnsupportedVersion(delta.format_version));
}
Ok(delta)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hash(seed: u8) -> ContentHash {
ContentHash::from_bytes([seed; 32])
}
#[test]
fn binding_delta_roundtrips_and_canonicalizes() {
let edge = ResolvedSemanticEdge {
source_occurrence: 3,
target_path: "api.rs".to_string(),
target_file_node: hash(2),
target_definition: 1,
kind: SemanticEdgeKind::Calls,
};
let delta = BindingDelta::new(
Some(hash(9)),
vec![
FileBindingDelta::new("z.rs", Some(hash(1)), vec![edge.clone(), edge]),
FileBindingDelta::new("a.rs", None, Vec::new()),
],
);
assert_eq!(delta.files[0].path, "a.rs");
assert_eq!(delta.files[1].replace_edges.len(), 1);
assert_eq!(
BindingDelta::decode(&delta.encode().unwrap()).unwrap(),
delta
);
}
}