Skip to content

Commit

Permalink
serde: initial header deserialization
Browse files Browse the repository at this point in the history
Feature request: #63
  • Loading branch information
jmcnamara committed Dec 22, 2023
1 parent 344d651 commit e761a61
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 3 deletions.
66 changes: 65 additions & 1 deletion src/serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,10 @@
use std::collections::HashMap;

use crate::{ColNum, Format, RowNum, Worksheet, XlsxError};
use serde::{ser, Serialize};
use serde::{
de::{self, Visitor},
ser, Deserialize, Deserializer, Serialize,
};

// -----------------------------------------------------------------------
// SerializerState, a struct to maintain row/column state and other metadata
Expand Down Expand Up @@ -2400,3 +2403,64 @@ impl<'a> ser::SerializeStructVariant for &'a mut SerializerHeader {
Ok(())
}
}

// -----------------------------------------------------------------------
// Header DeSerializer. This is the a simplified implementation of the
// Deserializer trait to capture the headers/field names only.
// -----------------------------------------------------------------------
pub(crate) struct DeSerializerHeader<'a> {
pub(crate) struct_name: &'a mut &'static str,
pub(crate) field_names: &'a mut &'static [&'static str],
}

impl<'de, 'a> Deserializer<'de> for DeSerializerHeader<'a> {
type Error = serde::de::value::Error;

fn deserialize_struct<V>(
self,
name: &'static str,
fields: &'static [&'static str],
_visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
*self.struct_name = name;
*self.field_names = fields;
Err(de::Error::custom("Deserialization error"))
}

fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
Err(de::Error::custom("Deserialization error"))
}

serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
byte_buf option unit unit_struct newtype_struct seq tuple
tuple_struct map enum identifier ignored_any
}
}

pub(crate) fn deserialize_headers<'de, T>() -> SerializerHeader
where
T: Deserialize<'de>,
{
let mut struct_name = "";
let mut field_names: &[&str] = &[""];

let _ = T::deserialize(DeSerializerHeader {
struct_name: &mut struct_name,
field_names: &mut field_names,
});

let struct_name = struct_name.to_string();
let field_names = field_names.iter().map(|&s| s.to_string()).collect();

SerializerHeader {
struct_name,
field_names,
}
}
51 changes: 51 additions & 0 deletions src/worksheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ use crate::SerializerHeader;
#[cfg(feature = "serde")]
use crate::CustomSerializeHeader;

#[cfg(feature = "serde")]
use serde::Deserialize;

#[cfg(feature = "serde")]
use crate::deserialize_headers;

use crate::drawing::{Drawing, DrawingCoordinates, DrawingInfo, DrawingObject};
use crate::error::XlsxError;
use crate::format::Format;
Expand Down Expand Up @@ -6537,6 +6543,51 @@ impl Worksheet {
Ok(self)
}

/// TODO
///
/// # Errors
///
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn serialize_headers_from_type<'de, T>(
&mut self,
row: RowNum,
col: ColNum,
) -> Result<&mut Worksheet, XlsxError>
where
T: Deserialize<'de>,
{
self.serialize_headers_with_format_from_type::<T>(row, col, &Format::default())
}

/// TODO
///
/// # Errors
///
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn serialize_headers_with_format_from_type<'de, T>(
&mut self,
row: RowNum,
col: ColNum,
format: &Format,
) -> Result<&mut Worksheet, XlsxError>
where
T: Deserialize<'de>,
{
// Deserialize the struct to determine the type name and the fields.
let headers = deserialize_headers::<T>();

// Convert the field names to custom header structs.
let custom_headers: Vec<CustomSerializeHeader> = headers
.field_names
.iter()
.map(|name| CustomSerializeHeader::new_with_format(name, format))
.collect();

self.serialize_headers_with_options(row, col, headers.struct_name, &custom_headers)
}

// Serialize the parent data structure to the worksheet.
#[cfg(feature = "serde")]
fn serialize_data_structure<T>(&mut self, data_structure: &T) -> Result<(), XlsxError>
Expand Down
36 changes: 35 additions & 1 deletion tests/integration/serde01.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

use crate::common;
use rust_xlsxwriter::{Workbook, XlsxError};
use serde::Serialize;
use serde::{Deserialize, Serialize};

// Test case for Serde serialization. First test isn't serialized.
fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> {
Expand Down Expand Up @@ -163,6 +163,28 @@ fn create_new_xlsx_file_7(filename: &str) -> Result<(), XlsxError> {
Ok(())
}

// Test case for Serde serialization. Header deserialization.
fn create_new_xlsx_file_8(filename: &str) -> Result<(), XlsxError> {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();

// Create a serializable test struct.
#[derive(Deserialize, Serialize)]
struct MyStruct {
col1: u8,
col2: i8,
}

let data = MyStruct { col1: 1, col2: -1 };

worksheet.serialize_headers_from_type::<MyStruct>(0, 0)?;
worksheet.serialize(&data)?;

workbook.save(filename)?;

Ok(())
}

#[test]
fn test_serde01_1() {
let test_runner = common::TestRunner::new()
Expand Down Expand Up @@ -246,3 +268,15 @@ fn test_serde01_7() {
test_runner.assert_eq();
test_runner.cleanup();
}

#[test]
fn test_serde01_8() {
let test_runner = common::TestRunner::new()
.set_name("serde01")
.set_function(create_new_xlsx_file_8)
.unique("8")
.initialize();

test_runner.assert_eq();
test_runner.cleanup();
}
42 changes: 41 additions & 1 deletion tests/integration/serde06.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

use crate::common;
use rust_xlsxwriter::{CustomSerializeHeader, Format, Workbook, XlsxError};
use serde::Serialize;
use serde::{Deserialize, Serialize};

// Test case for Serde serialization. First test isn't serialized.
fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> {
Expand Down Expand Up @@ -93,6 +93,34 @@ fn create_new_xlsx_file_3(filename: &str) -> Result<(), XlsxError> {
Ok(())
}

// Test case for Serde serialization. Header deserialization.
fn create_new_xlsx_file_4(filename: &str) -> Result<(), XlsxError> {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();
let bold = Format::new().set_bold();

worksheet.set_paper_size(9);

// Create a serializable test struct.
#[derive(Deserialize, Serialize)]
struct MyStruct {
col1: Vec<u16>,
col2: Vec<bool>,
}

let data = MyStruct {
col1: vec![123, 456, 789],
col2: vec![true, false, true],
};

worksheet.serialize_headers_with_format_from_type::<MyStruct>(0, 0, &bold)?;
worksheet.serialize(&data)?;

workbook.save(filename)?;

Ok(())
}

#[test]
fn test_serde06_1() {
let test_runner = common::TestRunner::new()
Expand Down Expand Up @@ -128,3 +156,15 @@ fn test_serde06_3() {
test_runner.assert_eq();
test_runner.cleanup();
}

#[test]
fn test_serde06_4() {
let test_runner = common::TestRunner::new()
.set_name("serde06")
.set_function(create_new_xlsx_file_4)
.unique("4")
.initialize();

test_runner.assert_eq();
test_runner.cleanup();
}

0 comments on commit e761a61

Please sign in to comment.