1use std::{convert::TryFrom, fmt, hash::Hash};
2
3use hex::FromHex;
4use rand::RngCore;
5use sha2::{Digest, Sha256};
6
7pub mod config;
8pub mod errors;
9#[allow(clippy::all)]
10pub mod generated;
11pub mod path;
12pub mod range;
13pub mod strand;
14pub mod traits;
15
16pub use config::Workspace;
17pub use generated::gen_core_capnp;
18pub use path::PathBlock;
19#[cfg(feature = "python-bindings")]
20use pyo3::pyclass;
21pub use strand::Strand;
22
23pub static NO_CHROMOSOME_INDEX: i64 = -1;
24pub static PRESERVE_EDIT_SITE_CHROMOSOME_INDEX: i64 = -2;
25
26pub const PATH_START_NODE_ID: HashId = HashId([
28 0x84, 0xd6, 0xad, 0xbd, 0x53, 0x95, 0x28, 0x19, 0x33, 0xfe, 0x41, 0xe8, 0x77, 0xd3, 0xa7, 0xf0,
29 0x2a, 0x3b, 0x19, 0x90, 0xa6, 0x5b, 0xe1, 0x90, 0x1b, 0x2c, 0x91, 0xfc, 0x68, 0x5e, 0x08, 0x3b,
30]);
31pub const PATH_END_NODE_ID: HashId = HashId([
32 0x1c, 0x7d, 0xfc, 0x64, 0x97, 0x7b, 0x08, 0x38, 0xaf, 0x07, 0x62, 0xd7, 0x33, 0x3d, 0xcb, 0x64,
33 0xc1, 0x75, 0xb1, 0x5e, 0x65, 0xa7, 0x00, 0x99, 0xec, 0x38, 0xf4, 0x6b, 0xf1, 0xa1, 0x5e, 0xa3,
34]);
35
36pub fn is_terminal(node_id: HashId) -> bool {
37 is_start_node(node_id) || is_end_node(node_id)
38}
39
40pub fn is_start_node(node_id: HashId) -> bool {
41 node_id == PATH_START_NODE_ID
42}
43
44pub fn is_end_node(node_id: HashId) -> bool {
45 node_id == PATH_END_NODE_ID
46}
47
48#[cfg_attr(feature = "python-bindings", pyclass)]
49#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
50pub struct HashId(pub [u8; 32]);
51
52impl serde::Serialize for HashId {
53 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
54 where
55 S: serde::Serializer,
56 {
57 let hex_str = hex::encode(self.0);
58 serializer.serialize_str(&hex_str)
59 }
60}
61
62impl<'de> serde::Deserialize<'de> for HashId {
63 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
64 where
65 D: serde::Deserializer<'de>,
66 {
67 struct HashIdVisitor;
68
69 impl<'de> serde::de::Visitor<'de> for HashIdVisitor {
70 type Value = HashId;
71
72 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
73 formatter.write_str("a 64-character hex string")
74 }
75
76 fn visit_str<E>(self, v: &str) -> Result<HashId, E>
77 where
78 E: serde::de::Error,
79 {
80 let bytes = hex::decode(v).map_err(E::custom)?;
81 if bytes.len() != 32 {
82 return Err(E::custom("expected 32 bytes"));
83 }
84 let mut arr = [0u8; 32];
85 arr.copy_from_slice(&bytes);
86 Ok(HashId(arr))
87 }
88 }
89
90 deserializer.deserialize_str(HashIdVisitor)
91 }
92}
93
94impl fmt::Display for HashId {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 for byte in &self.0 {
97 write!(f, "{byte:02x}")?;
98 }
99 Ok(())
100 }
101}
102
103impl TryFrom<String> for HashId {
104 type Error = hex::FromHexError;
105
106 fn try_from(s: String) -> Result<Self, Self::Error> {
107 let bytes = <[u8; 32]>::from_hex(&s)?;
108 Ok(HashId(bytes))
109 }
110}
111
112impl fmt::Debug for HashId {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 write!(f, "HashId({})", hex::encode(self.0))
115 }
116}
117
118impl TryFrom<&[u8]> for HashId {
119 type Error = &'static str;
120
121 fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
122 if slice.len() != 32 {
123 return Err("slice must be exactly 32 bytes long");
124 }
125
126 let mut array = [0u8; 32];
127 array.copy_from_slice(slice);
128 Ok(HashId(array))
129 }
130}
131
132impl TryFrom<&str> for HashId {
133 type Error = &'static str;
134
135 fn try_from(s: &str) -> Result<Self, Self::Error> {
136 let bytes = hex::decode(s).expect("invalid hex string");
137 Ok(HashId(bytes.try_into().expect("not 32 bytes")))
138 }
139}
140
141impl PartialEq<[u8; 32]> for HashId {
142 fn eq(&self, other: &[u8; 32]) -> bool {
143 &self.0 == other
144 }
145}
146
147impl PartialEq<HashId> for [u8; 32] {
148 fn eq(&self, other: &HashId) -> bool {
149 self == &other.0
150 }
151}
152
153use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
154
155impl FromSql for HashId {
156 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
157 match value {
158 ValueRef::Blob(b) => {
159 if b.len() == 32 {
160 let mut arr = [0u8; 32];
161 arr.copy_from_slice(b);
162 Ok(HashId(arr))
163 } else {
164 Err(FromSqlError::Other(Box::new(std::io::Error::new(
165 std::io::ErrorKind::InvalidData,
166 format!("expected 32-byte blob, got {}", b.len()),
167 ))))
168 }
169 }
170 _ => Err(FromSqlError::InvalidType),
171 }
172 }
173}
174
175impl ToSql for HashId {
176 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
177 Ok(ToSqlOutput::from(self.0.as_ref())) }
179}
180
181use rusqlite::types::Value;
182
183impl From<HashId> for Value {
184 fn from(h: HashId) -> Self {
185 Value::Blob(h.0.to_vec())
186 }
187}
188
189impl From<uuid::Uuid> for HashId {
190 fn from(uuid: uuid::Uuid) -> Self {
191 let mut buf = [0u8; 32];
192 buf[..16].copy_from_slice(uuid.as_bytes());
193 Self(buf)
194 }
195}
196
197impl HashId {
198 pub fn pad_str<T: ToString>(input: T) -> Self {
199 let s = input.to_string();
200 let hex = format!("{s:0>64}"); let bytes = hex::decode(hex).expect("invalid hex string");
202 HashId(bytes.try_into().expect("not 32 bytes"))
203 }
204
205 pub fn convert_str(s: &str) -> Self {
206 HashId(calculate_hash(s))
207 }
208
209 pub fn random_str() -> Self {
210 let mut rng = rand::rng();
211 let mut random_bytes = [0u8; 32];
212 rng.fill_bytes(&mut random_bytes);
213 Self(random_bytes)
214 }
215
216 pub fn uuid7() -> Self {
217 uuid::Uuid::now_v7().into()
218 }
219
220 pub fn extract_digits(&self) -> i64 {
222 let hex = format!("{self}");
223 let digits: String = hex
224 .chars()
225 .map(|c| {
226 if c.is_ascii_digit() {
227 c
228 } else {
229 (c.to_ascii_lowercase() as u8 - 49 + 1) as char
232 }
233 })
234 .take(15) .collect();
236
237 digits.parse().unwrap_or(0)
238 }
239
240 pub fn starts_with(&self, prefix: &str) -> bool {
241 if prefix.len() > 64 || prefix.is_empty() {
242 return false;
243 }
244
245 let end_byte = prefix.len() / 2 + prefix.len() % 2;
246 let encoded = hex::encode(&self.0[..end_byte]);
247 encoded.starts_with(prefix)
248 }
249}
250
251#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
252pub struct NodeIntervalBlock {
253 pub block_id: i64,
254 pub node_id: HashId,
255 pub start: i64,
256 pub end: i64,
257 pub sequence_start: i64,
258 pub sequence_end: i64,
259 pub strand: Strand,
260}
261
262pub fn calculate_hash(t: &str) -> [u8; 32] {
263 let mut hasher = Sha256::new();
264 hasher.update(t);
265 let result = hasher.finalize();
266 result.into()
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn it_hashes() {
275 let result: HashId = "a82639b6f8c3a6e536d8cc562c3b86ff4b012c84ab230c1e5be649aa9ad26d21"
276 .try_into()
277 .unwrap();
278 assert_eq!(calculate_hash("a test"), result);
279 }
280
281 #[cfg(test)]
282 mod hashid {
283 use super::*;
284
285 #[test]
286 fn test_starts_with() {
287 let hash: HashId = "a82639b6f8c3a6e536d8cc562c3b86ff4b012c84ab230c1e5be649aa9ad26d21"
288 .try_into()
289 .unwrap();
290 assert!(hash.starts_with("a826"));
291 assert!(
292 hash.starts_with(
293 "a82639b6f8c3a6e536d8cc562c3b86ff4b012c84ab230c1e5be649aa9ad26d21"
294 )
295 );
296 assert!(!hash.starts_with("b826"));
297 assert!(
298 !hash.starts_with(
299 "a82639b6f8c3a6e536d8cc562c3b86ff4b012c84ab230c1e5be649aa9ad26d210"
300 )
301 );
302 assert!(!hash.starts_with(""));
303 assert!(
304 !hash.starts_with(
305 "a82639b6f8c3a6e536d8cc562c3b86ff4b012c84ab230c1e5be649aa9ad26d219"
306 )
307 );
308 }
309 }
310}