Skip to main content

pyshacl_turbo_engine/
revision.rs

1//! Source identity for two independently built copies of the canonical engine.
2use 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}