Update AnyNotification to include notification params (#86)
* Update AnyNotification to not wipe out params that come with a notification * Add test coverage for notification with params * Allow params to be omitted when empty --------- Co-authored-by: Stephen Tallent <stallent@mercury.io> Co-authored-by: Mattt Zmuda <mattt@loopwork.com>
This commit is contained in:
parent
25dee3bb4c
commit
99517b0564
2 changed files with 89 additions and 5 deletions
|
|
@ -278,7 +278,7 @@ public protocol Notification: Hashable, Codable, Sendable {
|
||||||
/// A type-erased notification for message handling
|
/// A type-erased notification for message handling
|
||||||
struct AnyNotification: Notification, Sendable {
|
struct AnyNotification: Notification, Sendable {
|
||||||
static var name: String { "" }
|
static var name: String { "" }
|
||||||
typealias Parameters = Empty
|
typealias Parameters = Value
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AnyNotification {
|
extension AnyNotification {
|
||||||
|
|
@ -325,10 +325,11 @@ public struct Message<N: Notification>: Hashable, Codable, Sendable {
|
||||||
}
|
}
|
||||||
method = try container.decode(String.self, forKey: .method)
|
method = try container.decode(String.self, forKey: .method)
|
||||||
|
|
||||||
// Handle params field being optional
|
if N.Parameters.self is NotRequired.Type {
|
||||||
if N.Parameters.self == Empty.self {
|
// For NotRequired parameters, use decodeIfPresent or init()
|
||||||
// For Empty parameters, use Empty() if params is missing or null
|
params =
|
||||||
params = Empty() as! N.Parameters
|
(try container.decodeIfPresent(N.Parameters.self, forKey: .params)
|
||||||
|
?? (N.Parameters.self as! NotRequired.Type).init() as! N.Parameters)
|
||||||
} else if let value = try? container.decode(N.Parameters.self, forKey: .params) {
|
} else if let value = try? container.decode(N.Parameters.self, forKey: .params) {
|
||||||
// If params exists and can be decoded, use it
|
// If params exists and can be decoded, use it
|
||||||
params = value
|
params = value
|
||||||
|
|
|
||||||
|
|
@ -86,4 +86,87 @@ struct NotificationTests {
|
||||||
|
|
||||||
#expect(decoded.method == InitializedNotification.name)
|
#expect(decoded.method == InitializedNotification.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test("Resource updated notification with parameters")
|
||||||
|
func testResourceUpdatedNotification() throws {
|
||||||
|
let params = ResourceUpdatedNotification.Parameters(uri: "test://resource")
|
||||||
|
let notification = ResourceUpdatedNotification.message(params)
|
||||||
|
|
||||||
|
#expect(notification.method == ResourceUpdatedNotification.name)
|
||||||
|
#expect(notification.params.uri == "test://resource")
|
||||||
|
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
|
||||||
|
let data = try encoder.encode(notification)
|
||||||
|
|
||||||
|
// Verify the exact JSON structure
|
||||||
|
let json = try JSONDecoder().decode([String: Value].self, from: data)
|
||||||
|
#expect(json["jsonrpc"] == "2.0")
|
||||||
|
#expect(json["method"] == "notifications/resources/updated")
|
||||||
|
#expect(json["params"] != nil)
|
||||||
|
#expect(json.count == 3, "Should contain jsonrpc, method, and params fields")
|
||||||
|
|
||||||
|
// Verify we can decode it back
|
||||||
|
let decoded = try decoder.decode(Message<ResourceUpdatedNotification>.self, from: data)
|
||||||
|
#expect(decoded.method == ResourceUpdatedNotification.name)
|
||||||
|
#expect(decoded.params.uri == "test://resource")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("AnyNotification decoding - without params")
|
||||||
|
func testAnyNotificationDecodingWithoutParams() throws {
|
||||||
|
// Test decoding when params field is missing
|
||||||
|
let jsonString = """
|
||||||
|
{"jsonrpc":"2.0","method":"notifications/initialized"}
|
||||||
|
"""
|
||||||
|
let data = jsonString.data(using: .utf8)!
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let decoded = try decoder.decode(AnyMessage.self, from: data)
|
||||||
|
|
||||||
|
#expect(decoded.method == InitializedNotification.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("AnyNotification decoding - with null params")
|
||||||
|
func testAnyNotificationDecodingWithNullParams() throws {
|
||||||
|
// Test decoding when params field is null
|
||||||
|
let jsonString = """
|
||||||
|
{"jsonrpc":"2.0","method":"notifications/initialized","params":null}
|
||||||
|
"""
|
||||||
|
let data = jsonString.data(using: .utf8)!
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let decoded = try decoder.decode(AnyMessage.self, from: data)
|
||||||
|
|
||||||
|
#expect(decoded.method == InitializedNotification.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("AnyNotification decoding - with empty params")
|
||||||
|
func testAnyNotificationDecodingWithEmptyParams() throws {
|
||||||
|
// Test decoding when params field is empty
|
||||||
|
let jsonString = """
|
||||||
|
{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
|
||||||
|
"""
|
||||||
|
let data = jsonString.data(using: .utf8)!
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let decoded = try decoder.decode(AnyMessage.self, from: data)
|
||||||
|
|
||||||
|
#expect(decoded.method == InitializedNotification.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("AnyNotification decoding - with non-empty params")
|
||||||
|
func testAnyNotificationDecodingWithNonEmptyParams() throws {
|
||||||
|
// Test decoding when params field has values
|
||||||
|
let jsonString = """
|
||||||
|
{"jsonrpc":"2.0","method":"notifications/resources/updated","params":{"uri":"test://resource"}}
|
||||||
|
"""
|
||||||
|
let data = jsonString.data(using: .utf8)!
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let decoded = try decoder.decode(AnyMessage.self, from: data)
|
||||||
|
|
||||||
|
#expect(decoded.method == ResourceUpdatedNotification.name)
|
||||||
|
#expect(decoded.params.objectValue?["uri"]?.stringValue == "test://resource")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue