use async_trait::async_trait;
use crate::generated::api_types::{
SessionFsError, SessionFsErrorCode, SessionFsReaddirWithTypesEntry,
SessionFsReaddirWithTypesEntryType, SessionFsSetProviderConventions, SessionFsStatResult,
};
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct SessionFsConfig {
pub initial_cwd: String,
pub session_state_path: String,
pub conventions: SessionFsConventions,
}
impl SessionFsConfig {
pub fn new(
initial_cwd: impl Into<String>,
session_state_path: impl Into<String>,
conventions: SessionFsConventions,
) -> Self {
Self {
initial_cwd: initial_cwd.into(),
session_state_path: session_state_path.into(),
conventions,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SessionFsConventions {
Posix,
Windows,
}
impl SessionFsConventions {
pub(crate) fn into_wire(self) -> SessionFsSetProviderConventions {
match self {
Self::Posix => SessionFsSetProviderConventions::Posix,
Self::Windows => SessionFsSetProviderConventions::Windows,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, thiserror::Error)]
pub enum FsError {
#[error("not found: {0}")]
NotFound(String),
#[error("{0}")]
Other(String),
}
impl FsError {
pub(crate) fn into_wire(self) -> SessionFsError {
match self {
Self::NotFound(message) => SessionFsError {
code: SessionFsErrorCode::ENOENT,
message: Some(message),
},
Self::Other(message) => SessionFsError {
code: SessionFsErrorCode::UNKNOWN,
message: Some(message),
},
}
}
}
impl From<std::io::Error> for FsError {
fn from(err: std::io::Error) -> Self {
match err.kind() {
std::io::ErrorKind::NotFound => Self::NotFound(err.to_string()),
_ => Self::Other(err.to_string()),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct FileInfo {
pub is_file: bool,
pub is_directory: bool,
pub size: i64,
pub mtime: String,
pub birthtime: String,
}
impl FileInfo {
pub fn new(
is_file: bool,
is_directory: bool,
size: i64,
mtime: impl Into<String>,
birthtime: impl Into<String>,
) -> Self {
Self {
is_file,
is_directory,
size,
mtime: mtime.into(),
birthtime: birthtime.into(),
}
}
pub(crate) fn into_wire(self) -> SessionFsStatResult {
SessionFsStatResult {
is_file: self.is_file,
is_directory: self.is_directory,
size: self.size,
mtime: self.mtime,
birthtime: self.birthtime,
error: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DirEntryKind {
File,
Directory,
}
impl DirEntryKind {
fn into_wire(self) -> SessionFsReaddirWithTypesEntryType {
match self {
Self::File => SessionFsReaddirWithTypesEntryType::File,
Self::Directory => SessionFsReaddirWithTypesEntryType::Directory,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct DirEntry {
pub name: String,
pub kind: DirEntryKind,
}
impl DirEntry {
pub fn new(name: impl Into<String>, kind: DirEntryKind) -> Self {
Self {
name: name.into(),
kind,
}
}
pub(crate) fn into_wire(self) -> SessionFsReaddirWithTypesEntry {
SessionFsReaddirWithTypesEntry {
name: self.name,
r#type: self.kind.into_wire(),
}
}
}
#[async_trait]
pub trait SessionFsProvider: Send + Sync + 'static {
async fn read_file(&self, path: &str) -> Result<String, FsError> {
let _ = path;
Err(FsError::Other("read_file not supported".to_string()))
}
async fn write_file(
&self,
path: &str,
content: &str,
mode: Option<i64>,
) -> Result<(), FsError> {
let _ = (path, content, mode);
Err(FsError::Other("write_file not supported".to_string()))
}
async fn append_file(
&self,
path: &str,
content: &str,
mode: Option<i64>,
) -> Result<(), FsError> {
let _ = (path, content, mode);
Err(FsError::Other("append_file not supported".to_string()))
}
async fn exists(&self, path: &str) -> Result<bool, FsError> {
let _ = path;
Err(FsError::Other("exists not supported".to_string()))
}
async fn stat(&self, path: &str) -> Result<FileInfo, FsError> {
let _ = path;
Err(FsError::Other("stat not supported".to_string()))
}
async fn mkdir(&self, path: &str, recursive: bool, mode: Option<i64>) -> Result<(), FsError> {
let _ = (path, recursive, mode);
Err(FsError::Other("mkdir not supported".to_string()))
}
async fn readdir(&self, path: &str) -> Result<Vec<String>, FsError> {
let _ = path;
Err(FsError::Other("readdir not supported".to_string()))
}
async fn readdir_with_types(&self, path: &str) -> Result<Vec<DirEntry>, FsError> {
let _ = path;
Err(FsError::Other(
"readdir_with_types not supported".to_string(),
))
}
async fn rm(&self, path: &str, recursive: bool, force: bool) -> Result<(), FsError> {
let _ = (path, recursive, force);
Err(FsError::Other("rm not supported".to_string()))
}
async fn rename(&self, src: &str, dest: &str) -> Result<(), FsError> {
let _ = (src, dest);
Err(FsError::Other("rename not supported".to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fs_error_maps_io_not_found_to_enoent() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing.txt");
let fs_err: FsError = io_err.into();
assert!(matches!(fs_err, FsError::NotFound(_)));
let wire = fs_err.into_wire();
assert_eq!(wire.code, SessionFsErrorCode::ENOENT);
}
#[test]
fn fs_error_maps_other_io_to_unknown() {
let io_err = std::io::Error::other("disk full");
let fs_err: FsError = io_err.into();
assert!(matches!(fs_err, FsError::Other(_)));
let wire = fs_err.into_wire();
assert_eq!(wire.code, SessionFsErrorCode::UNKNOWN);
assert!(wire.message.unwrap().contains("disk full"));
}
#[test]
fn conventions_maps_to_wire() {
assert_eq!(
SessionFsConventions::Posix.into_wire(),
SessionFsSetProviderConventions::Posix
);
assert_eq!(
SessionFsConventions::Windows.into_wire(),
SessionFsSetProviderConventions::Windows
);
}
struct DefaultProvider;
#[async_trait]
impl SessionFsProvider for DefaultProvider {}
#[tokio::test]
async fn default_impls_return_unsupported() {
let p = DefaultProvider;
let err = p.read_file("/x").await.unwrap_err();
assert!(matches!(err, FsError::Other(ref m) if m.contains("not supported")));
}
}