use serde_json::Value as Json;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EditorFieldKind {
Boolean,
String,
Integer,
Enum,
StringMap,
Json,
Section,
}
#[derive(Clone, Copy)]
pub struct EditorFieldSpec {
pub name: &'static str,
pub label: &'static str,
pub kind: EditorFieldKind,
pub enum_values: &'static [&'static str],
pub optional: bool,
pub nested_schema: Option<fn() -> &'static EditorSchema>,
pub nested_default: Option<fn() -> Json>,
}
impl EditorFieldSpec {
pub fn schema(self) -> Option<&'static EditorSchema> {
self.nested_schema.map(|schema| schema())
}
pub fn default_value(self) -> Option<Json> {
self.nested_default.map(|default_value| default_value())
}
}
#[derive(Clone, Copy)]
pub struct EditorSchema {
pub fields: &'static [EditorFieldSpec],
}
impl EditorSchema {
pub fn field(self, name: &str) -> Option<EditorFieldSpec> {
self.fields.iter().copied().find(|field| field.name == name)
}
}
pub trait EditorConfig {
fn editor_schema() -> &'static EditorSchema;
}
#[macro_export]
macro_rules! editor_config {
(
impl $ty:ty {
$(
$field:ident => {
label: $label:literal,
kind: $kind:ident
$(, values: [$($value:literal),* $(,)?])?
$(, optional: $optional:literal)?
$(, nested: $nested:ty)?
$(, default: $default:ty)?
$(,)?
}
),* $(,)?
}
) => {
const _: fn(&$ty) = |value: &$ty| {
$(
let _ = &value.$field;
)*
};
impl $crate::config_editor::EditorConfig for $ty {
fn editor_schema() -> &'static $crate::config_editor::EditorSchema {
static SCHEMA: $crate::config_editor::EditorSchema = $crate::config_editor::EditorSchema {
fields: &[
$(
$crate::config_editor::EditorFieldSpec {
name: stringify!($field),
label: $label,
kind: $crate::editor_config!(@kind $kind),
enum_values: $crate::editor_config!(@values $($($value),*)?),
optional: $crate::editor_config!(@optional $($optional)?),
nested_schema: $crate::editor_config!(@nested $($nested)?),
nested_default: $crate::editor_config!(@default $($default)?),
}
),*
],
};
&SCHEMA
}
}
};
(@kind Boolean) => { $crate::config_editor::EditorFieldKind::Boolean };
(@kind String) => { $crate::config_editor::EditorFieldKind::String };
(@kind Integer) => { $crate::config_editor::EditorFieldKind::Integer };
(@kind Enum) => { $crate::config_editor::EditorFieldKind::Enum };
(@kind StringMap) => { $crate::config_editor::EditorFieldKind::StringMap };
(@kind Json) => { $crate::config_editor::EditorFieldKind::Json };
(@kind Section) => { $crate::config_editor::EditorFieldKind::Section };
(@values) => { &[] };
(@values $($value:literal),*) => { &[$($value),*] };
(@optional) => { false };
(@optional $optional:literal) => { $optional };
(@nested) => { None };
(@nested $nested:ty) => {
Some(<$nested as $crate::config_editor::EditorConfig>::editor_schema)
};
(@default) => { None };
(@default $default:ty) => {
Some(|| {
serde_json::to_value(<$default as Default>::default())
.expect("editor default value should serialize")
})
};
}