From e0e70d846d674177505ae66470c66b6764a110df Mon Sep 17 00:00:00 2001 From: toki Date: Sun, 24 May 2026 20:06:51 +0900 Subject: [PATCH] fix dart qualified protobuf type names --- dart/lib/src/communicator.dart | 102 +++++++++++++++++++++++-------- dart/test/communicator_test.dart | 72 +++++++++++++++++++++- 2 files changed, 146 insertions(+), 28 deletions(-) diff --git a/dart/lib/src/communicator.dart b/dart/lib/src/communicator.dart index af8f2d3..aa5ee08 100644 --- a/dart/lib/src/communicator.dart +++ b/dart/lib/src/communicator.dart @@ -10,7 +10,8 @@ abstract class Communicator { static const int maxNonce = 2147483647; Map _handlerDic = {}; - Map Function(List, int)> _requestHandlerDic = {}; + Map Function(String, List, int)> + _requestHandlerDic = {}; Map _pendingRequests = {}; late Map)> _instanceGenerator; Future _outboundWrite = Future.value(); @@ -49,11 +50,42 @@ abstract class Communicator { } T Function(List) getGenerator(String type) { - if (!_instanceGenerator.containsKey(type)) { + final generator = + _instanceGenerator[type] ?? _instanceGenerator[_shortTypeName(type)]; + if (generator == null) { throw Exception( 'Must set protobuf packet creator before use it. Type: ${(T).toString()}'); } - return _instanceGenerator[type] as T Function(List); + return generator as T Function(List); + } + + String _shortTypeName(String typeName) { + final lastDot = typeName.lastIndexOf('.'); + if (lastDot < 0 || lastDot == typeName.length - 1) { + return typeName; + } + return typeName.substring(lastDot + 1); + } + + V? _lookupByWireType(Map map, String typeName) { + return map[typeName] ?? map[_shortTypeName(typeName)]; + } + + GeneratedMessage _decodeMessage( + String typeName, + List data, { + String? fallbackTypeName, + }) { + final generator = _instanceGenerator[typeName] ?? + (fallbackTypeName == null + ? null + : _instanceGenerator[fallbackTypeName]) ?? + _instanceGenerator[_shortTypeName(typeName)]; + if (generator == null) { + throw Exception( + 'Must set protobuf packet creator before use it. Type: $typeName'); + } + return generator(data); } /// Serializes writes so stream transports do not interleave packets. @@ -100,8 +132,18 @@ abstract class Communicator { final expectedResponseType = Res.toString(); _pendingRequests[requestNonce] = _PendingRequest( expectedTypeName: expectedResponseType, - complete: (bytes) { - completer.complete(getGenerator(expectedResponseType)(bytes)); + complete: (typeName, bytes) { + final message = _decodeMessage(typeName, bytes, + fallbackTypeName: expectedResponseType); + if (message is! Res) { + completer.completeError( + StateError( + 'Response type mismatch for nonce $requestNonce: expected $expectedResponseType, got $typeName'), + StackTrace.current, + ); + return; + } + completer.complete(message); }, completeError: (error, stackTrace) { completer.completeError(error, stackTrace); @@ -139,8 +181,15 @@ abstract class Communicator { throw StateError( 'Type $reqType is already registered with addListener and cannot also use addRequestListener.'); } - _requestHandlerDic[reqType] = (List bytes, int requestNonce) async { - final req = getGenerator(reqType)(bytes); + _requestHandlerDic[reqType] = + (String typeName, List bytes, int requestNonce) async { + final message = + _decodeMessage(typeName, bytes, fallbackTypeName: reqType); + if (message is! Req) { + throw StateError( + 'Request type mismatch for nonce $requestNonce: expected $reqType, got $typeName'); + } + final req = message; final res = await handler(req); if (isAlive) { await queuePacket(PacketBase() @@ -157,23 +206,17 @@ abstract class Communicator { if (responseNonce > 0) { final pending = _pendingRequests.remove(responseNonce); if (pending == null) return; - if (typeName != pending.expectedTypeName) { - pending.completeError( - StateError( - 'Response type mismatch for nonce $responseNonce: expected ${pending.expectedTypeName}, got $typeName'), - StackTrace.current, - ); - return; - } - pending.complete(data); + pending.complete(typeName, data); return; } - if (_requestHandlerDic.containsKey(typeName)) { - _requestHandlerDic[typeName]!(data, incomingNonce); + final requestHandler = _lookupByWireType(_requestHandlerDic, typeName); + if (requestHandler != null) { + requestHandler(typeName, data, incomingNonce); return; } - if (_handlerDic.containsKey(typeName)) { - _handlerDic[typeName]?.onMessage(data); + final handler = _lookupByWireType(_handlerDic, typeName); + if (handler != null) { + handler.onMessage(typeName, data); } } @@ -184,7 +227,14 @@ abstract class Communicator { 'Type $type is already registered with addRequestListener and cannot also use addListener.'); } if (!_handlerDic.containsKey(type)) { - _handlerDic[type] = DataHandler(getGenerator(type)); + _handlerDic[type] = DataHandler((typeName, data) { + final message = _decodeMessage(typeName, data, fallbackTypeName: type); + if (message is! T) { + throw StateError( + 'Message type mismatch: expected $type, got $typeName'); + } + return message; + }); } var handler = _handlerDic[type] as DataHandler; handler.addListener(listener); @@ -200,19 +250,19 @@ abstract class Communicator { } abstract class IDataHandler { - void onMessage(List data); + void onMessage(String typeName, List data); } class DataHandler implements IDataHandler { - T Function(List) _generator; + T Function(String, List) _generator; List _listeners = []; DataHandler(this._generator); @override - void onMessage(List data) { + void onMessage(String typeName, List data) { for (var listener in _listeners) { - listener.call(_generator(data)); + listener.call(_generator(typeName, data)); } } @@ -228,7 +278,7 @@ class DataHandler implements IDataHandler { class _PendingRequest { final String expectedTypeName; - final void Function(List) complete; + final void Function(String, List) complete; final void Function(Object, StackTrace) completeError; _PendingRequest({ diff --git a/dart/test/communicator_test.dart b/dart/test/communicator_test.dart index acbf6aa..1968285 100644 --- a/dart/test/communicator_test.dart +++ b/dart/test/communicator_test.dart @@ -7,10 +7,13 @@ import 'package:proto_socket/proto_socket.dart'; class _FakeCommunicator extends Communicator { final _FakeTransport transport = _FakeTransport(); - _FakeCommunicator() { + _FakeCommunicator({bool packageQualifiedParser = false}) { isAlive = true; + final testDataType = packageQualifiedParser + ? 'example.TestData' + : TestData.getDefault().info_.qualifiedMessageName; initialize({ - TestData.getDefault().info_.qualifiedMessageName: TestData.fromBuffer, + testDataType: TestData.fromBuffer, HeartBeat.getDefault().info_.qualifiedMessageName: HeartBeat.fromBuffer, }, transport: transport); } @@ -177,6 +180,71 @@ void main() { expect((await wrappedFuture).message, 'wrapped response'); }); + test('sendRequest accepts package-qualified response typeName', () async { + final communicator = _FakeCommunicator(packageQualifiedParser: true); + final future = communicator.sendRequest( + TestData() + ..index = 1 + ..message = 'request', + ); + + await Future.delayed(Duration.zero); + final requestNonce = communicator.transport.sentPackets.single.nonce; + communicator.onReceivedData( + 'example.TestData', + (TestData() + ..index = 2 + ..message = 'qualified response') + .writeToBuffer(), + responseNonce: requestNonce, + ); + + expect((await future).message, 'qualified response'); + }); + + test('addListener accepts package-qualified message typeName', () { + final communicator = _FakeCommunicator(packageQualifiedParser: true); + final messages = []; + communicator.addListener(messages.add); + + communicator.onReceivedData( + 'example.TestData', + (TestData() + ..index = 3 + ..message = 'qualified event') + .writeToBuffer(), + ); + + expect(messages, hasLength(1)); + expect(messages.single.message, 'qualified event'); + }); + + test('addRequestListener accepts package-qualified request typeName', + () async { + final communicator = _FakeCommunicator(packageQualifiedParser: true); + communicator.addRequestListener((req) async { + return TestData() + ..index = req.index + 1 + ..message = 'echo: ${req.message}'; + }); + + communicator.onReceivedData( + 'example.TestData', + (TestData() + ..index = 4 + ..message = 'qualified request') + .writeToBuffer(), + incomingNonce: 12, + ); + await Future.delayed(Duration.zero); + + final response = communicator.transport.sentPackets.single; + expect(response.responseNonce, 12); + final data = TestData.fromBuffer(response.data); + expect(data.index, 5); + expect(data.message, 'echo: qualified request'); + }); + test( 'cannot register addRequestListener for a type already using addListener', () {