webc 5.0.0-rc.6

WebContainer implementation for wapm.io
Documentation
use std::collections::BTreeMap;

use crate::v2::read::{
    volume_header::{DirectoryMetadata, FileMetadata, HeaderEntry},
    VolumeHeaderError,
};
use bytes::Bytes;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DirEntry<'volume> {
    Dir(Directory<'volume>),
    File(FileEntry),
}

impl<'volume> DirEntry<'volume> {
    fn from_metadata(
        metadata: HeaderEntry<'volume>,
        data_section: Bytes,
    ) -> Result<Self, DirEntryError> {
        match metadata {
            HeaderEntry::Directory(dir) => Ok(Directory::new(dir, data_section).into()),
            HeaderEntry::File(meta) => {
                let file = FileEntry::from_metadata(meta, data_section)?;
                Ok(file.into())
            }
        }
    }
}

impl<'volume> From<Directory<'volume>> for DirEntry<'volume> {
    fn from(v: Directory<'volume>) -> Self {
        Self::Dir(v)
    }
}

impl From<FileEntry> for DirEntry<'_> {
    fn from(f: FileEntry) -> Self {
        Self::File(f)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Directory<'volume> {
    metadata: DirectoryMetadata<'volume>,
    data_section: Bytes,
}

impl<'volume> Directory<'volume> {
    pub(crate) fn new(metadata: DirectoryMetadata<'volume>, data_section: Bytes) -> Self {
        Directory {
            metadata,
            data_section,
        }
    }

    pub fn entries(
        &self,
    ) -> impl Iterator<Item = Result<(&'volume str, DirEntry<'volume>), DirEntryError>> + '_ {
        self.metadata.clone().entries().map(|result| {
            result
                .map_err(DirEntryError::from)
                .and_then(|(name, entry)| {
                    let entry = DirEntry::from_metadata(entry, self.data_section.clone())?;
                    Ok((name, entry))
                })
        })
    }

    pub fn is_empty(&self) -> bool {
        self.entries().filter_map(|result| result.ok()).count() > 0
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FileEntry {
    data: Bytes,
}

impl FileEntry {
    pub(crate) fn from_metadata(
        meta: FileMetadata,
        data_section: Bytes,
    ) -> Result<FileEntry, DirEntryError> {
        let FileMetadata {
            start_offset,
            end_offset,
            ..
        } = meta;

        if start_offset > data_section.len() || end_offset > data_section.len() {
            return Err(DirEntryError::FileOutOfBounds {
                start_offset,
                end_offset,
                data_section_length: data_section.len(),
            });
        }

        let data = data_section.slice(start_offset..end_offset);
        Ok(FileEntry { data })
    }

    pub fn bytes(&self) -> &Bytes {
        &self.data
    }

    pub fn len(&self) -> usize {
        self.bytes().len()
    }

    pub fn is_empty(&self) -> bool {
        self.bytes().is_empty()
    }
}

#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum DirEntryError {
    #[error("The volume header was corrupted")]
    Header(#[from] VolumeHeaderError),
    #[error("File data is out of bounds ({start_offset}..{end_offset} is outside of 0..{data_section_length})")]
    FileOutOfBounds {
        start_offset: usize,
        end_offset: usize,
        data_section_length: usize,
    },
}

impl TryFrom<DirEntry<'_>> for crate::v2::write::DirEntry<'_> {
    type Error = DirEntryError;

    fn try_from(value: DirEntry<'_>) -> Result<Self, Self::Error> {
        match value {
            DirEntry::Dir(d) => d.try_into().map(crate::v2::write::DirEntry::Dir),
            DirEntry::File(f) => Ok(crate::v2::write::DirEntry::File(f.into())),
        }
    }
}

impl TryFrom<Directory<'_>> for crate::v2::write::Directory<'_> {
    type Error = DirEntryError;

    fn try_from(dir: Directory<'_>) -> Result<Self, Self::Error> {
        let mut children = BTreeMap::new();

        for result in dir.entries() {
            let (name, entry) = result?;
            let name = name
                .parse()
                .expect("All names coming from the directory parser should be valid path segments");
            children.insert(name, entry.try_into()?);
        }

        Ok(crate::v2::write::Directory { children })
    }
}

impl From<FileEntry> for crate::v2::write::FileEntry<'_> {
    fn from(value: FileEntry) -> Self {
        crate::v2::write::FileEntry::Owned(value.bytes().clone())
    }
}