fix(elicitation): preserve enumNames through ElicitationSchema serde round-trip (#905)
* fix(elicitation): preserve enumNames through ElicitationSchema serde round-trip UntitledSingleSelectEnumSchema lacked deny_unknown_fields, so a legacy enum payload containing enumNames was silently matched by that variant (ignoring the field) rather than falling through to LegacyEnumSchema. The enumNames array was lost on re-serialization. Add deny_unknown_fields to UntitledSingleSelectEnumSchema so that any unknown field (including enumNames) causes serde to try the next untagged variant, reaching LegacyEnumSchema correctly. Also add skip_serializing_if = "Option::is_none" to LegacyEnumSchema::enum_names so that an untitled legacy enum without enumNames does not serialize "enumNames": null. Fixes #903 * test(elicitation): regenerate server schema snapshot for deny_unknown_fields deny_unknown_fields on UntitledSingleSelectEnumSchema makes schemars emit additionalProperties: false for that definition. Regenerate the golden schema fixtures to match (UPDATE_SCHEMA=1 cargo test -p rmcp --test test_message_schema --all-features).
This commit is contained in:
parent
bf71eb8b09
commit
4fd4986b62
3 changed files with 43 additions and 1 deletions
|
|
@ -544,12 +544,13 @@ pub struct LegacyEnumSchema {
|
|||
pub description: Option<Cow<'static, str>>,
|
||||
#[serde(rename = "enum")]
|
||||
pub enum_: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enum_names: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Untitled single-select
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[non_exhaustive]
|
||||
pub struct UntitledSingleSelectEnumSchema {
|
||||
|
|
@ -1742,6 +1743,45 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_legacy_enum_schema_roundtrip_preserves_enum_names() -> anyhow::Result<()> {
|
||||
// Regression test for: legacy enum payload with `enumNames` was silently
|
||||
// deserialized as `UntitledSingleSelectEnumSchema` (which has no `enumNames`
|
||||
// field), causing the array to be dropped on re-serialization.
|
||||
let input = serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"choice": {
|
||||
"type": "string",
|
||||
"enum": ["opt1", "opt2", "opt3"],
|
||||
"enumNames": ["Option One", "Option Two", "Option Three"]
|
||||
}
|
||||
}
|
||||
});
|
||||
let schema: ElicitationSchema = serde_json::from_value(input.clone())?;
|
||||
let output = serde_json::to_value(&schema)?;
|
||||
assert_eq!(
|
||||
output["properties"]["choice"]["enumNames"],
|
||||
serde_json::json!(["Option One", "Option Two", "Option Three"]),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_legacy_enum_schema_no_enum_names_omits_field() -> anyhow::Result<()> {
|
||||
// `LegacyEnumSchema` with `enum_names: None` must not serialize `"enumNames": null`.
|
||||
let schema = EnumSchema::Legacy(LegacyEnumSchema {
|
||||
type_: StringTypeConst,
|
||||
title: None,
|
||||
description: None,
|
||||
enum_: vec!["a".to_string(), "b".to_string()],
|
||||
enum_names: None,
|
||||
});
|
||||
let json = serde_json::to_value(&schema)?;
|
||||
assert!(!json.as_object().unwrap().contains_key("enumNames"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_schema_titled_multi_select_serialization() -> anyhow::Result<()> {
|
||||
let schema = EnumSchema::builder(vec!["US".to_string(), "UK".to_string()])
|
||||
|
|
|
|||
|
|
@ -3642,6 +3642,7 @@
|
|||
"$ref": "#/definitions/StringTypeConst"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"type",
|
||||
"enum"
|
||||
|
|
|
|||
|
|
@ -3642,6 +3642,7 @@
|
|||
"$ref": "#/definitions/StringTypeConst"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"type",
|
||||
"enum"
|
||||
|
|
|
|||
Loading…
Reference in a new issue