comparison rust/hg-core/src/revlog/node.rs @ 46033:88e741bf2d93

rust: use NodePrefix::from_hex instead of hex::decode directly This adds support for prefixes with an odd number of hex digits. Differential Revision: https://phab.mercurial-scm.org/D9490
author Simon Sapin <simon-commits@exyr.org>
date Wed, 02 Dec 2020 15:00:49 +0100
parents b0d6309ff50c
children cfb6c10c08c2
comparison
equal deleted inserted replaced
46032:8d6164098782 46033:88e741bf2d93
7 //! 7 //!
8 //! In Mercurial code base, it is customary to call "a node" the binary SHA 8 //! In Mercurial code base, it is customary to call "a node" the binary SHA
9 //! of a revision. 9 //! of a revision.
10 10
11 use hex::{self, FromHex, FromHexError}; 11 use hex::{self, FromHex, FromHexError};
12 use std::convert::{TryFrom, TryInto};
12 13
13 /// The length in bytes of a `Node` 14 /// The length in bytes of a `Node`
14 /// 15 ///
15 /// This constant is meant to ease refactors of this module, and 16 /// This constant is meant to ease refactors of this module, and
16 /// are private so that calling code does not expect all nodes have 17 /// are private so that calling code does not expect all nodes have
63 fn from(data: NodeData) -> Node { 64 fn from(data: NodeData) -> Node {
64 Node { data } 65 Node { data }
65 } 66 }
66 } 67 }
67 68
69 /// Return an error if the slice has an unexpected length
70 impl<'a> TryFrom<&'a [u8]> for &'a Node {
71 type Error = std::array::TryFromSliceError;
72
73 #[inline]
74 fn try_from(bytes: &'a [u8]) -> Result<&'a Node, Self::Error> {
75 let data = bytes.try_into()?;
76 // Safety: `#[repr(transparent)]` makes it ok to "wrap" the target
77 // of a reference to the type of the single field.
78 Ok(unsafe { std::mem::transmute::<&NodeData, &Node>(data) })
79 }
80 }
81
68 #[derive(Debug, PartialEq)] 82 #[derive(Debug, PartialEq)]
69 pub enum NodeError { 83 pub enum NodeError {
70 ExactLengthRequired(usize, String), 84 ExactLengthRequired(usize, String),
71 PrefixTooLong(String), 85 PrefixTooLong(String),
72 HexError(FromHexError, String), 86 HexError(FromHexError, String),
101 /// 115 ///
102 /// Exact length is required. 116 /// Exact length is required.
103 /// 117 ///
104 /// To be used in FFI and I/O only, in order to facilitate future 118 /// To be used in FFI and I/O only, in order to facilitate future
105 /// changes of hash format. 119 /// changes of hash format.
106 pub fn from_hex(hex: &str) -> Result<Node, NodeError> { 120 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Node, NodeError> {
107 Ok(NodeData::from_hex(hex) 121 Ok(NodeData::from_hex(hex.as_ref())
108 .map_err(|e| NodeError::from((e, hex)))? 122 .map_err(|e| NodeError::from((e, hex)))?
109 .into()) 123 .into())
110 } 124 }
111 125
112 /// Convert to hexadecimal string representation 126 /// Convert to hexadecimal string representation
124 pub fn as_bytes(&self) -> &[u8] { 138 pub fn as_bytes(&self) -> &[u8] {
125 &self.data 139 &self.data
126 } 140 }
127 } 141 }
128 142
129 impl<T: AsRef<str>> From<(FromHexError, T)> for NodeError { 143 impl<T: AsRef<[u8]>> From<(FromHexError, T)> for NodeError {
130 fn from(err_offender: (FromHexError, T)) -> Self { 144 fn from(err_offender: (FromHexError, T)) -> Self {
131 let (err, offender) = err_offender; 145 let (err, offender) = err_offender;
146 let offender = String::from_utf8_lossy(offender.as_ref()).into_owned();
132 match err { 147 match err {
133 FromHexError::InvalidStringLength => { 148 FromHexError::InvalidStringLength => {
134 NodeError::ExactLengthRequired( 149 NodeError::ExactLengthRequired(NODE_NYBBLES_LENGTH, offender)
135 NODE_NYBBLES_LENGTH,
136 offender.as_ref().to_owned(),
137 )
138 } 150 }
139 _ => NodeError::HexError(err, offender.as_ref().to_owned()), 151 _ => NodeError::HexError(err, offender),
140 } 152 }
141 } 153 }
142 } 154 }
143 155
144 /// The beginning of a binary revision SHA. 156 /// The beginning of a binary revision SHA.
169 )); 181 ));
170 } 182 }
171 183
172 let is_odd = len % 2 == 1; 184 let is_odd = len % 2 == 1;
173 let even_part = if is_odd { &hex[..len - 1] } else { hex }; 185 let even_part = if is_odd { &hex[..len - 1] } else { hex };
174 let mut buf: Vec<u8> = Vec::from_hex(&even_part) 186 let mut buf: Vec<u8> =
175 .map_err(|e| (e, String::from_utf8_lossy(hex)))?; 187 Vec::from_hex(&even_part).map_err(|e| (e, hex))?;
176 188
177 if is_odd { 189 if is_odd {
178 let latest_char = char::from(hex[len - 1]); 190 let latest_char = char::from(hex[len - 1]);
179 let latest_nybble = latest_char.to_digit(16).ok_or_else(|| { 191 let latest_nybble = latest_char.to_digit(16).ok_or_else(|| {
180 ( 192 (
181 FromHexError::InvalidHexCharacter { 193 FromHexError::InvalidHexCharacter {
182 c: latest_char, 194 c: latest_char,
183 index: len - 1, 195 index: len - 1,
184 }, 196 },
185 String::from_utf8_lossy(hex), 197 hex,
186 ) 198 )
187 })? as u8; 199 })? as u8;
188 buf.push(latest_nybble << 4); 200 buf.push(latest_nybble << 4);
189 } 201 }
190 Ok(NodePrefix { buf, is_odd }) 202 Ok(NodePrefix { buf, is_odd })
276 is_odd: false, 288 is_odd: false,
277 } 289 }
278 } 290 }
279 } 291 }
280 292
293 impl PartialEq<Node> for NodePrefixRef<'_> {
294 fn eq(&self, other: &Node) -> bool {
295 !self.is_odd && self.buf == other.data
296 }
297 }
298
281 #[cfg(test)] 299 #[cfg(test)]
282 mod tests { 300 mod tests {
283 use super::*; 301 use super::*;
284 302
285 fn sample_node() -> Node { 303 fn sample_node() -> Node {
290 ]); 308 ]);
291 data.into() 309 data.into()
292 } 310 }
293 311
294 /// Pad an hexadecimal string to reach `NODE_NYBBLES_LENGTH` 312 /// Pad an hexadecimal string to reach `NODE_NYBBLES_LENGTH`
295 /// 313 ///check_hash
296 /// The padding is made with zeros 314 /// The padding is made with zeros
297 pub fn hex_pad_right(hex: &str) -> String { 315 pub fn hex_pad_right(hex: &str) -> String {
298 let mut res = hex.to_string(); 316 let mut res = hex.to_string();
299 while res.len() < NODE_NYBBLES_LENGTH { 317 while res.len() < NODE_NYBBLES_LENGTH {
300 res.push('0'); 318 res.push('0');