Skip to main content

uqa_analysis/descriptor/
hash.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Canonical analyzer identity is separate from dictionary artifact identity.
8
9use std::{fmt, str::FromStr};
10
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12use sha2::{Digest, Sha256};
13
14use crate::{AnalysisError, AnalysisResult};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct AnalyzerFingerprint([u8; 32]);
18
19impl AnalyzerFingerprint {
20    /// The binary identity carried by persistent index metadata.
21    pub const fn as_bytes(&self) -> &[u8; 32] {
22        &self.0
23    }
24
25    /// Construct an identity value; descriptor restoration still verifies its contents separately.
26    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
27        Self(bytes)
28    }
29
30    pub(super) fn digest(bytes: &[u8]) -> Self {
31        let mut hash = Sha256::new();
32        hash.update(b"UQA analyzer descriptor\0");
33        hash.update(bytes);
34        Self(hash.finalize().into())
35    }
36}
37
38impl fmt::Display for AnalyzerFingerprint {
39    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
40        for byte in self.0 {
41            write!(formatter, "{byte:02x}")?;
42        }
43        Ok(())
44    }
45}
46
47impl FromStr for AnalyzerFingerprint {
48    type Err = AnalysisError;
49
50    fn from_str(text: &str) -> AnalysisResult<Self> {
51        if text.len() != 64 || !text.bytes().all(|byte| byte.is_ascii_hexdigit()) {
52            return Err(super::invalid("fingerprint requires 64 hexadecimal digits"));
53        }
54        let mut bytes = [0; 32];
55        for (index, byte) in bytes.iter_mut().enumerate() {
56            *byte = u8::from_str_radix(&text[index * 2..index * 2 + 2], 16)
57                .map_err(|_| super::invalid("invalid fingerprint byte"))?;
58        }
59        Ok(Self(bytes))
60    }
61}
62
63impl Serialize for AnalyzerFingerprint {
64    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
65        serializer.collect_str(self)
66    }
67}
68
69impl<'de> Deserialize<'de> for AnalyzerFingerprint {
70    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
71        String::deserialize(deserializer)?
72            .parse()
73            .map_err(serde::de::Error::custom)
74    }
75}