Mercurial > hg
comparison rust/hg-core/src/revlog/node.rs @ 46428:5893706af3de
rust: Simplify error type for reading hex node IDs
If a string is not valid hexadecimal it’s not that useful to track the precise reason.
Differential Revision: https://phab.mercurial-scm.org/D9861
author | Simon Sapin <simon.sapin@octobus.net> |
---|---|
date | Mon, 25 Jan 2021 12:28:39 +0100 |
parents | 6380efb82191 |
children | e61c2dc6e1c2 |
comparison
equal
deleted
inserted
replaced
46427:6380efb82191 | 46428:5893706af3de |
---|---|
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 bytes_cast::BytesCast; | 11 use bytes_cast::BytesCast; |
12 use hex::{self, FromHex, FromHexError}; | 12 use hex::{self, FromHex}; |
13 use std::convert::TryFrom; | 13 use std::convert::TryFrom; |
14 use std::fmt; | 14 use std::fmt; |
15 | 15 |
16 /// The length in bytes of a `Node` | 16 /// The length in bytes of a `Node` |
17 /// | 17 /// |
45 /// All other callers outside of unit tests should just handle `Node` values | 45 /// All other callers outside of unit tests should just handle `Node` values |
46 /// and never make any assumption on the actual length, using [`nybbles_len`] | 46 /// and never make any assumption on the actual length, using [`nybbles_len`] |
47 /// if they need a loop boundary. | 47 /// if they need a loop boundary. |
48 /// | 48 /// |
49 /// All methods that create a `Node` either take a type that enforces | 49 /// All methods that create a `Node` either take a type that enforces |
50 /// the size or fail immediately at runtime with [`ExactLengthRequired`]. | 50 /// the size or return an error at runtime. |
51 /// | 51 /// |
52 /// [`nybbles_len`]: #method.nybbles_len | 52 /// [`nybbles_len`]: #method.nybbles_len |
53 /// [`ExactLengthRequired`]: struct.NodeError#variant.ExactLengthRequired | |
54 #[derive(Clone, Debug, PartialEq, BytesCast)] | 53 #[derive(Clone, Debug, PartialEq, BytesCast)] |
55 #[repr(transparent)] | 54 #[repr(transparent)] |
56 pub struct Node { | 55 pub struct Node { |
57 data: NodeData, | 56 data: NodeData, |
58 } | 57 } |
88 } | 87 } |
89 Ok(()) | 88 Ok(()) |
90 } | 89 } |
91 } | 90 } |
92 | 91 |
93 #[derive(Debug, PartialEq)] | 92 #[derive(Debug)] |
94 pub enum NodeError { | 93 pub struct FromHexError; |
95 ExactLengthRequired(usize, String), | |
96 PrefixTooLong(String), | |
97 HexError(FromHexError, String), | |
98 } | |
99 | 94 |
100 /// Low level utility function, also for prefixes | 95 /// Low level utility function, also for prefixes |
101 fn get_nybble(s: &[u8], i: usize) -> u8 { | 96 fn get_nybble(s: &[u8], i: usize) -> u8 { |
102 if i % 2 == 0 { | 97 if i % 2 == 0 { |
103 s[i / 2] >> 4 | 98 s[i / 2] >> 4 |
126 /// | 121 /// |
127 /// Exact length is required. | 122 /// Exact length is required. |
128 /// | 123 /// |
129 /// To be used in FFI and I/O only, in order to facilitate future | 124 /// To be used in FFI and I/O only, in order to facilitate future |
130 /// changes of hash format. | 125 /// changes of hash format. |
131 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Node, NodeError> { | 126 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Node, FromHexError> { |
132 Ok(NodeData::from_hex(hex.as_ref()) | 127 Ok(NodeData::from_hex(hex.as_ref()) |
133 .map_err(|e| NodeError::from((e, hex)))? | 128 .map_err(|_| FromHexError)? |
134 .into()) | 129 .into()) |
135 } | 130 } |
136 | 131 |
137 /// Provide access to binary data | 132 /// Provide access to binary data |
138 /// | 133 /// |
139 /// This is needed by FFI layers, for instance to return expected | 134 /// This is needed by FFI layers, for instance to return expected |
140 /// binary values to Python. | 135 /// binary values to Python. |
141 pub fn as_bytes(&self) -> &[u8] { | 136 pub fn as_bytes(&self) -> &[u8] { |
142 &self.data | 137 &self.data |
143 } | |
144 } | |
145 | |
146 impl<T: AsRef<[u8]>> From<(FromHexError, T)> for NodeError { | |
147 fn from(err_offender: (FromHexError, T)) -> Self { | |
148 let (err, offender) = err_offender; | |
149 let offender = String::from_utf8_lossy(offender.as_ref()).into_owned(); | |
150 match err { | |
151 FromHexError::InvalidStringLength => { | |
152 NodeError::ExactLengthRequired(NODE_NYBBLES_LENGTH, offender) | |
153 } | |
154 _ => NodeError::HexError(err, offender), | |
155 } | |
156 } | 138 } |
157 } | 139 } |
158 | 140 |
159 /// The beginning of a binary revision SHA. | 141 /// The beginning of a binary revision SHA. |
160 /// | 142 /// |
173 /// Similarly to `hex::decode`, can be used with Unicode string types | 155 /// Similarly to `hex::decode`, can be used with Unicode string types |
174 /// (`String`, `&str`) as well as bytes. | 156 /// (`String`, `&str`) as well as bytes. |
175 /// | 157 /// |
176 /// To be used in FFI and I/O only, in order to facilitate future | 158 /// To be used in FFI and I/O only, in order to facilitate future |
177 /// changes of hash format. | 159 /// changes of hash format. |
178 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, NodeError> { | 160 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, FromHexError> { |
179 let hex = hex.as_ref(); | 161 let hex = hex.as_ref(); |
180 let len = hex.len(); | 162 let len = hex.len(); |
181 if len > NODE_NYBBLES_LENGTH { | 163 if len > NODE_NYBBLES_LENGTH { |
182 return Err(NodeError::PrefixTooLong( | 164 return Err(FromHexError); |
183 String::from_utf8_lossy(hex).to_owned().to_string(), | |
184 )); | |
185 } | 165 } |
186 | 166 |
187 let is_odd = len % 2 == 1; | 167 let is_odd = len % 2 == 1; |
188 let even_part = if is_odd { &hex[..len - 1] } else { hex }; | 168 let even_part = if is_odd { &hex[..len - 1] } else { hex }; |
189 let mut buf: Vec<u8> = | 169 let mut buf: Vec<u8> = |
190 Vec::from_hex(&even_part).map_err(|e| (e, hex))?; | 170 Vec::from_hex(&even_part).map_err(|_| FromHexError)?; |
191 | 171 |
192 if is_odd { | 172 if is_odd { |
193 let latest_char = char::from(hex[len - 1]); | 173 let latest_char = char::from(hex[len - 1]); |
194 let latest_nybble = latest_char.to_digit(16).ok_or_else(|| { | 174 let latest_nybble = |
195 ( | 175 latest_char.to_digit(16).ok_or_else(|| FromHexError)? as u8; |
196 FromHexError::InvalidHexCharacter { | |
197 c: latest_char, | |
198 index: len - 1, | |
199 }, | |
200 hex, | |
201 ) | |
202 })? as u8; | |
203 buf.push(latest_nybble << 4); | 176 buf.push(latest_nybble << 4); |
204 } | 177 } |
205 Ok(NodePrefix { buf, is_odd }) | 178 Ok(NodePrefix { buf, is_odd }) |
206 } | 179 } |
207 | 180 |
327 hex_pad_right("0123456789abcdeffedcba9876543210deadbeef") | 300 hex_pad_right("0123456789abcdeffedcba9876543210deadbeef") |
328 } | 301 } |
329 | 302 |
330 #[test] | 303 #[test] |
331 fn test_node_from_hex() { | 304 fn test_node_from_hex() { |
332 assert_eq!(Node::from_hex(&sample_node_hex()), Ok(sample_node())); | 305 assert_eq!(Node::from_hex(&sample_node_hex()).unwrap(), sample_node()); |
333 | 306 |
334 let mut short = hex_pad_right("0123"); | 307 let mut short = hex_pad_right("0123"); |
335 short.pop(); | 308 short.pop(); |
336 short.pop(); | 309 short.pop(); |
337 assert_eq!( | 310 assert!(Node::from_hex(&short).is_err()); |
338 Node::from_hex(&short), | |
339 Err(NodeError::ExactLengthRequired(NODE_NYBBLES_LENGTH, short)), | |
340 ); | |
341 | 311 |
342 let not_hex = hex_pad_right("012... oops"); | 312 let not_hex = hex_pad_right("012... oops"); |
343 assert_eq!( | 313 assert!(Node::from_hex(¬_hex).is_err(),); |
344 Node::from_hex(¬_hex), | |
345 Err(NodeError::HexError( | |
346 FromHexError::InvalidHexCharacter { c: '.', index: 3 }, | |
347 not_hex, | |
348 )), | |
349 ); | |
350 } | 314 } |
351 | 315 |
352 #[test] | 316 #[test] |
353 fn test_node_encode_hex() { | 317 fn test_node_encode_hex() { |
354 assert_eq!(format!("{:x}", sample_node()), sample_node_hex()); | 318 assert_eq!(format!("{:x}", sample_node()), sample_node_hex()); |
355 } | 319 } |
356 | 320 |
357 #[test] | 321 #[test] |
358 fn test_prefix_from_hex() -> Result<(), NodeError> { | 322 fn test_prefix_from_hex() -> Result<(), FromHexError> { |
359 assert_eq!( | 323 assert_eq!( |
360 NodePrefix::from_hex("0e1")?, | 324 NodePrefix::from_hex("0e1")?, |
361 NodePrefix { | 325 NodePrefix { |
362 buf: vec![14, 16], | 326 buf: vec![14, 16], |
363 is_odd: true | 327 is_odd: true |
384 Ok(()) | 348 Ok(()) |
385 } | 349 } |
386 | 350 |
387 #[test] | 351 #[test] |
388 fn test_prefix_from_hex_errors() { | 352 fn test_prefix_from_hex_errors() { |
389 assert_eq!( | 353 assert!(NodePrefix::from_hex("testgr").is_err()); |
390 NodePrefix::from_hex("testgr"), | |
391 Err(NodeError::HexError( | |
392 FromHexError::InvalidHexCharacter { c: 't', index: 0 }, | |
393 "testgr".to_string() | |
394 )) | |
395 ); | |
396 let mut long = format!("{:x}", NULL_NODE); | 354 let mut long = format!("{:x}", NULL_NODE); |
397 long.push('c'); | 355 long.push('c'); |
398 match NodePrefix::from_hex(&long) | 356 assert!(NodePrefix::from_hex(&long).is_err()) |
399 .expect_err("should be refused as too long") | 357 } |
400 { | 358 |
401 NodeError::PrefixTooLong(s) => assert_eq!(s, long), | 359 #[test] |
402 err => panic!(format!("Should have been TooLong, got {:?}", err)), | 360 fn test_is_prefix_of() -> Result<(), FromHexError> { |
403 } | |
404 } | |
405 | |
406 #[test] | |
407 fn test_is_prefix_of() -> Result<(), NodeError> { | |
408 let mut node_data = [0; NODE_BYTES_LENGTH]; | 361 let mut node_data = [0; NODE_BYTES_LENGTH]; |
409 node_data[0] = 0x12; | 362 node_data[0] = 0x12; |
410 node_data[1] = 0xca; | 363 node_data[1] = 0xca; |
411 let node = Node::from(node_data); | 364 let node = Node::from(node_data); |
412 assert!(NodePrefix::from_hex("12")?.borrow().is_prefix_of(&node)); | 365 assert!(NodePrefix::from_hex("12")?.borrow().is_prefix_of(&node)); |
415 assert!(!NodePrefix::from_hex("12d")?.borrow().is_prefix_of(&node)); | 368 assert!(!NodePrefix::from_hex("12d")?.borrow().is_prefix_of(&node)); |
416 Ok(()) | 369 Ok(()) |
417 } | 370 } |
418 | 371 |
419 #[test] | 372 #[test] |
420 fn test_get_nybble() -> Result<(), NodeError> { | 373 fn test_get_nybble() -> Result<(), FromHexError> { |
421 let prefix = NodePrefix::from_hex("dead6789cafe")?; | 374 let prefix = NodePrefix::from_hex("dead6789cafe")?; |
422 assert_eq!(prefix.borrow().get_nybble(0), 13); | 375 assert_eq!(prefix.borrow().get_nybble(0), 13); |
423 assert_eq!(prefix.borrow().get_nybble(7), 9); | 376 assert_eq!(prefix.borrow().get_nybble(7), 9); |
424 Ok(()) | 377 Ok(()) |
425 } | 378 } |