Refactor Metadata and SystemInfo response data types to support subtypes as singular parameters

pull/59/head
Chip Senkbeil 3 years ago
parent 4a4a06ef80
commit 97536c7b2b
No known key found for this signature in database
GPG Key ID: 35EF1F8EC72A4131

6
Cargo.lock generated

@ -429,7 +429,7 @@ dependencies = [
[[package]]
name = "distant"
version = "0.15.0-alpha.2"
version = "0.15.0-alpha.3"
dependencies = [
"assert_cmd",
"assert_fs",
@ -453,7 +453,7 @@ dependencies = [
[[package]]
name = "distant-core"
version = "0.15.0-alpha.2"
version = "0.15.0-alpha.3"
dependencies = [
"assert_fs",
"bytes",
@ -478,7 +478,7 @@ dependencies = [
[[package]]
name = "distant-ssh2"
version = "0.15.0-alpha.2"
version = "0.15.0-alpha.3"
dependencies = [
"assert_cmd",
"assert_fs",

@ -1,10 +1,9 @@
use crate::{
client::{RemoteLspProcess, RemoteProcess, RemoteProcessError, SessionChannel},
data::{DirEntry, Error as Failure, FileType, Request, RequestData, ResponseData},
data::{DirEntry, Error as Failure, Metadata, Request, RequestData, ResponseData, SystemInfo},
net::TransportError,
};
use derive_more::{Display, Error, From};
use serde::{Deserialize, Serialize};
use std::{future::Future, path::PathBuf, pin::Pin};
/// Represents an error that can occur related to convenience functions tied to a
@ -24,20 +23,6 @@ pub enum SessionChannelExtError {
pub type AsyncReturn<'a, T, E = SessionChannelExtError> =
Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'a>>;
/// Represents metadata about some path on a remote machine
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Metadata {
pub file_type: FileType,
pub len: u64,
pub readonly: bool,
pub canonicalized_path: Option<PathBuf>,
pub accessed: Option<u128>,
pub created: Option<u128>,
pub modified: Option<u128>,
}
/// Provides convenience functions on top of a [`SessionChannel`]
pub trait SessionChannelExt {
/// Appends to a remote file using the data from a collection of bytes
@ -135,7 +120,7 @@ pub trait SessionChannelExt {
&mut self,
tenant: impl Into<String>,
cmd: impl Into<String>,
args: Vec<String>,
args: Vec<impl Into<String>>,
) -> AsyncReturn<'_, RemoteProcess, RemoteProcessError>;
/// Spawns an LSP process on the remote machine
@ -143,9 +128,12 @@ pub trait SessionChannelExt {
&mut self,
tenant: impl Into<String>,
cmd: impl Into<String>,
args: Vec<String>,
args: Vec<impl Into<String>>,
) -> AsyncReturn<'_, RemoteLspProcess, RemoteProcessError>;
/// Retrieves information about the remote system
fn system_info(&mut self, tenant: impl Into<String>) -> AsyncReturn<'_, SystemInfo>;
/// Writes a remote file with the data from a collection of bytes
fn write_file(
&mut self,
@ -282,23 +270,7 @@ impl SessionChannelExt for SessionChannel {
resolve_file_type
},
|data| match data {
ResponseData::Metadata {
canonicalized_path,
file_type,
len,
readonly,
accessed,
created,
modified,
} => Ok(Metadata {
canonicalized_path,
file_type,
len,
readonly,
accessed,
created,
modified,
}),
ResponseData::Metadata(x) => Ok(x),
_ => Err(SessionChannelExtError::MismatchedResponse),
}
)
@ -394,10 +366,11 @@ impl SessionChannelExt for SessionChannel {
&mut self,
tenant: impl Into<String>,
cmd: impl Into<String>,
args: Vec<String>,
args: Vec<impl Into<String>>,
) -> AsyncReturn<'_, RemoteProcess, RemoteProcessError> {
let tenant = tenant.into();
let cmd = cmd.into();
let args = args.into_iter().map(Into::into).collect();
Box::pin(async move { RemoteProcess::spawn(tenant, self.clone(), cmd, args).await })
}
@ -405,13 +378,26 @@ impl SessionChannelExt for SessionChannel {
&mut self,
tenant: impl Into<String>,
cmd: impl Into<String>,
args: Vec<String>,
args: Vec<impl Into<String>>,
) -> AsyncReturn<'_, RemoteLspProcess, RemoteProcessError> {
let tenant = tenant.into();
let cmd = cmd.into();
let args = args.into_iter().map(Into::into).collect();
Box::pin(async move { RemoteLspProcess::spawn(tenant, self.clone(), cmd, args).await })
}
fn system_info(&mut self, tenant: impl Into<String>) -> AsyncReturn<'_, SystemInfo> {
make_body!(
self,
tenant,
RequestData::SystemInfo {},
|data| match data {
ResponseData::SystemInfo(x) => Ok(x),
_ => Err(SessionChannelExtError::MismatchedResponse),
}
)
}
fn write_file(
&mut self,
tenant: impl Into<String>,

@ -20,7 +20,7 @@ use tokio::{
};
mod ext;
pub use ext::{Metadata, SessionChannelExt, SessionChannelExtError};
pub use ext::{SessionChannelExt, SessionChannelExtError};
mod info;
pub use info::{SessionInfo, SessionInfoFile, SessionInfoParseError};

@ -330,32 +330,7 @@ pub enum ResponseData {
Exists(bool),
/// Represents metadata about some filesystem object (file, directory, symlink) on remote machine
Metadata {
/// Canonicalized path to the file or directory, resolving symlinks, only included
/// if flagged during the request
canonicalized_path: Option<PathBuf>,
/// Represents the type of the entry as a file/dir/symlink
file_type: FileType,
/// Size of the file/directory/symlink in bytes
len: u64,
/// Whether or not the file/directory/symlink is marked as unwriteable
readonly: bool,
/// Represents the last time (in milliseconds) when the file/directory/symlink was accessed;
/// can be optional as certain systems don't support this
accessed: Option<u128>,
/// Represents when (in milliseconds) the file/directory/symlink was created;
/// can be optional as certain systems don't support this
created: Option<u128>,
/// Represents the last time (in milliseconds) when the file/directory/symlink was modified;
/// can be optional as certain systems don't support this
modified: Option<u128>,
},
Metadata(Metadata),
/// Response to starting a new process
ProcStart {
@ -400,26 +375,59 @@ pub enum ResponseData {
},
/// Response to retrieving information about the server and the system it is on
SystemInfo {
/// Family of the operating system as described in
/// https://doc.rust-lang.org/std/env/consts/constant.FAMILY.html
family: String,
SystemInfo(SystemInfo),
}
/// Name of the specific operating system as described in
/// https://doc.rust-lang.org/std/env/consts/constant.OS.html
os: String,
/// Represents metadata about some path on a remote machine
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Metadata {
/// Canonicalized path to the file or directory, resolving symlinks, only included
/// if flagged during the request
pub canonicalized_path: Option<PathBuf>,
/// Architecture of the CPI as described in
/// https://doc.rust-lang.org/std/env/consts/constant.ARCH.html
arch: String,
/// Represents the type of the entry as a file/dir/symlink
pub file_type: FileType,
/// Current working directory of the running server process
current_dir: PathBuf,
/// Size of the file/directory/symlink in bytes
pub len: u64,
/// Primary separator for path components for the current platform
/// as defined in https://doc.rust-lang.org/std/path/constant.MAIN_SEPARATOR.html
main_separator: char,
},
/// Whether or not the file/directory/symlink is marked as unwriteable
pub readonly: bool,
/// Represents the last time (in milliseconds) when the file/directory/symlink was accessed;
/// can be optional as certain systems don't support this
pub accessed: Option<u128>,
/// Represents when (in milliseconds) the file/directory/symlink was created;
/// can be optional as certain systems don't support this
pub created: Option<u128>,
/// Represents the last time (in milliseconds) when the file/directory/symlink was modified;
/// can be optional as certain systems don't support this
pub modified: Option<u128>,
}
/// Represents information about a system
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SystemInfo {
/// Family of the operating system as described in
/// https://doc.rust-lang.org/std/env/consts/constant.FAMILY.html
pub family: String,
/// Name of the specific operating system as described in
/// https://doc.rust-lang.org/std/env/consts/constant.OS.html
pub os: String,
/// Architecture of the CPI as described in
/// https://doc.rust-lang.org/std/env/consts/constant.ARCH.html
pub arch: String,
/// Current working directory of the running server process
pub current_dir: PathBuf,
/// Primary separator for path components for the current platform
/// as defined in https://doc.rust-lang.org/std/path/constant.MAIN_SEPARATOR.html
pub main_separator: char,
}
/// Represents information about a single entry within a directory

@ -1,7 +1,8 @@
use crate::{
constants::{MAX_PIPE_CHUNK_SIZE, READ_PAUSE_MILLIS},
data::{
self, DirEntry, FileType, Request, RequestData, Response, ResponseData, RunningProcess,
self, DirEntry, FileType, Metadata, Request, RequestData, Response, ResponseData,
RunningProcess, SystemInfo,
},
server::distant::state::{Process, State},
};
@ -388,7 +389,7 @@ async fn metadata(
metadata.file_type()
};
Ok(Outgoing::from(ResponseData::Metadata {
Ok(Outgoing::from(ResponseData::Metadata(Metadata {
canonicalized_path,
accessed: metadata
.accessed()
@ -414,7 +415,7 @@ async fn metadata(
} else {
FileType::Symlink
},
}))
})))
}
async fn proc_run<F>(
@ -662,13 +663,13 @@ async fn proc_list(state: HState) -> Result<Outgoing, ServerError> {
}
async fn system_info() -> Result<Outgoing, ServerError> {
Ok(Outgoing::from(ResponseData::SystemInfo {
Ok(Outgoing::from(ResponseData::SystemInfo(SystemInfo {
family: env::consts::FAMILY.to_string(),
os: env::consts::OS.to_string(),
arch: env::consts::ARCH.to_string(),
current_dir: env::current_dir().unwrap_or_default(),
main_separator: std::path::MAIN_SEPARATOR,
}))
})))
}
#[cfg(test)]
@ -1930,13 +1931,13 @@ mod tests {
assert!(
matches!(
res.payload[0],
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path: None,
file_type: FileType::File,
len: 9,
readonly: false,
..
}
})
),
"Unexpected response: {:?}",
res.payload[0]
@ -1966,12 +1967,12 @@ mod tests {
assert!(
matches!(
res.payload[0],
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path: None,
file_type: FileType::Dir,
readonly: false,
..
}
})
),
"Unexpected response: {:?}",
res.payload[0]
@ -2004,12 +2005,12 @@ mod tests {
assert!(
matches!(
res.payload[0],
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path: None,
file_type: FileType::Symlink,
readonly: false,
..
}
})
),
"Unexpected response: {:?}",
res.payload[0]
@ -2040,12 +2041,12 @@ mod tests {
let res = rx.recv().await.unwrap();
assert_eq!(res.payload.len(), 1, "Wrong payload size");
match &res.payload[0] {
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path: Some(path),
file_type: FileType::Symlink,
readonly: false,
..
} => assert_eq!(
}) => assert_eq!(
path,
&file.path().canonicalize().unwrap(),
"Symlink canonicalized path does not match referenced file"
@ -2078,10 +2079,10 @@ mod tests {
let res = rx.recv().await.unwrap();
assert_eq!(res.payload.len(), 1, "Wrong payload size");
match &res.payload[0] {
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
file_type: FileType::File,
..
} => {}
}) => {}
x => panic!("Unexpected response: {:?}", x),
}
}
@ -2605,13 +2606,13 @@ mod tests {
assert_eq!(res.payload.len(), 1, "Wrong payload size");
assert_eq!(
res.payload[0],
ResponseData::SystemInfo {
ResponseData::SystemInfo(SystemInfo {
family: env::consts::FAMILY.to_string(),
os: env::consts::OS.to_string(),
arch: env::consts::ARCH.to_string(),
current_dir: env::current_dir().unwrap_or_default(),
main_separator: std::path::MAIN_SEPARATOR,
},
}),
"Unexpected response: {:?}",
res.payload[0]
);

@ -1,6 +1,6 @@
use async_compat::CompatExt;
use distant_core::{
data::{DirEntry, Error as DistantError, FileType, RunningProcess},
data::{DirEntry, Error as DistantError, FileType, Metadata, RunningProcess, SystemInfo},
Request, RequestData, Response, ResponseData,
};
use futures::future;
@ -558,7 +558,7 @@ async fn metadata(
FileType::Symlink
};
Ok(Outgoing::from(ResponseData::Metadata {
Ok(Outgoing::from(ResponseData::Metadata(Metadata {
canonicalized_path,
file_type,
len: stat.len(),
@ -567,7 +567,7 @@ async fn metadata(
accessed: stat.accessed.map(u128::from),
modified: stat.modified.map(u128::from),
created: None,
}))
})))
}
async fn proc_run<F>(
@ -871,11 +871,11 @@ async fn system_info(session: WezSession) -> io::Result<Outgoing> {
}
.to_string();
Ok(Outgoing::from(ResponseData::SystemInfo {
Ok(Outgoing::from(ResponseData::SystemInfo(SystemInfo {
family,
os: "".to_string(),
arch: "".to_string(),
current_dir,
main_separator: if is_windows { '\\' } else { '/' },
}))
})))
}

@ -1,7 +1,8 @@
use crate::sshd::*;
use assert_fs::{prelude::*, TempDir};
use distant_core::{
FileType, Request, RequestData, Response, ResponseData, RunningProcess, Session,
FileType, Metadata, Request, RequestData, Response, ResponseData, RunningProcess, Session,
SystemInfo,
};
use once_cell::sync::Lazy;
use predicates::prelude::*;
@ -1191,13 +1192,13 @@ async fn metadata_should_send_back_metadata_on_file_if_exists(#[future] session:
assert!(
matches!(
res.payload[0],
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path: None,
file_type: FileType::File,
len: 9,
readonly: false,
..
}
})
),
"Unexpected response: {:?}",
res.payload[0]
@ -1226,12 +1227,12 @@ async fn metadata_should_send_back_metadata_on_dir_if_exists(#[future] session:
assert!(
matches!(
res.payload[0],
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path: None,
file_type: FileType::Dir,
readonly: false,
..
}
})
),
"Unexpected response: {:?}",
res.payload[0]
@ -1263,12 +1264,12 @@ async fn metadata_should_send_back_metadata_on_symlink_if_exists(#[future] sessi
assert!(
matches!(
res.payload[0],
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path: None,
file_type: FileType::Symlink,
readonly: false,
..
}
})
),
"Unexpected response: {:?}",
res.payload[0]
@ -1298,12 +1299,12 @@ async fn metadata_should_include_canonicalized_path_if_flag_specified(#[future]
let res = session.send(req).await.unwrap();
assert_eq!(res.payload.len(), 1, "Wrong payload size");
match &res.payload[0] {
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path: Some(path),
file_type: FileType::Symlink,
readonly: false,
..
} => assert_eq!(
}) => assert_eq!(
path,
&file.path().canonicalize().unwrap(),
"Symlink canonicalized path does not match referenced file"
@ -1337,10 +1338,10 @@ async fn metadata_should_resolve_file_type_of_symlink_if_flag_specified(
let res = session.send(req).await.unwrap();
assert_eq!(res.payload.len(), 1, "Wrong payload size");
match &res.payload[0] {
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
file_type: FileType::File,
..
} => {}
}) => {}
x => panic!("Unexpected response: {:?}", x),
}
}
@ -1841,9 +1842,9 @@ async fn system_info_should_send_system_info_based_on_binary(#[future] session:
))
.await
.unwrap();
let current_dir = if let ResponseData::Metadata {
let current_dir = if let ResponseData::Metadata(Metadata {
canonicalized_path, ..
} = &res.payload[0]
}) = &res.payload[0]
{
canonicalized_path
.as_deref()
@ -1858,13 +1859,13 @@ async fn system_info_should_send_system_info_based_on_binary(#[future] session:
assert_eq!(res.payload.len(), 1, "Wrong payload size");
assert_eq!(
res.payload[0],
ResponseData::SystemInfo {
ResponseData::SystemInfo(SystemInfo {
family: env::consts::FAMILY.to_string(),
os: "".to_string(),
arch: "".to_string(),
current_dir,
main_separator: std::path::MAIN_SEPARATOR,
},
}),
"Unexpected response: {:?}",
res.payload[0]
);

@ -1,5 +1,8 @@
use crate::opt::Format;
use distant_core::{data::Error, Response, ResponseData};
use distant_core::{
data::{Error, Metadata, SystemInfo},
Response, ResponseData,
};
use log::*;
use std::io;
@ -106,7 +109,7 @@ fn format_shell(data: ResponseData) -> ResponseOut {
ResponseOut::StdoutLine("false".to_string())
}
}
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path,
file_type,
len,
@ -114,7 +117,7 @@ fn format_shell(data: ResponseData) -> ResponseOut {
accessed,
created,
modified,
} => ResponseOut::StdoutLine(format!(
}) => ResponseOut::StdoutLine(format!(
concat!(
"{}",
"Type: {}\n",
@ -153,13 +156,13 @@ fn format_shell(data: ResponseData) -> ResponseOut {
ResponseOut::StderrLine(format!("Proc {} failed", id))
}
}
ResponseData::SystemInfo {
ResponseData::SystemInfo(SystemInfo {
family,
os,
arch,
current_dir,
main_separator,
} => ResponseOut::StdoutLine(format!(
}) => ResponseOut::StdoutLine(format!(
concat!(
"Family: {:?}\n",
"Operating System: {:?}\n",

@ -6,7 +6,7 @@ use assert_cmd::Command;
use assert_fs::prelude::*;
use distant::ExitCode;
use distant_core::{
data::{Error, ErrorKind, FileType},
data::{Error, ErrorKind, FileType, Metadata},
Request, RequestData, Response, ResponseData,
};
use rstest::*;
@ -165,12 +165,12 @@ fn should_support_json_metadata_for_file(mut action_cmd: Command) {
assert!(
matches!(
res.payload[0],
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path: None,
file_type: FileType::File,
readonly: false,
..
},
}),
),
"Unexpected response: {:?}",
res.payload[0],
@ -207,12 +207,12 @@ fn should_support_json_metadata_for_directory(mut action_cmd: Command) {
assert!(
matches!(
res.payload[0],
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path: None,
file_type: FileType::Dir,
readonly: false,
..
},
}),
),
"Unexpected response: {:?}",
res.payload[0],
@ -250,12 +250,12 @@ fn should_support_json_metadata_for_including_a_canonicalized_path(mut action_cm
let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap();
match &res.payload[0] {
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
canonicalized_path: Some(path),
file_type: FileType::Symlink,
readonly: false,
..
} => assert_eq!(path, &file.path().canonicalize().unwrap()),
}) => assert_eq!(path, &file.path().canonicalize().unwrap()),
x => panic!("Unexpected response: {:?}", x),
}
}
@ -293,10 +293,10 @@ fn should_support_json_metadata_for_resolving_file_type_of_symlink(mut action_cm
assert!(
matches!(
res.payload[0],
ResponseData::Metadata {
ResponseData::Metadata(Metadata {
file_type: FileType::File,
..
},
}),
),
"Unexpected response: {:?}",
res.payload[0],

@ -12,3 +12,4 @@ mod metadata;
mod proc_run;
mod remove;
mod rename;
mod system_info;

@ -0,0 +1,64 @@
use crate::cli::{fixtures::*, utils::random_tenant};
use assert_cmd::Command;
use distant_core::{data::SystemInfo, Request, RequestData, Response, ResponseData};
use rstest::*;
use std::env;
#[rstest]
fn should_output_system_info(mut action_cmd: Command) {
// distant action system-info
action_cmd
.arg("system-info")
.assert()
.success()
.stdout(format!(
concat!(
"Family: {:?}\n",
"Operating System: {:?}\n",
"Arch: {:?}\n",
"Cwd: {:?}\n",
"Path Sep: {:?}\n",
),
env::consts::FAMILY.to_string(),
env::consts::OS.to_string(),
env::consts::ARCH.to_string(),
env::current_dir().unwrap_or_default(),
std::path::MAIN_SEPARATOR,
))
.stderr("");
}
#[rstest]
fn should_support_json_system_info(mut action_cmd: Command) {
let req = Request {
id: rand::random(),
tenant: random_tenant(),
payload: vec![RequestData::SystemInfo {}],
};
// distant action --format json --interactive
let cmd = action_cmd
.args(&["--format", "json"])
.arg("--interactive")
.write_stdin(format!("{}\n", serde_json::to_string(&req).unwrap()))
.assert()
.success()
.stderr("");
let res: Response = serde_json::from_slice(&cmd.get_output().stdout).unwrap();
match &res.payload[0] {
ResponseData::SystemInfo(info) => {
assert_eq!(
info,
&SystemInfo {
family: env::consts::FAMILY.to_string(),
os: env::consts::OS.to_string(),
arch: env::consts::ARCH.to_string(),
current_dir: env::current_dir().unwrap_or_default(),
main_separator: std::path::MAIN_SEPARATOR,
}
);
}
x => panic!("Unexpected response: {:?}", x),
}
}
Loading…
Cancel
Save