Ensure Request decoder handles missing/null params correctly (#22)

This commit is contained in:
Mattt 2025-03-18 10:02:05 -07:00 • committed by GitHub
commit 75322c3baf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -23,33 +23,33 @@ struct RequestTests {
@Test("Request initialization with parameters")
func testRequestInitialization() throws {
let id: ID = "test-id"
let params = TestMethod.Parameters(value: "test")
let request = Request<TestMethod>(id: id, method: TestMethod.name, params: params)
let id: ID = 1
let params = CallTool.Parameters(name: "test-tool")
let request = Request<CallTool>(id: id, method: CallTool.name, params: params)
#expect(request.id == id)
#expect(request.method == TestMethod.name)
#expect(request.params.value == "test")
#expect(request.method == CallTool.name)
#expect(request.params.name == "test-tool")
}
@Test("Request encoding and decoding")
func testRequestEncodingDecoding() throws {
let request = TestMethod.request(id: "test-id", TestMethod.Parameters(value: "test"))
let request = CallTool.request(id: 1, CallTool.Parameters(name: "test-tool"))
let encoder = JSONEncoder()
let decoder = JSONDecoder()
let data = try encoder.encode(request)
let decoded = try decoder.decode(Request<TestMethod>.self, from: data)
let decoded = try decoder.decode(Request<CallTool>.self, from: data)
#expect(decoded.id == request.id)
#expect(decoded.method == request.method)
#expect(decoded.params.value == request.params.value)
#expect(decoded.params.name == request.params.name)
}
@Test("Empty parameters request encoding")
func testEmptyParametersRequestEncoding() throws {
let request = EmptyMethod.request(id: "test-id")
let request = EmptyMethod.request(id: 1)
let encoder = JSONEncoder()
let decoder = JSONDecoder()
@ -66,14 +66,14 @@ struct RequestTests {
func testEmptyParametersRequestDecoding() throws {
// Create a minimal JSON string
let jsonString = """
{"jsonrpc":"2.0","id":"test-id","method":"empty.method"}
{"jsonrpc":"2.0","id":1,"method":"empty.method"}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<EmptyMethod>.self, from: data)
#expect(decoded.id == "test-id")
#expect(decoded.id == 1)
#expect(decoded.method == EmptyMethod.name)
}
@ -81,14 +81,14 @@ struct RequestTests {
func testNotRequiredParametersRequestDecodingWithParams() throws {
// Test decoding when params field is present
let jsonString = """
{"jsonrpc":"2.0","id":"test-id","method":"ping","params":{}}
{"jsonrpc":"2.0","id":1,"method":"ping","params":{}}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<Ping>.self, from: data)
#expect(decoded.id == "test-id")
#expect(decoded.id == 1)
#expect(decoded.method == Ping.name)
}
@ -96,14 +96,14 @@ struct RequestTests {
func testNotRequiredParametersRequestDecodingWithoutParams() throws {
// Test decoding when params field is missing
let jsonString = """
{"jsonrpc":"2.0","id":"test-id","method":"ping"}
{"jsonrpc":"2.0","id":1,"method":"ping"}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<Ping>.self, from: data)
#expect(decoded.id == "test-id")
#expect(decoded.id == 1)
#expect(decoded.method == Ping.name)
}
@ -111,14 +111,153 @@ struct RequestTests {
func testNotRequiredParametersRequestDecodingWithNullParams() throws {
// Test decoding when params field is null
let jsonString = """
{"jsonrpc":"2.0","id":"test-id","method":"ping","params":null}
{"jsonrpc":"2.0","id":1,"method":"ping","params":null}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<Ping>.self, from: data)
#expect(decoded.id == "test-id")
#expect(decoded.id == 1)
#expect(decoded.method == Ping.name)
}
@Test("Required parameters request decoding - missing params")
func testRequiredParametersRequestDecodingMissingParams() throws {
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"tools/call"}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
#expect(throws: DecodingError.self) {
_ = try decoder.decode(Request<CallTool>.self, from: data)
}
}
@Test("Required parameters request decoding - null params")
func testRequiredParametersRequestDecodingNullParams() throws {
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":null}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
#expect(throws: DecodingError.self) {
_ = try decoder.decode(Request<CallTool>.self, from: data)
}
}
@Test("Empty parameters request decoding - with null params")
func testEmptyParametersRequestDecodingNullParams() throws {
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"empty.method","params":null}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<EmptyMethod>.self, from: data)
#expect(decoded.id == 1)
#expect(decoded.method == EmptyMethod.name)
}
@Test("Empty parameters request decoding - with empty object params")
func testEmptyParametersRequestDecodingEmptyParams() throws {
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"empty.method","params":{}}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try decoder.decode(Request<EmptyMethod>.self, from: data)
#expect(decoded.id == 1)
#expect(decoded.method == EmptyMethod.name)
}
@Test("Initialize request decoding - requires params")
func testInitializeRequestDecodingRequiresParams() throws {
// Test missing params field
let missingParams = """
{"jsonrpc":"2.0","id":"test-id","method":"initialize"}
"""
let decoder = JSONDecoder()
#expect(throws: DecodingError.self) {
_ = try decoder.decode(
Request<Initialize>.self, from: missingParams.data(using: .utf8)!)
}
// Test null params
let nullParams = """
{"jsonrpc":"2.0","id":"test-id","method":"initialize","params":null}
"""
#expect(throws: DecodingError.self) {
_ = try decoder.decode(Request<Initialize>.self, from: nullParams.data(using: .utf8)!)
}
// Verify that empty object params works (since fields have defaults)
let emptyParams = """
{"jsonrpc":"2.0","id":"test-id","method":"initialize","params":{}}
"""
let decoded = try decoder.decode(
Request<Initialize>.self, from: emptyParams.data(using: .utf8)!)
#expect(decoded.params.protocolVersion == Version.latest)
#expect(decoded.params.clientInfo.name == "unknown")
}
@Test("Invalid parameters request decoding")
func testInvalidParametersRequestDecoding() throws {
let jsonString = """
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"invalid":"value"}}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
#expect(throws: DecodingError.self) {
_ = try decoder.decode(Request<CallTool>.self, from: data)
}
}
@Test("NotRequired parameters request decoding")
func testNotRequiredParametersRequestDecoding() throws {
// Test with missing params
let missingParams = """
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
"""
let decoder = JSONDecoder()
let decodedMissing = try decoder.decode(
Request<ListTools>.self,
from: missingParams.data(using: .utf8)!)
#expect(decodedMissing.id == 1)
#expect(decodedMissing.method == ListTools.name)
#expect(decodedMissing.params.cursor == nil)
// Test with null params
let nullParams = """
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":null}
"""
let decodedNull = try decoder.decode(
Request<ListTools>.self,
from: nullParams.data(using: .utf8)!)
#expect(decodedNull.params.cursor == nil)
// Test with empty object params
let emptyParams = """
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}
"""
let decodedEmpty = try decoder.decode(
Request<ListTools>.self,
from: emptyParams.data(using: .utf8)!)
#expect(decodedEmpty.params.cursor == nil)
// Test with provided cursor
let withCursor = """
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"cursor":"next-page"}}
"""
let decodedWithCursor = try decoder.decode(
Request<ListTools>.self,
from: withCursor.data(using: .utf8)!)
#expect(decodedWithCursor.params.cursor == "next-page")
}
}