comparison rust/chg/src/message.rs @ 44679:82adc720c0a3

rust-chg: add helper to parse instructions sent from server This is well structured version of runinstructions() of chg.c. Differential Revision: https://phab.mercurial-scm.org/D8378
author Yuya Nishihara <yuya@tcha.org>
date Sun, 07 Oct 2018 15:21:54 +0900
parents 8a7beeea655f
children 90e05b304902
comparison
equal deleted inserted replaced
44678:806f1f1ba430 44679:82adc720c0a3
8 use bytes::{BufMut, Bytes, BytesMut}; 8 use bytes::{BufMut, Bytes, BytesMut};
9 use std::error; 9 use std::error;
10 use std::ffi::{OsStr, OsString}; 10 use std::ffi::{OsStr, OsString};
11 use std::io; 11 use std::io;
12 use std::os::unix::ffi::OsStrExt; 12 use std::os::unix::ffi::OsStrExt;
13 use std::path::PathBuf;
13 14
14 pub use tokio_hglib::message::*; // re-exports 15 pub use tokio_hglib::message::*; // re-exports
15 16
16 /// Shell command type requested by the server. 17 /// Shell command type requested by the server.
17 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 18 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
63 _ => Err(new_parse_error(format!( 64 _ => Err(new_parse_error(format!(
64 "unknown command type: {}", 65 "unknown command type: {}",
65 decode_latin1(value) 66 decode_latin1(value)
66 ))), 67 ))),
67 } 68 }
69 }
70
71 /// Client-side instruction requested by the server.
72 #[derive(Clone, Debug, Eq, PartialEq)]
73 pub enum Instruction {
74 Exit(i32),
75 Reconnect,
76 Redirect(PathBuf),
77 Unlink(PathBuf),
78 }
79
80 /// Parses validation result into instructions.
81 pub fn parse_instructions(data: Bytes) -> io::Result<Vec<Instruction>> {
82 let mut instructions = Vec::new();
83 for l in data.split(|&c| c == b'\0') {
84 if l.is_empty() {
85 continue;
86 }
87 let mut s = l.splitn(2, |&c| c == b' ');
88 let inst = match (s.next().unwrap(), s.next()) {
89 (b"exit", Some(arg)) => decode_latin1(arg)
90 .parse()
91 .map(Instruction::Exit)
92 .map_err(|_| new_parse_error(format!("invalid exit code: {:?}", arg)))?,
93 (b"reconnect", None) => Instruction::Reconnect,
94 (b"redirect", Some(arg)) => {
95 Instruction::Redirect(OsStr::from_bytes(arg).to_owned().into())
96 }
97 (b"unlink", Some(arg)) => Instruction::Unlink(OsStr::from_bytes(arg).to_owned().into()),
98 _ => {
99 return Err(new_parse_error(format!("unknown command: {:?}", l)));
100 }
101 };
102 instructions.push(inst);
103 }
104 Ok(instructions)
68 } 105 }
69 106
70 // allocate large buffer as environment variables can be quite long 107 // allocate large buffer as environment variables can be quite long
71 const INITIAL_PACKED_ENV_VARS_CAPACITY: usize = 4096; 108 const INITIAL_PACKED_ENV_VARS_CAPACITY: usize = 4096;
72 109
166 fn parse_command_spec_unknown_type() { 203 fn parse_command_spec_unknown_type() {
167 assert!(parse_command_spec(Bytes::from_static(b"paper\0less")).is_err()); 204 assert!(parse_command_spec(Bytes::from_static(b"paper\0less")).is_err());
168 } 205 }
169 206
170 #[test] 207 #[test]
208 fn parse_instructions_good() {
209 let src = [
210 b"exit 123".as_ref(),
211 b"reconnect".as_ref(),
212 b"redirect /whatever".as_ref(),
213 b"unlink /someother".as_ref(),
214 ]
215 .join(&0);
216 let insts = vec![
217 Instruction::Exit(123),
218 Instruction::Reconnect,
219 Instruction::Redirect(path_buf_from(b"/whatever")),
220 Instruction::Unlink(path_buf_from(b"/someother")),
221 ];
222 assert_eq!(parse_instructions(Bytes::from(src)).unwrap(), insts);
223 }
224
225 #[test]
226 fn parse_instructions_empty() {
227 assert_eq!(parse_instructions(Bytes::new()).unwrap(), vec![]);
228 assert_eq!(
229 parse_instructions(Bytes::from_static(b"\0")).unwrap(),
230 vec![]
231 );
232 }
233
234 #[test]
235 fn parse_instructions_malformed_exit_code() {
236 assert!(parse_instructions(Bytes::from_static(b"exit foo")).is_err());
237 }
238
239 #[test]
240 fn parse_instructions_missing_argument() {
241 assert!(parse_instructions(Bytes::from_static(b"exit")).is_err());
242 assert!(parse_instructions(Bytes::from_static(b"redirect")).is_err());
243 assert!(parse_instructions(Bytes::from_static(b"unlink")).is_err());
244 }
245
246 #[test]
247 fn parse_instructions_unknown_command() {
248 assert!(parse_instructions(Bytes::from_static(b"quit 0")).is_err());
249 }
250
251 #[test]
171 fn pack_env_vars_os_good() { 252 fn pack_env_vars_os_good() {
172 assert_eq!( 253 assert_eq!(
173 pack_env_vars_os(vec![] as Vec<(OsString, OsString)>), 254 pack_env_vars_os(vec![] as Vec<(OsString, OsString)>),
174 Bytes::new() 255 Bytes::new()
175 ); 256 );
227 } 308 }
228 309
229 fn os_string_pair_from(k: &[u8], v: &[u8]) -> (OsString, OsString) { 310 fn os_string_pair_from(k: &[u8], v: &[u8]) -> (OsString, OsString) {
230 (os_string_from(k), os_string_from(v)) 311 (os_string_from(k), os_string_from(v))
231 } 312 }
232 } 313
314 fn path_buf_from(s: &[u8]) -> PathBuf {
315 os_string_from(s).into()
316 }
317 }