1use std::collections::HashMap;
2
3use rdx_ast::AttributeValue;
4use serde::{Deserialize, Serialize};
5
6mod validate;
7pub use validate::{Diagnostic, Severity, validate};
8
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
27pub struct Schema {
28 pub components: HashMap<String, ComponentSchema>,
30 #[serde(default)]
33 pub strict: bool,
34}
35
36impl Schema {
37 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn component(mut self, name: &str, schema: ComponentSchema) -> Self {
43 self.components.insert(name.to_string(), schema);
44 self
45 }
46
47 pub fn strict(mut self, strict: bool) -> Self {
49 self.strict = strict;
50 self
51 }
52}
53
54#[derive(Debug, Clone, Default, Serialize, Deserialize)]
56pub struct ComponentSchema {
57 pub props: HashMap<String, PropSchema>,
59 #[serde(default)]
61 pub self_closing: bool,
62 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub allowed_children: Option<Vec<String>>,
67 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub description: Option<String>,
70}
71
72impl ComponentSchema {
73 pub fn new() -> Self {
74 Self::default()
75 }
76
77 pub fn prop(mut self, name: &str, schema: PropSchema) -> Self {
79 self.props.insert(name.to_string(), schema);
80 self
81 }
82
83 pub fn self_closing(mut self, val: bool) -> Self {
85 self.self_closing = val;
86 self
87 }
88
89 pub fn allowed_children(mut self, names: Vec<&str>) -> Self {
91 self.allowed_children = Some(names.into_iter().map(|s| s.to_string()).collect());
92 self
93 }
94
95 pub fn description(mut self, desc: &str) -> Self {
97 self.description = Some(desc.to_string());
98 self
99 }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct PropSchema {
105 #[serde(rename = "type")]
107 pub prop_type: PropType,
108 #[serde(default)]
110 pub required: bool,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
113 pub default: Option<serde_json::Value>,
114 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub values: Option<Vec<String>>,
117 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub description: Option<String>,
120}
121
122impl PropSchema {
123 pub fn required(prop_type: PropType) -> Self {
125 PropSchema {
126 prop_type,
127 required: true,
128 default: None,
129 values: None,
130 description: None,
131 }
132 }
133
134 pub fn optional(prop_type: PropType) -> Self {
136 PropSchema {
137 prop_type,
138 required: false,
139 default: None,
140 values: None,
141 description: None,
142 }
143 }
144
145 pub fn enum_required(values: Vec<&str>) -> Self {
147 PropSchema {
148 prop_type: PropType::Enum,
149 required: true,
150 default: None,
151 values: Some(values.into_iter().map(|s| s.to_string()).collect()),
152 description: None,
153 }
154 }
155
156 pub fn enum_optional(values: Vec<&str>) -> Self {
158 PropSchema {
159 prop_type: PropType::Enum,
160 required: false,
161 default: None,
162 values: Some(values.into_iter().map(|s| s.to_string()).collect()),
163 description: None,
164 }
165 }
166
167 pub fn with_default(mut self, val: serde_json::Value) -> Self {
169 self.default = Some(val);
170 self
171 }
172
173 pub fn with_description(mut self, desc: &str) -> Self {
175 self.description = Some(desc.to_string());
176 self
177 }
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
182#[serde(rename_all = "lowercase")]
183pub enum PropType {
184 String,
186 Number,
188 Boolean,
190 Enum,
192 Object,
194 Array,
196 Variable,
198 Any,
200}
201
202pub(crate) fn type_matches(value: &AttributeValue, expected: &PropType) -> bool {
204 match expected {
205 PropType::Any => true,
206 PropType::String => matches!(value, AttributeValue::String(_)),
207 PropType::Number => matches!(value, AttributeValue::Number(_)),
208 PropType::Boolean => matches!(value, AttributeValue::Bool(_)),
209 PropType::Object => matches!(value, AttributeValue::Object(_)),
210 PropType::Array => matches!(value, AttributeValue::Array(_)),
211 PropType::Variable => matches!(value, AttributeValue::Variable(_)),
212 PropType::Enum => matches!(value, AttributeValue::String(_)),
213 }
214}
215
216pub(crate) fn value_type_name(value: &AttributeValue) -> &'static str {
218 match value {
219 AttributeValue::Null => "null",
220 AttributeValue::Bool(_) => "boolean",
221 AttributeValue::Number(_) => "number",
222 AttributeValue::String(_) => "string",
223 AttributeValue::Array(_) => "array",
224 AttributeValue::Object(_) => "object",
225 AttributeValue::Variable(_) => "variable",
226 }
227}