Skip to main content

moss_core/contract/
describe.rs

1//! Serializable payload for `moss describe --json`.
2//!
3//! The JSON shape is itself a contract — version it independently of
4//! `moss_html_version`. Bumping `describe_schema_version` is required for
5//! any breaking change to the JSON envelope.
6
7use serde::Serialize;
8use std::collections::BTreeMap;
9
10use crate::ast::shortcode::ShortcodeKind;
11use crate::contract::components::{COMPONENTS, Status};
12use crate::contract::frontmatter::{FrontmatterFieldJson, frontmatter_fields};
13use crate::contract::tokens::Tokens;
14
15pub const DESCRIBE_SCHEMA_VERSION: u32 = 4;
16pub const MOSS_HTML_VERSION: u32 = 1;
17
18#[derive(Serialize)]
19pub struct DescribePayload<'a> {
20    pub describe_schema_version: u32,
21    pub moss_html_version: u32,
22    pub moss_binary_version: &'static str,
23    pub tokens: BTreeMap<&'a str, Vec<TokenJson<'a>>>,
24    pub components: Vec<ComponentJson>,
25    pub frontmatter: Vec<FrontmatterFieldJson>,
26}
27
28#[derive(Serialize)]
29pub struct TokenJson<'a> {
30    pub name: &'a str,
31    pub value: &'a str,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub dark_value: Option<&'a str>,
34    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
35    pub type_hint: Option<&'a str>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub description: Option<&'a str>,
38}
39
40#[derive(Serialize)]
41pub struct ComponentJson {
42    pub class: &'static str,
43    pub kind: &'static str,
44    pub parent: &'static str,
45    pub data_attrs: Vec<DataAttrJson>,
46    pub example_html: &'static str,
47    pub example_markdown: &'static str,
48    pub status: &'static str,
49    pub since: &'static str,
50    pub description: &'static str,
51    /// True iff this class is the root class of an authorable shortcode
52    /// (i.e. it appears in `ShortcodeKind::all().map(|k| k.root_class())`).
53    /// Agents can use this flag to distinguish the 6 author-facing shortcodes
54    /// from the broader theme vocabulary.
55    pub authorable: bool,
56}
57
58#[derive(Serialize)]
59pub struct DataAttrJson {
60    pub name: &'static str,
61    pub values: &'static [&'static str],
62    pub default: &'static str,
63    pub description: &'static str,
64}
65
66impl<'a> DescribePayload<'a> {
67    pub fn new(tokens: &'a Tokens) -> Self {
68        let mut tokens_map: BTreeMap<&str, Vec<TokenJson>> = BTreeMap::new();
69        for group in &tokens.groups {
70            let entries: Vec<TokenJson> = group
71                .entries
72                .iter()
73                .map(|t| TokenJson {
74                    name: &t.name,
75                    value: &t.value,
76                    dark_value: t.dark_value.as_deref(),
77                    type_hint: t.type_hint.as_deref(),
78                    description: t.description.as_deref(),
79                })
80                .collect();
81            tokens_map.insert(&group.name, entries);
82        }
83
84        let authorable: std::collections::HashSet<&'static str> =
85            ShortcodeKind::all().map(|k| k.root_class()).collect();
86
87        let components: Vec<ComponentJson> = COMPONENTS
88            .iter()
89            .map(|c| ComponentJson {
90                class: c.class,
91                kind: c.kind,
92                parent: c.parent,
93                data_attrs: c
94                    .data_attrs
95                    .iter()
96                    .map(|a| DataAttrJson {
97                        name: a.name,
98                        values: a.values,
99                        default: a.default,
100                        description: a.description,
101                    })
102                    .collect(),
103                example_html: c.example_html,
104                example_markdown: c.example_markdown,
105                status: match c.status {
106                    Status::Confirmed => "confirmed",
107                    Status::Emerging => "emerging",
108                    Status::Retired => "retired",
109                },
110                since: c.since,
111                description: c.description,
112                authorable: authorable.contains(c.class),
113            })
114            .collect();
115
116        DescribePayload {
117            describe_schema_version: DESCRIBE_SCHEMA_VERSION,
118            moss_html_version: MOSS_HTML_VERSION,
119            moss_binary_version: env!("CARGO_PKG_VERSION"),
120            tokens: tokens_map,
121            components,
122            frontmatter: frontmatter_fields(),
123        }
124    }
125}