pyshacl_turbo_engine/
revision.rs1use sha2::{Digest, Sha256};
3
4pub const SOURCES: &[(&str, &[u8])] = &[
5 ("compile.rs", include_bytes!("compile.rs")),
6 ("components.rs", include_bytes!("components.rs")),
7 ("constraints.rs", include_bytes!("constraints.rs")),
8 ("display.rs", include_bytes!("display.rs")),
9 ("execute.rs", include_bytes!("execute.rs")),
10 ("expression.rs", include_bytes!("expression.rs")),
11 ("functions.rs", include_bytes!("functions.rs")),
12 ("graph.rs", include_bytes!("graph.rs")),
13 ("graph_read.rs", include_bytes!("graph_read.rs")),
14 ("graph_views.rs", include_bytes!("graph_views.rs")),
15 ("inference.rs", include_bytes!("inference.rs")),
16 ("inference/axioms.rs", include_bytes!("inference/axioms.rs")),
17 ("lib.rs", include_bytes!("lib.rs")),
18 ("ontology.rs", include_bytes!("ontology.rs")),
19 ("path.rs", include_bytes!("path.rs")),
20 ("pattern.rs", include_bytes!("pattern.rs")),
21 ("query_graph.rs", include_bytes!("query_graph.rs")),
22 ("revision.rs", include_bytes!("revision.rs")),
23 ("sparql.rs", include_bytes!("sparql.rs")),
24 ("sparql_regex.rs", include_bytes!("sparql_regex.rs")),
25 ("structural.rs", include_bytes!("structural.rs")),
26 ("value.rs", include_bytes!("value.rs")),
27];
28
29fn update_source(digest: &mut Sha256, source: &[u8]) {
30 for line in source.split_inclusive(|byte| *byte == b'\n') {
31 if let Some(prefix) = line.strip_suffix(b"\r\n") {
32 digest.update(prefix);
33 digest.update(b"\n");
34 } else {
35 digest.update(line);
36 }
37 }
38}
39
40pub fn fingerprint() -> String {
41 let mut digest = Sha256::new();
42 update_source(&mut digest, include_bytes!("../Cargo.toml"));
43 for (name, source) in SOURCES {
44 digest.update(name.as_bytes());
45 digest.update([0]);
46 update_source(&mut digest, source);
47 digest.update([0]);
48 }
49 digest
50 .finalize()
51 .iter()
52 .map(|byte| format!("{byte:02x}"))
53 .collect()
54}
55
56#[cfg(test)]
57mod tests {
58 #[test]
59 fn transport_line_endings_do_not_change_source_identity() {
60 use sha2::{Digest, Sha256};
61 let mut unix = Sha256::new();
62 let mut windows = Sha256::new();
63 super::update_source(&mut unix, b"first\nsecond\nlast\r");
64 super::update_source(&mut windows, b"first\r\nsecond\r\nlast\r");
65 assert_eq!(unix.finalize(), windows.finalize());
66 let mut bare = Sha256::new();
67 let mut empty = Sha256::new();
68 super::update_source(&mut bare, b"\r");
69 super::update_source(&mut empty, b"");
70 assert_ne!(bare.finalize(), empty.finalize());
71 }
72
73 #[test]
74 fn fingerprint_covers_every_engine_implementation_file() {
75 fn visit(root: &std::path::Path, directory: &std::path::Path, names: &mut Vec<String>) {
76 for entry in std::fs::read_dir(directory).unwrap() {
77 let path = entry.unwrap().path();
78 if path.is_dir() {
79 visit(root, &path, names);
80 } else if path.extension().is_some_and(|value| value == "rs")
81 && !path
82 .file_name()
83 .unwrap()
84 .to_str()
85 .unwrap()
86 .ends_with("_tests.rs")
87 {
88 names.push(
89 path.strip_prefix(root)
90 .unwrap()
91 .to_str()
92 .unwrap()
93 .replace('\\', "/"),
94 );
95 }
96 }
97 }
98 let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
99 let mut names = Vec::new();
100 visit(&root, &root, &mut names);
101 names.sort();
102 assert_eq!(
103 names,
104 super::SOURCES
105 .iter()
106 .map(|(name, _)| name.to_string())
107 .collect::<Vec<_>>()
108 );
109 assert_eq!(super::fingerprint().len(), 64);
110 }
111}