use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize};
use crate::canonical;
use crate::engine::EngineFlavor;
use crate::error::WorkbookError;
use crate::limits;
use crate::named_range::NamedRange;
use crate::strict_json;
use crate::validate;
use crate::worksheet::Worksheet;
pub const SCHEMA_VERSION: &str = "1";
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Workbook {
engine: EngineFlavor,
names: Vec<NamedRange>,
sheets: Vec<Worksheet>,
#[serde(deserialize_with = "de_version")]
version: String,
}
impl Workbook {
pub fn new(engine: EngineFlavor) -> Self {
Self {
engine,
names: Vec::new(),
sheets: Vec::new(),
version: SCHEMA_VERSION.to_owned(),
}
}
pub fn engine(&self) -> EngineFlavor {
self.engine
}
pub fn version(&self) -> &str {
&self.version
}
pub fn sheets(&self) -> &[Worksheet] {
&self.sheets
}
pub fn sheets_mut(&mut self) -> &mut Vec<Worksheet> {
&mut self.sheets
}
pub fn names(&self) -> &[NamedRange] {
&self.names
}
pub fn names_mut(&mut self) -> &mut Vec<NamedRange> {
&mut self.names
}
pub fn from_json(bytes: &[u8]) -> Result<Self, WorkbookError> {
if bytes.len() > limits::MAX_SERIALIZED_BYTES {
return Err(WorkbookError::Validation(format!(
"input is {} bytes, exceeding the {}-byte limit (scope ADR Decision 5)",
bytes.len(),
limits::MAX_SERIALIZED_BYTES
)));
}
let tree = strict_json::parse_no_dup_keys(bytes).map_err(WorkbookError::Validation)?;
validate::validate_document(&tree).map_err(WorkbookError::Validation)?;
serde_json::from_value(tree).map_err(|e| WorkbookError::Validation(e.to_string()))
}
pub fn to_json(&self) -> Result<String, WorkbookError> {
let mut tree =
serde_json::to_value(self).map_err(|e| WorkbookError::Validation(e.to_string()))?;
sort_names_by_name(&mut tree);
let canonical = canonical::to_canonical_string(&tree).map_err(WorkbookError::Validation)?;
if canonical.len() > limits::MAX_SERIALIZED_BYTES {
return Err(WorkbookError::Validation(format!(
"canonical workbook is {} bytes, exceeding the {}-byte limit (scope ADR Decision 5)",
canonical.len(),
limits::MAX_SERIALIZED_BYTES
)));
}
Ok(canonical)
}
}
fn sort_names_by_name(tree: &mut serde_json::Value) {
if let Some(names) = tree.get_mut("names").and_then(|v| v.as_array_mut()) {
names.sort_by(|a, b| {
let an = a.get("name").and_then(|v| v.as_str()).unwrap_or("");
let bn = b.get("name").and_then(|v| v.as_str()).unwrap_or("");
an.encode_utf16().cmp(bn.encode_utf16())
});
}
}
fn de_version<'de, D: Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> {
let version = String::deserialize(deserializer)?;
if version != SCHEMA_VERSION {
return Err(D::Error::custom(format!(
"unsupported schema version {version:?}: this version of \
truecalc-workbook reads version \"1\" (schema spec §10); \
upgrade truecalc to load this workbook"
)));
}
Ok(version)
}