1use std::{collections::BTreeMap, sync::Arc};
9
10use derive_more::{Display, From};
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13use serde_with::{DefaultOnError, serde_as, skip_serializing_none};
14
15use crate::{
16 ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME, IntoOption, Meta, RequestId,
17 SessionId, ToolCallId,
18};
19
20#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
26#[serde(transparent)]
27#[from(Arc<str>, String, &'static str)]
28#[non_exhaustive]
29pub struct ElicitationId(pub Arc<str>);
30
31impl ElicitationId {
32 #[must_use]
33 pub fn new(id: impl Into<Arc<str>>) -> Self {
34 Self(id.into())
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
40#[serde(rename_all = "kebab-case")]
41#[non_exhaustive]
42pub enum StringFormat {
43 Email,
45 Uri,
47 Date,
49 DateTime,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
55#[serde(rename_all = "snake_case")]
56#[non_exhaustive]
57pub enum ElicitationSchemaType {
58 #[default]
60 Object,
61}
62
63#[skip_serializing_none]
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
66#[non_exhaustive]
67pub struct EnumOption {
68 #[serde(rename = "const")]
70 pub value: String,
71 pub title: String,
73 #[serde(rename = "_meta")]
79 pub meta: Option<Meta>,
80}
81
82impl EnumOption {
83 #[must_use]
85 pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
86 Self {
87 value: value.into(),
88 title: title.into(),
89 meta: None,
90 }
91 }
92
93 #[must_use]
99 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
100 self.meta = meta.into_option();
101 self
102 }
103}
104
105#[skip_serializing_none]
110#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
111#[serde(rename_all = "camelCase")]
112#[non_exhaustive]
113pub struct StringPropertySchema {
114 pub title: Option<String>,
116 pub description: Option<String>,
118 pub min_length: Option<u32>,
120 pub max_length: Option<u32>,
122 pub pattern: Option<String>,
124 pub format: Option<StringFormat>,
126 pub default: Option<String>,
128 #[serde(rename = "enum")]
130 pub enum_values: Option<Vec<String>>,
131 #[serde(rename = "oneOf")]
133 pub one_of: Option<Vec<EnumOption>>,
134 #[serde(rename = "_meta")]
140 pub meta: Option<Meta>,
141}
142
143impl StringPropertySchema {
144 #[must_use]
146 pub fn new() -> Self {
147 Self::default()
148 }
149
150 #[must_use]
152 pub fn email() -> Self {
153 Self {
154 format: Some(StringFormat::Email),
155 ..Default::default()
156 }
157 }
158
159 #[must_use]
161 pub fn uri() -> Self {
162 Self {
163 format: Some(StringFormat::Uri),
164 ..Default::default()
165 }
166 }
167
168 #[must_use]
170 pub fn date() -> Self {
171 Self {
172 format: Some(StringFormat::Date),
173 ..Default::default()
174 }
175 }
176
177 #[must_use]
179 pub fn date_time() -> Self {
180 Self {
181 format: Some(StringFormat::DateTime),
182 ..Default::default()
183 }
184 }
185
186 #[must_use]
188 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
189 self.title = title.into_option();
190 self
191 }
192
193 #[must_use]
195 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
196 self.description = description.into_option();
197 self
198 }
199
200 #[must_use]
202 pub fn min_length(mut self, min_length: impl IntoOption<u32>) -> Self {
203 self.min_length = min_length.into_option();
204 self
205 }
206
207 #[must_use]
209 pub fn max_length(mut self, max_length: impl IntoOption<u32>) -> Self {
210 self.max_length = max_length.into_option();
211 self
212 }
213
214 #[must_use]
216 pub fn pattern(mut self, pattern: impl IntoOption<String>) -> Self {
217 self.pattern = pattern.into_option();
218 self
219 }
220
221 #[must_use]
223 pub fn format(mut self, format: impl IntoOption<StringFormat>) -> Self {
224 self.format = format.into_option();
225 self
226 }
227
228 #[must_use]
230 pub fn default_value(mut self, default: impl IntoOption<String>) -> Self {
231 self.default = default.into_option();
232 self
233 }
234
235 #[must_use]
237 pub fn enum_values(mut self, enum_values: impl IntoOption<Vec<String>>) -> Self {
238 self.enum_values = enum_values.into_option();
239 self
240 }
241
242 #[must_use]
244 pub fn one_of(mut self, one_of: impl IntoOption<Vec<EnumOption>>) -> Self {
245 self.one_of = one_of.into_option();
246 self
247 }
248
249 #[must_use]
255 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
256 self.meta = meta.into_option();
257 self
258 }
259}
260
261#[skip_serializing_none]
263#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
264#[serde(rename_all = "camelCase")]
265#[non_exhaustive]
266pub struct NumberPropertySchema {
267 pub title: Option<String>,
269 pub description: Option<String>,
271 pub minimum: Option<f64>,
273 pub maximum: Option<f64>,
275 pub default: Option<f64>,
277 #[serde(rename = "_meta")]
283 pub meta: Option<Meta>,
284}
285
286impl NumberPropertySchema {
287 #[must_use]
289 pub fn new() -> Self {
290 Self::default()
291 }
292
293 #[must_use]
295 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
296 self.title = title.into_option();
297 self
298 }
299
300 #[must_use]
302 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
303 self.description = description.into_option();
304 self
305 }
306
307 #[must_use]
309 pub fn minimum(mut self, minimum: impl IntoOption<f64>) -> Self {
310 self.minimum = minimum.into_option();
311 self
312 }
313
314 #[must_use]
316 pub fn maximum(mut self, maximum: impl IntoOption<f64>) -> Self {
317 self.maximum = maximum.into_option();
318 self
319 }
320
321 #[must_use]
323 pub fn default_value(mut self, default: impl IntoOption<f64>) -> Self {
324 self.default = default.into_option();
325 self
326 }
327
328 #[must_use]
334 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
335 self.meta = meta.into_option();
336 self
337 }
338}
339
340#[skip_serializing_none]
342#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
343#[serde(rename_all = "camelCase")]
344#[non_exhaustive]
345pub struct IntegerPropertySchema {
346 pub title: Option<String>,
348 pub description: Option<String>,
350 pub minimum: Option<i64>,
352 pub maximum: Option<i64>,
354 pub default: Option<i64>,
356 #[serde(rename = "_meta")]
362 pub meta: Option<Meta>,
363}
364
365impl IntegerPropertySchema {
366 #[must_use]
368 pub fn new() -> Self {
369 Self::default()
370 }
371
372 #[must_use]
374 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
375 self.title = title.into_option();
376 self
377 }
378
379 #[must_use]
381 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
382 self.description = description.into_option();
383 self
384 }
385
386 #[must_use]
388 pub fn minimum(mut self, minimum: impl IntoOption<i64>) -> Self {
389 self.minimum = minimum.into_option();
390 self
391 }
392
393 #[must_use]
395 pub fn maximum(mut self, maximum: impl IntoOption<i64>) -> Self {
396 self.maximum = maximum.into_option();
397 self
398 }
399
400 #[must_use]
402 pub fn default_value(mut self, default: impl IntoOption<i64>) -> Self {
403 self.default = default.into_option();
404 self
405 }
406
407 #[must_use]
413 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
414 self.meta = meta.into_option();
415 self
416 }
417}
418
419#[skip_serializing_none]
421#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
422#[serde(rename_all = "camelCase")]
423#[non_exhaustive]
424pub struct BooleanPropertySchema {
425 pub title: Option<String>,
427 pub description: Option<String>,
429 pub default: Option<bool>,
431 #[serde(rename = "_meta")]
437 pub meta: Option<Meta>,
438}
439
440impl BooleanPropertySchema {
441 #[must_use]
443 pub fn new() -> Self {
444 Self::default()
445 }
446
447 #[must_use]
449 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
450 self.title = title.into_option();
451 self
452 }
453
454 #[must_use]
456 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
457 self.description = description.into_option();
458 self
459 }
460
461 #[must_use]
463 pub fn default_value(mut self, default: impl IntoOption<bool>) -> Self {
464 self.default = default.into_option();
465 self
466 }
467
468 #[must_use]
474 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
475 self.meta = meta.into_option();
476 self
477 }
478}
479
480#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
482#[serde(rename_all = "snake_case")]
483#[non_exhaustive]
484pub enum ElicitationStringType {
485 #[default]
487 String,
488}
489
490#[skip_serializing_none]
492#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
493#[non_exhaustive]
494pub struct UntitledMultiSelectItems {
495 #[serde(rename = "type")]
497 pub type_: ElicitationStringType,
498 #[serde(rename = "enum")]
500 pub values: Vec<String>,
501 #[serde(rename = "_meta")]
507 pub meta: Option<Meta>,
508}
509
510impl UntitledMultiSelectItems {
511 #[must_use]
513 pub fn new(type_: ElicitationStringType, values: Vec<String>) -> Self {
514 Self {
515 type_,
516 values,
517 meta: None,
518 }
519 }
520
521 #[must_use]
527 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
528 self.meta = meta.into_option();
529 self
530 }
531}
532
533#[skip_serializing_none]
535#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
536#[non_exhaustive]
537pub struct TitledMultiSelectItems {
538 #[serde(rename = "anyOf")]
540 pub options: Vec<EnumOption>,
541 #[serde(rename = "_meta")]
547 pub meta: Option<Meta>,
548}
549
550impl TitledMultiSelectItems {
551 #[must_use]
553 pub fn new(options: Vec<EnumOption>) -> Self {
554 Self {
555 options,
556 meta: None,
557 }
558 }
559
560 #[must_use]
566 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
567 self.meta = meta.into_option();
568 self
569 }
570}
571
572#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
574#[serde(untagged)]
575#[non_exhaustive]
576pub enum MultiSelectItems {
577 Untitled(UntitledMultiSelectItems),
579 Titled(TitledMultiSelectItems),
581}
582
583#[skip_serializing_none]
585#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
586#[serde(rename_all = "camelCase")]
587#[non_exhaustive]
588pub struct MultiSelectPropertySchema {
589 pub title: Option<String>,
591 pub description: Option<String>,
593 pub min_items: Option<u64>,
595 pub max_items: Option<u64>,
597 pub items: MultiSelectItems,
599 pub default: Option<Vec<String>>,
601 #[serde(rename = "_meta")]
607 pub meta: Option<Meta>,
608}
609
610impl MultiSelectPropertySchema {
611 #[must_use]
613 pub fn new(values: Vec<String>) -> Self {
614 Self {
615 title: None,
616 description: None,
617 min_items: None,
618 max_items: None,
619 items: MultiSelectItems::Untitled(UntitledMultiSelectItems::new(
620 ElicitationStringType::String,
621 values,
622 )),
623 default: None,
624 meta: None,
625 }
626 }
627
628 #[must_use]
630 pub fn titled(options: Vec<EnumOption>) -> Self {
631 Self {
632 title: None,
633 description: None,
634 min_items: None,
635 max_items: None,
636 items: MultiSelectItems::Titled(TitledMultiSelectItems::new(options)),
637 default: None,
638 meta: None,
639 }
640 }
641
642 #[must_use]
644 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
645 self.title = title.into_option();
646 self
647 }
648
649 #[must_use]
651 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
652 self.description = description.into_option();
653 self
654 }
655
656 #[must_use]
658 pub fn min_items(mut self, min_items: impl IntoOption<u64>) -> Self {
659 self.min_items = min_items.into_option();
660 self
661 }
662
663 #[must_use]
665 pub fn max_items(mut self, max_items: impl IntoOption<u64>) -> Self {
666 self.max_items = max_items.into_option();
667 self
668 }
669
670 #[must_use]
672 pub fn default_value(mut self, default: impl IntoOption<Vec<String>>) -> Self {
673 self.default = default.into_option();
674 self
675 }
676
677 #[must_use]
683 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
684 self.meta = meta.into_option();
685 self
686 }
687}
688
689#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
695#[serde(tag = "type", rename_all = "snake_case")]
696#[schemars(extend("discriminator" = {"propertyName": "type"}))]
697#[non_exhaustive]
698pub enum ElicitationPropertySchema {
699 String(StringPropertySchema),
701 Number(NumberPropertySchema),
703 Integer(IntegerPropertySchema),
705 Boolean(BooleanPropertySchema),
707 Array(MultiSelectPropertySchema),
709}
710
711impl From<StringPropertySchema> for ElicitationPropertySchema {
712 fn from(schema: StringPropertySchema) -> Self {
713 Self::String(schema)
714 }
715}
716
717impl From<NumberPropertySchema> for ElicitationPropertySchema {
718 fn from(schema: NumberPropertySchema) -> Self {
719 Self::Number(schema)
720 }
721}
722
723impl From<IntegerPropertySchema> for ElicitationPropertySchema {
724 fn from(schema: IntegerPropertySchema) -> Self {
725 Self::Integer(schema)
726 }
727}
728
729impl From<BooleanPropertySchema> for ElicitationPropertySchema {
730 fn from(schema: BooleanPropertySchema) -> Self {
731 Self::Boolean(schema)
732 }
733}
734
735impl From<MultiSelectPropertySchema> for ElicitationPropertySchema {
736 fn from(schema: MultiSelectPropertySchema) -> Self {
737 Self::Array(schema)
738 }
739}
740
741fn default_object_type() -> ElicitationSchemaType {
742 ElicitationSchemaType::Object
743}
744
745#[skip_serializing_none]
750#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
751#[serde(rename_all = "camelCase")]
752#[non_exhaustive]
753pub struct ElicitationSchema {
754 #[serde(rename = "type", default = "default_object_type")]
756 pub type_: ElicitationSchemaType,
757 pub title: Option<String>,
759 #[serde(default)]
761 pub properties: BTreeMap<String, ElicitationPropertySchema>,
762 pub required: Option<Vec<String>>,
764 pub description: Option<String>,
766 #[serde(rename = "_meta")]
772 pub meta: Option<Meta>,
773}
774
775impl Default for ElicitationSchema {
776 fn default() -> Self {
777 Self {
778 type_: default_object_type(),
779 title: None,
780 properties: BTreeMap::new(),
781 required: None,
782 description: None,
783 meta: None,
784 }
785 }
786}
787
788impl ElicitationSchema {
789 #[must_use]
791 pub fn new() -> Self {
792 Self::default()
793 }
794
795 #[must_use]
797 pub fn title(mut self, title: impl IntoOption<String>) -> Self {
798 self.title = title.into_option();
799 self
800 }
801
802 #[must_use]
804 pub fn description(mut self, description: impl IntoOption<String>) -> Self {
805 self.description = description.into_option();
806 self
807 }
808
809 #[must_use]
815 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
816 self.meta = meta.into_option();
817 self
818 }
819
820 #[must_use]
822 pub fn property<S>(mut self, name: impl Into<String>, schema: S, required: bool) -> Self
823 where
824 S: Into<ElicitationPropertySchema>,
825 {
826 let name = name.into();
827 self.properties.insert(name.clone(), schema.into());
828
829 if required {
830 let required_fields = self.required.get_or_insert_with(Vec::new);
831 if !required_fields.contains(&name) {
832 required_fields.push(name);
833 }
834 } else if let Some(required_fields) = &mut self.required {
835 required_fields.retain(|field| field != &name);
836
837 if required_fields.is_empty() {
838 self.required = None;
839 }
840 }
841
842 self
843 }
844
845 #[must_use]
847 pub fn string(self, name: impl Into<String>, required: bool) -> Self {
848 self.property(name, StringPropertySchema::new(), required)
849 }
850
851 #[must_use]
853 pub fn email(self, name: impl Into<String>, required: bool) -> Self {
854 self.property(name, StringPropertySchema::email(), required)
855 }
856
857 #[must_use]
859 pub fn uri(self, name: impl Into<String>, required: bool) -> Self {
860 self.property(name, StringPropertySchema::uri(), required)
861 }
862
863 #[must_use]
865 pub fn date(self, name: impl Into<String>, required: bool) -> Self {
866 self.property(name, StringPropertySchema::date(), required)
867 }
868
869 #[must_use]
871 pub fn date_time(self, name: impl Into<String>, required: bool) -> Self {
872 self.property(name, StringPropertySchema::date_time(), required)
873 }
874
875 #[must_use]
877 pub fn number(self, name: impl Into<String>, min: f64, max: f64, required: bool) -> Self {
878 self.property(
879 name,
880 NumberPropertySchema::new().minimum(min).maximum(max),
881 required,
882 )
883 }
884
885 #[must_use]
887 pub fn integer(self, name: impl Into<String>, min: i64, max: i64, required: bool) -> Self {
888 self.property(
889 name,
890 IntegerPropertySchema::new().minimum(min).maximum(max),
891 required,
892 )
893 }
894
895 #[must_use]
897 pub fn boolean(self, name: impl Into<String>, required: bool) -> Self {
898 self.property(name, BooleanPropertySchema::new(), required)
899 }
900}
901
902#[serde_as]
908#[skip_serializing_none]
909#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
910#[serde(rename_all = "camelCase")]
911#[non_exhaustive]
912pub struct ElicitationCapabilities {
913 #[serde_as(deserialize_as = "DefaultOnError")]
915 #[schemars(extend("x-deserialize-default-on-error" = true))]
916 #[serde(default)]
917 pub form: Option<ElicitationFormCapabilities>,
918 #[serde_as(deserialize_as = "DefaultOnError")]
920 #[schemars(extend("x-deserialize-default-on-error" = true))]
921 #[serde(default)]
922 pub url: Option<ElicitationUrlCapabilities>,
923 #[serde(rename = "_meta")]
929 pub meta: Option<Meta>,
930}
931
932impl ElicitationCapabilities {
933 #[must_use]
934 pub fn new() -> Self {
935 Self::default()
936 }
937
938 #[must_use]
940 pub fn form(mut self, form: impl IntoOption<ElicitationFormCapabilities>) -> Self {
941 self.form = form.into_option();
942 self
943 }
944
945 #[must_use]
947 pub fn url(mut self, url: impl IntoOption<ElicitationUrlCapabilities>) -> Self {
948 self.url = url.into_option();
949 self
950 }
951
952 #[must_use]
958 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
959 self.meta = meta.into_option();
960 self
961 }
962}
963
964#[skip_serializing_none]
970#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
971#[serde(rename_all = "camelCase")]
972#[non_exhaustive]
973pub struct ElicitationFormCapabilities {
974 #[serde(rename = "_meta")]
980 pub meta: Option<Meta>,
981}
982
983impl ElicitationFormCapabilities {
984 #[must_use]
985 pub fn new() -> Self {
986 Self::default()
987 }
988
989 #[must_use]
995 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
996 self.meta = meta.into_option();
997 self
998 }
999}
1000
1001#[skip_serializing_none]
1007#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1008#[serde(rename_all = "camelCase")]
1009#[non_exhaustive]
1010pub struct ElicitationUrlCapabilities {
1011 #[serde(rename = "_meta")]
1017 pub meta: Option<Meta>,
1018}
1019
1020impl ElicitationUrlCapabilities {
1021 #[must_use]
1022 pub fn new() -> Self {
1023 Self::default()
1024 }
1025
1026 #[must_use]
1032 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1033 self.meta = meta.into_option();
1034 self
1035 }
1036}
1037
1038#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1044#[serde(untagged)]
1045#[non_exhaustive]
1046pub enum ElicitationScope {
1047 Session(ElicitationSessionScope),
1049 Request(ElicitationRequestScope),
1052}
1053
1054#[skip_serializing_none]
1064#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1065#[serde(rename_all = "camelCase")]
1066#[non_exhaustive]
1067pub struct ElicitationSessionScope {
1068 pub session_id: SessionId,
1070 pub tool_call_id: Option<ToolCallId>,
1072}
1073
1074impl ElicitationSessionScope {
1075 #[must_use]
1076 pub fn new(session_id: impl Into<SessionId>) -> Self {
1077 Self {
1078 session_id: session_id.into(),
1079 tool_call_id: None,
1080 }
1081 }
1082
1083 #[must_use]
1084 pub fn tool_call_id(mut self, tool_call_id: impl IntoOption<ToolCallId>) -> Self {
1085 self.tool_call_id = tool_call_id.into_option();
1086 self
1087 }
1088}
1089
1090#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1097#[serde(rename_all = "camelCase")]
1098#[non_exhaustive]
1099pub struct ElicitationRequestScope {
1100 pub request_id: RequestId,
1102}
1103
1104impl ElicitationRequestScope {
1105 #[must_use]
1106 pub fn new(request_id: impl Into<RequestId>) -> Self {
1107 Self {
1108 request_id: request_id.into(),
1109 }
1110 }
1111}
1112
1113impl From<ElicitationSessionScope> for ElicitationScope {
1114 fn from(scope: ElicitationSessionScope) -> Self {
1115 Self::Session(scope)
1116 }
1117}
1118
1119impl From<ElicitationRequestScope> for ElicitationScope {
1120 fn from(scope: ElicitationRequestScope) -> Self {
1121 Self::Request(scope)
1122 }
1123}
1124
1125#[skip_serializing_none]
1135#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1136#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1137#[serde(rename_all = "camelCase")]
1138#[non_exhaustive]
1139pub struct CreateElicitationRequest {
1140 #[serde(flatten)]
1142 pub mode: ElicitationMode,
1143 pub message: String,
1145 #[serde(rename = "_meta")]
1151 pub meta: Option<Meta>,
1152}
1153
1154impl CreateElicitationRequest {
1155 #[must_use]
1156 pub fn new(mode: impl Into<ElicitationMode>, message: impl Into<String>) -> Self {
1157 Self {
1158 mode: mode.into(),
1159 message: message.into(),
1160 meta: None,
1161 }
1162 }
1163
1164 #[must_use]
1166 pub fn scope(&self) -> &ElicitationScope {
1167 self.mode.scope()
1168 }
1169
1170 #[must_use]
1176 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1177 self.meta = meta.into_option();
1178 self
1179 }
1180}
1181
1182#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1188#[serde(tag = "mode", rename_all = "snake_case")]
1189#[schemars(extend("discriminator" = {"propertyName": "mode"}))]
1190#[non_exhaustive]
1191pub enum ElicitationMode {
1192 Form(ElicitationFormMode),
1194 Url(ElicitationUrlMode),
1196}
1197
1198impl From<ElicitationFormMode> for ElicitationMode {
1199 fn from(mode: ElicitationFormMode) -> Self {
1200 Self::Form(mode)
1201 }
1202}
1203
1204impl From<ElicitationUrlMode> for ElicitationMode {
1205 fn from(mode: ElicitationUrlMode) -> Self {
1206 Self::Url(mode)
1207 }
1208}
1209
1210impl ElicitationMode {
1211 #[must_use]
1213 pub fn scope(&self) -> &ElicitationScope {
1214 match self {
1215 Self::Form(f) => &f.scope,
1216 Self::Url(u) => &u.scope,
1217 }
1218 }
1219}
1220
1221#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1227#[serde(rename_all = "camelCase")]
1228#[non_exhaustive]
1229pub struct ElicitationFormMode {
1230 #[serde(flatten)]
1232 pub scope: ElicitationScope,
1233 pub requested_schema: ElicitationSchema,
1235}
1236
1237impl ElicitationFormMode {
1238 #[must_use]
1239 pub fn new(scope: impl Into<ElicitationScope>, requested_schema: ElicitationSchema) -> Self {
1240 Self {
1241 scope: scope.into(),
1242 requested_schema,
1243 }
1244 }
1245}
1246
1247#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1253#[serde(rename_all = "camelCase")]
1254#[non_exhaustive]
1255pub struct ElicitationUrlMode {
1256 #[serde(flatten)]
1258 pub scope: ElicitationScope,
1259 pub elicitation_id: ElicitationId,
1261 #[schemars(extend("format" = "uri"))]
1263 pub url: String,
1264}
1265
1266impl ElicitationUrlMode {
1267 #[must_use]
1268 pub fn new(
1269 scope: impl Into<ElicitationScope>,
1270 elicitation_id: impl Into<ElicitationId>,
1271 url: impl Into<String>,
1272 ) -> Self {
1273 Self {
1274 scope: scope.into(),
1275 elicitation_id: elicitation_id.into(),
1276 url: url.into(),
1277 }
1278 }
1279}
1280
1281#[skip_serializing_none]
1287#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1288#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
1289#[serde(rename_all = "camelCase")]
1290#[non_exhaustive]
1291pub struct CreateElicitationResponse {
1292 #[serde(flatten)]
1294 pub action: ElicitationAction,
1295 #[serde(rename = "_meta")]
1301 pub meta: Option<Meta>,
1302}
1303
1304impl CreateElicitationResponse {
1305 #[must_use]
1306 pub fn new(action: ElicitationAction) -> Self {
1307 Self { action, meta: None }
1308 }
1309
1310 #[must_use]
1316 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1317 self.meta = meta.into_option();
1318 self
1319 }
1320}
1321
1322#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1328#[serde(tag = "action", rename_all = "snake_case")]
1329#[schemars(extend("discriminator" = {"propertyName": "action"}))]
1330#[non_exhaustive]
1331pub enum ElicitationAction {
1332 Accept(ElicitationAcceptAction),
1334 Decline,
1336 Cancel,
1338}
1339
1340#[skip_serializing_none]
1346#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1347#[serde(rename_all = "camelCase")]
1348#[non_exhaustive]
1349pub struct ElicitationAcceptAction {
1350 #[serde(default)]
1352 pub content: Option<BTreeMap<String, ElicitationContentValue>>,
1353}
1354
1355impl ElicitationAcceptAction {
1356 #[must_use]
1357 pub fn new() -> Self {
1358 Self { content: None }
1359 }
1360
1361 #[must_use]
1363 pub fn content(
1364 mut self,
1365 content: impl IntoOption<BTreeMap<String, ElicitationContentValue>>,
1366 ) -> Self {
1367 self.content = content.into_option();
1368 self
1369 }
1370}
1371
1372#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
1373#[serde(untagged)]
1374#[non_exhaustive]
1375pub enum ElicitationContentValue {
1376 String(String),
1377 Integer(i64),
1378 Number(f64),
1379 Boolean(bool),
1380 StringArray(Vec<String>),
1381}
1382
1383impl From<String> for ElicitationContentValue {
1384 fn from(value: String) -> Self {
1385 Self::String(value)
1386 }
1387}
1388
1389impl From<&str> for ElicitationContentValue {
1390 fn from(value: &str) -> Self {
1391 Self::String(value.to_string())
1392 }
1393}
1394
1395impl From<i64> for ElicitationContentValue {
1396 fn from(value: i64) -> Self {
1397 Self::Integer(value)
1398 }
1399}
1400
1401impl From<i32> for ElicitationContentValue {
1402 fn from(value: i32) -> Self {
1403 Self::Integer(i64::from(value))
1404 }
1405}
1406
1407impl From<f64> for ElicitationContentValue {
1408 fn from(value: f64) -> Self {
1409 Self::Number(value)
1410 }
1411}
1412
1413impl From<bool> for ElicitationContentValue {
1414 fn from(value: bool) -> Self {
1415 Self::Boolean(value)
1416 }
1417}
1418
1419impl From<Vec<String>> for ElicitationContentValue {
1420 fn from(value: Vec<String>) -> Self {
1421 Self::StringArray(value)
1422 }
1423}
1424
1425impl From<Vec<&str>> for ElicitationContentValue {
1426 fn from(value: Vec<&str>) -> Self {
1427 Self::StringArray(value.into_iter().map(str::to_string).collect())
1428 }
1429}
1430
1431impl Default for ElicitationAcceptAction {
1432 fn default() -> Self {
1433 Self::new()
1434 }
1435}
1436
1437#[skip_serializing_none]
1443#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1444#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
1445#[serde(rename_all = "camelCase")]
1446#[non_exhaustive]
1447pub struct CompleteElicitationNotification {
1448 pub elicitation_id: ElicitationId,
1450 #[serde(rename = "_meta")]
1456 pub meta: Option<Meta>,
1457}
1458
1459impl CompleteElicitationNotification {
1460 #[must_use]
1461 pub fn new(elicitation_id: impl Into<ElicitationId>) -> Self {
1462 Self {
1463 elicitation_id: elicitation_id.into(),
1464 meta: None,
1465 }
1466 }
1467
1468 #[must_use]
1474 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
1475 self.meta = meta.into_option();
1476 self
1477 }
1478}
1479
1480#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1487#[serde(rename_all = "camelCase")]
1488#[non_exhaustive]
1489pub struct UrlElicitationRequiredData {
1490 pub elicitations: Vec<UrlElicitationRequiredItem>,
1492}
1493
1494impl UrlElicitationRequiredData {
1495 #[must_use]
1496 pub fn new(elicitations: Vec<UrlElicitationRequiredItem>) -> Self {
1497 Self { elicitations }
1498 }
1499}
1500
1501#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1507#[serde(rename_all = "camelCase")]
1508#[non_exhaustive]
1509pub struct UrlElicitationRequiredItem {
1510 pub mode: ElicitationUrlOnlyMode,
1512 pub elicitation_id: ElicitationId,
1514 #[schemars(extend("format" = "uri"))]
1516 pub url: String,
1517 pub message: String,
1519}
1520
1521#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
1523#[serde(rename_all = "snake_case")]
1524#[non_exhaustive]
1525pub enum ElicitationUrlOnlyMode {
1526 #[default]
1528 Url,
1529}
1530
1531impl UrlElicitationRequiredItem {
1532 #[must_use]
1533 pub fn new(
1534 elicitation_id: impl Into<ElicitationId>,
1535 url: impl Into<String>,
1536 message: impl Into<String>,
1537 ) -> Self {
1538 Self {
1539 mode: ElicitationUrlOnlyMode::Url,
1540 elicitation_id: elicitation_id.into(),
1541 url: url.into(),
1542 message: message.into(),
1543 }
1544 }
1545}
1546
1547#[cfg(test)]
1548mod tests {
1549 use super::*;
1550 use serde_json::json;
1551
1552 #[test]
1553 fn form_mode_request_serialization() {
1554 let schema = ElicitationSchema::new().string("name", true);
1555 let req = CreateElicitationRequest::new(
1556 ElicitationFormMode::new(ElicitationSessionScope::new("sess_1"), schema),
1557 "Please enter your name",
1558 );
1559
1560 let json = serde_json::to_value(&req).unwrap();
1561 assert_eq!(json["sessionId"], "sess_1");
1562 assert!(json.get("toolCallId").is_none());
1563 assert_eq!(json["mode"], "form");
1564 assert_eq!(json["message"], "Please enter your name");
1565 assert!(json["requestedSchema"].is_object());
1566 assert_eq!(json["requestedSchema"]["type"], "object");
1567 assert_eq!(
1568 json["requestedSchema"]["properties"]["name"]["type"],
1569 "string"
1570 );
1571
1572 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1573 assert_eq!(
1574 *roundtripped.scope(),
1575 ElicitationSessionScope::new("sess_1").into()
1576 );
1577 assert_eq!(roundtripped.message, "Please enter your name");
1578 assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
1579 }
1580
1581 #[test]
1582 fn url_mode_request_serialization() {
1583 let req = CreateElicitationRequest::new(
1584 ElicitationUrlMode::new(
1585 ElicitationSessionScope::new("sess_2").tool_call_id("tc_1"),
1586 "elic_1",
1587 "https://example.com/auth",
1588 ),
1589 "Please authenticate",
1590 );
1591
1592 let json = serde_json::to_value(&req).unwrap();
1593 assert_eq!(json["sessionId"], "sess_2");
1594 assert_eq!(json["toolCallId"], "tc_1");
1595 assert_eq!(json["mode"], "url");
1596 assert_eq!(json["elicitationId"], "elic_1");
1597 assert_eq!(json["url"], "https://example.com/auth");
1598 assert_eq!(json["message"], "Please authenticate");
1599
1600 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1601 assert_eq!(
1602 *roundtripped.scope(),
1603 ElicitationSessionScope::new("sess_2")
1604 .tool_call_id("tc_1")
1605 .into()
1606 );
1607 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1608 }
1609
1610 #[test]
1611 fn response_accept_serialization() {
1612 let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
1613 ElicitationAcceptAction::new().content(BTreeMap::from([(
1614 "name".to_string(),
1615 ElicitationContentValue::from("Alice"),
1616 )])),
1617 ));
1618
1619 let json = serde_json::to_value(&resp).unwrap();
1620 assert_eq!(json["action"], "accept");
1621 assert_eq!(json["content"]["name"], "Alice");
1622
1623 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1624 assert!(matches!(
1625 roundtripped.action,
1626 ElicitationAction::Accept(ElicitationAcceptAction {
1627 content: Some(_),
1628 ..
1629 })
1630 ));
1631 }
1632
1633 #[test]
1634 fn response_decline_serialization() {
1635 let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
1636
1637 let json = serde_json::to_value(&resp).unwrap();
1638 assert_eq!(json["action"], "decline");
1639
1640 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1641 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1642 }
1643
1644 #[test]
1645 fn response_cancel_serialization() {
1646 let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
1647
1648 let json = serde_json::to_value(&resp).unwrap();
1649 assert_eq!(json["action"], "cancel");
1650
1651 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1652 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1653 }
1654
1655 #[test]
1656 fn url_mode_request_scope_serialization() {
1657 let req = CreateElicitationRequest::new(
1658 ElicitationUrlMode::new(
1659 ElicitationRequestScope::new(RequestId::Number(42)),
1660 "elic_2",
1661 "https://example.com/setup",
1662 ),
1663 "Please complete setup",
1664 );
1665
1666 let json = serde_json::to_value(&req).unwrap();
1667 assert_eq!(json["requestId"], 42);
1668 assert!(json.get("sessionId").is_none());
1669 assert_eq!(json["mode"], "url");
1670 assert_eq!(json["elicitationId"], "elic_2");
1671 assert_eq!(json["url"], "https://example.com/setup");
1672 assert_eq!(json["message"], "Please complete setup");
1673
1674 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1675 assert_eq!(
1676 *roundtripped.scope(),
1677 ElicitationRequestScope::new(RequestId::Number(42)).into()
1678 );
1679 assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
1680 }
1681
1682 #[test]
1683 fn request_scope_request_serialization() {
1684 let req = CreateElicitationRequest::new(
1685 ElicitationFormMode::new(
1686 ElicitationRequestScope::new(RequestId::Number(99)),
1687 ElicitationSchema::new().string("workspace", true),
1688 ),
1689 "Enter workspace name",
1690 );
1691
1692 let json = serde_json::to_value(&req).unwrap();
1693 assert_eq!(json["requestId"], 99);
1694 assert!(json.get("sessionId").is_none());
1695
1696 let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1697 assert_eq!(
1698 *roundtripped.scope(),
1699 ElicitationRequestScope::new(RequestId::Number(99)).into()
1700 );
1701 }
1702
1703 #[test]
1710 fn client_response_serialization_accept() {
1711 use crate::ClientResponse;
1712
1713 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1714 ElicitationAction::Accept(ElicitationAcceptAction::new().content(BTreeMap::from([(
1715 "name".to_string(),
1716 ElicitationContentValue::from("Alice"),
1717 )]))),
1718 ));
1719 let json = serde_json::to_value(&resp).unwrap();
1720 assert_eq!(json["action"], "accept");
1721 assert_eq!(json["content"]["name"], "Alice");
1722
1723 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1725 assert!(matches!(roundtripped.action, ElicitationAction::Accept(_)));
1726 }
1727
1728 #[test]
1729 fn client_response_serialization_decline() {
1730 use crate::ClientResponse;
1731
1732 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1733 ElicitationAction::Decline,
1734 ));
1735 let json = serde_json::to_value(&resp).unwrap();
1736 assert_eq!(json["action"], "decline");
1737
1738 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1739 assert!(matches!(roundtripped.action, ElicitationAction::Decline));
1740 }
1741
1742 #[test]
1743 fn client_response_serialization_cancel() {
1744 use crate::ClientResponse;
1745
1746 let resp = ClientResponse::CreateElicitationResponse(CreateElicitationResponse::new(
1747 ElicitationAction::Cancel,
1748 ));
1749 let json = serde_json::to_value(&resp).unwrap();
1750 assert_eq!(json["action"], "cancel");
1751
1752 let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
1753 assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
1754 }
1755
1756 #[test]
1759 fn request_tolerates_extra_fields() {
1760 let json = json!({
1761 "sessionId": "sess_1",
1762 "mode": "form",
1763 "message": "Enter your name",
1764 "requestedSchema": {
1765 "type": "object",
1766 "properties": {
1767 "name": { "type": "string", "title": "Name" }
1768 },
1769 "required": ["name"]
1770 },
1771 "unknownStringField": "hello",
1772 "unknownNumberField": 42
1773 });
1774
1775 let req: CreateElicitationRequest = serde_json::from_value(json).unwrap();
1776 assert_eq!(*req.scope(), ElicitationSessionScope::new("sess_1").into());
1777 assert_eq!(req.message, "Enter your name");
1778 assert!(matches!(req.mode, ElicitationMode::Form(_)));
1779 }
1780
1781 #[test]
1782 fn completion_notification_serialization() {
1783 let notif = CompleteElicitationNotification::new("elic_1");
1784
1785 let json = serde_json::to_value(¬if).unwrap();
1786 assert_eq!(json["elicitationId"], "elic_1");
1787
1788 let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
1789 assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
1790 }
1791
1792 #[test]
1793 fn capabilities_form_only() {
1794 let caps = ElicitationCapabilities::new().form(ElicitationFormCapabilities::new());
1795
1796 let json = serde_json::to_value(&caps).unwrap();
1797 assert!(json["form"].is_object());
1798 assert!(json.get("url").is_none());
1799
1800 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1801 assert!(roundtripped.form.is_some());
1802 assert!(roundtripped.url.is_none());
1803 }
1804
1805 #[test]
1806 fn capabilities_url_only() {
1807 let caps = ElicitationCapabilities::new().url(ElicitationUrlCapabilities::new());
1808
1809 let json = serde_json::to_value(&caps).unwrap();
1810 assert!(json.get("form").is_none());
1811 assert!(json["url"].is_object());
1812
1813 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1814 assert!(roundtripped.form.is_none());
1815 assert!(roundtripped.url.is_some());
1816 }
1817
1818 #[test]
1819 fn capabilities_both() {
1820 let caps = ElicitationCapabilities::new()
1821 .form(ElicitationFormCapabilities::new())
1822 .url(ElicitationUrlCapabilities::new());
1823
1824 let json = serde_json::to_value(&caps).unwrap();
1825 assert!(json["form"].is_object());
1826 assert!(json["url"].is_object());
1827
1828 let roundtripped: ElicitationCapabilities = serde_json::from_value(json).unwrap();
1829 assert!(roundtripped.form.is_some());
1830 assert!(roundtripped.url.is_some());
1831 }
1832
1833 #[test]
1834 fn url_elicitation_required_data_serialization() {
1835 let data = UrlElicitationRequiredData::new(vec![UrlElicitationRequiredItem::new(
1836 "elic_1",
1837 "https://example.com/auth",
1838 "Please authenticate",
1839 )]);
1840
1841 let json = serde_json::to_value(&data).unwrap();
1842 assert_eq!(json["elicitations"][0]["mode"], "url");
1843 assert_eq!(json["elicitations"][0]["elicitationId"], "elic_1");
1844 assert_eq!(json["elicitations"][0]["url"], "https://example.com/auth");
1845
1846 let roundtripped: UrlElicitationRequiredData = serde_json::from_value(json).unwrap();
1847 assert_eq!(roundtripped.elicitations.len(), 1);
1848 assert_eq!(
1849 roundtripped.elicitations[0].mode,
1850 ElicitationUrlOnlyMode::Url
1851 );
1852 }
1853
1854 #[test]
1855 fn schema_default_sets_object_type() {
1856 let schema = ElicitationSchema::default();
1857
1858 assert_eq!(schema.type_, ElicitationSchemaType::Object);
1859 assert!(schema.properties.is_empty());
1860
1861 let json = serde_json::to_value(&schema).unwrap();
1862 assert_eq!(json["type"], "object");
1863 }
1864
1865 #[test]
1866 fn schema_builder_serialization() {
1867 let schema = ElicitationSchema::new()
1868 .string("name", true)
1869 .email("email", true)
1870 .integer("age", 0, 150, true)
1871 .boolean("newsletter", false)
1872 .description("User registration");
1873
1874 let json = serde_json::to_value(&schema).unwrap();
1875 assert_eq!(json["type"], "object");
1876 assert_eq!(json["description"], "User registration");
1877 assert_eq!(json["properties"]["name"]["type"], "string");
1878 assert_eq!(json["properties"]["email"]["type"], "string");
1879 assert_eq!(json["properties"]["email"]["format"], "email");
1880 assert_eq!(json["properties"]["age"]["type"], "integer");
1881 assert_eq!(json["properties"]["age"]["minimum"], 0);
1882 assert_eq!(json["properties"]["age"]["maximum"], 150);
1883 assert_eq!(json["properties"]["newsletter"]["type"], "boolean");
1884
1885 let required = json["required"].as_array().unwrap();
1886 assert!(required.contains(&json!("name")));
1887 assert!(required.contains(&json!("email")));
1888 assert!(required.contains(&json!("age")));
1889 assert!(!required.contains(&json!("newsletter")));
1890
1891 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1892 assert_eq!(roundtripped.properties.len(), 4);
1893 assert!(roundtripped.required.unwrap().contains(&"name".to_string()));
1894 }
1895
1896 #[test]
1897 fn schema_string_enum_serialization() {
1898 let schema = ElicitationSchema::new().property(
1899 "color",
1900 StringPropertySchema::new().enum_values(vec![
1901 "red".into(),
1902 "green".into(),
1903 "blue".into(),
1904 ]),
1905 true,
1906 );
1907
1908 let json = serde_json::to_value(&schema).unwrap();
1909 assert_eq!(json["properties"]["color"]["type"], "string");
1910 let enum_vals = json["properties"]["color"]["enum"].as_array().unwrap();
1911 assert_eq!(enum_vals.len(), 3);
1912
1913 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1914 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("color").unwrap()
1915 {
1916 assert_eq!(s.enum_values.as_ref().unwrap().len(), 3);
1917 } else {
1918 panic!("expected String variant");
1919 }
1920 }
1921
1922 #[test]
1923 fn schema_multi_select_serialization() {
1924 let schema = ElicitationSchema::new().property(
1925 "colors",
1926 MultiSelectPropertySchema::new(vec!["red".into(), "green".into(), "blue".into()])
1927 .min_items(1)
1928 .max_items(3),
1929 false,
1930 );
1931
1932 let json = serde_json::to_value(&schema).unwrap();
1933 assert_eq!(json["properties"]["colors"]["type"], "array");
1934 assert_eq!(json["properties"]["colors"]["items"]["type"], "string");
1935 assert_eq!(json["properties"]["colors"]["minItems"], 1);
1936 assert_eq!(json["properties"]["colors"]["maxItems"], 3);
1937
1938 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1939 assert!(matches!(
1940 roundtripped.properties.get("colors").unwrap(),
1941 ElicitationPropertySchema::Array(_)
1942 ));
1943 }
1944
1945 #[test]
1946 fn schema_titled_enum_serialization() {
1947 let schema = ElicitationSchema::new().property(
1948 "country",
1949 StringPropertySchema::new().one_of(vec![
1950 EnumOption::new("us", "United States"),
1951 EnumOption::new("uk", "United Kingdom"),
1952 ]),
1953 true,
1954 );
1955
1956 let json = serde_json::to_value(&schema).unwrap();
1957 assert_eq!(json["properties"]["country"]["type"], "string");
1958 let one_of = json["properties"]["country"]["oneOf"].as_array().unwrap();
1959 assert_eq!(one_of.len(), 2);
1960 assert_eq!(one_of[0]["const"], "us");
1961 assert_eq!(one_of[0]["title"], "United States");
1962
1963 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1964 if let ElicitationPropertySchema::String(s) =
1965 roundtripped.properties.get("country").unwrap()
1966 {
1967 assert_eq!(s.one_of.as_ref().unwrap().len(), 2);
1968 } else {
1969 panic!("expected String variant");
1970 }
1971 }
1972
1973 #[test]
1974 fn schema_number_property_serialization() {
1975 let schema = ElicitationSchema::new().number("rating", 0.0, 5.0, true);
1976
1977 let json = serde_json::to_value(&schema).unwrap();
1978 assert_eq!(json["properties"]["rating"]["type"], "number");
1979 assert_eq!(json["properties"]["rating"]["minimum"], 0.0);
1980 assert_eq!(json["properties"]["rating"]["maximum"], 5.0);
1981
1982 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
1983 if let ElicitationPropertySchema::Number(n) = roundtripped.properties.get("rating").unwrap()
1984 {
1985 assert_eq!(n.minimum, Some(0.0));
1986 assert_eq!(n.maximum, Some(5.0));
1987 } else {
1988 panic!("expected Number variant");
1989 }
1990 }
1991
1992 #[test]
1993 fn schema_string_format_serialization() {
1994 let schema = ElicitationSchema::new()
1995 .uri("website", true)
1996 .date("birthday", true)
1997 .date_time("updated_at", false);
1998
1999 let json = serde_json::to_value(&schema).unwrap();
2000 assert_eq!(json["properties"]["website"]["type"], "string");
2001 assert_eq!(json["properties"]["website"]["format"], "uri");
2002 assert_eq!(json["properties"]["birthday"]["type"], "string");
2003 assert_eq!(json["properties"]["birthday"]["format"], "date");
2004 assert_eq!(json["properties"]["updated_at"]["type"], "string");
2005 assert_eq!(json["properties"]["updated_at"]["format"], "date-time");
2006
2007 let required = json["required"].as_array().unwrap();
2008 assert!(required.contains(&json!("website")));
2009 assert!(required.contains(&json!("birthday")));
2010 assert!(!required.contains(&json!("updated_at")));
2011 }
2012
2013 #[test]
2014 fn schema_string_pattern_serialization() {
2015 let schema = ElicitationSchema::new().property(
2016 "name",
2017 StringPropertySchema::new()
2018 .min_length(1)
2019 .max_length(64)
2020 .pattern("^[a-zA-Z_][a-zA-Z0-9_]*$"),
2021 true,
2022 );
2023
2024 let json = serde_json::to_value(&schema).unwrap();
2025 assert_eq!(json["properties"]["name"]["type"], "string");
2026 assert_eq!(
2027 json["properties"]["name"]["pattern"],
2028 "^[a-zA-Z_][a-zA-Z0-9_]*$"
2029 );
2030
2031 let roundtripped: ElicitationSchema = serde_json::from_value(json).unwrap();
2032 if let ElicitationPropertySchema::String(s) = roundtripped.properties.get("name").unwrap() {
2033 assert_eq!(s.pattern.as_deref(), Some("^[a-zA-Z_][a-zA-Z0-9_]*$"));
2034 } else {
2035 panic!("expected String variant");
2036 }
2037 }
2038
2039 #[test]
2040 fn schema_property_updates_required_state() {
2041 let schema = ElicitationSchema::new()
2042 .string("name", true)
2043 .email("name", false);
2044
2045 let json = serde_json::to_value(&schema).unwrap();
2046 assert!(json.get("required").is_none());
2047 assert_eq!(json["properties"]["name"]["format"], "email");
2048 }
2049
2050 #[test]
2051 fn schema_rejects_invalid_object_type() {
2052 let err = serde_json::from_value::<ElicitationSchema>(json!({
2053 "type": "array",
2054 "properties": {
2055 "name": {
2056 "type": "string"
2057 }
2058 }
2059 }))
2060 .unwrap_err();
2061
2062 assert!(err.to_string().contains("unknown variant"));
2063 }
2064
2065 #[test]
2066 fn titled_multi_select_items_reject_one_of() {
2067 let err = serde_json::from_value::<TitledMultiSelectItems>(json!({
2068 "oneOf": [
2069 {
2070 "const": "red",
2071 "title": "Red"
2072 }
2073 ]
2074 }))
2075 .unwrap_err();
2076
2077 assert!(err.to_string().contains("missing field `anyOf`"));
2078 }
2079
2080 #[test]
2081 fn response_accept_rejects_non_object_content() {
2082 let err = serde_json::from_value::<CreateElicitationResponse>(json!({
2083 "action": "accept",
2084 "content": "Alice"
2085 }))
2086 .unwrap_err();
2087
2088 assert!(err.to_string().contains("invalid type"));
2089 }
2090
2091 #[test]
2092 fn response_accept_rejects_nested_object_content() {
2093 let err = serde_json::from_value::<CreateElicitationResponse>(json!({
2094 "action": "accept",
2095 "content": {
2096 "profile": {
2097 "name": "Alice"
2098 }
2099 }
2100 }))
2101 .unwrap_err();
2102
2103 assert!(err.to_string().contains("data did not match any variant"));
2104 }
2105
2106 #[test]
2107 fn response_accept_allows_primitive_and_string_array_content() {
2108 let response = CreateElicitationResponse::new(ElicitationAction::Accept(
2109 ElicitationAcceptAction::new().content(BTreeMap::from([
2110 ("name".to_string(), ElicitationContentValue::from("Alice")),
2111 ("age".to_string(), ElicitationContentValue::from(30_i32)),
2112 ("score".to_string(), ElicitationContentValue::from(9.5_f64)),
2113 (
2114 "subscribed".to_string(),
2115 ElicitationContentValue::from(true),
2116 ),
2117 (
2118 "tags".to_string(),
2119 ElicitationContentValue::from(vec!["rust", "acp"]),
2120 ),
2121 ])),
2122 ));
2123
2124 let json = serde_json::to_value(&response).unwrap();
2125 assert_eq!(json["action"], "accept");
2126 assert_eq!(json["content"]["name"], "Alice");
2127 assert_eq!(json["content"]["age"], 30);
2128 assert_eq!(json["content"]["score"], 9.5);
2129 assert_eq!(json["content"]["subscribed"], true);
2130 assert_eq!(json["content"]["tags"][0], "rust");
2131 assert_eq!(json["content"]["tags"][1], "acp");
2132 }
2133}