Skip to content

Commit b373515

Browse files
ktosoyim-lee
andauthored
Follow up: binary or xml plist (#557)
* throw instead of fatal error when reading bytes in Serialization * =ser allow binary or xml property list serialization * Apply suggestions from code review Co-Authored-By: Yim Lee <[email protected]> Co-authored-by: Yim Lee <[email protected]>
1 parent 20df5b0 commit b373515

File tree

7 files changed

+134
-58
lines changed

7 files changed

+134
-58
lines changed

Docs/serialization.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ NOTE: While messages locally are passed simply by storing the passed message in
5252

5353
==== Selecting the default Codable Coders
5454

55+
NOTE: By default, Foundation's JSON and PropertyList (binary or xml) coders are supported.
56+
+
57+
You can configure which coder should be used by default by setting `settings.serialization.defaultSerializerID`.
58+
5559
The ActorSystem's serialization infrastructure allows for specifying what coder should be used for certain messages.
5660

5761
If a type is safe to be serialized (see <<serialization_register>>), it will be serialized using the serializer associated with its type. If no serializer is specified for its specific type, the default Codable coder will be used.

Sources/DistributedActors/Serialization/Serialization+Codable.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ extension Decodable {
3434
}
3535

3636
extension Decodable {
37-
static func _decode(from buffer: inout NIO.ByteBuffer, using decoder: PropertyListDecoder) throws -> Self {
37+
static func _decode(from buffer: inout NIO.ByteBuffer, using decoder: PropertyListDecoder, format _format: PropertyListSerialization.PropertyListFormat) throws -> Self {
3838
let readableBytes = buffer.readableBytes
3939

4040
return try buffer.withUnsafeMutableReadableBytes {
4141
// we are getting the pointer from a ByteBuffer, so it should be valid and force unwrap should be fine
4242
let data = Data(bytesNoCopy: $0.baseAddress!, count: readableBytes, deallocator: .none)
43-
return try decoder.decode(Self.self, from: data)
43+
var format = _format
44+
return try decoder.decode(Self.self, from: data, format: &format)
4445
}
4546
}
4647
}

Sources/DistributedActors/Serialization/Serialization+SerializerID.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ extension Serialization {
3131
switch self.value {
3232
case SerializerID.doNotSerialize.value:
3333
return "serializerID:doNotSerialize(\(self.value))"
34+
case SerializerID.protobufRepresentable.value:
35+
return "serializerID:protobufRepresentable(\(self.value))"
3436
case SerializerID.specializedWithTypeHint.value:
3537
return "serializerID:specialized(\(self.value))"
3638
case SerializerID.foundationJSON.value:
3739
return "serializerID:jsonCodable(\(self.value))"
38-
case SerializerID.foundationPropertyList.value:
39-
return "serializerID:plistCodable(\(self.value))"
40-
case SerializerID.protobufRepresentable.value:
41-
return "serializerID:protobufRepresentable(\(self.value))"
40+
case SerializerID.foundationPropertyListBinary.value:
41+
return "serializerID:foundationPropertyListBinary(\(self.value))"
42+
case SerializerID.foundationPropertyListXML.value:
43+
return "serializerID:foundationPropertyListXML(\(self.value))"
4244
default:
4345
return "serializerID:\(self.value)"
4446
}
@@ -66,10 +68,12 @@ extension Serialization.SerializerID {
6668
public static let doNotSerialize: SerializerID = 0
6769

6870
public static let specializedWithTypeHint: SerializerID = 1
69-
public static let foundationJSON: SerializerID = 2
70-
public static let foundationPropertyList: SerializerID = 3
71-
public static let protobufRepresentable: SerializerID = 4
72-
// ... reserved = 5
71+
public static let protobufRepresentable: SerializerID = 2
72+
73+
public static let foundationJSON: SerializerID = 3
74+
public static let foundationPropertyListBinary: SerializerID = 4
75+
public static let foundationPropertyListXML: SerializerID = 5
76+
// ... reserved = 6
7377
// ... -- || --
7478
// ... reserved = 16
7579

Sources/DistributedActors/Serialization/Serialization+Serializers+Codable.swift

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@ import NIOFoundationCompat
1717

1818
import Foundation // for Codable
1919

20-
/// Allows for serialization of messages using any compatible `Encoder` and `Decoder` pair.
21-
///
22-
/// Such serializer may be registered with `Serialization` and assigned either as default (see `Serialization.Settings
20+
/// Allows for serialization of messages using the Foundation's `JSONEncoder` and `JSONDecoder`.
2321
///
2422
/// - Note: Take care to ensure that both "ends" (sending and receiving members of a cluster)
2523
/// use the same encoding/decoding mechanism for a specific message.
26-
public class JSONCodableSerializer<Message: Codable>: Serializer<Message> {
24+
// TODO: would be nice to be able to abstract over the coders (using TopLevelDecoder-like types) then rename this to `AnyCodableSerializer`
25+
internal class JSONCodableSerializer<Message: Codable>: Serializer<Message> {
2726
internal let allocate: ByteBufferAllocator
2827
internal var encoder: JSONEncoder
2928
internal var decoder: JSONDecoder
@@ -45,7 +44,7 @@ public class JSONCodableSerializer<Message: Codable>: Serializer<Message> {
4544

4645
public override func deserialize(from bytes: ByteBuffer) throws -> Message {
4746
guard let data = bytes.getData(at: 0, length: bytes.readableBytes) else {
48-
fatalError("Could not read data! Was: \(bytes), trying to deserialize: \(Message.self)")
47+
throw SerializationError.unableToDeserialize(hint: "Could not read data! Was: \(bytes), trying to deserialize: \(Message.self)")
4948
}
5049

5150
return try self.decoder.decode(Message.self, from: data)
@@ -63,20 +62,29 @@ public class JSONCodableSerializer<Message: Codable>: Serializer<Message> {
6362
}
6463
}
6564

66-
/// Allows for serialization of messages using any compatible `Encoder` and `Decoder` pair.
67-
///
68-
/// Such serializer may be registered with `Serialization` and assigned either as default (see `Serialization.Settings
65+
/// Allows for serialization of messages using the Foundation's `PropertyListEncoder` and `PropertyListDecoder`, using the specified format.
6966
///
7067
/// - Note: Take care to ensure that both "ends" (sending and receiving members of a cluster)
7168
/// use the same encoding/decoding mechanism for a specific message.
72-
public class PropertyListCodableSerializer<Message: Codable>: Serializer<Message> {
69+
// TODO: would be nice to be able to abstract over the coders (using TopLevelDecoder-like types) then rename this to `AnyCodableSerializer`
70+
internal class PropertyListCodableSerializer<Message: Codable>: Serializer<Message> {
7371
internal let allocate: ByteBufferAllocator
74-
internal var encoder: PropertyListEncoder
75-
internal var decoder: PropertyListDecoder
72+
internal let encoder: PropertyListEncoder
73+
internal let decoder: PropertyListDecoder
74+
internal let format: PropertyListSerialization.PropertyListFormat
75+
76+
public init(allocator: ByteBufferAllocator, format: PropertyListSerialization.PropertyListFormat) {
77+
self.allocate = allocator
78+
self.format = format
79+
self.encoder = PropertyListEncoder()
80+
self.encoder.outputFormat = format
81+
self.decoder = PropertyListDecoder()
82+
}
7683

7784
public init(allocator: ByteBufferAllocator, encoder: PropertyListEncoder = .init(), decoder: PropertyListDecoder = .init()) {
7885
self.allocate = allocator
7986
self.encoder = encoder
87+
self.format = encoder.outputFormat
8088
self.decoder = decoder
8189
}
8290

@@ -91,10 +99,11 @@ public class PropertyListCodableSerializer<Message: Codable>: Serializer<Message
9199

92100
public override func deserialize(from bytes: ByteBuffer) throws -> Message {
93101
guard let data = bytes.getData(at: 0, length: bytes.readableBytes) else {
94-
fatalError("Could not read data! Was: \(bytes), trying to deserialize: \(Message.self)")
102+
throw SerializationError.unableToDeserialize(hint: "Could not read data! Was: \(bytes), trying to deserialize: \(Message.self)")
95103
}
96104

97-
return try self.decoder.decode(Message.self, from: data)
105+
var format = self.format
106+
return try self.decoder.decode(Message.self, from: data, format: &format)
98107
}
99108

100109
public override func setSerializationContext(_ context: Serialization.Context) {

Sources/DistributedActors/Serialization/Serialization.swift

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,22 @@ extension Serialization {
306306
serializer.setSerializationContext(self.context)
307307
return serializer
308308

309-
case Serialization.SerializerID.foundationPropertyList:
310-
let serializer = PropertyListCodableSerializer<Message>(allocator: self.allocator)
309+
case Serialization.SerializerID.foundationPropertyListBinary,
310+
Serialization.SerializerID.foundationPropertyListXML:
311+
let format: PropertyListSerialization.PropertyListFormat
312+
switch manifest.serializerID {
313+
case Serialization.SerializerID.foundationPropertyListBinary:
314+
format = .binary
315+
case Serialization.SerializerID.foundationPropertyListXML:
316+
format = .xml
317+
default:
318+
fatalError("Unable to make PropertyList serializer for serializerID: \(manifest.serializerID); type: \(String(reflecting: Message.self))")
319+
}
320+
321+
let serializer = PropertyListCodableSerializer<Message>(
322+
allocator: self.allocator,
323+
format: format
324+
)
311325
serializer.setSerializationContext(self.context)
312326
return serializer
313327

@@ -376,20 +390,27 @@ extension Serialization {
376390
"""
377391
)
378392

393+
case .protobufRepresentable:
394+
let encoder = TopLevelProtobufBlobEncoder(allocator: self.allocator)
395+
encoder.userInfo[.actorSerializationContext] = self.context
396+
result = try encodableMessage._encode(using: encoder)
397+
379398
case .foundationJSON:
380399
let encoder = JSONEncoder()
381400
encoder.userInfo[.actorSerializationContext] = self.context
382401
result = try encodableMessage._encode(using: encoder, allocator: self.allocator)
383402

384-
case .foundationPropertyList:
403+
case .foundationPropertyListBinary:
385404
let encoder = PropertyListEncoder()
405+
encoder.outputFormat = .binary
386406
encoder.userInfo[.actorSerializationContext] = self.context
387407
result = try encodableMessage._encode(using: encoder, allocator: self.allocator)
388408

389-
case .protobufRepresentable:
390-
let encoder = TopLevelProtobufBlobEncoder(allocator: self.allocator)
409+
case .foundationPropertyListXML:
410+
let encoder = PropertyListEncoder()
411+
encoder.outputFormat = .xml
391412
encoder.userInfo[.actorSerializationContext] = self.context
392-
result = try encodableMessage._encode(using: encoder)
413+
result = try encodableMessage._encode(using: encoder, allocator: self.allocator)
393414

394415
case let otherSerializerID:
395416
throw SerializationError.unableToMakeSerializer(hint: "SerializerID: \(otherSerializerID), messageType: \(messageType), manifest: \(manifest)")
@@ -484,20 +505,25 @@ extension Serialization {
484505
Known specializedSerializerMakers: \(self.settings.specializedSerializerMakers)
485506
"""
486507
)
508+
509+
case .protobufRepresentable:
510+
let decoder = TopLevelProtobufBlobDecoder()
511+
decoder.userInfo[.actorSerializationContext] = self.context
512+
result = try decodableMessageType._decode(from: &bytes, using: decoder)
513+
487514
case .foundationJSON:
488515
let decoder = JSONDecoder()
489516
decoder.userInfo[.actorSerializationContext] = self.context
490517
result = try decodableMessageType._decode(from: &bytes, using: decoder)
491518

492-
case .foundationPropertyList:
519+
case .foundationPropertyListBinary:
493520
let decoder = PropertyListDecoder()
494521
decoder.userInfo[.actorSerializationContext] = self.context
495-
result = try decodableMessageType._decode(from: &bytes, using: decoder)
496-
497-
case .protobufRepresentable:
498-
let decoder = TopLevelProtobufBlobDecoder()
522+
result = try decodableMessageType._decode(from: &bytes, using: decoder, format: .binary)
523+
case .foundationPropertyListXML:
524+
let decoder = PropertyListDecoder()
499525
decoder.userInfo[.actorSerializationContext] = self.context
500-
result = try decodableMessageType._decode(from: &bytes, using: decoder)
526+
result = try decodableMessageType._decode(from: &bytes, using: decoder, format: .xml)
501527

502528
case let otherSerializerID:
503529
throw SerializationError.unableToMakeSerializer(hint: "SerializerID: \(otherSerializerID), messageType: \(manifestMessageType), manifest: \(manifest)")

Sources/DistributedActors/Serialization/TopLevelProtobufCoders.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ struct TopLevelProtobufBlobSingleValueDecodingContainer: SingleValueDecodingCont
227227

228228
if type is Data.Type {
229229
guard let data = bytes.getData(at: 0, length: bytes.readableBytes) else {
230-
fatalError("Could not read data! Was: \(bytes), trying to deserialize: \(T.self)")
230+
throw SerializationError.unableToDeserialize(hint: "Could not read data! Was: \(bytes), trying to deserialize: \(T.self)")
231231
}
232232
return data as! T
233233
} else if type is NIO.ByteBuffer.Type {

Tests/DistributedActorsTests/SerializationTests.swift

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ class SerializationTests: ActorSystemTestBase {
2828
settings.serialization.register(HasIntRef.self)
2929
settings.serialization.register(HasInterestingMessageRef.self)
3030
settings.serialization.register(CodableTestingError.self)
31+
32+
settings.serialization.register(PListBinCodableTest.self, serializerID: .foundationPropertyListBinary)
33+
settings.serialization.register(PListXMLCodableTest.self, serializerID: .foundationPropertyListXML)
3134
}
3235
}
3336

@@ -349,34 +352,53 @@ class SerializationTests: ActorSystemTestBase {
349352
codableTestingError.shouldEqual(codableError)
350353
}
351354

352-
func test_plist() throws {
353-
struct Test: Codable, Equatable {
354-
let name: String
355-
let items: [String]
355+
// ==== ----------------------------------------------------------------------------------------------------------------
356+
// MARK: PList coding
357+
358+
func test_plist_binary() throws {
359+
let test = PListBinCodableTest(name: "foo", items: ["bar", "baz", "baz", "baz", "baz", "baz", "baz", "baz", "baz", "baz", "baz", "baz", "baz", "baz", "baz", "baz"])
360+
361+
var (manifest, bytes) = try! shouldNotThrow {
362+
try system.serialization.serialize(test)
356363
}
357364

358-
let s2 = ActorSystem("SerializeMessages") { settings in
359-
settings.serialization.serializeLocalMessages = true
360-
settings.serialization.register(Test.self, serializerID: .foundationPropertyList)
365+
let back = try! system.serialization.deserialize(as: PListBinCodableTest.self, from: &bytes, using: manifest)
366+
367+
back.shouldEqual(test)
368+
}
369+
370+
func test_plist_xml() throws {
371+
let test = PListXMLCodableTest(name: "foo", items: ["bar", "baz"])
372+
373+
var (manifest, bytes) = try shouldNotThrow {
374+
try system.serialization.serialize(test)
361375
}
362376

363-
do {
364-
let p = self.testKit.spawnTestProbe("p1", expecting: Test.self)
365-
let echo: ActorRef<Test> = try s2.spawn(
366-
"echo",
367-
.receiveMessage { msg in
368-
p.ref.tell(Test(name: "echo:\(msg.name)", items: msg.items.map { "echo:\($0)" }))
369-
return .same
370-
}
371-
)
377+
let back = try system.serialization.deserialize(as: PListXMLCodableTest.self, from: &bytes, using: manifest)
372378

373-
echo.tell(Test(name: "foo", items: ["bar", "baz"])) // is a built-in serializable message
374-
try p.expectMessage(Test(name: "echo:foo", items: ["echo:bar", "echo:baz"]))
375-
} catch {
376-
s2.shutdown().wait()
377-
throw error
379+
back.shouldEqual(test)
380+
}
381+
382+
func test_plist_throws_whenWrongFormat() throws {
383+
let test = PListXMLCodableTest(name: "foo", items: ["bar", "baz"])
384+
385+
var (manifest, bytes) = try shouldNotThrow {
386+
try system.serialization.serialize(test)
378387
}
379-
s2.shutdown().wait()
388+
389+
let system2 = ActorSystem("OtherSystem") { settings in
390+
settings.serialization.register(PListXMLCodableTest.self, serializerID: .foundationPropertyListBinary) // on purpose "wrong" format
391+
}
392+
defer {
393+
system2.shutdown().wait()
394+
}
395+
396+
_ = shouldThrow {
397+
_ = try system2.serialization.deserialize(as: PListXMLCodableTest.self, from: &bytes, using: manifest)
398+
}
399+
400+
let back = try system.serialization.deserialize(as: PListXMLCodableTest.self, from: &bytes, using: manifest)
401+
back.shouldEqual(test)
380402
}
381403
}
382404

@@ -467,3 +489,13 @@ private enum CodableTestingError: String, Error, Equatable, Codable {
467489
case errorA
468490
case errorB
469491
}
492+
493+
private struct PListBinCodableTest: Codable, Equatable {
494+
let name: String
495+
let items: [String]
496+
}
497+
498+
private struct PListXMLCodableTest: Codable, Equatable {
499+
let name: String
500+
let items: [String]
501+
}

0 commit comments

Comments
 (0)