Patchwork D8378: rust-chg: add helper to parse instructions sent from server

login
register
mail settings
Submitter phabricator
Date April 6, 2020, 2:19 p.m.
Message ID <differential-rev-PHID-DREV-wzwmayv7boexoqxdveqe-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/46026/
State Superseded
Headers show

Comments

phabricator - April 6, 2020, 2:19 p.m.
yuja created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This is well structured version of runinstructions() of chg.c.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D8378

AFFECTED FILES
  rust/chg/src/message.rs

CHANGE DETAILS




To: yuja, #hg-reviewers
Cc: mercurial-devel
phabricator - April 8, 2020, 8:03 a.m.
Alphare added a comment.
Alphare accepted this revision.


  I had to squint hard at the c code to find any discrepancy, of which there seems to be none.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D8378/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D8378

To: yuja, #hg-reviewers, Alphare
Cc: Alphare, mercurial-devel
phabricator - April 9, 2020, 10:38 a.m.
pulkit added a comment.


  Queued the whole series based on @Alphare review. Many thanks to both of you.

REPOSITORY
  rHG Mercurial

BRANCH
  default

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D8378/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D8378

To: yuja, #hg-reviewers, Alphare, pulkit
Cc: Alphare, mercurial-devel

Patch

diff --git a/rust/chg/src/message.rs b/rust/chg/src/message.rs
--- a/rust/chg/src/message.rs
+++ b/rust/chg/src/message.rs
@@ -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()
+    }
 }