fix: prevent CallToolResult and GetTaskPayloadResult from shadowing CustomResult in untagged enums (#771)
The `#[serde(default)]` on `CallToolResult.content` (added in #752) made all fields optional, causing `CallToolResult` to greedily match any JSON object during `#[serde(untagged)]` deserialization of `ServerResult`. Similarly, `GetTaskPayloadResult(Value)` matched everything before `CustomResult(Value)` could be reached. Fix by replacing derived `Deserialize` impls with custom ones: - `CallToolResult`: require at least one known field to be present - `GetTaskPayloadResult`: always fail (indistinguishable from `CustomResult` in JSON; construct programmatically via `::new()`)
This commit is contained in:
parent
30cdc38c9a
commit
c8c0c0cffc
1 changed files with 63 additions and 2 deletions
|
|
@ -123,7 +123,7 @@ pub struct GetTaskResult {
|
|||
/// (e.g., `CallToolResult` for `tools/call`). This is represented as
|
||||
/// an open object. The payload is the original request's result
|
||||
/// serialized as a JSON value.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[non_exhaustive]
|
||||
pub struct GetTaskPayloadResult(pub Value);
|
||||
|
|
@ -135,6 +135,25 @@ impl GetTaskPayloadResult {
|
|||
}
|
||||
}
|
||||
|
||||
// Custom Deserialize that always fails, so that `GetTaskPayloadResult` is skipped
|
||||
// during `#[serde(untagged)]` enum deserialization (e.g. `ServerResult`).
|
||||
// The payload has the same JSON shape as `CustomResult(Value)`, so they are
|
||||
// indistinguishable. `CustomResult` acts as the catch-all instead.
|
||||
// `GetTaskPayloadResult` should be constructed programmatically via `::new()`.
|
||||
impl<'de> serde::Deserialize<'de> for GetTaskPayloadResult {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
// Consume the value so the deserializer state stays consistent.
|
||||
serde::de::IgnoredAny::deserialize(deserializer)?;
|
||||
Err(serde::de::Error::custom(
|
||||
"GetTaskPayloadResult cannot be deserialized directly; \
|
||||
use CustomResult as the catch-all",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Response to a `tasks/cancel` request.
|
||||
///
|
||||
/// Per spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue