use encoding::Encoding;
use java_properties::LineContent::{Comment, KVPair};
use java_properties::PropertiesIter;
use serde::de::{self, IntoDeserializer, MapAccess, Visitor};
use serde::forward_to_deserialize_any;
use std::fmt;
use std::io;
use std::num::{ParseFloatError, ParseIntError};
use std::str::ParseBoolError;
use crate::UTF8_ENCODING;
mod field;
pub struct Deserializer<R: io::Read> {
inner: PropertiesIter<R>,
}
impl<R: io::Read> Deserializer<R> {
pub fn from_reader(reader: R) -> Self {
Self {
inner: PropertiesIter::new(reader),
}
}
pub fn from_reader_with_encoding(reader: R, encoding: &'static dyn Encoding) -> Self {
Self {
inner: PropertiesIter::new_with_encoding(reader, encoding),
}
}
}
impl<'a> Deserializer<io::Cursor<&'a str>> {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &'a str) -> Self {
Self::from_reader_with_encoding(io::Cursor::new(s), UTF8_ENCODING)
}
}
impl<'a> Deserializer<io::Cursor<&'a [u8]>> {
pub fn from_slice(s: &'a [u8]) -> Self {
Self::from_reader(io::Cursor::new(s))
}
pub fn from_slice_with_encoding(s: &'a [u8], encoding: &'static dyn Encoding) -> Self {
Self::from_reader_with_encoding(io::Cursor::new(s), encoding)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Custom {
msg: String,
},
Properties(java_properties::PropertiesError),
ParseIntError(ParseIntError),
ParseFloatError(ParseFloatError),
ParseBoolError(ParseBoolError),
NotSupported,
}
impl From<java_properties::PropertiesError> for Error {
fn from(e: java_properties::PropertiesError) -> Self {
Self::Properties(e)
}
}
impl From<ParseIntError> for Error {
fn from(e: ParseIntError) -> Self {
Self::ParseIntError(e)
}
}
impl From<ParseFloatError> for Error {
fn from(e: ParseFloatError) -> Self {
Self::ParseFloatError(e)
}
}
impl From<ParseBoolError> for Error {
fn from(e: ParseBoolError) -> Self {
Self::ParseBoolError(e)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Custom { msg } => write!(f, "Custom: {:?}", msg),
Self::NotSupported => write!(f, "Not supported"),
Self::Properties(e) => e.fmt(f),
Self::ParseIntError(e) => e.fmt(f),
Self::ParseFloatError(e) => e.fmt(f),
Self::ParseBoolError(e) => e.fmt(f),
}
}
}
impl std::error::Error for Error {}
impl serde::de::Error for Error {
fn custom<T>(msg: T) -> Self
where
T: std::fmt::Display,
{
Self::Custom {
msg: msg.to_string(),
}
}
}
impl<'de, I: io::Read> de::Deserializer<'de> for Deserializer<I> {
type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_map(PropertiesMapAccess {
de: self,
line_value: None,
})
}
forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
bytes byte_buf option unit unit_struct newtype_struct seq tuple
tuple_struct map struct enum identifier ignored_any
}
}
struct PropertiesMapAccess<I: io::Read> {
de: Deserializer<I>,
line_value: Option<String>,
}
impl<'de, I: io::Read> MapAccess<'de> for PropertiesMapAccess<I> {
type Error = Error;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
where
K: serde::de::DeserializeSeed<'de>,
{
while let Some(line) = self.de.inner.next().transpose()? {
match line.consume_content() {
Comment(_) => {} KVPair(key, value) => {
self.line_value = Some(value);
return seed.deserialize(key.into_deserializer()).map(Some);
}
};
}
Ok(None)
}
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
where
V: serde::de::DeserializeSeed<'de>,
{
let value = self.line_value.take().unwrap();
seed.deserialize(field::FieldDeserializer(value))
}
}
#[cfg(test)]
mod tests {
use serde::Deserialize;
use crate::de::Deserializer;
#[derive(Debug, Clone, PartialEq, Deserialize)]
struct Workload {
recordcount: usize,
operationcount: usize,
workload: String,
readallfields: bool,
readproportion: f32,
updateproportion: f32,
scanproportion: f32,
insertproportion: f32,
requestdistribution: String,
}
#[test]
fn test() {
let data = "
recordcount=1000
operationcount=1000
workload=site.ycsb.workloads.CoreWorkload
readallfields=true
readproportion=0.5
updateproportion=0.5
scanproportion=0
insertproportion=0
requestdistribution=zipfian
";
let deserializer = Deserializer::from_str(data);
let workload_a = Workload::deserialize(deserializer).unwrap();
assert_eq!(
workload_a,
Workload {
recordcount: 1000,
operationcount: 1000,
workload: "site.ycsb.workloads.CoreWorkload".to_string(),
readallfields: true,
readproportion: 0.5,
updateproportion: 0.5,
scanproportion: 0.0,
insertproportion: 0.0,
requestdistribution: "zipfian".to_string(),
}
);
}
}