1use crate::shapes::OneOrMany;
2use crate::{config_enum, config_struct, is_false};
3use moon_common::Id;
4use rustc_hash::FxHashMap;
5use schematic::{Config, ValidateError, validate};
6use serde_json::Value;
7
8macro_rules! var_setting {
9 ($name:ident, $ty:ty) => {
10 config_struct!(
11 #[derive(Config)]
13 pub struct $name {
14 #[setting(alias = "defaultValue")]
16 pub default: $ty,
17
18 #[serde(skip_serializing_if = "is_false")]
20 pub internal: bool,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub order: Option<usize>,
25
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub prompt: Option<String>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub required: Option<bool>,
33 }
34 );
35 };
36}
37
38var_setting!(TemplateVariableArraySetting, Vec<Value>);
39var_setting!(TemplateVariableBoolSetting, bool);
40var_setting!(TemplateVariableNumberSetting, isize);
41var_setting!(TemplateVariableObjectSetting, FxHashMap<String, Value>);
42var_setting!(TemplateVariableStringSetting, String);
43
44config_struct!(
45 #[derive(Config)]
46 pub struct TemplateVariableEnumValueConfig {
47 pub label: String,
49
50 pub value: String,
52 }
53);
54
55config_enum!(
56 #[derive(Config)]
57 #[serde(untagged)]
58 pub enum TemplateVariableEnumValue {
59 String(String),
60 #[setting(nested)]
61 Object(TemplateVariableEnumValueConfig),
62 }
63);
64
65config_enum!(
66 #[derive(Config)]
67 #[serde(untagged)]
68 pub enum TemplateVariableEnumDefault {
69 String(String),
70 #[setting(default)]
71 List(Vec<String>),
72 }
73);
74
75impl TemplateVariableEnumDefault {
76 pub fn to_vec(&self) -> Vec<&String> {
77 match self {
78 Self::String(value) => vec![value],
79 Self::List(list) => list.iter().collect(),
80 }
81 }
82}
83
84fn validate_enum_default<C>(
85 default_value: &PartialTemplateVariableEnumDefault,
86 partial: &PartialTemplateVariableEnumSetting,
87 _context: &C,
88 _finalize: bool,
89) -> Result<(), ValidateError> {
90 if let Some(values) = &partial.values {
91 if let PartialTemplateVariableEnumDefault::List(list) = default_value {
92 if !partial.multiple.is_some_and(|m| m) && !list.is_empty() {
94 return Err(ValidateError::new(
95 "multiple default values is not allowed unless `multiple` is enabled",
96 ));
97 }
98 }
99
100 let values = values
101 .iter()
102 .flat_map(|v| match v {
103 PartialTemplateVariableEnumValue::String(value) => Some(value),
104 PartialTemplateVariableEnumValue::Object(cfg) => cfg.value.as_ref(),
105 })
106 .collect::<Vec<_>>();
107
108 let matches = match default_value {
109 PartialTemplateVariableEnumDefault::String(inner) => values.contains(&inner),
110 PartialTemplateVariableEnumDefault::List(list) => {
111 list.iter().all(|v| values.contains(&v))
112 }
113 };
114
115 if !matches {
116 return Err(ValidateError::new(
117 "invalid default value, must be a value configured in `values`",
118 ));
119 }
120 }
121
122 Ok(())
123}
124
125config_struct!(
126 #[derive(Config)]
127 pub struct TemplateVariableEnumSetting {
128 #[setting(alias = "defaultValue", nested, validate = validate_enum_default)]
130 pub default: TemplateVariableEnumDefault,
131
132 #[serde(default, skip_serializing_if = "is_false")]
134 pub internal: bool,
135
136 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub multiple: Option<bool>,
139
140 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub order: Option<usize>,
143
144 #[serde(default, skip_serializing_if = "Option::is_none")]
146 pub prompt: Option<String>,
147
148 #[setting(nested)]
150 pub values: Vec<TemplateVariableEnumValue>,
151 }
152);
153
154impl TemplateVariableEnumSetting {
155 pub fn get_labels(&self) -> Vec<&String> {
156 self.values
157 .iter()
158 .map(|v| match v {
159 TemplateVariableEnumValue::String(value) => value,
160 TemplateVariableEnumValue::Object(cfg) => &cfg.label,
161 })
162 .collect()
163 }
164
165 pub fn get_values(&self) -> Vec<&String> {
166 self.values
167 .iter()
168 .map(|v| match v {
169 TemplateVariableEnumValue::String(value) => value,
170 TemplateVariableEnumValue::Object(cfg) => &cfg.value,
171 })
172 .collect()
173 }
174
175 pub fn is_multiple(&self) -> bool {
176 self.multiple.is_some_and(|v| v)
177 }
178}
179
180config_enum!(
181 #[derive(Config)]
183 #[serde(tag = "type")]
184 pub enum TemplateVariable {
185 #[setting(nested)]
187 Array(TemplateVariableArraySetting),
188
189 #[setting(nested)]
191 Boolean(TemplateVariableBoolSetting),
192
193 #[setting(nested)]
195 Enum(TemplateVariableEnumSetting),
196
197 #[setting(nested)]
199 Number(TemplateVariableNumberSetting),
200
201 #[setting(nested)]
203 Object(TemplateVariableObjectSetting),
204
205 #[setting(nested)]
207 String(TemplateVariableStringSetting),
208 }
209);
210
211impl TemplateVariable {
212 pub fn get_order(&self) -> usize {
213 let order = match self {
214 Self::Array(cfg) => cfg.order.as_ref(),
215 Self::Boolean(cfg) => cfg.order.as_ref(),
216 Self::Enum(cfg) => cfg.order.as_ref(),
217 Self::Number(cfg) => cfg.order.as_ref(),
218 Self::Object(cfg) => cfg.order.as_ref(),
219 Self::String(cfg) => cfg.order.as_ref(),
220 };
221
222 order.copied().unwrap_or(100)
223 }
224
225 pub fn get_prompt(&self) -> Option<&String> {
226 match self {
227 Self::Array(cfg) => cfg.prompt.as_ref(),
228 Self::Boolean(cfg) => cfg.prompt.as_ref(),
229 Self::Enum(cfg) => cfg.prompt.as_ref(),
230 Self::Number(cfg) => cfg.prompt.as_ref(),
231 Self::Object(cfg) => cfg.prompt.as_ref(),
232 Self::String(cfg) => cfg.prompt.as_ref(),
233 }
234 }
235
236 pub fn is_internal(&self) -> bool {
237 match self {
238 Self::Array(cfg) => cfg.internal,
239 Self::Boolean(cfg) => cfg.internal,
240 Self::Enum(cfg) => cfg.internal,
241 Self::Number(cfg) => cfg.internal,
242 Self::Object(cfg) => cfg.internal,
243 Self::String(cfg) => cfg.internal,
244 }
245 }
246
247 pub fn is_multiple(&self) -> bool {
248 match self {
249 Self::Enum(cfg) => cfg.is_multiple(),
250 _ => false,
251 }
252 }
253
254 pub fn is_required(&self) -> bool {
255 match self {
256 Self::Array(cfg) => cfg.required,
257 Self::Boolean(cfg) => cfg.required,
258 Self::Number(cfg) => cfg.required,
259 Self::Object(cfg) => cfg.required,
260 Self::String(cfg) => cfg.required,
261 _ => None,
262 }
263 .is_some_and(|v| v)
264 }
265}
266
267config_struct!(
268 #[derive(Config)]
271 pub struct TemplateConfig {
272 #[setting(rename = "$schema")]
273 pub schema: String,
274
275 #[setting(validate = validate::not_empty)]
277 pub description: String,
278
279 #[serde(default, skip_serializing_if = "Option::is_none")]
282 pub destination: Option<String>,
283
284 #[serde(default, skip_serializing_if = "OneOrMany::is_empty")]
286 pub extends: OneOrMany<Id>,
287
288 #[serde(default, skip_serializing_if = "Option::is_none")]
290 pub id: Option<Id>,
291
292 #[setting(validate = validate::not_empty)]
294 pub title: String,
295
296 #[setting(nested)]
299 #[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
300 pub variables: FxHashMap<String, TemplateVariable>,
301 }
302);