Skip to main content

markplus_core/
json.rs

1//    Copyright [2026] [Purnendu Kumar]
2
3//    Licensed under the Apache License, Version 2.0 (the "License");
4//    you may not use this file except in compliance with the License.
5//    You may obtain a copy of the License at
6
7//        http://www.apache.org/licenses/LICENSE-2.0
8
9//    Unless required by applicable law or agreed to in writing, software
10//    distributed under the License is distributed on an "AS IS" BASIS,
11//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//    See the License for the specific language governing permissions and
13//    limitations under the License.
14
15//! Wire format for the compiled site asset (`note_XXX.json`).
16//!
17//! [`SiteAsset`] is the top-level output of [`crate::parse_document`].
18//! It contains:
19//! - `schema` — integer version so renderers can detect breaking changes.
20//! - `meta` — parsed YAML frontmatter as a JSON value tree (native only).
21//! - `ast` — array of MarkPlus block nodes (see [`crate::ast`]).
22
23use serde::{Deserialize, Serialize};
24use serde_json::Value;
25
26use crate::CompileError;
27
28// ---------------------------------------------------------------------------
29// Wire format: note_XXXX.json
30//
31//  { "schema": 1, "meta": {...}, "ast": [...] }
32//
33//  Web client fetches this once per page.
34//  - "meta" is displayed immediately (title, date, tags).
35//  - "ast"  is passed to a renderer (wasm or external) to produce HTML/Typst.
36// ---------------------------------------------------------------------------
37
38/// The JSON asset written to `dist/static_api/note_XXX.json`.
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40pub struct SiteAsset {
41    /// Schema version — allows renderers to detect incompatible AST shapes.
42    pub schema: u32,
43    /// Frontmatter metadata deserialized from YAML into a JSON value tree.
44    /// `null` when the document has no frontmatter block.
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub meta: Option<Value>,
47    /// MarkPlus AST — array of block nodes.
48    pub ast: Vec<Value>,
49}
50
51impl SiteAsset {
52    /// Current wire-format schema version for serialized site assets.
53    pub const SCHEMA_VERSION: u32 = 1;
54
55    /// Build a site asset from optional frontmatter metadata and AST blocks.
56    pub fn new(meta: Option<Value>, ast: Vec<Value>) -> Self {
57        Self {
58            schema: Self::SCHEMA_VERSION,
59            meta,
60            ast,
61        }
62    }
63
64    /// Serialize to compact JSON (wire format).
65    pub fn to_json(&self) -> Result<String, serde_json::Error> {
66        serde_json::to_string(self)
67    }
68
69    /// Serialize to pretty-printed JSON (debug / human-readable).
70    pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
71        serde_json::to_string_pretty(self)
72    }
73}
74
75// ---------------------------------------------------------------------------
76// Frontmatter parsing (native only — serde_yml not compiled into wasm)
77// ---------------------------------------------------------------------------
78
79/// Parse YAML frontmatter text into a JSON value tree (native targets only).
80///
81/// Returns `None` when `raw` is `None` or empty.
82/// Returns `Err(CompileError::InvalidFrontmatter)` on malformed YAML.
83#[cfg(not(target_arch = "wasm32"))]
84pub fn parse_frontmatter(raw: Option<&str>) -> Result<Option<Value>, CompileError> {
85    match raw.map(str::trim).filter(|s| !s.is_empty()) {
86        Some(yaml) => serde_yml::from_str(yaml)
87            .map(Some)
88            .map_err(|e| CompileError::InvalidFrontmatter(e.to_string())),
89        None => Ok(None),
90    }
91}
92
93/// No-op on wasm targets — frontmatter is always `None`.
94///
95/// `serde_yml` is not compiled into the wasm binary. The native deploy pass
96/// is responsible for parsing frontmatter before writing the `note.json` asset.
97#[cfg(target_arch = "wasm32")]
98pub fn parse_frontmatter(_raw: Option<&str>) -> Result<Option<Value>, CompileError> {
99    Ok(None)
100}