Mercurial > hg
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 } |