1use std::{collections::BTreeMap, sync::Arc};
9
10use derive_more::{Display, From};
11use schemars::{JsonSchema, Schema};
12use serde::{Deserialize, Serialize};
13use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
14
15use super::{
16 ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME, Meta, RequestId, SessionId,
17 ToolCallId,
18};
19use crate::IntoOption;
20use crate::SkipListener;
21
22#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
28#[serde(transparent)]
29#[from(forward)]
30#[non_exhaustive]
31pub struct ElicitationId(pub Arc<str>);
32
33impl ElicitationId {
34 #[must_use]
36 pub fn new(id: impl Into<Self>) -> Self {
37 id.into()
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
43#[serde(rename_all = "kebab-case")]
44#[non_exhaustive]
45pub enum StringFormat {
46 Email,
48 Uri,
50 Date,
52 DateTime,
54 #[serde(untagged)]
59 Other(String),
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
64#[serde(rename_all = "snake_case")]
65#[non_exhaustive]
66pub enum ElicitationSchemaType {
67 #[default]
69 Object,
70}
71
72#[serde_as]
74#[skip_serializing_none]
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
76#[non_exhaustive]
77pub struct EnumOption {
78 #[serde(rename = "const")]
80 pub value: String,
81 pub title: String,
83 #[serde_as(deserialize_as = "DefaultOnError")]
85 #[schemars(extend("x-deserialize-default-on-error" = true))]
86 #[serde(default)]
87 pub description: Option<String>,
88 #[serde_as(deserialize_as = "DefaultOnError")]
94 #[schemars(extend("x-deserialize-default-on-error" = true))]
95 #[serde(default)]
96 #[serde(rename = "_meta")]
97 pub meta: Option<Meta>,
98}
99
100impl EnumOption {
101 #[must_use]
103 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
104 Self {
105 value: value.into(),
106 title: title.into(),
107 description: None,
108 meta: None,
109 }
110 }
111
112 #[must_use]
114 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
115 self.description = description.into_option();
116 self
117 }
118
119 #[must_use]
125 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
126 self.meta = meta.into_option();
127 self
128 }
129}
130
131#[serde_as]
136#[skip_serializing_none]
137#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
138#[serde(rename_all = "camelCase")]
139#[non_exhaustive]
140pub struct StringPropertySchema {
141 #[serde_as(deserialize_as = "DefaultOnError")]
143 #[schemars(extend("x-deserialize-default-on-error" = true))]
144 #[serde(default)]
145 pub title: Option<String>,
146 #[serde_as(deserialize_as = "DefaultOnError")]
148 #[schemars(extend("x-deserialize-default-on-error" = true))]
149 #[serde(default)]
150 pub description: Option<String>,
151 #[serde(default)]
153 pub min_length: Option<u32>,
154 #[serde(default)]
156 pub max_length: Option<u32>,
157 #[schemars(extend("format" = "regex"))]
159 #[serde(default)]
160 pub pattern: Option<String>,
161 #[serde(default)]
163 pub format: Option<StringFormat>,
164 #[serde_as(deserialize_as = "DefaultOnError")]
166 #[schemars(extend("x-deserialize-default-on-error" = true))]
167 #[serde(default)]
168 pub default: Option<String>,
169 #[schemars(length(min = 1))]
172 #[serde(default)]
173 #[serde(rename = "enum")]
174 pub enum_values: Option<Vec<String>>,
175 #[schemars(length(min = 1))]
178 #[serde(default)]
179 #[serde(rename = "oneOf")]
180 pub one_of: Option<Vec<EnumOption>>,
181 #[serde_as(deserialize_as = "DefaultOnError")]
187 #[schemars(extend("x-deserialize-default-on-error" = true))]
188 #[serde(default)]
189 #[serde(rename = "_meta")]
190 pub meta: Option<Meta>,
191}
192
193impl StringPropertySchema {
194 #[must_use]
196 pub fn new() -> Self {
197 Self::default()
198 }
199
200 #[must_use]
202 pub fn email() -> Self {
203 Self {
204 format: Some(StringFormat::Email),
205 ..Default::default()
206 }
207 }
208
209 #[must_use]
211 pub fn uri() -> Self {
212 Self {
213 format: Some(StringFormat::Uri),
214 ..Default::default()
215 }
216 }
217
218 #[must_use]
220 pub fn date() -> Self {
221 Self {
222 format: Some(StringFormat::Date),
223 ..Default::default()
224 }
225 }
226
227 #[must_use]
229 pub fn date_time() -> Self {
230 Self {
231 format: Some(StringFormat::DateTime),
232 ..Default::default()
233 }
234 }
235
236 #[must_use]
238 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
239 self.title = title.into_option();
240 self
241 }
242
243 #[must_use]
245 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
246 self.description = description.into_option();
247 self
248 }
249
250 #[must_use]
252 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
253 self.min_length = min_length.into_option();
254 self
255 }
256
257 #[must_use]
259 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
260 self.max_length = max_length.into_option();
261 self
262 }
263
264 #[must_use]
266 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
267 self.pattern = pattern.into_option();
268 self
269 }
270
271 #[must_use]
273 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
274 self.format = format.into_option();
275 self
276 }
277
278 #[must_use]
280 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
281 self.default = default.into_option();
282 self
283 }
284
285 #[must_use]
287 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
288 self.enum_values = enum_values.into_option();
289 self
290 }
291
292 #[must_use]
294 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
295 self.one_of = one_of.into_option();
296 self
297 }
298
299 #[must_use]
305 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
306 self.meta = meta.into_option();
307 self
308 }
309}
310
311#[serde_as]
313#[skip_serializing_none]
314#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
315#[serde(rename_all = "camelCase")]
316#[non_exhaustive]
317pub struct NumberPropertySchema {
318 #[serde_as(deserialize_as = "DefaultOnError")]
320 #[schemars(extend("x-deserialize-default-on-error" = true))]
321 #[serde(default)]
322 pub title: Option<String>,
323 #[serde_as(deserialize_as = "DefaultOnError")]
325 #[schemars(extend("x-deserialize-default-on-error" = true))]
326 #[serde(default)]
327 pub description: Option<String>,
328 #[serde(default)]
330 pub minimum: Option<f64>,
331 #[serde(default)]
333 pub maximum: Option<f64>,
334 #[serde_as(deserialize_as = "DefaultOnError")]
336 #[schemars(extend("x-deserialize-default-on-error" = true))]
337 #[serde(default)]
338 pub default: Option<f64>,
339 #[serde_as(deserialize_as = "DefaultOnError")]
345 #[schemars(extend("x-deserialize-default-on-error" = true))]
346 #[serde(default)]
347 #[serde(rename = "_meta")]
348 pub meta: Option<Meta>,
349}
350
351impl NumberPropertySchema {
352 #[must_use]
354 pub fn new() -> Self {
355 Self::default()
356 }
357
358 #[must_use]
360 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
361 self.title = title.into_option();
362 self
363 }
364
365 #[must_use]
367 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
368 self.description = description.into_option();
369 self
370 }
371
372 #[must_use]
374 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
375 self.minimum = minimum.into_option();
376 self
377 }
378
379 #[must_use]
381 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
382 self.maximum = maximum.into_option();
383 self
384 }
385
386 #[must_use]
388 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
389 self.default = default.into_option();
390 self
391 }
392
393 #[must_use]
399 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
400 self.meta = meta.into_option();
401 self
402 }
403}
404
405#[serde_as]
407#[skip_serializing_none]
408#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
409#[serde(rename_all = "camelCase")]
410#[non_exhaustive]
411pub struct IntegerPropertySchema {
412 #[serde_as(deserialize_as = "DefaultOnError")]
414 #[schemars(extend("x-deserialize-default-on-error" = true))]
415 #[serde(default)]
416 pub title: Option<String>,
417 #[serde_as(deserialize_as = "DefaultOnError")]
419 #[schemars(extend("x-deserialize-default-on-error" = true))]
420 #[serde(default)]
421 pub description: Option<String>,
422 #[serde(default)]
424 pub minimum: Option<i64>,
425 #[serde(default)]
427 pub maximum: Option<i64>,
428 #[serde_as(deserialize_as = "DefaultOnError")]
430 #[schemars(extend("x-deserialize-default-on-error" = true))]
431 #[serde(default)]
432 pub default: Option<i64>,
433 #[serde_as(deserialize_as = "DefaultOnError")]
439 #[schemars(extend("x-deserialize-default-on-error" = true))]
440 #[serde(default)]
441 #[serde(rename = "_meta")]
442 pub meta: Option<Meta>,
443}
444
445impl IntegerPropertySchema {
446 #[must_use]
448 pub fn new() -> Self {
449 Self::default()
450 }
451
452 #[must_use]
454 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
455 self.title = title.into_option();
456 self
457 }
458
459 #[must_use]
461 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
462 self.description = description.into_option();
463 self
464 }
465
466 #[must_use]
468 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
469 self.minimum = minimum.into_option();
470 self
471 }
472
473 #[must_use]
475 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
476 self.maximum = maximum.into_option();
477 self
478 }
479
480 #[must_use]
482 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
483 self.default = default.into_option();
484 self
485 }
486
487 #[must_use]
493 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
494 self.meta = meta.into_option();
495 self
496 }
497}
498
499#[serde_as]
501#[skip_serializing_none]
502#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
503#[serde(rename_all = "camelCase")]
504#[non_exhaustive]
505pub struct BooleanPropertySchema {
506 #[serde_as(deserialize_as = "DefaultOnError")]
508 #[schemars(extend("x-deserialize-default-on-error" = true))]
509 #[serde(default)]
510 pub title: Option<String>,
511 #[serde_as(deserialize_as = "DefaultOnError")]
513 #[schemars(extend("x-deserialize-default-on-error" = true))]
514 #[serde(default)]
515 pub description: Option<String>,
516 #[serde_as(deserialize_as = "DefaultOnError")]
518 #[schemars(extend("x-deserialize-default-on-error" = true))]
519 #[serde(default)]
520 pub default: Option<bool>,
521 #[serde_as(deserialize_as = "DefaultOnError")]
527 #[schemars(extend("x-deserialize-default-on-error" = true))]
528 #[serde(default)]
529 #[serde(rename = "_meta")]
530 pub meta: Option<Meta>,
531}
532
533impl BooleanPropertySchema {
534 #[must_use]
536 pub fn new() -> Self {
537 Self::default()
538 }
539
540 #[must_use]
542 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
543 self.title = title.into_option();
544 self
545 }
546
547 #[must_use]
549 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
550 self.description = description.into_option();
551 self
552 }
553
554 #[must_use]
556 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
557 self.default = default.into_option();
558 self
559 }
560
561 #[must_use]
567 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
568 self.meta = meta.into_option();
569 self
570 }
571}
572
573#[serde_as]
575#[skip_serializing_none]
576#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
577#[non_exhaustive]
578pub struct StringMultiSelectItems {
579 #[schemars(length(min = 1))]
581 #[serde(rename = "enum")]
582 pub values: Vec<String>,
583 #[serde_as(deserialize_as = "DefaultOnError")]
589 #[schemars(extend("x-deserialize-default-on-error" = true))]
590 #[serde(default)]
591 #[serde(rename = "_meta")]
592 pub meta: Option<Meta>,
593}
594
595impl StringMultiSelectItems {
596 #[must_use]
598 pub fn new(values: Vec<String>) -> Self {
599 Self { values, meta: None }
600 }
601
602 #[must_use]
608 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
609 self.meta = meta.into_option();
610 self
611 }
612}
613
614#[serde_as]
616#[skip_serializing_none]
617#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
618#[non_exhaustive]
619pub struct TitledMultiSelectItems {
620 #[schemars(length(min = 1))]
622 #[serde(rename = "anyOf")]
623 pub options: Vec<EnumOption>,
624 #[serde_as(deserialize_as = "DefaultOnError")]
630 #[schemars(extend("x-deserialize-default-on-error" = true))]
631 #[serde(default)]
632 #[serde(rename = "_meta")]
633 pub meta: Option<Meta>,
634}
635
636impl TitledMultiSelectItems {
637 #[must_use]
639 pub fn new(options: Vec<EnumOption>) -> Self {
640 Self {
641 options,
642 meta: None,
643 }
644 }
645
646 #[must_use]
652 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
653 self.meta = meta.into_option();
654 self
655 }
656}
657
658#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
664#[schemars(inline)]
665#[schemars(transform = other_multi_select_items_schema)]
666#[serde(rename_all = "camelCase")]
667#[non_exhaustive]
668pub struct OtherMultiSelectItems {
669 #[serde(rename = "type")]
675 pub type_: String,
676 #[serde(flatten)]
678 pub fields: BTreeMap<String, serde_json::Value>,
679}
680
681impl OtherMultiSelectItems {
682 #[must_use]
684 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
685 fields.remove("type");
686 Self {
687 type_: type_.into(),
688 fields,
689 }
690 }
691}
692
693impl<'de> Deserialize<'de> for OtherMultiSelectItems {
694 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
695 where
696 D: serde::Deserializer<'de>,
697 {
698 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
699 let type_ = fields
700 .remove("type")
701 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
702 let serde_json::Value::String(type_) = type_ else {
703 return Err(serde::de::Error::custom("`type` must be a string"));
704 };
705
706 if is_known_multi_select_item_type(&type_) {
707 return Err(serde::de::Error::custom(format!(
708 "known multi-select item type `{type_}` did not match its schema"
709 )));
710 }
711
712 Ok(Self { type_, fields })
713 }
714}
715
716const KNOWN_MULTI_SELECT_ITEM_TYPES: &[&str] = &["string"];
717
718fn is_known_multi_select_item_type(type_: &str) -> bool {
719 KNOWN_MULTI_SELECT_ITEM_TYPES.contains(&type_)
720}
721
722fn other_multi_select_items_schema(schema: &mut Schema) {
723 super::schema_util::reject_known_string_discriminators(
724 schema,
725 "type",
726 KNOWN_MULTI_SELECT_ITEM_TYPES,
727 );
728}
729
730#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
732#[serde(tag = "type", rename_all = "snake_case")]
733#[non_exhaustive]
734pub enum MultiSelectItems {
735 String(StringMultiSelectItems),
737 #[serde(untagged)]
739 Other(OtherMultiSelectItems),
740 #[serde(untagged)]
742 Titled(TitledMultiSelectItems),
743}
744
745#[serde_as]
747#[skip_serializing_none]
748#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
749#[serde(rename_all = "camelCase")]
750#[non_exhaustive]
751pub struct MultiSelectPropertySchema {
752 #[serde_as(deserialize_as = "DefaultOnError")]
754 #[schemars(extend("x-deserialize-default-on-error" = true))]
755 #[serde(default)]
756 pub title: Option<String>,
757 #[serde_as(deserialize_as = "DefaultOnError")]
759 #[schemars(extend("x-deserialize-default-on-error" = true))]
760 #[serde(default)]
761 pub description: Option<String>,
762 #[serde(default)]
764 pub min_items: Option<u64>,
765 #[serde(default)]
767 pub max_items: Option<u64>,
768 pub items: MultiSelectItems,
770 #[serde_as(deserialize_as = "DefaultOnError<Option<VecSkipError<_, SkipListener>>>")]
772 #[schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true))]
773 #[serde(default)]
774 pub default: Option<Vec<String>>,
775 #[serde_as(deserialize_as = "DefaultOnError")]
781 #[schemars(extend("x-deserialize-default-on-error" = true))]
782 #[serde(default)]
783 #[serde(rename = "_meta")]
784 pub meta: Option<Meta>,
785}
786
787impl MultiSelectPropertySchema {
788 #[must_use]
790 pub fn new(values: Vec<String>) -> Self {
791 Self {
792 title: None,
793 description: None,
794 min_items: None,
795 max_items: None,
796 items: MultiSelectItems::String(StringMultiSelectItems::new(values)),
797 default: None,
798 meta: None,
799 }
800 }
801
802 #[must_use]
804 pub fn titled(options: Vec<EnumOption>) -> Self {
805 Self {
806 title: None,
807 description: None,
808 min_items: None,
809 max_items: None,
810 items: MultiSelectItems::Titled(TitledMultiSelectItems {
811 options,
812 meta: None,
813 }),
814 default: None,
815 meta: None,
816 }
817 }
818
819 #[must_use]
821 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
822 self.title = title.into_option();
823 self
824 }
825
826 #[must_use]
828 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
829 self.description = description.into_option();
830 self
831 }
832
833 #[must_use]
835 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
836 self.min_items = min_items.into_option();
837 self
838 }
839
840 #[must_use]
842 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
843 self.max_items = max_items.into_option();
844 self
845 }
846
847 #[must_use]
849 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
850 self.default = default.into_option();
851 self
852 }
853
854 #[must_use]
860 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
861 self.meta = meta.into_option();
862 self
863 }
864}
865
866#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
872#[serde(tag = "type", rename_all = "snake_case")]
873#[non_exhaustive]
874pub enum ElicitationPropertySchema {
875 String(StringPropertySchema),
877 Number(NumberPropertySchema),
879 Integer(IntegerPropertySchema),
881 Boolean(BooleanPropertySchema),
883 Array(MultiSelectPropertySchema),
885 #[serde(untagged)]
895 Other(OtherElicitationPropertySchema),
896}
897
898#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
904#[schemars(inline)]
905#[schemars(transform = other_elicitation_property_schema_schema)]
906#[serde(rename_all = "camelCase")]
907#[non_exhaustive]
908pub struct OtherElicitationPropertySchema {
909 #[serde(rename = "type")]
915 pub type_: String,
916 #[serde(flatten)]
918 pub fields: BTreeMap<String, serde_json::Value>,
919}
920
921impl OtherElicitationPropertySchema {
922 #[must_use]
924 pub fn new(type_: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
925 fields.remove("type");
926 Self {
927 type_: type_.into(),
928 fields,
929 }
930 }
931}
932
933impl<'de> Deserialize<'de> for OtherElicitationPropertySchema {
934 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
935 where
936 D: serde::Deserializer<'de>,
937 {
938 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
939 let type_ = fields
940 .remove("type")
941 .ok_or_else(|| serde::de::Error::missing_field("type"))?;
942 let serde_json::Value::String(type_) = type_ else {
943 return Err(serde::de::Error::custom("`type` must be a string"));
944 };
945
946 if is_known_elicitation_property_schema_type(&type_) {
947 return Err(serde::de::Error::custom(format!(
948 "known elicitation property schema type `{type_}` did not match its schema"
949 )));
950 }
951
952 Ok(Self { type_, fields })
953 }
954}
955
956const KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES: &[&str] =
957 &["string", "number", "integer", "boolean", "array"];
958
959fn is_known_elicitation_property_schema_type(type_: &str) -> bool {
960 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES.contains(&type_)
961}
962
963fn other_elicitation_property_schema_schema(schema: &mut Schema) {
964 super::schema_util::reject_known_string_discriminators(
965 schema,
966 "type",
967 KNOWN_ELICITATION_PROPERTY_SCHEMA_TYPES,
968 );
969}
970
971impl From<StringPropertySchema> for ElicitationPropertySchema {
972 fn from(schema: StringPropertySchema) -> Self {
973 Self::String(schema)
974 }
975}
976
977impl From<NumberPropertySchema> for ElicitationPropertySchema {
978 fn from(schema: NumberPropertySchema) -> Self {
979 Self::Number(schema)
980 }
981}
982
983impl From<IntegerPropertySchema> for ElicitationPropertySchema {
984 fn from(schema: IntegerPropertySchema) -> Self {
985 Self::Integer(schema)
986 }
987}
988
989impl From<BooleanPropertySchema> for ElicitationPropertySchema {
990 fn from(schema: BooleanPropertySchema) -> Self {
991 Self::Boolean(schema)
992 }
993}
994
995impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
996 fn from(schema: MultiSelectPropertySchema) -> Self {
997 Self::Array(schema)
998 }
999}
1000
1001fn default_object_type() -> ElicitationSchemaType {
1002 ElicitationSchemaType::Object
1003}
1004
1005#[serde_as]
1010#[skip_serializing_none]
1011#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1012#[serde(rename_all = "camelCase")]
1013#[non_exhaustive]
1014pub struct ElicitationSchema {
1015 #[serde_as(deserialize_as = "DefaultOnError")]
1017 #[schemars(extend("x-deserialize-default-on-error" = true))]
1018 #[serde(rename = "type", default = "default_object_type")]
1019 pub type_: ElicitationSchemaType,
1020 #[serde_as(deserialize_as = "DefaultOnError")]
1022 #[schemars(extend("x-deserialize-default-on-error" = true))]
1023 #[serde(default)]
1024 pub title: Option<String>,
1025 #[serde(default)]
1027 pub properties: BTreeMap<String, ElicitationPropertySchema>,
1028 #[serde(default)]
1030 pub required: Option<Vec<String>>,
1031 #[serde_as(deserialize_as = "DefaultOnError")]
1033 #[schemars(extend("x-deserialize-default-on-error" = true))]
1034 #[serde(default)]
1035 pub description: Option<String>,
1036 #[serde_as(deserialize_as = "DefaultOnError")]
1042 #[schemars(extend("x-deserialize-default-on-error" = true))]
1043 #[serde(default)]
1044 #[serde(rename = "_meta")]
1045 pub meta: Option<Meta>,
1046}
1047
1048impl Default for ElicitationSchema {
1049 fn default() -> Self {
1050 Self {
1051 type_: default_object_type(),
1052 title: None,
1053 properties: BTreeMap::new(),
1054 required: None,
1055 description: None,
1056 meta: None,
1057 }
1058 }
1059}
1060
1061impl ElicitationSchema {
1062 #[must_use]
1064 pub fn new() -> Self {
1065 Self::default()
1066 }
1067
1068 #[must_use]
1070 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
1071 self.title = title.into_option();
1072 self
1073 }
1074
1075 #[must_use]
1077 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
1078 self.description = description.into_option();
1079 self
1080 }
1081
1082 #[must_use]
1088 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1089 self.meta = meta.into_option();
1090 self
1091 }
1092
1093 #[must_use]
1095 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
1096 where
1097 S: Into<ElicitationPropertySchema>,
1098 {
1099 let name = name.into();
1100 self.properties.insert(name.clone(), schema.into());
1101
1102 if required {
1103 let required_fields = self.required.get_or_insert_with(Vec::new);
1104 if !required_fields.contains(&name) {
1105 required_fields.push(name);
1106 }
1107 } else if let Some(required_fields) = &mut self.required {
1108 required_fields.retain(|field| field != &name);
1109
1110 if required_fields.is_empty() {
1111 self.required = None;
1112 }
1113 }
1114
1115 self
1116 }
1117
1118 #[must_use]
1120 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
1121 self.property(name, StringPropertySchema::new(), required)
1122 }
1123
1124 #[must_use]
1126 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
1127 self.property(name, StringPropertySchema::email(), required)
1128 }
1129
1130 #[must_use]
1132 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
1133 self.property(name, StringPropertySchema::uri(), required)
1134 }
1135
1136 #[must_use]
1138 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
1139 self.property(name, StringPropertySchema::date(), required)
1140 }
1141
1142 #[must_use]
1144 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
1145 self.property(name, StringPropertySchema::date_time(), required)
1146 }
1147
1148 #[must_use]
1150 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
1151 self.property(
1152 name,
1153 NumberPropertySchema::new().minimum(min).maximum(max),
1154 required,
1155 )
1156 }
1157
1158 #[must_use]
1160 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
1161 self.property(
1162 name,
1163 IntegerPropertySchema::new().minimum(min).maximum(max),
1164 required,
1165 )
1166 }
1167
1168 #[must_use]
1170 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
1171 self.property(name, BooleanPropertySchema::new(), required)
1172 }
1173}
1174
1175#[serde_as]
1181#[skip_serializing_none]
1182#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1183#[serde(rename_all = "camelCase")]
1184#[non_exhaustive]
1185pub struct ElicitationCapabilities {
1186 #[serde_as(deserialize_as = "DefaultOnError")]
1191 #[schemars(extend("x-deserialize-default-on-error" = true))]
1192 #[serde(default)]
1193 pub form: Option<ElicitationFormCapabilities>,
1194 #[serde_as(deserialize_as = "DefaultOnError")]
1199 #[schemars(extend("x-deserialize-default-on-error" = true))]
1200 #[serde(default)]
1201 pub url: Option<ElicitationUrlCapabilities>,
1202 #[serde_as(deserialize_as = "DefaultOnError")]
1208 #[schemars(extend("x-deserialize-default-on-error" = true))]
1209 #[serde(default)]
1210 #[serde(rename = "_meta")]
1211 pub meta: Option<Meta>,
1212}
1213
1214impl ElicitationCapabilities {
1215 #[must_use]
1217 pub fn new() -> Self {
1218 Self::default()
1219 }
1220
1221 #[must_use]
1226 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
1227 self.form = form.into_option();
1228 self
1229 }
1230
1231 #[must_use]
1236 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
1237 self.url = url.into_option();
1238 self
1239 }
1240
1241 #[must_use]
1247 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1248 self.meta = meta.into_option();
1249 self
1250 }
1251}
1252
1253#[serde_as]
1261#[skip_serializing_none]
1262#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1263#[serde(rename_all = "camelCase")]
1264#[non_exhaustive]
1265pub struct ElicitationFormCapabilities {
1266 #[serde_as(deserialize_as = "DefaultOnError")]
1272 #[schemars(extend("x-deserialize-default-on-error" = true))]
1273 #[serde(default)]
1274 #[serde(rename = "_meta")]
1275 pub meta: Option<Meta>,
1276}
1277
1278impl ElicitationFormCapabilities {
1279 #[must_use]
1281 pub fn new() -> Self {
1282 Self::default()
1283 }
1284
1285 #[must_use]
1291 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1292 self.meta = meta.into_option();
1293 self
1294 }
1295}
1296
1297#[serde_as]
1305#[skip_serializing_none]
1306#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1307#[serde(rename_all = "camelCase")]
1308#[non_exhaustive]
1309pub struct ElicitationUrlCapabilities {
1310 #[serde_as(deserialize_as = "DefaultOnError")]
1316 #[schemars(extend("x-deserialize-default-on-error" = true))]
1317 #[serde(default)]
1318 #[serde(rename = "_meta")]
1319 pub meta: Option<Meta>,
1320}
1321
1322impl ElicitationUrlCapabilities {
1323 #[must_use]
1325 pub fn new() -> Self {
1326 Self::default()
1327 }
1328
1329 #[must_use]
1335 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1336 self.meta = meta.into_option();
1337 self
1338 }
1339}
1340
1341#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1347#[serde(untagged)]
1348#[non_exhaustive]
1349pub enum ElicitationScope {
1350 Session(ElicitationSessionScope),
1352 Request(ElicitationRequestScope),
1355}
1356
1357#[serde_as]
1367#[skip_serializing_none]
1368#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1369#[serde(rename_all = "camelCase")]
1370#[non_exhaustive]
1371pub struct ElicitationSessionScope {
1372 pub session_id: SessionId,
1374 #[serde_as(deserialize_as = "DefaultOnError")]
1376 #[schemars(extend("x-deserialize-default-on-error" = true))]
1377 #[serde(default)]
1378 pub tool_call_id: Option<ToolCallId>,
1379}
1380
1381impl ElicitationSessionScope {
1382 #[must_use]
1384 pub fn new(session_id: impl Into<SessionId>) -> Self {
1385 Self {
1386 session_id: session_id.into(),
1387 tool_call_id: None,
1388 }
1389 }
1390
1391 #[must_use]
1393 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1394 self.tool_call_id = tool_call_id.into_option();
1395 self
1396 }
1397}
1398
1399#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1406#[serde(rename_all = "camelCase")]
1407#[non_exhaustive]
1408pub struct ElicitationRequestScope {
1409 pub request_id: RequestId,
1411}
1412
1413impl ElicitationRequestScope {
1414 #[must_use]
1416 pub fn new(request_id: impl Into<RequestId>) -> Self {
1417 Self {
1418 request_id: request_id.into(),
1419 }
1420 }
1421}
1422
1423impl From<ElicitationSessionScope> for ElicitationScope {
1424 fn from(scope: ElicitationSessionScope) -> Self {
1425 Self::Session(scope)
1426 }
1427}
1428
1429impl From<ElicitationRequestScope> for ElicitationScope {
1430 fn from(scope: ElicitationRequestScope) -> Self {
1431 Self::Request(scope)
1432 }
1433}
1434
1435#[serde_as]
1445#[skip_serializing_none]
1446#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1447#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1448#[serde(rename_all = "camelCase")]
1449#[non_exhaustive]
1450pub struct CreateElicitationRequest {
1451 #[serde(flatten)]
1453 pub mode: ElicitationMode,
1454 pub message: String,
1456 #[serde_as(deserialize_as = "DefaultOnError")]
1462 #[schemars(extend("x-deserialize-default-on-error" = true))]
1463 #[serde(default)]
1464 #[serde(rename = "_meta")]
1465 pub meta: Option<Meta>,
1466}
1467
1468impl CreateElicitationRequest {
1469 #[must_use]
1471 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1472 Self {
1473 mode: mode.into(),
1474 message: message.into(),
1475 meta: None,
1476 }
1477 }
1478
1479 #[must_use]
1481 pub fn scope(&self) -> &ElicitationScope {
1482 self.mode.scope()
1483 }
1484
1485 #[must_use]
1491 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1492 self.meta = meta.into_option();
1493 self
1494 }
1495}
1496
1497#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1503#[serde(tag = "mode", rename_all = "snake_case")]
1504#[non_exhaustive]
1505pub enum ElicitationMode {
1506 Form(ElicitationFormMode),
1508 Url(ElicitationUrlMode),
1510 #[serde(untagged)]
1520 Other(OtherElicitationMode),
1521}
1522
1523#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1529#[schemars(inline)]
1530#[schemars(transform = other_elicitation_mode_schema)]
1531#[serde(rename_all = "camelCase")]
1532#[non_exhaustive]
1533pub struct OtherElicitationMode {
1534 pub mode: String,
1540 #[serde(flatten)]
1542 pub scope: ElicitationScope,
1543 #[serde(flatten)]
1545 pub fields: BTreeMap<String, serde_json::Value>,
1546}
1547
1548impl OtherElicitationMode {
1549 #[must_use]
1551 pub fn new(
1552 mode: impl Into<String>,
1553 scope: impl Into<ElicitationScope>,
1554 mut fields: BTreeMap<String, serde_json::Value>,
1555 ) -> Self {
1556 fields.remove("mode");
1557 remove_elicitation_scope_fields(&mut fields);
1558 Self {
1559 mode: mode.into(),
1560 scope: scope.into(),
1561 fields,
1562 }
1563 }
1564}
1565
1566impl<'de> Deserialize<'de> for OtherElicitationMode {
1567 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1568 where
1569 D: serde::Deserializer<'de>,
1570 {
1571 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1572 let mode = fields
1573 .remove("mode")
1574 .ok_or_else(|| serde::de::Error::missing_field("mode"))?;
1575 let serde_json::Value::String(mode) = mode else {
1576 return Err(serde::de::Error::custom("`mode` must be a string"));
1577 };
1578
1579 if is_known_elicitation_mode(&mode) {
1580 return Err(serde::de::Error::custom(format!(
1581 "known elicitation mode `{mode}` did not match its schema"
1582 )));
1583 }
1584
1585 let scope = serde_json::from_value::<ElicitationScope>(serde_json::Value::Object(
1586 fields.clone().into_iter().collect(),
1587 ))
1588 .map_err(serde::de::Error::custom)?;
1589 remove_elicitation_scope_fields(&mut fields);
1590
1591 Ok(Self {
1592 mode,
1593 scope,
1594 fields,
1595 })
1596 }
1597}
1598
1599const KNOWN_ELICITATION_MODES: &[&str] = &["form", "url"];
1600
1601fn is_known_elicitation_mode(mode: &str) -> bool {
1602 KNOWN_ELICITATION_MODES.contains(&mode)
1603}
1604
1605fn remove_elicitation_scope_fields(fields: &mut BTreeMap<String, serde_json::Value>) {
1606 fields.remove("sessionId");
1607 fields.remove("toolCallId");
1608 fields.remove("requestId");
1609}
1610
1611fn other_elicitation_mode_schema(schema: &mut Schema) {
1612 super::schema_util::reject_known_string_discriminators(schema, "mode", KNOWN_ELICITATION_MODES);
1613}
1614
1615impl From<ElicitationFormMode> for ElicitationMode {
1616 fn from(mode: ElicitationFormMode) -> Self {
1617 Self::Form(mode)
1618 }
1619}
1620
1621impl From<ElicitationUrlMode> for ElicitationMode {
1622 fn from(mode: ElicitationUrlMode) -> Self {
1623 Self::Url(mode)
1624 }
1625}
1626
1627impl From<OtherElicitationMode> for ElicitationMode {
1628 fn from(mode: OtherElicitationMode) -> Self {
1629 Self::Other(mode)
1630 }
1631}
1632
1633impl ElicitationMode {
1634 #[must_use]
1636 pub fn scope(&self) -> &ElicitationScope {
1637 match self {
1638 Self::Form(f) => &f.scope,
1639 Self::Url(u) => &u.scope,
1640 Self::Other(other) => &other.scope,
1641 }
1642 }
1643}
1644
1645#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1651#[serde(rename_all = "camelCase")]
1652#[non_exhaustive]
1653pub struct ElicitationFormMode {
1654 #[serde(flatten)]
1656 pub scope: ElicitationScope,
1657 pub requested_schema: ElicitationSchema,
1659}
1660
1661impl ElicitationFormMode {
1662 #[must_use]
1664 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1665 Self {
1666 scope: scope.into(),
1667 requested_schema,
1668 }
1669 }
1670}
1671
1672#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1678#[serde(rename_all = "camelCase")]
1679#[non_exhaustive]
1680pub struct ElicitationUrlMode {
1681 #[serde(flatten)]
1683 pub scope: ElicitationScope,
1684 pub elicitation_id: ElicitationId,
1686 #[schemars(url)]
1688 pub url: String,
1689}
1690
1691impl ElicitationUrlMode {
1692 #[must_use]
1694 pub fn new(
1695 scope: impl Into<ElicitationScope>,
1696 elicitation_id: impl Into<ElicitationId>,
1697 url: impl Into<String>,
1698 ) -> Self {
1699 Self {
1700 scope: scope.into(),
1701 elicitation_id: elicitation_id.into(),
1702 url: url.into(),
1703 }
1704 }
1705}
1706
1707#[serde_as]
1713#[skip_serializing_none]
1714#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1715#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1716#[serde(rename_all = "camelCase")]
1717#[non_exhaustive]
1718pub struct CreateElicitationResponse {
1719 #[serde(flatten)]
1721 pub action: ElicitationAction,
1722 #[serde_as(deserialize_as = "DefaultOnError")]
1728 #[schemars(extend("x-deserialize-default-on-error" = true))]
1729 #[serde(default)]
1730 #[serde(rename = "_meta")]
1731 pub meta: Option<Meta>,
1732}
1733
1734impl CreateElicitationResponse {
1735 #[must_use]
1737 pub fn new(action: impl Into<ElicitationAction>) -> Self {
1738 Self {
1739 action: action.into(),
1740 meta: None,
1741 }
1742 }
1743
1744 #[must_use]
1750 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1751 self.meta = meta.into_option();
1752 self
1753 }
1754}
1755
1756#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1762#[serde(tag = "action", rename_all = "snake_case")]
1763#[non_exhaustive]
1764pub enum ElicitationAction {
1765 Accept(ElicitationAcceptAction),
1767 Decline,
1769 Cancel,
1771 #[serde(untagged)]
1781 Other(OtherElicitationAction),
1782}
1783
1784#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq)]
1790#[schemars(inline)]
1791#[schemars(transform = other_elicitation_action_schema)]
1792#[serde(rename_all = "camelCase")]
1793#[non_exhaustive]
1794pub struct OtherElicitationAction {
1795 pub action: String,
1801 #[serde(flatten)]
1803 pub fields: BTreeMap<String, serde_json::Value>,
1804}
1805
1806impl OtherElicitationAction {
1807 #[must_use]
1809 pub fn new(action: impl Into<String>, mut fields: BTreeMap<String, serde_json::Value>) -> Self {
1810 fields.remove("action");
1811 Self {
1812 action: action.into(),
1813 fields,
1814 }
1815 }
1816}
1817
1818impl<'de> Deserialize<'de> for OtherElicitationAction {
1819 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1820 where
1821 D: serde::Deserializer<'de>,
1822 {
1823 let mut fields = BTreeMap::<String, serde_json::Value>::deserialize(deserializer)?;
1824 let action = fields
1825 .remove("action")
1826 .ok_or_else(|| serde::de::Error::missing_field("action"))?;
1827 let serde_json::Value::String(action) = action else {
1828 return Err(serde::de::Error::custom("`action` must be a string"));
1829 };
1830
1831 if is_known_elicitation_action(&action) {
1832 return Err(serde::de::Error::custom(format!(
1833 "known elicitation action `{action}` did not match its schema"
1834 )));
1835 }
1836
1837 Ok(Self { action, fields })
1838 }
1839}
1840
1841const KNOWN_ELICITATION_ACTIONS: &[&str] = &["accept", "decline", "cancel"];
1842
1843fn is_known_elicitation_action(action: &str) -> bool {
1844 KNOWN_ELICITATION_ACTIONS.contains(&action)
1845}
1846
1847fn other_elicitation_action_schema(schema: &mut Schema) {
1848 super::schema_util::reject_known_string_discriminators(
1849 schema,
1850 "action",
1851 KNOWN_ELICITATION_ACTIONS,
1852 );
1853}
1854
1855impl From<ElicitationAcceptAction> for ElicitationAction {
1856 fn from(action: ElicitationAcceptAction) -> Self {
1857 Self::Accept(action)
1858 }
1859}
1860
1861impl From<OtherElicitationAction> for ElicitationAction {
1862 fn from(action: OtherElicitationAction) -> Self {
1863 Self::Other(action)
1864 }
1865}
1866
1867#[serde_as]
1873#[skip_serializing_none]
1874#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1875#[serde(rename_all = "camelCase")]
1876#[non_exhaustive]
1877pub struct ElicitationAcceptAction {
1878 #[serde(default)]
1880 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1881}
1882
1883impl ElicitationAcceptAction {
1884 #[must_use]
1886 pub fn new() -> Self {
1887 Self { content: None }
1888 }
1889
1890 #[must_use]
1892 pub fn content(
1893 mut self,
1894 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1895 ) -> Self {
1896 self.content = content.into_option();
1897 self
1898 }
1899}
1900
1901#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1903#[serde(untagged)]
1904#[non_exhaustive]
1905pub enum ElicitationContentValue {
1906 String(String),
1908 Integer(i64),
1910 Number(f64),
1912 Boolean(bool),
1914 StringArray(Vec<String>),
1916}
1917
1918impl From<String> for ElicitationContentValue {
1919 fn from(value: String) -> Self {
1920 Self::String(value)
1921 }
1922}
1923
1924impl From<&str> for ElicitationContentValue {
1925 fn from(value: &str) -> Self {
1926 Self::String(value.to_string())
1927 }
1928}
1929
1930impl From<i64> for ElicitationContentValue {
1931 fn from(value: i64) -> Self {
1932 Self::Integer(value)
1933 }
1934}
1935
1936impl From<i32> for ElicitationContentValue {
1937 fn from(value: i32) -> Self {
1938 Self::Integer(i64::from(value))
1939 }
1940}
1941
1942impl From<f64> for ElicitationContentValue {
1943 fn from(value: f64) -> Self {
1944 Self::Number(value)
1945 }
1946}
1947
1948impl From<bool> for ElicitationContentValue {
1949 fn from(value: bool) -> Self {
1950 Self::Boolean(value)
1951 }
1952}
1953
1954impl From<Vec<String>> for ElicitationContentValue {
1955 fn from(value: Vec<String>) -> Self {
1956 Self::StringArray(value)
1957 }
1958}
1959
1960impl From<Vec<&str>> for ElicitationContentValue {
1961 fn from(value: Vec<&str>) -> Self {
1962 Self::StringArray(value.into_iter().map(str::to_string).collect())
1963 }
1964}
1965
1966impl Default for ElicitationAcceptAction {
1967 fn default() -> Self {
1968 Self::new()
1969 }
1970}
1971
1972#[serde_as]
1978#[skip_serializing_none]
1979#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1980#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
1981#[serde(rename_all = "camelCase")]
1982#[non_exhaustive]
1983pub struct CompleteElicitationNotification {
1984 pub elicitation_id: ElicitationId,
1986 #[serde_as(deserialize_as = "DefaultOnError")]
1992 #[schemars(extend("x-deserialize-default-on-error" = true))]
1993 #[serde(default)]
1994 #[serde(rename = "_meta")]
1995 pub meta: Option<Meta>,
1996}
1997
1998impl CompleteElicitationNotification {
1999 #[must_use]
2001 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
2002 Self {
2003 elicitation_id: elicitation_id.into(),
2004 meta: None,
2005 }
2006 }
2007
2008 #[must_use]
2014 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
2015 self.meta = meta.into_option();
2016 self
2017 }
2018}
2019
2020#[cfg(test)]
2021mod tests {
2022 use super::*;
2023 use serde_json::json;
2024
2025 #[test]
2026 fn form_mode_request_serialization() {
2027 let schema = ElicitationSchema::new().string("name", true);
2028 let req = CreateElicitationRequest::new(
2029 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
2030 "Please enter your name",
2031 );
2032
2033 let json = serde_json::to_value(&req).unwrap();
2034 assert_eq!(json["sessionId"], "sess_1");
2035 assert!(json.get("toolCallId").is_none());
2036 assert_eq!(json["mode"], "form");
2037 assert_eq!(json["message"], "Please enter your name");
2038 assert!(json["requestedSchema"].is_object());
2039 assert_eq!(json["requestedSchema"]["type"], "object");
2040 assert_eq!(
2041 json["requestedSchema"]["properties"]["name"]["type"],
2042 "string"
2043 );
2044
2045 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2046 assert_eq!(
2047 *roundtripped.scope(),
2048 ElicitationSessionScope::new("sess_1").into()
2049 );
2050 assert_eq!(roundtripped.message, "Please enter your name");
2051 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
2052 }
2053
2054 #[test]
2055 fn url_mode_request_serialization() {
2056 let req = CreateElicitationRequest::new(
2057 ElicitationUrlMode::new(
2058 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
2059 "elic_1",
2060 "https://example.com/auth",
2061 ),
2062 "Please authenticate",
2063 );
2064
2065 let json = serde_json::to_value(&req).unwrap();
2066 assert_eq!(json["sessionId"], "sess_2");
2067 assert_eq!(json["toolCallId"], "tc_1");
2068 assert_eq!(json["mode"], "url");
2069 assert_eq!(json["elicitationId"], "elic_1");
2070 assert_eq!(json["url"], "https://example.com/auth");
2071 assert_eq!(json["message"], "Please authenticate");
2072
2073 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2074 assert_eq!(
2075 *roundtripped.scope(),
2076 ElicitationSessionScope::new("sess_2")
2077 .tool_call_id("tc_1")
2078 .into()
2079 );
2080 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2081 }
2082
2083 #[test]
2084 fn response_accept_serialization() {
2085 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
2086 ElicitationAcceptAction::new().content(BTreeMap::from([(
2087 "name".to_string(),
2088 ElicitationContentValue::from("Alice"),
2089 )])),
2090 ));
2091
2092 let json = serde_json::to_value(&resp).unwrap();
2093 assert_eq!(json["action"], "accept");
2094 assert_eq!(json["content"]["name"], "Alice");
2095
2096 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2097 assert!(matches!(
2098 roundtripped.action,
2099 ElicitationAction::Accept(ElicitationAcceptAction {
2100 content: Some(_),
2101 ..
2102 })
2103 ));
2104 }
2105
2106 #[test]
2107 fn response_decline_serialization() {
2108 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
2109
2110 let json = serde_json::to_value(&resp).unwrap();
2111 assert_eq!(json["action"], "decline");
2112
2113 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2114 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2115 }
2116
2117 #[test]
2118 fn response_cancel_serialization() {
2119 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
2120
2121 let json = serde_json::to_value(&resp).unwrap();
2122 assert_eq!(json["action"], "cancel");
2123
2124 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2125 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2126 }
2127
2128 #[test]
2129 fn unknown_action_response_serialization() {
2130 let json = json!({
2131 "action": "_defer",
2132 "reason": "waiting",
2133 "retryAfterMs": 1000
2134 });
2135
2136 let resp: CreateElicitationResponse = serde_json::from_value(json.clone()).unwrap();
2137 let ElicitationAction::Other(other) = &resp.action else {
2138 panic!("expected unknown elicitation action");
2139 };
2140
2141 assert_eq!(other.action, "_defer");
2142 assert_eq!(other.fields.get("reason"), Some(&json!("waiting")));
2143 assert_eq!(other.fields.get("retryAfterMs"), Some(&json!(1000)));
2144 assert_eq!(serde_json::to_value(&resp).unwrap(), json);
2145 }
2146
2147 #[test]
2148 fn unknown_action_does_not_hide_known_action() {
2149 assert!(
2150 serde_json::from_value::<OtherElicitationAction>(json!({
2151 "action": "accept",
2152 "content": {}
2153 }))
2154 .is_err()
2155 );
2156 assert!(serde_json::from_value::<ElicitationAction>(json!({})).is_err());
2157 }
2158
2159 #[test]
2160 fn url_mode_request_scope_serialization() {
2161 let req = CreateElicitationRequest::new(
2162 ElicitationUrlMode::new(
2163 ElicitationRequestScope::new(RequestId::Number(42)),
2164 "elic_2",
2165 "https://example.com/setup",
2166 ),
2167 "Please complete setup",
2168 );
2169
2170 let json = serde_json::to_value(&req).unwrap();
2171 assert_eq!(json["requestId"], 42);
2172 assert!(json.get("sessionId").is_none());
2173 assert_eq!(json["mode"], "url");
2174 assert_eq!(json["elicitationId"], "elic_2");
2175 assert_eq!(json["url"], "https://example.com/setup");
2176 assert_eq!(json["message"], "Please complete setup");
2177
2178 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2179 assert_eq!(
2180 *roundtripped.scope(),
2181 ElicitationRequestScope::new(RequestId::Number(42)).into()
2182 );
2183 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
2184 }
2185
2186 #[test]
2187 fn unknown_mode_request_serialization() {
2188 let json = json!({
2189 "requestId": 42,
2190 "mode": "_browser",
2191 "message": "Open a browser window",
2192 "target": "login"
2193 });
2194
2195 let req: CreateElicitationRequest = serde_json::from_value(json.clone()).unwrap();
2196 let ElicitationMode::Other(other) = &req.mode else {
2197 panic!("expected unknown elicitation mode");
2198 };
2199
2200 assert_eq!(other.mode, "_browser");
2201 assert_eq!(
2202 other.scope,
2203 ElicitationRequestScope::new(RequestId::Number(42)).into()
2204 );
2205 assert_eq!(other.fields.get("target"), Some(&json!("login")));
2206 assert_eq!(
2207 *req.scope(),
2208 ElicitationRequestScope::new(RequestId::Number(42)).into()
2209 );
2210 assert_eq!(serde_json::to_value(&req).unwrap(), json);
2211 }
2212
2213 #[test]
2214 fn unknown_mode_does_not_hide_malformed_known_mode() {
2215 let missing_requested_schema = json!({
2216 "requestId": 42,
2217 "mode": "form",
2218 "message": "Enter your name"
2219 });
2220
2221 assert!(
2222 serde_json::from_value::<CreateElicitationRequest>(missing_requested_schema).is_err()
2223 );
2224 assert!(serde_json::from_value::<ElicitationMode>(json!({})).is_err());
2225 }
2226
2227 #[test]
2228 fn request_scope_request_serialization() {
2229 let req = CreateElicitationRequest::new(
2230 ElicitationFormMode::new(
2231 ElicitationRequestScope::new(RequestId::Number(99)),
2232 ElicitationSchema::new().string("workspace", true),
2233 ),
2234 "Enter workspace name",
2235 );
2236
2237 let json = serde_json::to_value(&req).unwrap();
2238 assert_eq!(json["requestId"], 99);
2239 assert!(json.get("sessionId").is_none());
2240
2241 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2242 assert_eq!(
2243 *roundtripped.scope(),
2244 ElicitationRequestScope::new(RequestId::Number(99)).into()
2245 );
2246 }
2247
2248 #[test]
2252 fn client_response_serialization_accept() {
2253 use crate::v2::ClientResponse;
2254
2255 let resp =
2256 ClientResponse::CreateElicitationResponse(Box::new(CreateElicitationResponse::new(
2257 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from(
2258 [("name".to_string(), ElicitationContentValue::from("Alice"))],
2259 ))),
2260 )));
2261 let json = serde_json::to_value(&resp).unwrap();
2262 assert_eq!(json["action"], "accept");
2263 assert_eq!(json["content"]["name"], "Alice");
2264
2265 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2267 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
2268 }
2269
2270 #[test]
2271 fn client_response_serialization_decline() {
2272 use crate::v2::ClientResponse;
2273
2274 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2275 CreateElicitationResponse::new(ElicitationAction::Decline),
2276 ));
2277 let json = serde_json::to_value(&resp).unwrap();
2278 assert_eq!(json["action"], "decline");
2279
2280 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2281 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
2282 }
2283
2284 #[test]
2285 fn client_response_serialization_cancel() {
2286 use crate::v2::ClientResponse;
2287
2288 let resp = ClientResponse::CreateElicitationResponse(Box::new(
2289 CreateElicitationResponse::new(ElicitationAction::Cancel),
2290 ));
2291 let json = serde_json::to_value(&resp).unwrap();
2292 assert_eq!(json["action"], "cancel");
2293
2294 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
2295 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
2296 }
2297
2298 #[test]
2301 fn request_tolerates_extra_fields() {
2302 let json = json!({
2303 "sessionId": "sess_1",
2304 "mode": "form",
2305 "message": "Enter your name",
2306 "requestedSchema": {
2307 "type": "object",
2308 "properties": {
2309 "name": { "type": "string", "title": "Name" }
2310 },
2311 "required": ["name"]
2312 },
2313 "unknownStringField": "hello",
2314 "unknownNumberField": 42
2315 });
2316
2317 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
2318 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
2319 assert_eq!(req.message, "Enter your name");
2320 assert!(matches!(req.mode, ElicitationMode::Form(_)));
2321 }
2322
2323 #[test]
2324 fn completion_notification_serialization() {
2325 let notif = CompleteElicitationNotification::new("elic_1");
2326
2327 let json = serde_json::to_value(¬if).unwrap();
2328 assert_eq!(json["elicitationId"], "elic_1");
2329
2330 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
2331 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
2332 }
2333
2334 #[test]
2335 fn capabilities_form_only() {
2336 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
2337
2338 let json = serde_json::to_value(&caps).unwrap();
2339 assert!(json["form"].is_object());
2340 assert!(json.get("url").is_none());
2341
2342 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2343 assert!(roundtripped.form.is_some());
2344 assert!(roundtripped.url.is_none());
2345 }
2346
2347 #[test]
2348 fn capabilities_url_only() {
2349 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
2350
2351 let json = serde_json::to_value(&caps).unwrap();
2352 assert!(json.get("form").is_none());
2353 assert!(json["url"].is_object());
2354
2355 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2356 assert!(roundtripped.form.is_none());
2357 assert!(roundtripped.url.is_some());
2358 }
2359
2360 #[test]
2361 fn capabilities_both() {
2362 let caps = ElicitationCapabilities::new()
2363 .form(ElicitationFormCapabilities::new())
2364 .url(ElicitationUrlCapabilities::new());
2365
2366 let json = serde_json::to_value(&caps).unwrap();
2367 assert!(json["form"].is_object());
2368 assert!(json["url"].is_object());
2369
2370 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
2371 assert!(roundtripped.form.is_some());
2372 assert!(roundtripped.url.is_some());
2373 }
2374
2375 #[test]
2376 fn schema_default_sets_object_type() {
2377 let schema = ElicitationSchema::default();
2378
2379 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2380 assert!(schema.properties.is_empty());
2381
2382 let json = serde_json::to_value(&schema).unwrap();
2383 assert_eq!(json["type"], "object");
2384 }
2385
2386 #[test]
2387 fn schema_builder_serialization() {
2388 let schema = ElicitationSchema::new()
2389 .string("name", true)
2390 .email("email", true)
2391 .integer("age", 0, 150, true)
2392 .boolean("newsletter", false)
2393 .description("User registration");
2394
2395 let json = serde_json::to_value(&schema).unwrap();
2396 assert_eq!(json["type"], "object");
2397 assert_eq!(json["description"], "User registration");
2398 assert_eq!(json["properties"]["name"]["type"], "string");
2399 assert_eq!(json["properties"]["email"]["type"], "string");
2400 assert_eq!(json["properties"]["email"]["format"], "email");
2401 assert_eq!(json["properties"]["age"]["type"], "integer");
2402 assert_eq!(json["properties"]["age"]["minimum"], 0);
2403 assert_eq!(json["properties"]["age"]["maximum"], 150);
2404 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
2405
2406 let required = json["required"].as_array().unwrap();
2407 assert!(required.contains(&json!("name")));
2408 assert!(required.contains(&json!("email")));
2409 assert!(required.contains(&json!("age")));
2410 assert!(!required.contains(&json!("newsletter")));
2411
2412 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2413 assert_eq!(roundtripped.properties.len(), 4);
2414 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
2415 }
2416
2417 #[test]
2418 fn schema_string_enum_serialization() {
2419 let schema = ElicitationSchema::new().property(
2420 "color",
2421 StringPropertySchema::new().enum_values(vec![
2422 "red".into(),
2423 "green".into(),
2424 "blue".into(),
2425 ]),
2426 true,
2427 );
2428
2429 let json = serde_json::to_value(&schema).unwrap();
2430 assert_eq!(json["properties"]["color"]["type"], "string");
2431 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
2432 assert_eq!(enum_vals.len(), 3);
2433
2434 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2435 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
2436 {
2437 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
2438 } else {
2439 panic!("expected String variant");
2440 }
2441 }
2442
2443 #[test]
2444 fn schema_multi_select_serialization() {
2445 let schema = ElicitationSchema::new().property(
2446 "colors",
2447 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
2448 .min_items(1)
2449 .max_items(3),
2450 false,
2451 );
2452
2453 let json = serde_json::to_value(&schema).unwrap();
2454 assert_eq!(json["properties"]["colors"]["type"], "array");
2455 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
2456 assert_eq!(json["properties"]["colors"]["minItems"], 1);
2457 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
2458
2459 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2460 let ElicitationPropertySchema::Array(array) =
2461 roundtripped.properties.get("colors").unwrap()
2462 else {
2463 panic!("expected Array variant");
2464 };
2465 let MultiSelectItems::String(items) = &array.items else {
2466 panic!("expected String multi-select items");
2467 };
2468 assert_eq!(items.values.len(), 3);
2469 }
2470
2471 #[test]
2472 fn multi_select_titled_items_keep_mcp_shape() {
2473 let items = MultiSelectItems::Titled(TitledMultiSelectItems::new(vec![EnumOption::new(
2474 "#ff0000", "Red",
2475 )]));
2476
2477 let json = serde_json::to_value(&items).unwrap();
2478 assert!(json.get("type").is_none());
2479 assert_eq!(json["anyOf"][0]["const"], "#ff0000");
2480 assert_eq!(json["anyOf"][0]["title"], "Red");
2481
2482 let roundtripped: MultiSelectItems = serde_json::from_value(json).unwrap();
2483 assert!(matches!(roundtripped, MultiSelectItems::Titled(_)));
2484 }
2485
2486 #[test]
2487 fn multi_select_items_preserve_unknown_type() {
2488 let json = json!({
2489 "type": "_token",
2490 "format": "workspace",
2491 "anyOf": [
2492 { "const": "repo", "title": "Repository" }
2493 ]
2494 });
2495
2496 let items: MultiSelectItems = serde_json::from_value(json.clone()).unwrap();
2497 let MultiSelectItems::Other(other) = &items else {
2498 panic!("expected unknown multi-select items");
2499 };
2500
2501 assert_eq!(other.type_, "_token");
2502 assert_eq!(other.fields.get("format"), Some(&json!("workspace")));
2503 assert_eq!(other.fields.get("anyOf"), Some(&json["anyOf"]));
2504 assert_eq!(serde_json::to_value(&items).unwrap(), json);
2505 }
2506
2507 #[test]
2508 fn multi_select_items_unknown_does_not_hide_malformed_string_type() {
2509 assert!(
2510 serde_json::from_value::<MultiSelectItems>(json!({
2511 "type": "string"
2512 }))
2513 .is_err()
2514 );
2515 assert!(
2516 serde_json::from_value::<OtherMultiSelectItems>(json!({
2517 "type": "string",
2518 "format": "workspace"
2519 }))
2520 .is_err()
2521 );
2522 }
2523
2524 #[test]
2525 fn property_schema_preserves_unknown_type() {
2526 let schema: ElicitationSchema = serde_json::from_value(json!({
2527 "type": "object",
2528 "properties": {
2529 "location": {
2530 "type": "_location",
2531 "title": "Location",
2532 "precision": "city"
2533 }
2534 }
2535 }))
2536 .unwrap();
2537
2538 let ElicitationPropertySchema::Other(unknown) = schema.properties.get("location").unwrap()
2539 else {
2540 panic!("expected unknown property schema");
2541 };
2542
2543 assert_eq!(unknown.type_, "_location");
2544 assert_eq!(unknown.fields.get("title"), Some(&json!("Location")));
2545 assert_eq!(unknown.fields.get("precision"), Some(&json!("city")));
2546 assert_eq!(
2547 serde_json::to_value(ElicitationPropertySchema::Other(unknown.clone())).unwrap(),
2548 json!({
2549 "type": "_location",
2550 "title": "Location",
2551 "precision": "city"
2552 })
2553 );
2554 }
2555
2556 #[test]
2557 fn property_schema_unknown_does_not_hide_malformed_known_type() {
2558 assert!(
2559 serde_json::from_value::<ElicitationPropertySchema>(json!({
2560 "type": "array"
2561 }))
2562 .is_err()
2563 );
2564 assert!(serde_json::from_value::<ElicitationPropertySchema>(json!({})).is_err());
2565 }
2566
2567 #[test]
2568 fn schema_titled_enum_serialization() {
2569 let schema = ElicitationSchema::new().property(
2570 "country",
2571 StringPropertySchema::new().one_of(vec![
2572 EnumOption::new("us", "United States").description("Use US English spelling."),
2573 EnumOption::new("uk", "United Kingdom"),
2574 ]),
2575 true,
2576 );
2577
2578 let json = serde_json::to_value(&schema).unwrap();
2579 assert_eq!(json["properties"]["country"]["type"], "string");
2580 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
2581 assert_eq!(one_of.len(), 2);
2582 assert_eq!(one_of[0]["const"], "us");
2583 assert_eq!(one_of[0]["title"], "United States");
2584 assert_eq!(one_of[0]["description"], "Use US English spelling.");
2585 assert!(one_of[1].get("description").is_none());
2586
2587 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2588 if let ElicitationPropertySchema::String(s) =
2589 roundtripped.properties.get("country").unwrap()
2590 {
2591 let one_of = s.one_of.as_ref().unwrap();
2592 assert_eq!(one_of.len(), 2);
2593 assert_eq!(
2594 one_of[0].description.as_deref(),
2595 Some("Use US English spelling.")
2596 );
2597 assert!(one_of[1].description.is_none());
2598 } else {
2599 panic!("expected String variant");
2600 }
2601 }
2602
2603 #[test]
2604 fn schema_number_property_serialization() {
2605 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
2606
2607 let json = serde_json::to_value(&schema).unwrap();
2608 assert_eq!(json["properties"]["rating"]["type"], "number");
2609 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
2610 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
2611
2612 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2613 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
2614 {
2615 assert_eq!(n.minimum, Some(0.0));
2616 assert_eq!(n.maximum, Some(5.0));
2617 } else {
2618 panic!("expected Number variant");
2619 }
2620 }
2621
2622 #[test]
2623 fn schema_string_format_serialization() {
2624 let schema = ElicitationSchema::new()
2625 .uri("website", true)
2626 .date("birthday", true)
2627 .date_time("updated_at", false);
2628
2629 let json = serde_json::to_value(&schema).unwrap();
2630 assert_eq!(json["properties"]["website"]["type"], "string");
2631 assert_eq!(json["properties"]["website"]["format"], "uri");
2632 assert_eq!(json["properties"]["birthday"]["type"], "string");
2633 assert_eq!(json["properties"]["birthday"]["format"], "date");
2634 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2635 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2636
2637 let required = json["required"].as_array().unwrap();
2638 assert!(required.contains(&json!("website")));
2639 assert!(required.contains(&json!("birthday")));
2640 assert!(!required.contains(&json!("updated_at")));
2641 }
2642
2643 #[test]
2644 fn schema_string_pattern_serialization() {
2645 let schema = ElicitationSchema::new().property(
2646 "name",
2647 StringPropertySchema::new()
2648 .min_length(1)
2649 .max_length(64)
2650 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2651 true,
2652 );
2653
2654 let json = serde_json::to_value(&schema).unwrap();
2655 assert_eq!(json["properties"]["name"]["type"], "string");
2656 assert_eq!(
2657 json["properties"]["name"]["pattern"],
2658 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2659 );
2660
2661 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2662 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2663 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2664 } else {
2665 panic!("expected String variant");
2666 }
2667 }
2668
2669 #[test]
2670 fn schema_property_updates_required_state() {
2671 let schema = ElicitationSchema::new()
2672 .string("name", true)
2673 .email("name", false);
2674
2675 let json = serde_json::to_value(&schema).unwrap();
2676 assert!(json.get("required").is_none());
2677 assert_eq!(json["properties"]["name"]["format"], "email");
2678 }
2679
2680 #[test]
2681 fn schema_defaults_invalid_object_type() {
2682 let schema = serde_json::from_value::<ElicitationSchema>(json!({
2683 "type": "array",
2684 "properties": {
2685 "name": {
2686 "type": "string"
2687 }
2688 }
2689 }))
2690 .unwrap();
2691
2692 assert_eq!(schema.type_, ElicitationSchemaType::Object);
2693 assert!(schema.properties.contains_key("name"));
2694 }
2695
2696 #[test]
2697 fn titled_multi_select_items_reject_one_of() {
2698 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2699 "oneOf": [
2700 {
2701 "const": "red",
2702 "title": "Red"
2703 }
2704 ]
2705 }))
2706 .unwrap_err();
2707
2708 assert!(err.to_string().contains("missing field `anyOf`"));
2709 }
2710
2711 #[test]
2712 fn response_accept_rejects_non_object_content() {
2713 assert!(
2714 serde_json::from_value::<CreateElicitationResponse>(json!({
2715 "action": "accept",
2716 "content": "Alice"
2717 }))
2718 .is_err()
2719 );
2720 }
2721
2722 #[test]
2723 fn response_accept_rejects_nested_object_content() {
2724 assert!(
2725 serde_json::from_value::<CreateElicitationResponse>(json!({
2726 "action": "accept",
2727 "content": {
2728 "profile": {
2729 "name": "Alice"
2730 }
2731 }
2732 }))
2733 .is_err()
2734 );
2735 }
2736
2737 #[test]
2738 fn response_accept_allows_primitive_and_string_array_content() {
2739 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2740 ElicitationAcceptAction::new().content(BTreeMap::from([
2741 ("name".to_string(), ElicitationContentValue::from("Alice")),
2742 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2743 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2744 (
2745 "subscribed".to_string(),
2746 ElicitationContentValue::from(true),
2747 ),
2748 (
2749 "tags".to_string(),
2750 ElicitationContentValue::from(vec!["rust", "acp"]),
2751 ),
2752 ])),
2753 ));
2754
2755 let json = serde_json::to_value(&response).unwrap();
2756 assert_eq!(json["action"], "accept");
2757 assert_eq!(json["content"]["name"], "Alice");
2758 assert_eq!(json["content"]["age"], 30);
2759 assert_eq!(json["content"]["score"], 9.5);
2760 assert_eq!(json["content"]["subscribed"], true);
2761 assert_eq!(json["content"]["tags"][0], "rust");
2762 assert_eq!(json["content"]["tags"][1], "acp");
2763 }
2764}