moss_core/contract/
frontmatter.rs1use crate::schema::{FieldType, Widget};
9use crate::schema_fields::BUILTIN_FIELDS;
10use serde::Serialize;
11
12#[derive(Serialize)]
15pub struct FrontmatterFieldJson {
16 pub name: &'static str,
18 #[serde(rename = "type")]
21 pub field_type: &'static str,
22 pub widget: &'static str,
24 #[serde(skip_serializing_if = "Option::is_none")]
27 pub default: Option<&'static str>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub enum_values: Option<&'static [&'static str]>,
31 pub description: &'static str,
33 pub group: &'static str,
36 pub skip_schema: bool,
41}
42
43fn field_type_str(ft: &FieldType) -> &'static str {
45 match ft {
46 FieldType::String => "string",
47 FieldType::Boolean => "boolean",
48 FieldType::Integer => "integer",
49 FieldType::Number => "number",
50 FieldType::Array => "array",
51 FieldType::Object => "object",
52 FieldType::OneOf => "one_of",
53 }
54}
55
56fn widget_str(w: &Widget) -> &'static str {
58 match w {
59 Widget::TextInput => "text-input",
60 Widget::TextArea => "text-area",
61 Widget::DatePicker => "date-picker",
62 Widget::NumberInput => "number-input",
63 Widget::Checkbox => "checkbox",
64 Widget::Select => "select",
65 Widget::TagInput => "tag-input",
66 Widget::FilePicker => "file-picker",
67 Widget::CodeEditor => "code-editor",
68 Widget::Union => "union",
69 Widget::WikilinkPicker => "wikilink-picker",
70 Widget::WikilinkListPicker => "wikilink-list-picker",
71 }
72}
73
74pub fn frontmatter_fields() -> Vec<FrontmatterFieldJson> {
80 BUILTIN_FIELDS
81 .iter()
82 .map(|bf| FrontmatterFieldJson {
83 name: bf.name,
84 field_type: field_type_str(&bf.field_type),
85 widget: widget_str(&bf.widget),
86 default: bf.default_json,
87 enum_values: bf.enum_values,
88 description: bf.description,
89 group: bf.group,
90 skip_schema: bf.skip_schema,
91 })
92 .collect()
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn frontmatter_fields_non_empty_and_includes_children_style() {
101 let fields = frontmatter_fields();
102 assert!(
103 fields.len() >= 30,
104 "expected at least 30 fields, got {}",
105 fields.len()
106 );
107
108 let cs = fields
109 .iter()
110 .find(|f| f.name == "children_style")
111 .expect("children_style must be present");
112
113 assert_eq!(cs.field_type, "string");
114 assert_eq!(cs.widget, "select");
115 assert_eq!(
116 cs.enum_values,
117 Some(&["list", "summary", "grid"][..])
118 );
119 assert!(!cs.skip_schema);
120 assert_eq!(cs.group, "Child Styles");
121 }
122
123 #[test]
124 fn frontmatter_fields_includes_skip_schema_entries() {
125 let fields = frontmatter_fields();
126 let uid = fields.iter().find(|f| f.name == "uid").expect("uid");
127 assert!(uid.skip_schema, "uid must be skip_schema");
128 }
129
130 #[test]
139 fn uid_description_never_claims_the_uid_is_derivable() {
140 let fields = frontmatter_fields();
141 let uid = fields.iter().find(|f| f.name == "uid").expect("uid");
142 let lower = uid.description.to_lowercase();
143 assert!(
144 !lower.contains("content-addressab"),
145 "uid is RANDOM, not content-addressable: {:?}",
146 uid.description
147 );
148 assert!(
149 lower.contains("random"),
150 "the uid description must say it is random: {:?}",
151 uid.description
152 );
153 }
154
155 #[test]
156 fn frontmatter_fields_no_duplicate_names() {
157 let fields = frontmatter_fields();
158 let mut seen = std::collections::HashSet::new();
159 for f in &fields {
160 assert!(
161 seen.insert(f.name),
162 "duplicate name '{}' in frontmatter_fields()",
163 f.name
164 );
165 }
166 }
167
168}