1use serde::{Deserialize, Serialize};
21use std::{borrow::Cow, collections::BTreeMap};
22
23use crate::protocol::version::const_string;
25
26const_string!(ObjectTypeConst = "object");
31const_string!(StringTypeConst = "string");
32const_string!(NumberTypeConst = "number");
33const_string!(IntegerTypeConst = "integer");
34const_string!(BooleanTypeConst = "boolean");
35const_string!(EnumTypeConst = "string");
36const_string!(ArrayTypeConst = "array");
37
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
49#[serde(untagged)]
50pub enum PrimitiveSchema {
51 Enum(EnumSchema),
53 String(StringSchema),
55 Number(NumberSchema),
57 Integer(IntegerSchema),
59 Boolean(BooleanSchema),
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "kebab-case")]
70pub enum StringFormat {
71 Email,
73 Uri,
75 Date,
77 DateTime,
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct StringSchema {
85 #[serde(rename = "type")]
86 pub type_: StringTypeConst,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub title: Option<Cow<'static, str>>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub description: Option<Cow<'static, str>>,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub min_length: Option<u32>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub max_length: Option<u32>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub format: Option<StringFormat>,
97}
98
99impl Default for StringSchema {
100 fn default() -> Self {
101 Self {
102 type_: StringTypeConst,
103 title: None,
104 description: None,
105 min_length: None,
106 max_length: None,
107 format: None,
108 }
109 }
110}
111
112impl StringSchema {
113 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn email() -> Self {
118 Self {
119 format: Some(StringFormat::Email),
120 ..Default::default()
121 }
122 }
123
124 pub fn uri() -> Self {
125 Self {
126 format: Some(StringFormat::Uri),
127 ..Default::default()
128 }
129 }
130
131 pub fn date() -> Self {
132 Self {
133 format: Some(StringFormat::Date),
134 ..Default::default()
135 }
136 }
137
138 pub fn date_time() -> Self {
139 Self {
140 format: Some(StringFormat::DateTime),
141 ..Default::default()
142 }
143 }
144
145 pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
146 self.title = Some(title.into());
147 self
148 }
149
150 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
151 self.description = Some(description.into());
152 self
153 }
154
155 pub fn with_length(mut self, min: u32, max: u32) -> Result<Self, &'static str> {
156 if min > max {
157 return Err("min_length must be <= max_length");
158 }
159 self.min_length = Some(min);
160 self.max_length = Some(max);
161 Ok(self)
162 }
163
164 pub fn length(mut self, min: u32, max: u32) -> Self {
165 assert!(min <= max, "min_length must be <= max_length");
166 self.min_length = Some(min);
167 self.max_length = Some(max);
168 self
169 }
170
171 pub fn min_length(mut self, min: u32) -> Self {
172 self.min_length = Some(min);
173 self
174 }
175
176 pub fn max_length(mut self, max: u32) -> Self {
177 self.max_length = Some(max);
178 self
179 }
180
181 pub fn format(mut self, format: StringFormat) -> Self {
182 self.format = Some(format);
183 self
184 }
185}
186
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
196#[serde(rename_all = "camelCase")]
197pub struct NumberSchema {
198 #[serde(rename = "type")]
199 pub type_: NumberTypeConst,
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub title: Option<Cow<'static, str>>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub description: Option<Cow<'static, str>>,
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub minimum: Option<f64>,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub maximum: Option<f64>,
208}
209
210impl Default for NumberSchema {
211 fn default() -> Self {
212 Self {
213 type_: NumberTypeConst,
214 title: None,
215 description: None,
216 minimum: None,
217 maximum: None,
218 }
219 }
220}
221
222impl NumberSchema {
223 pub fn new() -> Self {
224 Self::default()
225 }
226
227 pub fn with_range(mut self, min: f64, max: f64) -> Result<Self, &'static str> {
228 if min > max {
229 return Err("minimum must be <= maximum");
230 }
231 self.minimum = Some(min);
232 self.maximum = Some(max);
233 Ok(self)
234 }
235
236 pub fn range(mut self, min: f64, max: f64) -> Self {
237 assert!(min <= max, "minimum must be <= maximum");
238 self.minimum = Some(min);
239 self.maximum = Some(max);
240 self
241 }
242
243 pub fn minimum(mut self, min: f64) -> Self {
244 self.minimum = Some(min);
245 self
246 }
247
248 pub fn maximum(mut self, max: f64) -> Self {
249 self.maximum = Some(max);
250 self
251 }
252
253 pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
254 self.title = Some(title.into());
255 self
256 }
257
258 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
259 self.description = Some(description.into());
260 self
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
266#[serde(rename_all = "camelCase")]
267pub struct IntegerSchema {
268 #[serde(rename = "type")]
269 pub type_: IntegerTypeConst,
270 #[serde(skip_serializing_if = "Option::is_none")]
271 pub title: Option<Cow<'static, str>>,
272 #[serde(skip_serializing_if = "Option::is_none")]
273 pub description: Option<Cow<'static, str>>,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub minimum: Option<i64>,
276 #[serde(skip_serializing_if = "Option::is_none")]
277 pub maximum: Option<i64>,
278}
279
280impl Default for IntegerSchema {
281 fn default() -> Self {
282 Self {
283 type_: IntegerTypeConst,
284 title: None,
285 description: None,
286 minimum: None,
287 maximum: None,
288 }
289 }
290}
291
292impl IntegerSchema {
293 pub fn new() -> Self {
294 Self::default()
295 }
296
297 pub fn with_range(mut self, min: i64, max: i64) -> Result<Self, &'static str> {
298 if min > max {
299 return Err("minimum must be <= maximum");
300 }
301 self.minimum = Some(min);
302 self.maximum = Some(max);
303 Ok(self)
304 }
305
306 pub fn range(mut self, min: i64, max: i64) -> Self {
307 assert!(min <= max, "minimum must be <= maximum");
308 self.minimum = Some(min);
309 self.maximum = Some(max);
310 self
311 }
312
313 pub fn minimum(mut self, min: i64) -> Self {
314 self.minimum = Some(min);
315 self
316 }
317
318 pub fn maximum(mut self, max: i64) -> Self {
319 self.maximum = Some(max);
320 self
321 }
322
323 pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
324 self.title = Some(title.into());
325 self
326 }
327
328 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
329 self.description = Some(description.into());
330 self
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
336#[serde(rename_all = "camelCase")]
337pub struct BooleanSchema {
338 #[serde(rename = "type")]
339 pub type_: BooleanTypeConst,
340 #[serde(skip_serializing_if = "Option::is_none")]
341 pub title: Option<Cow<'static, str>>,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 pub description: Option<Cow<'static, str>>,
344 #[serde(skip_serializing_if = "Option::is_none")]
345 pub default: Option<bool>,
346}
347
348impl Default for BooleanSchema {
349 fn default() -> Self {
350 Self {
351 type_: BooleanTypeConst,
352 title: None,
353 description: None,
354 default: None,
355 }
356 }
357}
358
359impl BooleanSchema {
360 pub fn new() -> Self {
361 Self::default()
362 }
363
364 pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
365 self.title = Some(title.into());
366 self
367 }
368
369 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
370 self.description = Some(description.into());
371 self
372 }
373
374 pub fn with_default(mut self, default: bool) -> Self {
375 self.default = Some(default);
376 self
377 }
378}
379
380#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
386pub struct EnumOption {
387 #[serde(rename = "const")]
389 pub const_value: String,
390 pub title: String,
392}
393
394impl EnumOption {
395 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
396 Self {
397 const_value: value.into(),
398 title: title.into(),
399 }
400 }
401}
402
403#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
405pub struct UntitledEnumItems {
406 #[serde(rename = "type")]
407 pub type_: StringTypeConst,
408 #[serde(rename = "enum")]
409 pub enum_values: Vec<String>,
410}
411
412#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
414#[serde(rename_all = "camelCase")]
415pub struct TitledEnumItems {
416 pub any_of: Vec<EnumOption>,
417}
418
419#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
425#[serde(rename_all = "camelCase")]
426pub struct UntitledSingleSelectEnumSchema {
427 #[serde(rename = "type")]
428 pub type_: StringTypeConst,
429 #[serde(rename = "enum")]
430 pub enum_values: Vec<String>,
431 #[serde(skip_serializing_if = "Option::is_none")]
432 pub title: Option<Cow<'static, str>>,
433 #[serde(skip_serializing_if = "Option::is_none")]
434 pub description: Option<Cow<'static, str>>,
435 #[serde(skip_serializing_if = "Option::is_none")]
436 pub default: Option<String>,
437}
438
439impl UntitledSingleSelectEnumSchema {
440 pub fn new(values: impl Into<Vec<String>>) -> Self {
441 Self {
442 type_: StringTypeConst,
443 enum_values: values.into(),
444 title: None,
445 description: None,
446 default: None,
447 }
448 }
449
450 pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
451 self.title = Some(title.into());
452 self
453 }
454
455 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
456 self.description = Some(description.into());
457 self
458 }
459
460 pub fn with_default(mut self, default: impl Into<String>) -> Self {
461 self.default = Some(default.into());
462 self
463 }
464}
465
466#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
468#[serde(rename_all = "camelCase")]
469pub struct TitledSingleSelectEnumSchema {
470 #[serde(rename = "type")]
471 pub type_: StringTypeConst,
472 pub one_of: Vec<EnumOption>,
473 #[serde(skip_serializing_if = "Option::is_none")]
474 pub title: Option<Cow<'static, str>>,
475 #[serde(skip_serializing_if = "Option::is_none")]
476 pub description: Option<Cow<'static, str>>,
477 #[serde(skip_serializing_if = "Option::is_none")]
478 pub default: Option<String>,
479}
480
481impl TitledSingleSelectEnumSchema {
482 pub fn new(options: impl Into<Vec<EnumOption>>) -> Self {
483 Self {
484 type_: StringTypeConst,
485 one_of: options.into(),
486 title: None,
487 description: None,
488 default: None,
489 }
490 }
491
492 pub fn from_pairs<I, V, T>(pairs: I) -> Self
494 where
495 I: IntoIterator<Item = (V, T)>,
496 V: Into<String>,
497 T: Into<String>,
498 {
499 Self::new(
500 pairs
501 .into_iter()
502 .map(|(v, t)| EnumOption::new(v, t))
503 .collect::<Vec<_>>(),
504 )
505 }
506
507 pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
508 self.title = Some(title.into());
509 self
510 }
511
512 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
513 self.description = Some(description.into());
514 self
515 }
516
517 pub fn with_default(mut self, default: impl Into<String>) -> Self {
518 self.default = Some(default.into());
519 self
520 }
521}
522
523#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
529#[serde(rename_all = "camelCase")]
530pub struct UntitledMultiSelectEnumSchema {
531 #[serde(rename = "type")]
532 pub type_: ArrayTypeConst,
533 pub items: UntitledEnumItems,
534 #[serde(skip_serializing_if = "Option::is_none")]
535 pub min_items: Option<u32>,
536 #[serde(skip_serializing_if = "Option::is_none")]
537 pub max_items: Option<u32>,
538 #[serde(skip_serializing_if = "Option::is_none")]
539 pub title: Option<Cow<'static, str>>,
540 #[serde(skip_serializing_if = "Option::is_none")]
541 pub description: Option<Cow<'static, str>>,
542 #[serde(skip_serializing_if = "Option::is_none")]
543 pub default: Option<Vec<String>>,
544}
545
546impl UntitledMultiSelectEnumSchema {
547 pub fn new(values: impl Into<Vec<String>>) -> Self {
548 Self {
549 type_: ArrayTypeConst,
550 items: UntitledEnumItems {
551 type_: StringTypeConst,
552 enum_values: values.into(),
553 },
554 min_items: None,
555 max_items: None,
556 title: None,
557 description: None,
558 default: None,
559 }
560 }
561
562 pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
563 self.title = Some(title.into());
564 self
565 }
566
567 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
568 self.description = Some(description.into());
569 self
570 }
571
572 pub fn with_default(mut self, default: impl Into<Vec<String>>) -> Self {
573 self.default = Some(default.into());
574 self
575 }
576
577 pub fn min_items(mut self, min: u32) -> Self {
578 self.min_items = Some(min);
579 self
580 }
581
582 pub fn max_items(mut self, max: u32) -> Self {
583 self.max_items = Some(max);
584 self
585 }
586
587 pub fn items_range(mut self, min: u32, max: u32) -> Self {
588 self.min_items = Some(min);
589 self.max_items = Some(max);
590 self
591 }
592}
593
594#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
596#[serde(rename_all = "camelCase")]
597pub struct TitledMultiSelectEnumSchema {
598 #[serde(rename = "type")]
599 pub type_: ArrayTypeConst,
600 pub items: TitledEnumItems,
601 #[serde(skip_serializing_if = "Option::is_none")]
602 pub min_items: Option<u32>,
603 #[serde(skip_serializing_if = "Option::is_none")]
604 pub max_items: Option<u32>,
605 #[serde(skip_serializing_if = "Option::is_none")]
606 pub title: Option<Cow<'static, str>>,
607 #[serde(skip_serializing_if = "Option::is_none")]
608 pub description: Option<Cow<'static, str>>,
609 #[serde(skip_serializing_if = "Option::is_none")]
610 pub default: Option<Vec<String>>,
611}
612
613impl TitledMultiSelectEnumSchema {
614 pub fn new(options: impl Into<Vec<EnumOption>>) -> Self {
615 Self {
616 type_: ArrayTypeConst,
617 items: TitledEnumItems {
618 any_of: options.into(),
619 },
620 min_items: None,
621 max_items: None,
622 title: None,
623 description: None,
624 default: None,
625 }
626 }
627
628 pub fn from_pairs<I, V, T>(pairs: I) -> Self
630 where
631 I: IntoIterator<Item = (V, T)>,
632 V: Into<String>,
633 T: Into<String>,
634 {
635 Self::new(
636 pairs
637 .into_iter()
638 .map(|(v, t)| EnumOption::new(v, t))
639 .collect::<Vec<_>>(),
640 )
641 }
642
643 pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
644 self.title = Some(title.into());
645 self
646 }
647
648 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
649 self.description = Some(description.into());
650 self
651 }
652
653 pub fn with_default(mut self, default: impl Into<Vec<String>>) -> Self {
654 self.default = Some(default.into());
655 self
656 }
657
658 pub fn min_items(mut self, min: u32) -> Self {
659 self.min_items = Some(min);
660 self
661 }
662
663 pub fn max_items(mut self, max: u32) -> Self {
664 self.max_items = Some(max);
665 self
666 }
667
668 pub fn items_range(mut self, min: u32, max: u32) -> Self {
669 self.min_items = Some(min);
670 self.max_items = Some(max);
671 self
672 }
673}
674
675#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
684#[serde(rename_all = "camelCase")]
685pub struct LegacyTitledEnumSchema {
686 #[serde(rename = "type")]
687 pub type_: StringTypeConst,
688 #[serde(rename = "enum")]
689 pub enum_values: Vec<String>,
690 pub enum_names: Vec<String>,
692 #[serde(skip_serializing_if = "Option::is_none")]
693 pub title: Option<Cow<'static, str>>,
694 #[serde(skip_serializing_if = "Option::is_none")]
695 pub description: Option<Cow<'static, str>>,
696 #[serde(skip_serializing_if = "Option::is_none")]
697 pub default: Option<String>,
698}
699
700impl LegacyTitledEnumSchema {
701 pub fn new(values: Vec<String>, names: Vec<String>) -> Self {
702 debug_assert_eq!(
703 values.len(),
704 names.len(),
705 "enum values and names must have same length"
706 );
707 Self {
708 type_: StringTypeConst,
709 enum_values: values,
710 enum_names: names,
711 title: None,
712 description: None,
713 default: None,
714 }
715 }
716
717 pub fn from_pairs<I, V, N>(pairs: I) -> Self
719 where
720 I: IntoIterator<Item = (V, N)>,
721 V: Into<String>,
722 N: Into<String>,
723 {
724 let (values, names): (Vec<_>, Vec<_>) =
725 pairs.into_iter().map(|(v, n)| (v.into(), n.into())).unzip();
726 Self::new(values, names)
727 }
728
729 pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
730 self.title = Some(title.into());
731 self
732 }
733
734 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
735 self.description = Some(description.into());
736 self
737 }
738
739 pub fn with_default(mut self, default: impl Into<String>) -> Self {
740 self.default = Some(default.into());
741 self
742 }
743}
744
745#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
755#[serde(untagged)]
756pub enum EnumSchema {
757 TitledSingleSelect(TitledSingleSelectEnumSchema),
759 TitledMultiSelect(TitledMultiSelectEnumSchema),
761 UntitledMultiSelect(UntitledMultiSelectEnumSchema),
763 UntitledSingleSelect(UntitledSingleSelectEnumSchema),
765 Legacy(LegacyTitledEnumSchema),
767}
768
769impl EnumSchema {
770 pub fn single_select<I, S>(values: I) -> Self
772 where
773 I: IntoIterator<Item = S>,
774 S: Into<String>,
775 {
776 Self::UntitledSingleSelect(UntitledSingleSelectEnumSchema::new(
777 values.into_iter().map(Into::into).collect::<Vec<_>>(),
778 ))
779 }
780
781 pub fn single_select_titled<I, V, T>(pairs: I) -> Self
783 where
784 I: IntoIterator<Item = (V, T)>,
785 V: Into<String>,
786 T: Into<String>,
787 {
788 Self::TitledSingleSelect(TitledSingleSelectEnumSchema::from_pairs(pairs))
789 }
790
791 pub fn multi_select<I, S>(values: I) -> Self
793 where
794 I: IntoIterator<Item = S>,
795 S: Into<String>,
796 {
797 Self::UntitledMultiSelect(UntitledMultiSelectEnumSchema::new(
798 values.into_iter().map(Into::into).collect::<Vec<_>>(),
799 ))
800 }
801
802 pub fn multi_select_titled<I, V, T>(pairs: I) -> Self
804 where
805 I: IntoIterator<Item = (V, T)>,
806 V: Into<String>,
807 T: Into<String>,
808 {
809 Self::TitledMultiSelect(TitledMultiSelectEnumSchema::from_pairs(pairs))
810 }
811
812 pub fn title(self, title: impl Into<Cow<'static, str>>) -> Self {
814 let title = title.into();
815 match self {
816 Self::TitledSingleSelect(s) => Self::TitledSingleSelect(s.title(title)),
817 Self::TitledMultiSelect(s) => Self::TitledMultiSelect(s.title(title)),
818 Self::UntitledMultiSelect(s) => Self::UntitledMultiSelect(s.title(title)),
819 Self::UntitledSingleSelect(s) => Self::UntitledSingleSelect(s.title(title)),
820 Self::Legacy(s) => Self::Legacy(s.title(title)),
821 }
822 }
823
824 pub fn description(self, description: impl Into<Cow<'static, str>>) -> Self {
826 let description = description.into();
827 match self {
828 Self::TitledSingleSelect(s) => Self::TitledSingleSelect(s.description(description)),
829 Self::TitledMultiSelect(s) => Self::TitledMultiSelect(s.description(description)),
830 Self::UntitledMultiSelect(s) => Self::UntitledMultiSelect(s.description(description)),
831 Self::UntitledSingleSelect(s) => Self::UntitledSingleSelect(s.description(description)),
832 Self::Legacy(s) => Self::Legacy(s.description(description)),
833 }
834 }
835}
836
837#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
839#[serde(rename_all = "camelCase")]
840pub struct ElicitationSchema {
841 #[serde(rename = "type")]
842 pub type_: ObjectTypeConst,
843 #[serde(skip_serializing_if = "Option::is_none")]
844 pub title: Option<Cow<'static, str>>,
845 pub properties: BTreeMap<String, PrimitiveSchema>,
846 #[serde(skip_serializing_if = "Option::is_none")]
847 pub required: Option<Vec<String>>,
848 #[serde(skip_serializing_if = "Option::is_none")]
849 pub description: Option<Cow<'static, str>>,
850}
851
852impl ElicitationSchema {
853 pub fn new(properties: BTreeMap<String, PrimitiveSchema>) -> Self {
854 Self {
855 type_: ObjectTypeConst,
856 title: None,
857 properties,
858 required: None,
859 description: None,
860 }
861 }
862
863 pub fn builder() -> ElicitationSchemaBuilder {
864 ElicitationSchemaBuilder::new()
865 }
866}
867
868#[derive(Debug, Default)]
870pub struct ElicitationSchemaBuilder {
871 pub properties: BTreeMap<String, PrimitiveSchema>,
872 pub required: Vec<String>,
873 pub title: Option<Cow<'static, str>>,
874 pub description: Option<Cow<'static, str>>,
875}
876
877impl ElicitationSchemaBuilder {
878 pub fn new() -> Self {
879 Self::default()
880 }
881
882 pub fn property(mut self, name: impl Into<String>, schema: PrimitiveSchema) -> Self {
883 self.properties.insert(name.into(), schema);
884 self
885 }
886
887 pub fn required_property(mut self, name: impl Into<String>, schema: PrimitiveSchema) -> Self {
888 let name_str = name.into();
889 self.required.push(name_str.clone());
890 self.properties.insert(name_str, schema);
891 self
892 }
893
894 pub fn required_email(self, name: impl Into<String>) -> Self {
895 self.required_property(name, PrimitiveSchema::String(StringSchema::email()))
896 }
897
898 pub fn optional_bool(self, name: impl Into<String>, default: bool) -> Self {
899 self.property(
900 name,
901 PrimitiveSchema::Boolean(BooleanSchema::new().with_default(default)),
902 )
903 }
904
905 pub fn required_integer(self, name: impl Into<String>, min: i64, max: i64) -> Self {
906 self.required_property(
907 name,
908 PrimitiveSchema::Integer(IntegerSchema::new().range(min, max)),
909 )
910 }
911
912 pub fn required_enum<I, S>(self, name: impl Into<String>, values: I) -> Self
914 where
915 I: IntoIterator<Item = S>,
916 S: Into<String>,
917 {
918 self.required_property(
919 name,
920 PrimitiveSchema::Enum(EnumSchema::single_select(values)),
921 )
922 }
923
924 pub fn optional_enum<I, S>(self, name: impl Into<String>, values: I, default: S) -> Self
926 where
927 I: IntoIterator<Item = S>,
928 S: Into<String>,
929 {
930 let values_vec: Vec<String> = values.into_iter().map(Into::into).collect();
931 let schema = UntitledSingleSelectEnumSchema::new(values_vec).with_default(default);
932 self.property(
933 name,
934 PrimitiveSchema::Enum(EnumSchema::UntitledSingleSelect(schema)),
935 )
936 }
937
938 pub fn required_enum_titled<I, V, T>(self, name: impl Into<String>, options: I) -> Self
940 where
941 I: IntoIterator<Item = (V, T)>,
942 V: Into<String>,
943 T: Into<String>,
944 {
945 self.required_property(
946 name,
947 PrimitiveSchema::Enum(EnumSchema::single_select_titled(options)),
948 )
949 }
950
951 pub fn required_multi_enum<I, S>(self, name: impl Into<String>, values: I) -> Self
953 where
954 I: IntoIterator<Item = S>,
955 S: Into<String>,
956 {
957 self.required_property(
958 name,
959 PrimitiveSchema::Enum(EnumSchema::multi_select(values)),
960 )
961 }
962
963 pub fn optional_multi_enum<I, S>(self, name: impl Into<String>, values: I) -> Self
965 where
966 I: IntoIterator<Item = S>,
967 S: Into<String>,
968 {
969 self.property(
970 name,
971 PrimitiveSchema::Enum(EnumSchema::multi_select(values)),
972 )
973 }
974
975 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
976 self.description = Some(description.into());
977 self
978 }
979
980 pub fn build(self) -> Result<ElicitationSchema, &'static str> {
981 if !self.required.is_empty() {
983 for field_name in &self.required {
984 if !self.properties.contains_key(field_name) {
985 return Err("Required field does not exist in properties");
986 }
987 }
988 }
989
990 Ok(ElicitationSchema {
991 type_: ObjectTypeConst,
992 title: self.title,
993 properties: self.properties,
994 required: if self.required.is_empty() {
995 None
996 } else {
997 Some(self.required)
998 },
999 description: self.description,
1000 })
1001 }
1002
1003 pub fn build_unchecked(self) -> ElicitationSchema {
1004 self.build().expect("Invalid elicitation schema")
1005 }
1006}
1007
1008#[cfg(test)]
1009mod tests {
1010 use super::*;
1011 use serde_json::json;
1012
1013 #[test]
1014 fn test_untitled_single_select_serialization() {
1015 let schema = UntitledSingleSelectEnumSchema::new(vec![
1016 "red".to_string(),
1017 "green".to_string(),
1018 "blue".to_string(),
1019 ])
1020 .title("Color")
1021 .with_default("red");
1022
1023 let json = serde_json::to_value(&schema).unwrap();
1024 assert_eq!(json["type"], "string");
1025 assert_eq!(json["enum"], json!(["red", "green", "blue"]));
1026 assert_eq!(json["title"], "Color");
1027 assert_eq!(json["default"], "red");
1028 }
1029
1030 #[test]
1031 fn test_untitled_single_select_deserialization() {
1032 let json = json!({
1033 "type": "string",
1034 "enum": ["a", "b", "c"],
1035 "title": "Choice"
1036 });
1037
1038 let schema: UntitledSingleSelectEnumSchema = serde_json::from_value(json).unwrap();
1039 assert_eq!(schema.enum_values, vec!["a", "b", "c"]);
1040 assert_eq!(schema.title.as_deref(), Some("Choice"));
1041 }
1042
1043 #[test]
1044 fn test_titled_single_select_serialization() {
1045 let schema = TitledSingleSelectEnumSchema::from_pairs([
1046 ("#FF0000", "Red"),
1047 ("#00FF00", "Green"),
1048 ("#0000FF", "Blue"),
1049 ])
1050 .title("Color Selection");
1051
1052 let json = serde_json::to_value(&schema).unwrap();
1053 assert_eq!(json["type"], "string");
1054 assert_eq!(json["oneOf"][0]["const"], "#FF0000");
1055 assert_eq!(json["oneOf"][0]["title"], "Red");
1056 assert_eq!(json["title"], "Color Selection");
1057 }
1058
1059 #[test]
1060 fn test_titled_single_select_deserialization() {
1061 let json = json!({
1062 "type": "string",
1063 "oneOf": [
1064 { "const": "val1", "title": "Value 1" },
1065 { "const": "val2", "title": "Value 2" }
1066 ]
1067 });
1068
1069 let schema: TitledSingleSelectEnumSchema = serde_json::from_value(json).unwrap();
1070 assert_eq!(schema.one_of.len(), 2);
1071 assert_eq!(schema.one_of[0].const_value, "val1");
1072 assert_eq!(schema.one_of[0].title, "Value 1");
1073 }
1074
1075 #[test]
1076 fn test_untitled_multi_select_serialization() {
1077 let schema = UntitledMultiSelectEnumSchema::new(vec![
1078 "apple".to_string(),
1079 "banana".to_string(),
1080 "cherry".to_string(),
1081 ])
1082 .min_items(1)
1083 .max_items(2)
1084 .title("Fruits");
1085
1086 let json = serde_json::to_value(&schema).unwrap();
1087 assert_eq!(json["type"], "array");
1088 assert_eq!(json["items"]["type"], "string");
1089 assert_eq!(json["items"]["enum"], json!(["apple", "banana", "cherry"]));
1090 assert_eq!(json["minItems"], 1);
1091 assert_eq!(json["maxItems"], 2);
1092 }
1093
1094 #[test]
1095 fn test_titled_multi_select_serialization() {
1096 let schema =
1097 TitledMultiSelectEnumSchema::from_pairs([("opt1", "Option 1"), ("opt2", "Option 2")])
1098 .items_range(1, 2)
1099 .with_default(vec!["opt1".to_string()]);
1100
1101 let json = serde_json::to_value(&schema).unwrap();
1102 assert_eq!(json["type"], "array");
1103 assert_eq!(json["items"]["anyOf"][0]["const"], "opt1");
1104 assert_eq!(json["items"]["anyOf"][0]["title"], "Option 1");
1105 assert_eq!(json["default"], json!(["opt1"]));
1106 }
1107
1108 #[test]
1109 fn test_legacy_enum_serialization() {
1110 let schema = LegacyTitledEnumSchema::from_pairs([("v1", "Value 1"), ("v2", "Value 2")]);
1111
1112 let json = serde_json::to_value(&schema).unwrap();
1113 assert_eq!(json["type"], "string");
1114 assert_eq!(json["enum"], json!(["v1", "v2"]));
1115 assert_eq!(json["enumNames"], json!(["Value 1", "Value 2"]));
1116 }
1117
1118 #[test]
1119 fn test_enum_schema_factory_methods() {
1120 let single = EnumSchema::single_select(["a", "b", "c"]);
1122 assert!(matches!(single, EnumSchema::UntitledSingleSelect(_)));
1123
1124 let titled = EnumSchema::single_select_titled([("val", "Title")]);
1126 assert!(matches!(titled, EnumSchema::TitledSingleSelect(_)));
1127
1128 let multi = EnumSchema::multi_select(["x", "y"]);
1130 assert!(matches!(multi, EnumSchema::UntitledMultiSelect(_)));
1131
1132 let multi_titled = EnumSchema::multi_select_titled([("a", "A"), ("b", "B")]);
1134 assert!(matches!(multi_titled, EnumSchema::TitledMultiSelect(_)));
1135 }
1136
1137 #[test]
1138 fn test_enum_schema_deserialization_dispatch() {
1139 let json = json!({
1141 "type": "string",
1142 "oneOf": [{ "const": "x", "title": "X" }]
1143 });
1144 let schema: EnumSchema = serde_json::from_value(json).unwrap();
1145 assert!(matches!(schema, EnumSchema::TitledSingleSelect(_)));
1146
1147 let json = json!({
1149 "type": "string",
1150 "enum": ["a", "b"]
1151 });
1152 let schema: EnumSchema = serde_json::from_value(json).unwrap();
1153 assert!(matches!(schema, EnumSchema::UntitledSingleSelect(_)));
1154
1155 let json = json!({
1157 "type": "array",
1158 "items": {
1159 "anyOf": [{ "const": "x", "title": "X" }]
1160 }
1161 });
1162 let schema: EnumSchema = serde_json::from_value(json).unwrap();
1163 assert!(matches!(schema, EnumSchema::TitledMultiSelect(_)));
1164
1165 let json = json!({
1167 "type": "array",
1168 "items": {
1169 "type": "string",
1170 "enum": ["a", "b"]
1171 }
1172 });
1173 let schema: EnumSchema = serde_json::from_value(json).unwrap();
1174 assert!(matches!(schema, EnumSchema::UntitledMultiSelect(_)));
1175 }
1176
1177 #[test]
1178 fn test_elicitation_builder_with_enum() {
1179 let schema = ElicitationSchema::builder()
1180 .required_enum("color", ["red", "green", "blue"])
1181 .required_enum_titled("size", [("s", "Small"), ("m", "Medium"), ("l", "Large")])
1182 .required_multi_enum("tags", ["tag1", "tag2", "tag3"])
1183 .build()
1184 .unwrap();
1185
1186 assert_eq!(
1187 schema.required,
1188 Some(vec![
1189 "color".to_string(),
1190 "size".to_string(),
1191 "tags".to_string()
1192 ])
1193 );
1194 assert!(schema.properties.contains_key("color"));
1195 assert!(schema.properties.contains_key("size"));
1196 assert!(schema.properties.contains_key("tags"));
1197 }
1198
1199 #[test]
1200 fn test_enum_schema_roundtrip() {
1201 let original = EnumSchema::single_select_titled([("#FF0000", "Red"), ("#00FF00", "Green")])
1203 .title("Pick a color")
1204 .description("Choose your favorite");
1205
1206 let json = serde_json::to_string(&original).unwrap();
1207 let parsed: EnumSchema = serde_json::from_str(&json).unwrap();
1208
1209 let json2 = serde_json::to_string(&parsed).unwrap();
1211 assert_eq!(json, json2);
1212 }
1213
1214 #[test]
1215 fn test_primitive_schema_with_enum() {
1216 let enum_schema = EnumSchema::single_select(["a", "b", "c"]);
1217 let primitive = PrimitiveSchema::Enum(enum_schema);
1218
1219 let json = serde_json::to_value(&primitive).unwrap();
1220 assert_eq!(json["type"], "string");
1221 assert_eq!(json["enum"], json!(["a", "b", "c"]));
1222 }
1223}