ofdsdk 0.1.1

Strongly typed OFD SDK for Rust
Documentation
#[cfg(feature = "parts")]
use super::SdkError;

pub fn resolve_zip_file_path(path: &str) -> String {
  let mut stack = Vec::new();

  for component in path.split('/') {
    match component {
      "" | "." => {}
      ".." => {
        stack.pop();
      }
      _ => {
        stack.push(component);
      }
    }
  }
  stack.join("/")
}

#[cfg(feature = "parts")]
pub fn zip_parent_dirs(path: &str) -> Vec<String> {
  let mut dirs = Vec::new();
  let mut current = String::new();

  for component in path.split('/').filter(|component| !component.is_empty()) {
    if !current.is_empty() {
      current.push('/');
    }
    current.push_str(component);
    current.push('/');
    dirs.push(current.clone());
    current.pop();
  }

  dirs.pop();
  dirs
}

#[inline]
pub fn zip_parent_dir(path: &str) -> String {
  match path.rfind('/') {
    Some(index) => path[..index + 1].to_string(),
    None => String::new(),
  }
}

#[inline]
pub fn resolve_zip_child_path(base_dir: &str, target: &str) -> String {
  if target.starts_with('/') {
    resolve_zip_file_path(target)
  } else {
    resolve_zip_file_path(&format!("{base_dir}{target}"))
  }
}

#[cfg(feature = "parts")]
pub fn read_zip_data<R: std::io::Read + std::io::Seek>(
  archive: &mut zip::ZipArchive<R>,
  path: &str,
) -> Result<Vec<u8>, SdkError> {
  use std::io::Read;

  let normalized_path = resolve_zip_file_path(path);
  let mut zip_entry = archive.by_name(&normalized_path)?;
  let mut data = Vec::with_capacity(zip_entry.size() as usize);
  zip_entry.read_to_end(&mut data)?;
  Ok(data)
}

#[cfg(feature = "parts")]
pub fn load_zip_parts<R, T, F>(
  _current_dir: &str,
  child_paths: Vec<String>,
  archive: &mut zip::ZipArchive<R>,
  mut load: F,
) -> Result<Vec<T>, SdkError>
where
  R: std::io::Read + std::io::Seek,
  F: FnMut(&str, &mut zip::ZipArchive<R>) -> Result<T, SdkError>,
{
  let mut parts = Vec::with_capacity(child_paths.len());

  for child_path in child_paths {
    parts.push(load(&child_path, archive)?);
  }

  Ok(parts)
}

#[cfg(feature = "parts")]
pub fn load_zip_parts_with_context<R, T, C, F>(
  _current_dir: &str,
  child_paths: Vec<String>,
  child_contexts: Vec<C>,
  child_api_name: &str,
  archive: &mut zip::ZipArchive<R>,
  mut load: F,
) -> Result<Vec<T>, SdkError>
where
  R: std::io::Read + std::io::Seek,
  F: FnMut(&str, C, &mut zip::ZipArchive<R>) -> Result<T, SdkError>,
{
  if child_paths.len() != child_contexts.len() {
    return Err(SdkError::CommonError(format!(
      "mismatched child contexts for {}",
      child_api_name
    )));
  }

  let mut parts = Vec::with_capacity(child_paths.len());

  for (child_path, child_context) in child_paths.into_iter().zip(child_contexts.into_iter()) {
    parts.push(load(&child_path, child_context, archive)?);
  }

  Ok(parts)
}

#[cfg(feature = "parts")]
pub fn load_optional_zip_part<R, T, F>(
  _current_dir: &str,
  child_paths: Vec<String>,
  archive: &mut zip::ZipArchive<R>,
  mut load: F,
) -> Result<Option<Box<T>>, SdkError>
where
  R: std::io::Read + std::io::Seek,
  F: FnMut(&str, &mut zip::ZipArchive<R>) -> Result<T, SdkError>,
{
  match child_paths.len() {
    0 => Ok(None),
    1 => Ok(Some(Box::new(load(&child_paths[0], archive)?))),
    _ => Err(SdkError::CommonError(
      "multiple child paths for optional part".to_string(),
    )),
  }
}

#[cfg(feature = "parts")]
pub fn load_optional_zip_part_with_context<R, T, C, F>(
  _current_dir: &str,
  child_paths: Vec<String>,
  child_contexts: Vec<C>,
  child_api_name: &str,
  archive: &mut zip::ZipArchive<R>,
  mut load: F,
) -> Result<Option<Box<T>>, SdkError>
where
  R: std::io::Read + std::io::Seek,
  F: FnMut(&str, C, &mut zip::ZipArchive<R>) -> Result<T, SdkError>,
{
  if child_paths.len() != child_contexts.len() {
    return Err(SdkError::CommonError(format!(
      "mismatched child contexts for {}",
      child_api_name
    )));
  }

  match child_paths.len() {
    0 => Ok(None),
    1 => {
      let child_path = child_paths.into_iter().next().unwrap();
      let child_context = child_contexts.into_iter().next().unwrap();
      Ok(Some(Box::new(load(&child_path, child_context, archive)?)))
    }
    _ => Err(SdkError::CommonError(
      "multiple child paths for optional part".to_string(),
    )),
  }
}

#[cfg(feature = "parts")]
pub fn load_required_zip_part<R, T, F>(
  current_dir: &str,
  child_paths: Vec<String>,
  child_api_name: &str,
  archive: &mut zip::ZipArchive<R>,
  load: F,
) -> Result<Box<T>, SdkError>
where
  R: std::io::Read + std::io::Seek,
  F: FnMut(&str, &mut zip::ZipArchive<R>) -> Result<T, SdkError>,
{
  load_optional_zip_part(current_dir, child_paths, archive, load)?
    .ok_or_else(|| SdkError::CommonError(child_api_name.to_string()))
}

#[cfg(feature = "parts")]
pub fn load_required_zip_part_with_context<R, T, C, F>(
  current_dir: &str,
  child_paths: Vec<String>,
  child_contexts: Vec<C>,
  child_api_name: &str,
  archive: &mut zip::ZipArchive<R>,
  load: F,
) -> Result<Box<T>, SdkError>
where
  R: std::io::Read + std::io::Seek,
  F: FnMut(&str, C, &mut zip::ZipArchive<R>) -> Result<T, SdkError>,
{
  load_optional_zip_part_with_context(
    current_dir,
    child_paths,
    child_contexts,
    child_api_name,
    archive,
    load,
  )?
  .ok_or_else(|| SdkError::CommonError(child_api_name.to_string()))
}

#[cfg(feature = "parts")]
pub fn save_zip_data<W: std::io::Write + std::io::Seek>(
  inner_path: &str,
  data: &[u8],
  zip: &mut zip::ZipWriter<W>,
  entry_set: &mut std::collections::HashSet<String>,
) -> Result<(), SdkError> {
  use std::io::Write;

  let options = zip::write::SimpleFileOptions::default()
    .compression_method(zip::CompressionMethod::Deflated)
    .unix_permissions(0o755);

  if inner_path.is_empty() {
    return Err(SdkError::CommonError("empty inner_path".to_string()));
  }

  for dir_path in zip_parent_dirs(inner_path) {
    if !entry_set.contains(&dir_path) {
      zip.add_directory(&dir_path, options)?;
      entry_set.insert(dir_path);
    }
  }

  if !entry_set.contains(inner_path) {
    zip.start_file(inner_path, options)?;
    zip.write_all(data)?;
    entry_set.insert(inner_path.to_string());
  }

  Ok(())
}

#[cfg(feature = "parts")]
pub fn save_zip_parts<W, T, F>(
  children: &[T],
  zip: &mut zip::ZipWriter<W>,
  entry_set: &mut std::collections::HashSet<String>,
  mut save: F,
) -> Result<(), SdkError>
where
  W: std::io::Write + std::io::Seek,
  F: FnMut(
    &T,
    &mut zip::ZipWriter<W>,
    &mut std::collections::HashSet<String>,
  ) -> Result<(), SdkError>,
{
  for child in children {
    save(child, zip, entry_set)?;
  }

  Ok(())
}

#[cfg(feature = "parts")]
pub fn save_optional_zip_part<W, T, F>(
  child: &Option<Box<T>>,
  zip: &mut zip::ZipWriter<W>,
  entry_set: &mut std::collections::HashSet<String>,
  mut save: F,
) -> Result<(), SdkError>
where
  W: std::io::Write + std::io::Seek,
  F: FnMut(
    &T,
    &mut zip::ZipWriter<W>,
    &mut std::collections::HashSet<String>,
  ) -> Result<(), SdkError>,
{
  if let Some(child) = child {
    save(child, zip, entry_set)?;
  }

  Ok(())
}

#[cfg(feature = "parts")]
pub fn save_required_zip_part<W, T, F>(
  child: &T,
  zip: &mut zip::ZipWriter<W>,
  entry_set: &mut std::collections::HashSet<String>,
  mut save: F,
) -> Result<(), SdkError>
where
  W: std::io::Write + std::io::Seek,
  F: FnMut(
    &T,
    &mut zip::ZipWriter<W>,
    &mut std::collections::HashSet<String>,
  ) -> Result<(), SdkError>,
{
  save(child, zip, entry_set)
}