@@ -13,3 +13,122 @@ fn test_tool_list_result() {
1313 } )
1414 ) ) ;
1515}
16+
17+ /// Regression tests for `#[serde(untagged)]` deserialization of `ServerResult`.
18+ ///
19+ /// `ServerResult` is an untagged enum, so serde tries each variant in declaration
20+ /// order. `GetTaskPayloadResult` has a custom `Deserialize` impl that always fails
21+ /// so it is skipped, and `CustomResult(Value)` acts as the catch-all. If variant
22+ /// ordering changes or the custom impl is removed, these tests will catch the
23+ /// regression.
24+ mod untagged_server_result {
25+ use rmcp:: model:: { CallToolResult , JsonRpcResponse , ServerJsonRpcMessage , ServerResult } ;
26+ use serde_json:: json;
27+
28+ /// Helper: wrap a result value in a JSON-RPC response envelope.
29+ fn wrap_response ( result : serde_json:: Value ) -> serde_json:: Value {
30+ json ! ( {
31+ "jsonrpc" : "2.0" ,
32+ "id" : 1 ,
33+ "result" : result
34+ } )
35+ }
36+
37+ /// Parse a JSON-RPC response and return the inner `ServerResult`.
38+ fn parse_result ( json : serde_json:: Value ) -> ServerResult {
39+ let msg: ServerJsonRpcMessage = serde_json:: from_value ( json) . unwrap ( ) ;
40+ match msg {
41+ ServerJsonRpcMessage :: Response ( JsonRpcResponse { result, .. } ) => result,
42+ other => panic ! ( "expected Response, got {other:?}" ) ,
43+ }
44+ }
45+
46+ #[ test]
47+ fn initialize_result_deserializes_to_correct_variant ( ) {
48+ let result = parse_result ( wrap_response ( json ! ( {
49+ "protocolVersion" : "2025-03-26" ,
50+ "capabilities" : { } ,
51+ "serverInfo" : {
52+ "name" : "test-server" ,
53+ "version" : "1.0.0"
54+ }
55+ } ) ) ) ;
56+ assert ! (
57+ matches!( result, ServerResult :: InitializeResult ( _) ) ,
58+ "expected InitializeResult, got {result:?}"
59+ ) ;
60+ }
61+
62+ #[ test]
63+ fn call_tool_result_deserializes_to_correct_variant ( ) {
64+ let result = parse_result ( wrap_response ( json ! ( {
65+ "content" : [
66+ { "type" : "text" , "text" : "hello" }
67+ ]
68+ } ) ) ) ;
69+ assert ! (
70+ matches!( result, ServerResult :: CallToolResult ( _) ) ,
71+ "expected CallToolResult, got {result:?}"
72+ ) ;
73+ }
74+
75+ #[ test]
76+ fn empty_object_deserializes_to_empty_result ( ) {
77+ let result = parse_result ( wrap_response ( json ! ( { } ) ) ) ;
78+ assert ! (
79+ matches!( result, ServerResult :: EmptyResult ( _) ) ,
80+ "expected EmptyResult, got {result:?}"
81+ ) ;
82+ }
83+
84+ #[ test]
85+ fn unknown_shape_falls_through_to_custom_result ( ) {
86+ // A value that doesn't match any known result type should land in
87+ // CustomResult, NOT GetTaskPayloadResult.
88+ let result = parse_result ( wrap_response ( json ! ( {
89+ "some_unknown_field" : "some_value" ,
90+ "number" : 42
91+ } ) ) ) ;
92+ assert ! (
93+ matches!( result, ServerResult :: CustomResult ( _) ) ,
94+ "expected CustomResult, got {result:?}"
95+ ) ;
96+ }
97+
98+ #[ test]
99+ fn arbitrary_json_value_does_not_deserialize_as_get_task_payload_result ( ) {
100+ // GetTaskPayloadResult wraps a bare Value, but its custom Deserialize
101+ // always fails so serde skips it during untagged resolution.
102+ // Any JSON value must fall through to CustomResult instead.
103+ for value in [ json ! ( 42 ) , json ! ( "hello" ) , json ! ( null) , json ! ( [ 1 , 2 , 3 ] ) ] {
104+ let result = parse_result ( wrap_response ( value. clone ( ) ) ) ;
105+ assert ! (
106+ matches!( result, ServerResult :: CustomResult ( _) ) ,
107+ "value {value} should deserialize as CustomResult, got {result:?}"
108+ ) ;
109+ }
110+ }
111+
112+ #[ test]
113+ fn round_trip_initialize_result_preserves_variant ( ) {
114+ let json = json ! ( {
115+ "protocolVersion" : "2025-03-26" ,
116+ "capabilities" : { } ,
117+ "serverInfo" : { "name" : "test" , "version" : "1.0" }
118+ } ) ;
119+ // Parse as ServerResult, serialize back, parse again — must stay InitializeResult.
120+ let result = parse_result ( wrap_response ( json. clone ( ) ) ) ;
121+ assert ! ( matches!( & result, ServerResult :: InitializeResult ( _) ) ) ;
122+ let reserialized = serde_json:: to_value ( & result) . unwrap ( ) ;
123+ let result2 = parse_result ( wrap_response ( reserialized) ) ;
124+ assert ! ( matches!( result2, ServerResult :: InitializeResult ( _) ) ) ;
125+ }
126+
127+ #[ test]
128+ fn round_trip_call_tool_result_preserves_variant ( ) {
129+ let original = CallToolResult :: success ( vec ! [ rmcp:: model:: Content :: text( "hello world" ) ] ) ;
130+ let json = serde_json:: to_value ( & original) . unwrap ( ) ;
131+ let result = parse_result ( wrap_response ( json) ) ;
132+ assert ! ( matches!( result, ServerResult :: CallToolResult ( _) ) ) ;
133+ }
134+ }
0 commit comments