xzip 0.4.1

ZIP CLI with explicit filename encoding for pack and unpack workflows.
Documentation
use encoding_rs::{Encoding, GBK, SHIFT_JIS, UTF_8};

use crate::error::XzipError;

#[derive(Clone, Copy, Debug)]
pub enum EncodingKind {
    Utf8,
    Gbk,
    ShiftJis,
}

impl EncodingKind {
    pub fn from_label(value: &str) -> Result<Self, XzipError> {
        let normalized = value.trim().to_ascii_lowercase();
        match normalized.as_str() {
            "utf8" | "utf-8" | "unicode" => Ok(Self::Utf8),
            "gbk" | "cp936" | "936" => Ok(Self::Gbk),
            "shift_jis" | "shift-jis" | "sjis" | "cp932" => Ok(Self::ShiftJis),
            _ => Err(XzipError::UnsupportedEncoding(value.to_string())),
        }
    }

    pub fn label(self) -> &'static str {
        match self {
            Self::Utf8 => "utf-8",
            Self::Gbk => "gbk",
            Self::ShiftJis => "shift_jis",
        }
    }

    fn encoder(self) -> &'static Encoding {
        match self {
            Self::Utf8 => UTF_8,
            Self::Gbk => GBK,
            Self::ShiftJis => SHIFT_JIS,
        }
    }

    pub fn decode(self, input: &[u8]) -> Result<String, XzipError> {
        let (decoded, _, had_errors) = self.encoder().decode(input);
        if had_errors {
            return Err(XzipError::DecodeEntryName {
                encoding: self.label(),
            });
        }
        Ok(decoded.into_owned())
    }

    pub fn encode(self, input: &str) -> Result<Vec<u8>, XzipError> {
        let (encoded, _, had_errors) = self.encoder().encode(input);
        if had_errors {
            return Err(XzipError::EncodePath {
                path: input.to_string(),
                encoding: self.label(),
            });
        }
        Ok(encoded.into_owned())
    }
}