use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
pub type DeserializeFunction<T> = fn(EncodedStructData) -> Result<T, Box<dyn std::error::Error>>;
pub trait GpcasFileStruct: 'static + DeserializeOwned + Serialize {
const FILE_IDENTIFIER: &'static str;
const CURRENT_FILE_VERSION: usize;
const COMPATIBLE_VERSIONS: &'static [(usize, DeserializeFunction<Self>)];
}
pub enum FileEncodingType {
Binary,
Json,
}
pub enum EncodedStructData<'a> {
Binary(&'a [u8]),
Json(serde_json::Value),
}
#[derive(Deserialize, Serialize)]
pub struct GpcasFile {
pub identifier: String,
pub version: usize,
pub content: Vec<u8>,
}
#[derive(Debug)]
pub enum DeserializationError {
CorruptFileData(Box<dyn std::error::Error>),
IncompatibleFileVersion(usize),
NoGpcasFile,
WrongFileType(&'static str, String),
}
pub fn deserialize<T: GpcasFileStruct>(
raw: &[u8],
encoding_type: FileEncodingType,
) -> Result<T, DeserializationError> {
match encoding_type {
FileEncodingType::Binary => {
if let Ok(gpcas_file) = bincode::deserialize::<GpcasFile>(raw) {
if gpcas_file.identifier.as_str() != T::FILE_IDENTIFIER {
return Err(DeserializationError::WrongFileType(
T::FILE_IDENTIFIER,
gpcas_file.identifier,
));
}
if gpcas_file.version == T::CURRENT_FILE_VERSION {
bincode::deserialize::<T>(gpcas_file.content.as_slice())
.map_err(|e| DeserializationError::CorruptFileData(Box::new(e)))
} else if let Ok(index) =
T::COMPATIBLE_VERSIONS.binary_search_by_key(&gpcas_file.version, |(i, _)| *i)
{
T::COMPATIBLE_VERSIONS[index].1(EncodedStructData::Binary(
gpcas_file.content.as_slice(),
))
.map_err(|e| DeserializationError::CorruptFileData(e))
} else {
Err(DeserializationError::IncompatibleFileVersion(
gpcas_file.version,
))
}
} else {
Err(DeserializationError::NoGpcasFile)
}
}
FileEncodingType::Json => {
if let Ok(wrapper) = serde_json::from_slice::<serde_json::Value>(raw) {
if wrapper.is_object()
&& wrapper.as_object().unwrap().len() == 3
&& wrapper.as_object().unwrap().contains_key("identifier")
&& wrapper.as_object().unwrap().contains_key("version")
&& wrapper.as_object().unwrap().contains_key("data")
&& wrapper.as_object().unwrap()["identifier"].is_string()
&& wrapper.as_object().unwrap()["version"].is_u64()
&& wrapper.as_object().unwrap()["data"].is_object()
{
let wrapper = wrapper.as_object().unwrap();
if wrapper["identifier"].as_str().unwrap() != T::FILE_IDENTIFIER {
return Err(DeserializationError::WrongFileType(
T::FILE_IDENTIFIER,
wrapper["identifier"].as_str().unwrap().to_string(),
));
}
let version = wrapper["version"].as_u64().unwrap() as usize;
if version == T::CURRENT_FILE_VERSION {
serde_json::from_value(wrapper["data"].clone())
.map_err(|e| DeserializationError::CorruptFileData(Box::new(e)))
} else if let Ok(index) =
T::COMPATIBLE_VERSIONS.binary_search_by_key(&version, |(i, _)| *i)
{
T::COMPATIBLE_VERSIONS[index].1(EncodedStructData::Json(
wrapper["data"].clone(),
))
.map_err(|e| DeserializationError::CorruptFileData(e))
} else {
Err(DeserializationError::IncompatibleFileVersion(
wrapper["version"].as_u64().unwrap() as usize,
))
}
} else {
Err(DeserializationError::NoGpcasFile)
}
} else {
Err(DeserializationError::NoGpcasFile)
}
}
}
}
pub fn deserialize_upgrade_from<Old, Current>(
data: EncodedStructData,
) -> Result<Current, Box<dyn std::error::Error>>
where
Old: DeserializeOwned,
Current: GpcasFileStruct + From<Old>,
{
match data {
EncodedStructData::Binary(bytes) => Ok(bincode::deserialize::<Old>(bytes)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?
.into()),
EncodedStructData::Json(value) => Ok(serde_json::from_value::<Old>(value)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?
.into()),
}
}
pub fn serialise<T: GpcasFileStruct>(from: &T, encoding_type: FileEncodingType) -> Vec<u8> {
match encoding_type {
FileEncodingType::Binary => bincode::serialize(&GpcasFile {
identifier: T::FILE_IDENTIFIER.to_string(),
version: T::CURRENT_FILE_VERSION,
content: bincode::serialize(from).unwrap(),
})
.unwrap(),
FileEncodingType::Json => {
let mut map = serde_json::Map::with_capacity(3);
map.insert(
"identifier".to_string(),
serde_json::Value::String(T::FILE_IDENTIFIER.to_string()),
);
map.insert(
"version".to_string(),
serde_json::Value::Number(serde_json::Number::from(T::CURRENT_FILE_VERSION)),
);
map.insert(
"data".to_string(),
serde_json::value::to_value(from).unwrap(),
);
serde_json::to_vec_pretty(&serde_json::Value::Object(map)).unwrap()
}
}
}
impl TryFrom<&str> for FileEncodingType {
type Error = ();
fn try_from(raw: &str) -> Result<Self, Self::Error> {
match raw.to_ascii_lowercase().as_str() {
"binary" | "bin" => Ok(FileEncodingType::Binary),
"json" => Ok(FileEncodingType::Json),
_ => Err(()),
}
}
}
impl std::fmt::Display for DeserializationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeserializationError::CorruptFileData(e) => {
write!(
f,
"Couldn't read file data as there was an error during deserialization: \"{}\"",
e
)
}
DeserializationError::IncompatibleFileVersion(version) => {
write!(
f,
"Couldn't read file because the file version ({}) is incompatible with this software version.",
version
)
}
DeserializationError::NoGpcasFile => {
write!(f, "Couldn't read file because it is no valid gpcas file.")
}
DeserializationError::WrongFileType(actual, expected) => {
write!(
f,
"Couldn't read file because of a wrong file type: expected {}, found {}",
expected, actual
)
}
}
}
}
impl std::error::Error for DeserializationError {}