rust/chg/src/message.rs
changeset 44679 82adc720c0a3
parent 44674 8a7beeea655f
child 44685 90e05b304902
--- a/rust/chg/src/message.rs	Thu Apr 02 13:53:33 2020 -0700
+++ b/rust/chg/src/message.rs	Sun Oct 07 15:21:54 2018 +0900
@@ -10,6 +10,7 @@
 use std::ffi::{OsStr, OsString};
 use std::io;
 use std::os::unix::ffi::OsStrExt;
+use std::path::PathBuf;
 
 pub use tokio_hglib::message::*; // re-exports
 
@@ -67,6 +68,42 @@
     }
 }
 
+/// Client-side instruction requested by the server.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Instruction {
+    Exit(i32),
+    Reconnect,
+    Redirect(PathBuf),
+    Unlink(PathBuf),
+}
+
+/// Parses validation result into instructions.
+pub fn parse_instructions(data: Bytes) -> io::Result<Vec<Instruction>> {
+    let mut instructions = Vec::new();
+    for l in data.split(|&c| c == b'\0') {
+        if l.is_empty() {
+            continue;
+        }
+        let mut s = l.splitn(2, |&c| c == b' ');
+        let inst = match (s.next().unwrap(), s.next()) {
+            (b"exit", Some(arg)) => decode_latin1(arg)
+                .parse()
+                .map(Instruction::Exit)
+                .map_err(|_| new_parse_error(format!("invalid exit code: {:?}", arg)))?,
+            (b"reconnect", None) => Instruction::Reconnect,
+            (b"redirect", Some(arg)) => {
+                Instruction::Redirect(OsStr::from_bytes(arg).to_owned().into())
+            }
+            (b"unlink", Some(arg)) => Instruction::Unlink(OsStr::from_bytes(arg).to_owned().into()),
+            _ => {
+                return Err(new_parse_error(format!("unknown command: {:?}", l)));
+            }
+        };
+        instructions.push(inst);
+    }
+    Ok(instructions)
+}
+
 // allocate large buffer as environment variables can be quite long
 const INITIAL_PACKED_ENV_VARS_CAPACITY: usize = 4096;
 
@@ -168,6 +205,50 @@
     }
 
     #[test]
+    fn parse_instructions_good() {
+        let src = [
+            b"exit 123".as_ref(),
+            b"reconnect".as_ref(),
+            b"redirect /whatever".as_ref(),
+            b"unlink /someother".as_ref(),
+        ]
+        .join(&0);
+        let insts = vec![
+            Instruction::Exit(123),
+            Instruction::Reconnect,
+            Instruction::Redirect(path_buf_from(b"/whatever")),
+            Instruction::Unlink(path_buf_from(b"/someother")),
+        ];
+        assert_eq!(parse_instructions(Bytes::from(src)).unwrap(), insts);
+    }
+
+    #[test]
+    fn parse_instructions_empty() {
+        assert_eq!(parse_instructions(Bytes::new()).unwrap(), vec![]);
+        assert_eq!(
+            parse_instructions(Bytes::from_static(b"\0")).unwrap(),
+            vec![]
+        );
+    }
+
+    #[test]
+    fn parse_instructions_malformed_exit_code() {
+        assert!(parse_instructions(Bytes::from_static(b"exit foo")).is_err());
+    }
+
+    #[test]
+    fn parse_instructions_missing_argument() {
+        assert!(parse_instructions(Bytes::from_static(b"exit")).is_err());
+        assert!(parse_instructions(Bytes::from_static(b"redirect")).is_err());
+        assert!(parse_instructions(Bytes::from_static(b"unlink")).is_err());
+    }
+
+    #[test]
+    fn parse_instructions_unknown_command() {
+        assert!(parse_instructions(Bytes::from_static(b"quit 0")).is_err());
+    }
+
+    #[test]
     fn pack_env_vars_os_good() {
         assert_eq!(
             pack_env_vars_os(vec![] as Vec<(OsString, OsString)>),
@@ -229,4 +310,8 @@
     fn os_string_pair_from(k: &[u8], v: &[u8]) -> (OsString, OsString) {
         (os_string_from(k), os_string_from(v))
     }
+
+    fn path_buf_from(s: &[u8]) -> PathBuf {
+        os_string_from(s).into()
+    }
 }