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}