1use super::constrained_strings::{Description, Identifier};
10use super::parameters::{validate_ui_label, FileFilter, FlexFloat, FlexInt};
11use crate::types::{DataFlow, ObjectType};
12use openjd_expr::ExprValue;
13use serde::Deserialize;
14
15#[derive(Debug, Clone, Deserialize)]
17#[serde(rename_all = "camelCase", deny_unknown_fields)]
18pub struct BoolUserInterface {
19 pub control: Option<String>,
20 pub label: Option<String>,
21 pub group_label: Option<String>,
22}
23
24#[derive(Debug, Clone, Deserialize)]
26#[serde(rename_all = "camelCase", deny_unknown_fields)]
27pub struct RangeExprUserInterface {
28 pub control: Option<String>,
29 pub label: Option<String>,
30 pub group_label: Option<String>,
31}
32
33#[derive(Debug, Clone, Deserialize)]
35#[serde(rename_all = "camelCase", deny_unknown_fields)]
36pub struct ListSimpleUserInterface {
37 pub control: Option<String>,
38 pub label: Option<String>,
39 pub group_label: Option<String>,
40}
41
42#[derive(Debug, Clone, Deserialize)]
44#[serde(rename_all = "camelCase", deny_unknown_fields)]
45pub struct ListPathUserInterface {
46 pub control: Option<String>,
47 pub label: Option<String>,
48 pub group_label: Option<String>,
49 pub file_filters: Option<Vec<FileFilter>>,
50 pub file_filter_default: Option<FileFilter>,
51}
52
53#[derive(Debug, Clone, Deserialize)]
55#[serde(rename_all = "camelCase", deny_unknown_fields)]
56pub struct ListIntUserInterface {
57 pub control: Option<String>,
58 pub label: Option<String>,
59 pub group_label: Option<String>,
60 pub single_step_delta: Option<FlexInt>,
61}
62
63#[derive(Debug, Clone, Deserialize)]
65#[serde(rename_all = "camelCase", deny_unknown_fields)]
66pub struct ListFloatUserInterface {
67 pub control: Option<String>,
68 pub label: Option<String>,
69 pub group_label: Option<String>,
70 pub decimals: Option<FlexInt>,
71 pub single_step_delta: Option<FlexFloat>,
72}
73
74#[derive(Debug, Clone, Deserialize)]
76#[serde(rename_all = "camelCase", deny_unknown_fields)]
77pub struct HiddenOnlyUserInterface {
78 pub control: Option<String>,
79 pub label: Option<String>,
80 pub group_label: Option<String>,
81}
82
83#[derive(Debug, Clone, Deserialize)]
85#[serde(rename_all = "camelCase", deny_unknown_fields)]
86pub struct JobBoolParameterDefinition {
87 pub name: Identifier,
88 pub description: Option<Description>,
89 pub default: Option<BoolValue>,
90 pub user_interface: Option<BoolUserInterface>,
91}
92
93#[derive(Debug, Clone)]
95pub struct BoolValue(pub bool);
96
97impl<'de> Deserialize<'de> for BoolValue {
98 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
99 let val = serde_json::Value::deserialize(deserializer)?;
100 match &val {
101 serde_json::Value::Bool(b) => Ok(BoolValue(*b)),
102 serde_json::Value::Number(n) => {
103 if let Some(i) = n.as_i64() {
104 match i {
105 0 => Ok(BoolValue(false)),
106 1 => Ok(BoolValue(true)),
107 _ => Err(serde::de::Error::custom(format!("Invalid bool value: {i}"))),
108 }
109 } else if let Some(f) = n.as_f64() {
110 if f == 0.0 {
111 Ok(BoolValue(false))
112 } else if f == 1.0 {
113 Ok(BoolValue(true))
114 } else {
115 Err(serde::de::Error::custom(format!("Invalid bool value: {f}")))
116 }
117 } else {
118 Err(serde::de::Error::custom("Invalid bool value"))
119 }
120 }
121 serde_json::Value::String(s) => match s.to_lowercase().as_str() {
122 "true" | "yes" | "on" | "1" => Ok(BoolValue(true)),
123 "false" | "no" | "off" | "0" => Ok(BoolValue(false)),
124 _ => Err(serde::de::Error::custom(format!(
125 "Invalid bool value: '{s}'"
126 ))),
127 },
128 _ => Err(serde::de::Error::custom("Invalid bool value")),
129 }
130 }
131}
132
133impl JobBoolParameterDefinition {
134 pub fn check_value_constraints(&self, value: &ExprValue) -> Result<(), String> {
135 match value {
136 ExprValue::Bool(_) => Ok(()),
137 ExprValue::Int(0) | ExprValue::Int(1) => Ok(()),
138 ExprValue::String(s) => match s.to_lowercase().as_str() {
139 "true" | "false" | "yes" | "no" | "on" | "off" | "1" | "0" => Ok(()),
140 _ => Err(format!(
141 "Parameter '{}': value '{}' is not a valid bool",
142 self.name, s
143 )),
144 },
145 _ => Err(format!(
146 "Parameter '{}': expected bool, got {}",
147 self.name,
148 value.type_name()
149 )),
150 }
151 }
152
153 pub fn validate_definition(&self) -> Result<(), Vec<String>> {
154 let mut errors = Vec::new();
155 if let Some(ui) = &self.user_interface {
156 validate_ui(
157 self.name.as_str(),
158 &ui.label,
159 &ui.group_label,
160 &ui.control,
161 &["CHECK_BOX", "HIDDEN"],
162 &mut errors,
163 );
164 }
165 if errors.is_empty() {
166 Ok(())
167 } else {
168 Err(errors)
169 }
170 }
171}
172
173#[derive(Debug, Clone, Deserialize)]
175#[serde(rename_all = "camelCase", deny_unknown_fields)]
176pub struct JobRangeExprParameterDefinition {
177 pub name: Identifier,
178 pub description: Option<Description>,
179 pub default: Option<String>,
180 pub min_length: Option<usize>,
181 pub max_length: Option<usize>,
182 pub user_interface: Option<RangeExprUserInterface>,
183}
184
185impl JobRangeExprParameterDefinition {
186 pub fn check_value_constraints(&self, value: &ExprValue) -> Result<(), String> {
187 let s = match value {
188 ExprValue::RangeExpr(r) => r.to_string(),
189 ExprValue::String(s) => {
190 s.parse::<openjd_expr::RangeExpr>().map_err(|_| {
191 format!(
192 "Parameter '{}': value '{}' is not a valid range expression",
193 self.name, s
194 )
195 })?;
196 s.clone()
197 }
198 _ => {
199 return Err(format!(
200 "Parameter '{}': expected range_expr, got {}",
201 self.name,
202 value.type_name()
203 ))
204 }
205 };
206 if let Some(min) = self.min_length {
207 let char_len = s.chars().count();
208 if char_len < min {
209 return Err(format!(
210 "Parameter '{}': value length {} is less than minimum {min}",
211 self.name, char_len
212 ));
213 }
214 }
215 if let Some(max) = self.max_length {
216 let char_len = s.chars().count();
217 if char_len > max {
218 return Err(format!(
219 "Parameter '{}': value length {} exceeds maximum {max}",
220 self.name, char_len
221 ));
222 }
223 }
224 Ok(())
225 }
226
227 pub fn validate_definition(&self) -> Result<(), Vec<String>> {
228 let mut errors = Vec::new();
229 if let Some(default) = &self.default {
230 if default.parse::<openjd_expr::RangeExpr>().is_err() {
231 errors.push(format!(
232 "Parameter '{}': default '{}' is not a valid range expression.",
233 self.name, default
234 ));
235 }
236 }
237 if let Some(ui) = &self.user_interface {
238 validate_ui(
239 self.name.as_str(),
240 &ui.label,
241 &ui.group_label,
242 &ui.control,
243 &["LINE_EDIT", "HIDDEN"],
244 &mut errors,
245 );
246 }
247 if errors.is_empty() {
248 Ok(())
249 } else {
250 Err(errors)
251 }
252 }
253}
254
255#[derive(Debug, Clone, Deserialize)]
257#[serde(rename_all = "camelCase", deny_unknown_fields)]
258pub struct JobListStringParameterDefinition {
259 pub name: Identifier,
260 pub description: Option<Description>,
261 pub default: Option<Vec<String>>,
262 pub min_length: Option<usize>,
263 pub max_length: Option<usize>,
264 pub item: Option<ListStringItemConstraints>,
265 pub user_interface: Option<ListSimpleUserInterface>,
266}
267
268#[derive(Debug, Clone, Deserialize)]
269#[serde(rename_all = "camelCase", deny_unknown_fields)]
270pub struct ListStringItemConstraints {
271 pub allowed_values: Option<Vec<String>>,
272 pub min_length: Option<usize>,
273 pub max_length: Option<usize>,
274}
275
276impl JobListStringParameterDefinition {
277 pub fn check_value_constraints(&self, value: &ExprValue) -> Result<(), String> {
278 let items = match value {
279 ExprValue::ListString(v, _) | ExprValue::ListPath(v, _, _) => v,
280 _ => {
281 return Err(format!(
282 "Parameter '{}': expected list[string], got {}",
283 self.name,
284 value.type_name()
285 ))
286 }
287 };
288 check_list_length(&self.name, items.len(), self.min_length, self.max_length)?;
289 if let Some(item) = &self.item {
290 check_string_items(&self.name, items, item)?;
291 }
292 Ok(())
293 }
294
295 pub fn validate_definition(&self) -> Result<(), Vec<String>> {
296 let mut errors = Vec::new();
297 validate_list_length(
298 &self.name,
299 &self.default,
300 self.min_length,
301 self.max_length,
302 &mut errors,
303 );
304 if let (Some(default), Some(item)) = (&self.default, &self.item) {
305 validate_string_item_defaults(&self.name, default, item, &mut errors);
306 }
307 if let Some(ui) = &self.user_interface {
308 validate_ui(
309 self.name.as_str(),
310 &ui.label,
311 &ui.group_label,
312 &ui.control,
313 &["LINE_EDIT_LIST", "HIDDEN"],
314 &mut errors,
315 );
316 }
317 if errors.is_empty() {
318 Ok(())
319 } else {
320 Err(errors)
321 }
322 }
323}
324
325#[derive(Debug, Clone, Deserialize)]
327#[serde(rename_all = "camelCase", deny_unknown_fields)]
328pub struct JobListPathParameterDefinition {
329 pub name: Identifier,
330 pub description: Option<Description>,
331 pub object_type: Option<ObjectType>,
332 pub data_flow: Option<DataFlow>,
333 pub default: Option<Vec<String>>,
334 pub min_length: Option<usize>,
335 pub max_length: Option<usize>,
336 pub item: Option<ListStringItemConstraints>,
337 pub user_interface: Option<ListPathUserInterface>,
338}
339
340impl JobListPathParameterDefinition {
341 pub fn check_value_constraints(&self, value: &ExprValue) -> Result<(), String> {
342 let items = match value {
343 ExprValue::ListString(v, _) | ExprValue::ListPath(v, _, _) => v,
344 _ => {
345 return Err(format!(
346 "Parameter '{}': expected list[path], got {}",
347 self.name,
348 value.type_name()
349 ))
350 }
351 };
352 check_list_length(&self.name, items.len(), self.min_length, self.max_length)?;
353 if let Some(item) = &self.item {
354 check_string_items(&self.name, items, item)?;
355 }
356 Ok(())
357 }
358
359 pub fn validate_definition(&self) -> Result<(), Vec<String>> {
360 let mut errors = Vec::new();
361 validate_list_length(
362 &self.name,
363 &self.default,
364 self.min_length,
365 self.max_length,
366 &mut errors,
367 );
368 if let (Some(default), Some(item)) = (&self.default, &self.item) {
369 validate_string_item_defaults(&self.name, default, item, &mut errors);
370 }
371 if let Some(ui) = &self.user_interface {
372 validate_ui(
373 self.name.as_str(),
374 &ui.label,
375 &ui.group_label,
376 &ui.control,
377 &[
378 "CHOOSE_INPUT_FILE_LIST",
379 "CHOOSE_OUTPUT_FILE_LIST",
380 "CHOOSE_DIRECTORY_LIST",
381 "HIDDEN",
382 ],
383 &mut errors,
384 );
385 }
386 if errors.is_empty() {
387 Ok(())
388 } else {
389 Err(errors)
390 }
391 }
392}
393
394#[derive(Debug, Clone, Deserialize)]
396#[serde(rename_all = "camelCase", deny_unknown_fields)]
397pub struct JobListIntParameterDefinition {
398 pub name: Identifier,
399 pub description: Option<Description>,
400 pub default: Option<Vec<FlexInt>>,
401 pub min_length: Option<usize>,
402 pub max_length: Option<usize>,
403 pub item: Option<ListIntItemConstraints>,
404 pub user_interface: Option<ListIntUserInterface>,
405}
406
407#[derive(Debug, Clone, Deserialize)]
408#[serde(rename_all = "camelCase", deny_unknown_fields)]
409pub struct ListIntItemConstraints {
410 pub allowed_values: Option<Vec<FlexInt>>,
411 pub min_value: Option<FlexInt>,
412 pub max_value: Option<FlexInt>,
413}
414
415impl JobListIntParameterDefinition {
416 pub fn check_value_constraints(&self, value: &ExprValue) -> Result<(), String> {
417 let items = match value {
418 ExprValue::ListInt(v) => v,
419 _ => {
420 return Err(format!(
421 "Parameter '{}': expected list[int], got {}",
422 self.name,
423 value.type_name()
424 ))
425 }
426 };
427 check_list_length(&self.name, items.len(), self.min_length, self.max_length)?;
428 if let Some(item) = &self.item {
429 check_int_items(&self.name, items, item, "item")?;
430 }
431 Ok(())
432 }
433
434 pub fn validate_definition(&self) -> Result<(), Vec<String>> {
435 let mut errors = Vec::new();
436 validate_list_length(
437 &self.name,
438 &self.default,
439 self.min_length,
440 self.max_length,
441 &mut errors,
442 );
443 if let (Some(default), Some(item)) = (&self.default, &self.item) {
444 validate_int_item_defaults(&self.name, default, item, "default", "item", &mut errors);
445 }
446 if let Some(ui) = &self.user_interface {
447 validate_ui(
448 self.name.as_str(),
449 &ui.label,
450 &ui.group_label,
451 &ui.control,
452 &["SPIN_BOX_LIST", "HIDDEN"],
453 &mut errors,
454 );
455 }
456 if errors.is_empty() {
457 Ok(())
458 } else {
459 Err(errors)
460 }
461 }
462}
463
464#[derive(Debug, Clone, Deserialize)]
466#[serde(rename_all = "camelCase", deny_unknown_fields)]
467pub struct JobListFloatParameterDefinition {
468 pub name: Identifier,
469 pub description: Option<Description>,
470 pub default: Option<Vec<super::parameters::FlexFloat>>,
471 pub min_length: Option<usize>,
472 pub max_length: Option<usize>,
473 pub item: Option<ListFloatItemConstraints>,
474 pub user_interface: Option<ListFloatUserInterface>,
475}
476
477#[derive(Debug, Clone, Deserialize)]
478#[serde(rename_all = "camelCase", deny_unknown_fields)]
479pub struct ListFloatItemConstraints {
480 pub allowed_values: Option<Vec<super::parameters::FlexFloat>>,
481 pub min_value: Option<super::parameters::FlexFloat>,
482 pub max_value: Option<super::parameters::FlexFloat>,
483}
484
485impl JobListFloatParameterDefinition {
486 pub fn check_value_constraints(&self, value: &ExprValue) -> Result<(), String> {
487 let items = match value {
488 ExprValue::ListFloat(v) => v,
489 _ => {
490 return Err(format!(
491 "Parameter '{}': expected list[float], got {}",
492 self.name,
493 value.type_name()
494 ))
495 }
496 };
497 check_list_length(&self.name, items.len(), self.min_length, self.max_length)?;
498 if let Some(item) = &self.item {
499 for (i, v) in items.iter().enumerate() {
500 if let Some(min) = &item.min_value {
501 if v.value() < min.0 {
502 return Err(format!(
503 "Parameter '{}': item[{i}] {} is less than minimum {}",
504 self.name, v, min.0
505 ));
506 }
507 }
508 if let Some(max) = &item.max_value {
509 if v.value() > max.0 {
510 return Err(format!(
511 "Parameter '{}': item[{i}] {} exceeds maximum {}",
512 self.name, v, max.0
513 ));
514 }
515 }
516 if let Some(allowed) = &item.allowed_values {
517 if !allowed.iter().any(|a| a.0 == v.value()) {
518 return Err(format!(
519 "Parameter '{}': item[{i}] {} is not in allowed values",
520 self.name, v
521 ));
522 }
523 }
524 }
525 }
526 Ok(())
527 }
528
529 pub fn validate_definition(&self) -> Result<(), Vec<String>> {
530 let mut errors = Vec::new();
531 validate_list_length(
532 &self.name,
533 &self.default,
534 self.min_length,
535 self.max_length,
536 &mut errors,
537 );
538 if let (Some(default), Some(item)) = (&self.default, &self.item) {
539 for (i, v) in default.iter().enumerate() {
540 if let Some(min) = &item.min_value {
541 if v.0 < min.0 {
542 errors.push(format!(
543 "Parameter '{}': default[{i}] {} < item minValue {}.",
544 self.name, v.0, min.0
545 ));
546 }
547 }
548 if let Some(max) = &item.max_value {
549 if v.0 > max.0 {
550 errors.push(format!(
551 "Parameter '{}': default[{i}] {} > item maxValue {}.",
552 self.name, v.0, max.0
553 ));
554 }
555 }
556 }
557 }
558 if let Some(ui) = &self.user_interface {
559 validate_ui(
560 self.name.as_str(),
561 &ui.label,
562 &ui.group_label,
563 &ui.control,
564 &["SPIN_BOX_LIST", "HIDDEN"],
565 &mut errors,
566 );
567 }
568 if errors.is_empty() {
569 Ok(())
570 } else {
571 Err(errors)
572 }
573 }
574}
575
576#[derive(Debug, Clone, Deserialize)]
578#[serde(rename_all = "camelCase", deny_unknown_fields)]
579pub struct JobListBoolParameterDefinition {
580 pub name: Identifier,
581 pub description: Option<Description>,
582 pub default: Option<Vec<BoolValue>>,
583 pub min_length: Option<usize>,
584 pub max_length: Option<usize>,
585 pub user_interface: Option<ListSimpleUserInterface>,
586}
587
588impl JobListBoolParameterDefinition {
589 pub fn check_value_constraints(&self, value: &ExprValue) -> Result<(), String> {
590 let items = match value {
591 ExprValue::ListBool(v) => v,
592 _ => {
593 return Err(format!(
594 "Parameter '{}': expected list[bool], got {}",
595 self.name,
596 value.type_name()
597 ))
598 }
599 };
600 check_list_length(&self.name, items.len(), self.min_length, self.max_length)?;
601 Ok(())
602 }
603
604 pub fn validate_definition(&self) -> Result<(), Vec<String>> {
605 let mut errors = Vec::new();
606 validate_list_length(
607 &self.name,
608 &self.default,
609 self.min_length,
610 self.max_length,
611 &mut errors,
612 );
613 if let Some(ui) = &self.user_interface {
614 validate_ui(
615 self.name.as_str(),
616 &ui.label,
617 &ui.group_label,
618 &ui.control,
619 &["CHECK_BOX_LIST", "HIDDEN"],
620 &mut errors,
621 );
622 }
623 if errors.is_empty() {
624 Ok(())
625 } else {
626 Err(errors)
627 }
628 }
629}
630
631#[derive(Debug, Clone, Deserialize)]
633#[serde(rename_all = "camelCase", deny_unknown_fields)]
634pub struct JobListListIntParameterDefinition {
635 pub name: Identifier,
636 pub description: Option<Description>,
637 pub default: Option<Vec<Vec<FlexInt>>>,
638 pub min_length: Option<usize>,
639 pub max_length: Option<usize>,
640 pub item: Option<ListListIntItemConstraints>,
641 pub user_interface: Option<HiddenOnlyUserInterface>,
642}
643
644#[derive(Debug, Clone, Deserialize)]
645#[serde(rename_all = "camelCase", deny_unknown_fields)]
646pub struct ListListIntItemConstraints {
647 pub min_length: Option<usize>,
648 pub max_length: Option<usize>,
649 pub item: Option<ListIntItemConstraints>,
650}
651
652impl JobListListIntParameterDefinition {
653 pub fn check_value_constraints(&self, value: &ExprValue) -> Result<(), String> {
654 let items = match value {
655 ExprValue::ListList(v, _, _) => v,
656 _ => {
657 return Err(format!(
658 "Parameter '{}': expected list[list[int]], got {}",
659 self.name,
660 value.type_name()
661 ))
662 }
663 };
664 check_list_length(&self.name, items.len(), self.min_length, self.max_length)?;
665 if let Some(item) = &self.item {
666 for (i, inner) in items.iter().enumerate() {
667 let ints = match inner {
668 ExprValue::ListInt(v) => v,
669 _ => {
670 return Err(format!(
671 "Parameter '{}': item[{i}] expected list[int], got {}",
672 self.name,
673 inner.type_name()
674 ))
675 }
676 };
677 if let Some(min) = item.min_length {
678 if ints.len() < min {
679 return Err(format!(
680 "Parameter '{}': item[{i}] length {} is less than minimum {min}",
681 self.name,
682 ints.len()
683 ));
684 }
685 }
686 if let Some(max) = item.max_length {
687 if ints.len() > max {
688 return Err(format!(
689 "Parameter '{}': item[{i}] length {} exceeds maximum {max}",
690 self.name,
691 ints.len()
692 ));
693 }
694 }
695 if let Some(inner_item) = &item.item {
696 check_int_items(&self.name, ints, inner_item, &format!("item[{i}]"))?;
697 }
698 }
699 }
700 Ok(())
701 }
702
703 pub fn validate_definition(&self) -> Result<(), Vec<String>> {
704 let mut errors = Vec::new();
705 validate_list_length(
706 &self.name,
707 &self.default,
708 self.min_length,
709 self.max_length,
710 &mut errors,
711 );
712 if let (Some(default), Some(item)) = (&self.default, &self.item) {
713 for (i, inner) in default.iter().enumerate() {
714 if let Some(min) = item.min_length {
715 if inner.len() < min {
716 errors.push(format!("Parameter '{}': default[{i}] inner list length {} < item minLength {min}.", self.name, inner.len()));
717 }
718 }
719 if let Some(max) = item.max_length {
720 if inner.len() > max {
721 errors.push(format!("Parameter '{}': default[{i}] inner list length {} > item maxLength {max}.", self.name, inner.len()));
722 }
723 }
724 if let Some(inner_item) = &item.item {
725 validate_int_item_defaults(
726 &self.name,
727 inner,
728 inner_item,
729 &format!("default[{i}]"),
730 "item.item",
731 &mut errors,
732 );
733 }
734 }
735 }
736 if let Some(ui) = &self.user_interface {
737 validate_ui(
738 self.name.as_str(),
739 &ui.label,
740 &ui.group_label,
741 &ui.control,
742 &["HIDDEN"],
743 &mut errors,
744 );
745 }
746 if errors.is_empty() {
747 Ok(())
748 } else {
749 Err(errors)
750 }
751 }
752}
753
754fn check_list_length(
756 param_name: &Identifier,
757 len: usize,
758 min_length: Option<usize>,
759 max_length: Option<usize>,
760) -> Result<(), String> {
761 if let Some(min) = min_length {
762 if len < min {
763 return Err(format!(
764 "Parameter '{param_name}': list length {len} is less than minimum {min}"
765 ));
766 }
767 }
768 if let Some(max) = max_length {
769 if len > max {
770 return Err(format!(
771 "Parameter '{param_name}': list length {len} exceeds maximum {max}"
772 ));
773 }
774 }
775 Ok(())
776}
777
778fn validate_list_length<T>(
780 param_name: &Identifier,
781 default: &Option<Vec<T>>,
782 min_length: Option<usize>,
783 max_length: Option<usize>,
784 errors: &mut Vec<String>,
785) {
786 if let Some(default) = default {
787 if let Some(min) = min_length {
788 if default.len() < min {
789 errors.push(format!(
790 "Parameter '{param_name}': default list length {} < minLength {min}.",
791 default.len()
792 ));
793 }
794 }
795 if let Some(max) = max_length {
796 if default.len() > max {
797 errors.push(format!(
798 "Parameter '{param_name}': default list length {} > maxLength {max}.",
799 default.len()
800 ));
801 }
802 }
803 }
804}
805
806fn validate_ui(
808 name: &str,
809 label: &Option<String>,
810 group_label: &Option<String>,
811 control: &Option<String>,
812 allowed_controls: &[&str],
813 errors: &mut Vec<String>,
814) {
815 errors.extend(validate_ui_label(label, "label", name));
816 errors.extend(validate_ui_label(group_label, "groupLabel", name));
817 if let Some(c) = control {
818 if !allowed_controls.contains(&c.as_str()) {
819 errors.push(format!("Parameter '{name}': unknown control '{c}'."));
820 }
821 }
822}
823
824fn check_string_items(
826 name: &Identifier,
827 items: &[String],
828 item: &ListStringItemConstraints,
829) -> Result<(), String> {
830 for (i, s) in items.iter().enumerate() {
831 if let Some(min) = item.min_length {
832 let char_len = s.chars().count();
833 if char_len < min {
834 return Err(format!(
835 "Parameter '{name}': item[{i}] length {} is less than minimum {min}",
836 char_len
837 ));
838 }
839 }
840 if let Some(max) = item.max_length {
841 let char_len = s.chars().count();
842 if char_len > max {
843 return Err(format!(
844 "Parameter '{name}': item[{i}] length {} exceeds maximum {max}",
845 char_len
846 ));
847 }
848 }
849 if let Some(allowed) = &item.allowed_values {
850 if !allowed.contains(s) {
851 return Err(format!(
852 "Parameter '{name}': item[{i}] '{s}' is not in allowed values"
853 ));
854 }
855 }
856 }
857 Ok(())
858}
859
860fn validate_string_item_defaults(
862 name: &Identifier,
863 default: &[String],
864 item: &ListStringItemConstraints,
865 errors: &mut Vec<String>,
866) {
867 for (i, v) in default.iter().enumerate() {
868 if let Some(allowed) = &item.allowed_values {
869 if !allowed.contains(v) {
870 errors.push(format!(
871 "Parameter '{name}': default[{i}] '{v}' not in item allowedValues."
872 ));
873 }
874 }
875 if let Some(min) = item.min_length {
876 let char_len = v.chars().count();
877 if char_len < min {
878 errors.push(format!(
879 "Parameter '{name}': default[{i}] length {} < item minLength {min}.",
880 char_len
881 ));
882 }
883 }
884 if let Some(max) = item.max_length {
885 let char_len = v.chars().count();
886 if char_len > max {
887 errors.push(format!(
888 "Parameter '{name}': default[{i}] length {} > item maxLength {max}.",
889 char_len
890 ));
891 }
892 }
893 }
894}
895
896fn check_int_items(
898 name: &Identifier,
899 items: &[i64],
900 item: &ListIntItemConstraints,
901 prefix: &str,
902) -> Result<(), String> {
903 for (i, v) in items.iter().enumerate() {
904 if let Some(min) = &item.min_value {
905 if *v < min.0 {
906 return Err(format!(
907 "Parameter '{name}': {prefix}[{i}] {v} is less than minimum {}",
908 min.0
909 ));
910 }
911 }
912 if let Some(max) = &item.max_value {
913 if *v > max.0 {
914 return Err(format!(
915 "Parameter '{name}': {prefix}[{i}] {v} exceeds maximum {}",
916 max.0
917 ));
918 }
919 }
920 if let Some(allowed) = &item.allowed_values {
921 if !allowed.iter().any(|a| a.0 == *v) {
922 return Err(format!(
923 "Parameter '{name}': {prefix}[{i}] {v} is not in allowed values"
924 ));
925 }
926 }
927 }
928 Ok(())
929}
930
931fn validate_int_item_defaults(
933 name: &Identifier,
934 defaults: &[FlexInt],
935 item: &ListIntItemConstraints,
936 prefix: &str,
937 constraint_label: &str,
938 errors: &mut Vec<String>,
939) {
940 for (i, v) in defaults.iter().enumerate() {
941 if let Some(min) = &item.min_value {
942 if v.0 < min.0 {
943 errors.push(format!(
944 "Parameter '{name}': {prefix}[{i}] {} < {constraint_label} minValue {}.",
945 v.0, min.0
946 ));
947 }
948 }
949 if let Some(max) = &item.max_value {
950 if v.0 > max.0 {
951 errors.push(format!(
952 "Parameter '{name}': {prefix}[{i}] {} > {constraint_label} maxValue {}.",
953 v.0, max.0
954 ));
955 }
956 }
957 if let Some(allowed) = &item.allowed_values {
958 if !allowed.iter().any(|a| a.0 == v.0) {
959 errors.push(format!(
960 "Parameter '{name}': {prefix}[{i}] {} not in {constraint_label} allowedValues.",
961 v.0
962 ));
963 }
964 }
965 }
966}