use crate::schema::{FieldType, Widget};
use crate::schema_fields::BUILTIN_FIELDS;
use serde::Serialize;
#[derive(Serialize)]
pub struct FrontmatterFieldJson {
pub name: &'static str,
#[serde(rename = "type")]
pub field_type: &'static str,
pub widget: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enum_values: Option<&'static [&'static str]>,
pub description: &'static str,
pub group: &'static str,
pub skip_schema: bool,
}
fn field_type_str(ft: &FieldType) -> &'static str {
match ft {
FieldType::String => "string",
FieldType::Boolean => "boolean",
FieldType::Integer => "integer",
FieldType::Number => "number",
FieldType::Array => "array",
FieldType::Object => "object",
FieldType::OneOf => "one_of",
}
}
fn widget_str(w: &Widget) -> &'static str {
match w {
Widget::TextInput => "text-input",
Widget::TextArea => "text-area",
Widget::DatePicker => "date-picker",
Widget::NumberInput => "number-input",
Widget::Checkbox => "checkbox",
Widget::Select => "select",
Widget::TagInput => "tag-input",
Widget::FilePicker => "file-picker",
Widget::CodeEditor => "code-editor",
Widget::Union => "union",
Widget::WikilinkPicker => "wikilink-picker",
Widget::WikilinkListPicker => "wikilink-list-picker",
}
}
pub fn frontmatter_fields() -> Vec<FrontmatterFieldJson> {
BUILTIN_FIELDS
.iter()
.map(|bf| FrontmatterFieldJson {
name: bf.name,
field_type: field_type_str(&bf.field_type),
widget: widget_str(&bf.widget),
default: bf.default_json,
enum_values: bf.enum_values,
description: bf.description,
group: bf.group,
skip_schema: bf.skip_schema,
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frontmatter_fields_non_empty_and_includes_children_style() {
let fields = frontmatter_fields();
assert!(
fields.len() >= 30,
"expected at least 30 fields, got {}",
fields.len()
);
let cs = fields
.iter()
.find(|f| f.name == "children_style")
.expect("children_style must be present");
assert_eq!(cs.field_type, "string");
assert_eq!(cs.widget, "select");
assert_eq!(
cs.enum_values,
Some(&["list", "summary", "grid"][..])
);
assert!(!cs.skip_schema);
assert_eq!(cs.group, "Child Styles");
}
#[test]
fn frontmatter_fields_includes_skip_schema_entries() {
let fields = frontmatter_fields();
let uid = fields.iter().find(|f| f.name == "uid").expect("uid");
assert!(uid.skip_schema, "uid must be skip_schema");
}
#[test]
fn uid_description_never_claims_the_uid_is_derivable() {
let fields = frontmatter_fields();
let uid = fields.iter().find(|f| f.name == "uid").expect("uid");
let lower = uid.description.to_lowercase();
assert!(
!lower.contains("content-addressab"),
"uid is RANDOM, not content-addressable: {:?}",
uid.description
);
assert!(
lower.contains("random"),
"the uid description must say it is random: {:?}",
uid.description
);
}
#[test]
fn frontmatter_fields_no_duplicate_names() {
let fields = frontmatter_fields();
let mut seen = std::collections::HashSet::new();
for f in &fields {
assert!(
seen.insert(f.name),
"duplicate name '{}' in frontmatter_fields()",
f.name
);
}
}
}