#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Data {
inner: DataInner,
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum DataInner {
Binary(Vec<u8>),
Text(String),
}
#[derive(Clone, Debug, PartialEq, Eq, Copy, Hash)]
pub enum DataFormat {
Binary,
Text,
}
impl Data {
pub fn binary(raw: impl Into<Vec<u8>>) -> Self {
Self {
inner: DataInner::Binary(raw.into()),
}
}
pub fn text(raw: impl Into<String>) -> Self {
Self {
inner: DataInner::Text(raw.into()),
}
}
pub fn new() -> Self {
Self::text("")
}
pub fn read_from(
path: &std::path::Path,
data_format: Option<DataFormat>,
) -> Result<Self, crate::Error> {
let data = match data_format {
Some(df) => match df {
DataFormat::Binary => {
let data = std::fs::read(&path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
Self::binary(data)
}
DataFormat::Text => {
let data = std::fs::read_to_string(&path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
Self::text(data)
}
},
None => {
let data = std::fs::read(&path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
Self::binary(data).try_coerce(DataFormat::Text)
}
};
Ok(data)
}
pub fn write_to(&self, path: &std::path::Path) -> Result<(), crate::Error> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
format!("Failed to create parent dir for {}: {}", path.display(), e)
})?;
}
std::fs::write(path, self.to_bytes())
.map_err(|e| format!("Failed to write {}: {}", path.display(), e).into())
}
pub fn map_text(self, op: impl FnOnce(&str) -> String) -> Self {
match self.inner {
DataInner::Binary(data) => Self::binary(data),
DataInner::Text(data) => Self::text(op(&data)),
}
}
pub fn make_text(&mut self) -> Result<(), std::str::Utf8Error> {
*self = Self::text(std::mem::take(self).into_string()?);
Ok(())
}
pub fn into_string(self) -> Result<String, std::str::Utf8Error> {
match self.inner {
DataInner::Binary(data) => {
let data = String::from_utf8(data).map_err(|e| e.utf8_error())?;
Ok(data)
}
DataInner::Text(data) => Ok(data),
}
}
pub fn as_str(&self) -> Option<&str> {
match &self.inner {
DataInner::Binary(_) => None,
DataInner::Text(data) => Some(data.as_str()),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
match &self.inner {
DataInner::Binary(data) => data.clone(),
DataInner::Text(data) => data.clone().into_bytes(),
}
}
pub fn try_coerce(self, format: DataFormat) -> Self {
match format {
DataFormat::Binary => Self::binary(self.to_bytes()),
DataFormat::Text => match self.inner {
DataInner::Binary(data) => {
if is_binary(&data) {
Self::binary(data)
} else {
match String::from_utf8(data) {
Ok(data) => Self::text(data),
Err(err) => {
let data = err.into_bytes();
Self::binary(data)
}
}
}
}
DataInner::Text(data) => Self::text(data),
},
}
}
pub fn format(&self) -> DataFormat {
match &self.inner {
DataInner::Binary(_) => DataFormat::Binary,
DataInner::Text(_) => DataFormat::Text,
}
}
}
impl std::fmt::Display for Data {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.inner {
DataInner::Binary(data) => String::from_utf8_lossy(data).fmt(f),
DataInner::Text(data) => data.fmt(f),
}
}
}
impl Default for Data {
fn default() -> Self {
Self::new()
}
}
impl<'d> From<&'d Data> for Data {
fn from(other: &'d Data) -> Self {
other.clone()
}
}
impl From<Vec<u8>> for Data {
fn from(other: Vec<u8>) -> Self {
Self::binary(other)
}
}
impl<'b> From<&'b [u8]> for Data {
fn from(other: &'b [u8]) -> Self {
other.to_owned().into()
}
}
impl From<String> for Data {
fn from(other: String) -> Self {
Self::text(other)
}
}
impl<'s> From<&'s String> for Data {
fn from(other: &'s String) -> Self {
other.clone().into()
}
}
impl<'s> From<&'s str> for Data {
fn from(other: &'s str) -> Self {
other.to_owned().into()
}
}
#[cfg(feature = "detect-encoding")]
fn is_binary(data: &[u8]) -> bool {
match content_inspector::inspect(data) {
content_inspector::ContentType::BINARY |
content_inspector::ContentType::UTF_16LE |
content_inspector::ContentType::UTF_16BE |
content_inspector::ContentType::UTF_32LE |
content_inspector::ContentType::UTF_32BE => {
true
},
content_inspector::ContentType::UTF_8 |
content_inspector::ContentType::UTF_8_BOM => {
false
},
}
}
#[cfg(not(feature = "detect-encoding"))]
fn is_binary(_data: &[u8]) -> bool {
false
}