mod edge_kind;
pub use edge_kind::{EdgeKind, EdgeKindError};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum EntityType {
NamedType,
TraitBound,
ModulePath,
ErrorVariant,
TestRelation,
DocConcept,
Annotation,
LiteralString,
TypeAlias,
ConstantSymbol,
ExternalCrate,
ConceptCluster,
NaturalLanguagePhrase,
}
impl EntityType {
pub fn as_str(&self) -> &'static str {
match self {
Self::NamedType => "NamedType",
Self::TraitBound => "TraitBound",
Self::ModulePath => "ModulePath",
Self::ErrorVariant => "ErrorVariant",
Self::TestRelation => "TestRelation",
Self::DocConcept => "DocConcept",
Self::Annotation => "Annotation",
Self::LiteralString => "LiteralString",
Self::TypeAlias => "TypeAlias",
Self::ConstantSymbol => "ConstantSymbol",
Self::ExternalCrate => "ExternalCrate",
Self::ConceptCluster => "ConceptCluster",
Self::NaturalLanguagePhrase => "NaturalLanguagePhrase",
}
}
}
pub mod tables {
pub const ENTITIES: &str = "entities";
pub const ENTITY_EDGES: &str = "entity_edges";
pub const CHUNK_ENTITIES: &str = "chunk_entities";
pub const ENTITY_CHUNKS: &str = "entity_chunks";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RawEntity {
pub id: String,
pub entity_type: EntityType,
pub text: String,
pub span: (usize, usize),
pub file: String,
pub line: usize,
}
impl RawEntity {
pub fn new(
entity_type: EntityType,
text: String,
span: (usize, usize),
file: &str,
line: usize,
) -> Self {
let mut h = Sha256::new();
h.update(entity_type.as_str().as_bytes());
h.update(b"\0");
h.update(text.as_bytes());
h.update(b"\0");
h.update(file.as_bytes());
let id = format!("{:x}", h.finalize());
Self {
id,
entity_type,
text,
span,
file: file.to_string(),
line,
}
}
}
pub fn fact_hash_str(s: &str) -> String {
let digest = Sha256::digest(s.as_bytes());
format!(
"{:02x}{:02x}{:02x}{:02x}",
digest[0], digest[1], digest[2], digest[3]
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_entity_id_is_stable() {
let a = RawEntity::new(EntityType::NamedType, "Foo".into(), (0, 3), "src/x.rs", 1);
let b = RawEntity::new(
EntityType::NamedType,
"Foo".into(),
(10, 13),
"src/x.rs",
99,
);
assert_eq!(a.id, b.id);
}
#[test]
fn raw_entity_id_changes_with_type() {
let a = RawEntity::new(EntityType::NamedType, "Foo".into(), (0, 3), "src/x.rs", 1);
let b = RawEntity::new(EntityType::ModulePath, "Foo".into(), (0, 3), "src/x.rs", 1);
assert_ne!(a.id, b.id);
}
#[test]
fn fact_hash_str_is_deterministic() {
let a = fact_hash_str("rust-analyzer cargo crate/Foo#");
let b = fact_hash_str("rust-analyzer cargo crate/Foo#");
assert_eq!(a, b);
}
#[test]
fn fact_hash_str_output_shape() {
for input in &["", "a", "hello", "rust-analyzer cargo crate/Foo#"] {
let h = fact_hash_str(input);
assert_eq!(
h.len(),
8,
"expected 8 hex chars for {:?}, got {:?}",
input,
h
);
assert!(
h.chars().all(|c| c.is_ascii_hexdigit()),
"non-hex char in output for {:?}: {:?}",
input,
h
);
}
}
#[test]
fn fact_hash_str_pinned_values() {
assert_eq!(fact_hash_str("rust-analyzer cargo crate/Foo#"), "51557b36");
assert_eq!(fact_hash_str(""), "e3b0c442");
assert_eq!(fact_hash_str("hello"), "2cf24dba");
assert_eq!(fact_hash_str("a"), "ca978112");
}
#[test]
fn entity_type_as_str_round_trip() {
let variants = [
EntityType::NamedType,
EntityType::TraitBound,
EntityType::ModulePath,
EntityType::ErrorVariant,
EntityType::TestRelation,
EntityType::DocConcept,
EntityType::Annotation,
EntityType::LiteralString,
EntityType::TypeAlias,
EntityType::ConstantSymbol,
EntityType::ExternalCrate,
EntityType::ConceptCluster,
EntityType::NaturalLanguagePhrase,
];
for v in variants {
assert!(!v.as_str().is_empty());
}
}
}