use std::marker::PhantomData;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::error::CoreResult;
use crate::metadata::Metadata;
use crate::section::Section;
use crate::validate::validate_sections;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Draft;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Validated;
pub struct Document<S = Draft> {
sections: Vec<Section>,
metadata: Metadata,
_state: PhantomData<S>,
}
impl<S> Document<S> {
pub fn sections(&self) -> &[Section] {
&self.sections
}
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
pub fn section_count(&self) -> usize {
self.sections.len()
}
pub fn is_empty(&self) -> bool {
self.sections.is_empty()
}
}
impl Document<Draft> {
pub fn new() -> Self {
Self { sections: Vec::new(), metadata: Metadata::default(), _state: PhantomData }
}
pub fn with_metadata(metadata: Metadata) -> Self {
Self { sections: Vec::new(), metadata, _state: PhantomData }
}
pub fn add_section(&mut self, section: Section) {
self.sections.push(section);
}
pub fn set_metadata(&mut self, metadata: Metadata) {
self.metadata = metadata;
}
pub fn metadata_mut(&mut self) -> &mut Metadata {
&mut self.metadata
}
pub fn sections_mut(&mut self) -> &mut [Section] {
&mut self.sections
}
pub fn validate(self) -> CoreResult<Document<Validated>> {
validate_sections(&self.sections)?;
Ok(Document { sections: self.sections, metadata: self.metadata, _state: PhantomData })
}
}
impl Default for Document<Draft> {
fn default() -> Self {
Self::new()
}
}
impl<S> std::fmt::Debug for Document<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Document")
.field("sections", &self.sections)
.field("metadata", &self.metadata)
.finish()
}
}
impl<S> Clone for Document<S> {
fn clone(&self) -> Self {
Self {
sections: self.sections.clone(),
metadata: self.metadata.clone(),
_state: PhantomData,
}
}
}
impl<S> PartialEq for Document<S> {
fn eq(&self, other: &Self) -> bool {
self.sections == other.sections && self.metadata == other.metadata
}
}
impl<S> Eq for Document<S> {}
impl<S> std::fmt::Display for Document<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Document({} sections)", self.sections.len())
}
}
impl<S> Serialize for Document<S> {
fn serialize<Ser: serde::Serializer>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> {
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("Document", 2)?;
state.serialize_field("sections", &self.sections)?;
state.serialize_field("metadata", &self.metadata)?;
state.end()
}
}
impl<'de> Deserialize<'de> for Document<Draft> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
struct DocumentData {
sections: Vec<Section>,
metadata: Metadata,
}
let data = DocumentData::deserialize(deserializer)?;
Ok(Document { sections: data.sections, metadata: data.metadata, _state: PhantomData })
}
}
impl<S> JsonSchema for Document<S> {
fn schema_name() -> std::borrow::Cow<'static, str> {
"Document".into()
}
fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "object",
"properties": {
"sections": gen.subschema_for::<Vec<Section>>(),
"metadata": gen.subschema_for::<crate::metadata::Metadata>(),
},
"required": ["sections", "metadata"]
})
}
}
const _: () = {
#[allow(dead_code)]
fn assert_send<T: Send>() {}
#[allow(dead_code)]
fn assert_sync<T: Sync>() {}
#[allow(dead_code)]
fn verify() {
assert_send::<Document<Draft>>();
assert_sync::<Document<Draft>>();
assert_send::<Document<Validated>>();
assert_sync::<Document<Validated>>();
}
};
#[cfg(test)]
mod tests {
use super::*;
use crate::error::CoreError;
use crate::page::PageSettings;
use crate::paragraph::Paragraph;
use crate::run::Run;
use hwpforge_foundation::{CharShapeIndex, ParaShapeIndex};
fn valid_section() -> Section {
Section::with_paragraphs(
vec![Paragraph::with_runs(
vec![Run::text("Hello", CharShapeIndex::new(0))],
ParaShapeIndex::new(0),
)],
PageSettings::a4(),
)
}
#[test]
fn new_creates_empty_draft() {
let doc = Document::new();
assert!(doc.is_empty());
assert_eq!(doc.section_count(), 0);
assert!(doc.metadata().title.is_none());
}
#[test]
fn with_metadata() {
let meta = Metadata { title: Some("Test".to_string()), ..Metadata::default() };
let doc = Document::with_metadata(meta);
assert_eq!(doc.metadata().title.as_deref(), Some("Test"));
}
#[test]
fn default_is_new() {
let a = Document::new();
let b = Document::default();
assert_eq!(a, b);
}
#[test]
fn add_section() {
let mut doc = Document::new();
doc.add_section(valid_section());
assert_eq!(doc.section_count(), 1);
assert!(!doc.is_empty());
}
#[test]
fn add_multiple_sections() {
let mut doc = Document::new();
doc.add_section(valid_section());
doc.add_section(valid_section());
doc.add_section(valid_section());
assert_eq!(doc.section_count(), 3);
}
#[test]
fn set_metadata() {
let mut doc = Document::new();
doc.set_metadata(Metadata { title: Some("New".to_string()), ..Metadata::default() });
assert_eq!(doc.metadata().title.as_deref(), Some("New"));
}
#[test]
fn metadata_mut() {
let mut doc = Document::new();
doc.metadata_mut().title = Some("Mutated".to_string());
assert_eq!(doc.metadata().title.as_deref(), Some("Mutated"));
}
#[test]
fn sections_mut() {
let mut doc = Document::new();
doc.add_section(valid_section());
doc.add_section(valid_section());
assert_eq!(doc.sections_mut().len(), 2);
}
#[test]
fn validate_success() {
let mut doc = Document::new();
doc.add_section(valid_section());
let validated = doc.validate().unwrap();
assert_eq!(validated.section_count(), 1);
}
#[test]
fn validate_empty_document_fails() {
let doc = Document::new();
let err = doc.validate().unwrap_err();
assert!(matches!(err, CoreError::Validation(_)));
}
#[test]
fn validate_empty_section_fails() {
let mut doc = Document::new();
doc.add_section(Section::new(PageSettings::a4()));
assert!(doc.validate().is_err());
}
#[test]
fn validate_consumes_draft() {
let mut doc = Document::new();
doc.add_section(valid_section());
let _validated = doc.validate().unwrap();
}
#[test]
fn validated_has_read_methods() {
let mut doc = Document::new();
doc.add_section(valid_section());
let validated = doc.validate().unwrap();
assert_eq!(validated.section_count(), 1);
assert!(!validated.is_empty());
assert_eq!(validated.sections().len(), 1);
assert!(validated.metadata().title.is_none());
}
#[test]
fn display_draft() {
let doc = Document::new();
assert_eq!(doc.to_string(), "Document(0 sections)");
}
#[test]
fn display_validated() {
let mut doc = Document::new();
doc.add_section(valid_section());
let validated = doc.validate().unwrap();
assert_eq!(validated.to_string(), "Document(1 sections)");
}
#[test]
fn equality_draft() {
let mut a = Document::new();
a.add_section(valid_section());
let mut b = Document::new();
b.add_section(valid_section());
assert_eq!(a, b);
}
#[test]
fn equality_validated() {
let mut a = Document::new();
a.add_section(valid_section());
let mut b = Document::new();
b.add_section(valid_section());
let va = a.validate().unwrap();
let vb = b.validate().unwrap();
assert_eq!(va, vb);
}
#[test]
fn clone_draft() {
let mut doc = Document::new();
doc.add_section(valid_section());
let cloned = doc.clone();
assert_eq!(doc, cloned);
}
#[test]
fn clone_validated() {
let mut doc = Document::new();
doc.add_section(valid_section());
let validated = doc.validate().unwrap();
let cloned = validated.clone();
assert_eq!(validated, cloned);
}
#[test]
fn serde_roundtrip_draft() {
let mut doc = Document::new();
doc.add_section(valid_section());
doc.set_metadata(Metadata { title: Some("Test".to_string()), ..Metadata::default() });
let json = serde_json::to_string(&doc).unwrap();
let back: Document<Draft> = serde_json::from_str(&json).unwrap();
assert_eq!(doc, back);
}
#[test]
fn serde_roundtrip_validated_deserializes_to_draft() {
let mut doc = Document::new();
doc.add_section(valid_section());
let validated = doc.validate().unwrap();
let json = serde_json::to_string(&validated).unwrap();
let back: Document<Draft> = serde_json::from_str(&json).unwrap();
let re_validated = back.validate().unwrap();
assert_eq!(validated, re_validated);
}
#[test]
fn serde_empty_document() {
let doc = Document::new();
let json = serde_json::to_string(&doc).unwrap();
let back: Document<Draft> = serde_json::from_str(&json).unwrap();
assert_eq!(doc, back);
}
#[test]
fn complex_document_roundtrip() {
use crate::control::Control;
use crate::image::{Image, ImageFormat};
use crate::table::{Table, TableCell, TableRow};
use hwpforge_foundation::HwpUnit;
let cell = TableCell::new(
vec![Paragraph::with_runs(
vec![Run::text("cell", CharShapeIndex::new(0))],
ParaShapeIndex::new(0),
)],
HwpUnit::from_mm(50.0).unwrap(),
);
let table = Table::new(vec![TableRow::new(vec![cell])]);
let link = Control::Hyperlink {
text: "click".to_string(),
url: "https://example.com".to_string(),
};
let img = Image::new(
"test.png",
HwpUnit::from_mm(10.0).unwrap(),
HwpUnit::from_mm(10.0).unwrap(),
ImageFormat::Png,
);
let section = Section::with_paragraphs(
vec![
Paragraph::with_runs(
vec![
Run::text("Hello ", CharShapeIndex::new(0)),
Run::text("world", CharShapeIndex::new(1)),
],
ParaShapeIndex::new(0),
),
Paragraph::with_runs(
vec![Run::table(table, CharShapeIndex::new(0))],
ParaShapeIndex::new(1),
),
Paragraph::with_runs(
vec![Run::control(link, CharShapeIndex::new(0))],
ParaShapeIndex::new(0),
),
Paragraph::with_runs(
vec![Run::image(img, CharShapeIndex::new(0))],
ParaShapeIndex::new(0),
),
],
PageSettings::a4(),
);
let mut doc = Document::with_metadata(Metadata {
title: Some("Complex Doc".to_string()),
author: Some("Author".to_string()),
keywords: vec!["test".to_string()],
..Metadata::default()
});
doc.add_section(section);
let validated = doc.validate().unwrap();
let json = serde_json::to_string_pretty(&validated).unwrap();
let back: Document<Draft> = serde_json::from_str(&json).unwrap();
let re_validated = back.validate().unwrap();
assert_eq!(validated, re_validated);
}
#[test]
fn debug_output() {
let doc = Document::new();
let s = format!("{doc:?}");
assert!(s.contains("Document"), "debug: {s}");
assert!(s.contains("sections"), "debug: {s}");
}
}