use pagesift_api::types::Document;
use serde::{Deserialize, Serialize};
use crate::codec;
const MAGIC: [u8; 4] = *b"PSDC";
const VERSION: u16 = 1;
const HEADER: usize = MAGIC.len() + std::mem::size_of::<u16>();
#[derive(Serialize)]
struct Out<'a> {
doc: &'a Document,
link_text: &'a Option<Vec<String>>,
alternates: &'a [(String, String)],
}
#[derive(Deserialize)]
struct In {
doc: Document,
link_text: Option<Vec<String>>,
alternates: Vec<(String, String)>,
}
pub fn encode(doc: &Document) -> Result<Vec<u8>, codec::Error> {
let mut out = Vec::with_capacity(HEADER + doc.markdown.as_ref().map_or(0, String::len));
out.extend_from_slice(&MAGIC);
out.extend_from_slice(&VERSION.to_le_bytes());
codec::to_writer(
&mut out,
&Out {
doc,
link_text: &doc.link_text,
alternates: &doc.alternates,
},
)?;
Ok(out)
}
pub fn decode(bytes: &[u8]) -> Option<Document> {
let (head, body) = bytes.split_at_checked(HEADER)?;
if head[..MAGIC.len()] != MAGIC || head[MAGIC.len()..] != VERSION.to_le_bytes() {
return None;
}
let In {
mut doc,
link_text,
alternates,
} = codec::from_slice(body).ok()?;
doc.link_text = link_text;
doc.alternates = alternates;
Some(doc)
}
#[cfg(test)]
mod tests {
use super::*;
use pagesift_api::types::{Metadata, PagesiftMeta};
#[test]
fn round_trips_the_fields_json_drops_and_rejects_other_versions() {
let mut meta = Metadata {
title: Some("T".into()),
status_code: 200,
proxy_used: "basic".into(),
pagesift: Some(PagesiftMeta::default()),
..Default::default()
};
meta.extra.insert("ogImage".into(), "x.png".into());
let doc = Document {
markdown: Some("# T\n\n\"quoted\"\n".into()),
links: Some(vec!["https://a/".into()]),
link_text: Some(vec!["anchor".into()]),
alternates: vec![("en".into(), "https://a/en".into())],
metadata: meta,
..Default::default()
};
let bytes = encode(&doc).unwrap();
assert_eq!(decode(&bytes), Some(doc.clone()));
assert_eq!(decode(&serde_json::to_vec(&doc).unwrap()), None);
let mut other = bytes.clone();
other[MAGIC.len()] ^= 1;
assert_eq!(decode(&other), None);
assert_eq!(decode(&bytes[..bytes.len() - 1]), None);
}
}