Skip to content

Instantly share code, notes, and snippets.

@nanpuyue
Created April 24, 2022 14:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nanpuyue/472ca47e13d02fbfa2dbeea4c70d51bb to your computer and use it in GitHub Desktop.
Save nanpuyue/472ca47e13d02fbfa2dbeea4c70d51bb to your computer and use it in GitHub Desktop.
// date: 2022-04-24
// license: GPLv3 https://www.gnu.org/licenses/gpl-3.0.txt
// author: nanpuyue <nanpuyue@gmail.com> https://blog.nanpuyue.com
use std::fmt::{self, Debug, Display, Formatter};
use std::iter::Peekable;
use std::str::Lines;
use std::{error, result};
use serde::de::{self, Visitor};
use serde::forward_to_deserialize_any;
use serde::Deserialize;
const DELIM: char = ':';
type Result<T> = result::Result<T, Error>;
pub fn from_str<'a, T: Deserialize<'a>>(s: &'a str) -> Result<T> {
T::deserialize(&mut Deserializer::from_str(s))
}
pub struct Deserializer<'de> {
lines: Peekable<Lines<'de>>,
}
#[derive(Debug)]
pub enum Error {
Message(String),
KeyNotFound,
ValueNotFound,
InvalidValue,
Unimplemented,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(&self, f)
}
}
impl error::Error for Error {}
impl de::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::Message(msg.to_string())
}
}
struct MapAccess<'a, 'de>(&'a mut Deserializer<'de>);
impl<'a, 'de> de::MapAccess<'de> for MapAccess<'a, 'de> {
type Error = Error;
fn next_key_seed<K: de::DeserializeSeed<'de>>(&mut self, seed: K) -> Result<Option<K::Value>> {
Ok(seed.deserialize(&mut *self.0).ok())
}
fn next_value_seed<V: de::DeserializeSeed<'de>>(&mut self, seed: V) -> Result<V::Value> {
seed.deserialize(&mut *self.0)
}
}
impl<'de> Deserializer<'de> {
fn from_str(s: &'de str) -> Self {
Self {
lines: s.lines().peekable(),
}
}
fn next_key(&mut self) -> Option<&'de str> {
self.lines.peek().and_then(|&x| x.split(DELIM).next())
}
fn next_value(&mut self) -> Option<&'de str> {
self.lines
.next()
.and_then(|x| x.split(DELIM).nth(1).map(str::trim))
}
}
impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
type Error = Error;
fn deserialize_any<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value> {
Err(Error::Unimplemented)
}
fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
self.deserialize_str(visitor)
}
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_some(self)
}
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
let value = self.next_value().ok_or(Error::ValueNotFound)?;
visitor.visit_borrowed_str(value)
}
fn deserialize_f64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
let value = self
.next_value()
.ok_or(Error::ValueNotFound)
.and_then(|x| x.parse().map_err(|_| Error::InvalidValue))?;
visitor.visit_f64(value)
}
fn deserialize_identifier<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
let ident = self.next_key().ok_or(Error::KeyNotFound)?;
visitor.visit_borrowed_str(ident)
}
fn deserialize_map<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_map(MapAccess(self))
}
fn deserialize_struct<V: Visitor<'de>>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value> {
self.deserialize_map(visitor)
}
forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 char
bytes byte_buf unit unit_struct newtype_struct seq tuple
tuple_struct enum ignored_any
}
}
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SomeStruct<'a> {
pub field_a: &'a str,
pub field_b: Option<f64>,
pub field_c: Option<f64>,
}
fn main() {
let input = "FieldA:helloworld\nFieldB:1.234567\nFieldC:7.654321\n";
let data = from_str::<SomeStruct>(input).unwrap();
assert_eq!(
dbg!(data),
SomeStruct {
field_a: "helloworld",
field_b: Some(1.234567),
field_c: Some(7.654321)
}
);
let input = "FieldA:helloworld\nFieldC:7.654321\n";
let data = from_str::<SomeStruct>(input).unwrap();
assert_eq!(
dbg!(data),
SomeStruct {
field_a: "helloworld",
field_b: None,
field_c: Some(7.654321)
}
);
let input = "FieldB:1.234567\nFieldC:7.654321\n";
from_str::<SomeStruct>(input).expect_err("should be error");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment