diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 57774567..a42726ac 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - sdk: [3.5, dev] + sdk: [stable, dev] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c @@ -27,7 +27,7 @@ jobs: - name: Install dependencies run: dart pub get - name: Check formatting (using dev dartfmt release) - if: ${{ matrix.sdk == 'dev' }} + if: ${{ matrix.sdk == 'stable' }} run: dart format --output=none --set-exit-if-changed . - name: Analyze code (introp and examples) run: | @@ -60,7 +60,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - sdk: [3.5, dev] + sdk: [stable, dev] platform: [vm, chrome] exclude: # We only run Chrome tests on Linux. No need to run them diff --git a/CHANGELOG.md b/CHANGELOG.md index 343e949a..7c91dd63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ +## 4.1.1-wip + +- Require Dart 3.8. +- Dart format all files for the new 3.8 formatter. +- Require package:googleapis_auth +- Require package:http 1.4.0 +- Require package:lints 6.0.0 +- Require package:protobuf 4.1.0 + ## 4.1.0 -* Add a `serverInterceptors` argument to `ConnectionServer`. These interceptors are acting - as middleware, wrapping a `ServiceMethod` invocation. + +* Add a `serverInterceptors` argument to `ConnectionServer`. These interceptors + are acting as middleware, wrapping a `ServiceMethod` invocation. * Make sure that `CallOptions.mergeWith` is symmetric: given `WebCallOptions` it should return `WebCallOptions`. @@ -16,8 +26,8 @@ * Internal optimization to client code. * Small fixes, such as ports in testing and enabling `timeline_test.dart`. -* When the keep alive manager runs into a timeout, it will finish the transport instead of closing - the connection, as defined in the gRPC spec. +* When the keep alive manager runs into a timeout, it will finish the transport + instead of closing the connection, as defined in the gRPC spec. * Upgrade to `package:lints` version 5.0.0 and Dart SDK version 3.5.0. * Upgrade `example/grpc-web` code. * Update xhr transport to migrate off legacy JS/HTML apis. diff --git a/analysis_options.yaml b/analysis_options.yaml index 18020d59..e350e1a4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -17,6 +17,5 @@ linter: - prefer_final_locals - prefer_relative_imports - prefer_single_quotes - # Enable once 3.7 is stable. - # - strict_top_level_inference + - strict_top_level_inference - test_types_in_equals diff --git a/example/googleapis/analysis_options.yaml b/example/googleapis/analysis_options.yaml index d8941fc5..f04c6cf0 100644 --- a/example/googleapis/analysis_options.yaml +++ b/example/googleapis/analysis_options.yaml @@ -1,5 +1 @@ include: ../../analysis_options.yaml - -linter: - rules: - directives_ordering: false diff --git a/example/googleapis/bin/logging.dart b/example/googleapis/bin/logging.dart index b3039f22..e524990d 100644 --- a/example/googleapis/bin/logging.dart +++ b/example/googleapis/bin/logging.dart @@ -16,18 +16,19 @@ import 'dart:async'; import 'dart:io'; -import 'package:grpc/grpc.dart'; - import 'package:googleapis/src/generated/google/api/monitored_resource.pb.dart'; import 'package:googleapis/src/generated/google/logging/type/log_severity.pb.dart'; import 'package:googleapis/src/generated/google/logging/v2/log_entry.pb.dart'; import 'package:googleapis/src/generated/google/logging/v2/logging.pbgrpc.dart'; +import 'package:grpc/grpc.dart'; Future main() async { final serviceAccountFile = File('logging-service-account.json'); if (!serviceAccountFile.existsSync()) { - print('File logging-service-account.json not found. Please follow the ' - 'steps in README.md to create it.'); + print( + 'File logging-service-account.json not found. Please follow the ' + 'steps in README.md to create it.', + ); exit(-1); } @@ -37,19 +38,25 @@ Future main() async { ]; final authenticator = ServiceAccountAuthenticator( - serviceAccountFile.readAsStringSync(), scopes); + serviceAccountFile.readAsStringSync(), + scopes, + ); final projectId = authenticator.projectId; final channel = ClientChannel('logging.googleapis.com'); - final logging = - LoggingServiceV2Client(channel, options: authenticator.toCallOptions); + final logging = LoggingServiceV2Client( + channel, + options: authenticator.toCallOptions, + ); final request = WriteLogEntriesRequest() - ..entries.add(LogEntry() - ..logName = 'projects/$projectId/logs/example' - ..severity = LogSeverity.INFO - ..resource = (MonitoredResource()..type = 'global') - ..textPayload = 'This is a log entry!'); + ..entries.add( + LogEntry() + ..logName = 'projects/$projectId/logs/example' + ..severity = LogSeverity.INFO + ..resource = (MonitoredResource()..type = 'global') + ..textPayload = 'This is a log entry!', + ); await logging.writeLogEntries(request); await channel.shutdown(); diff --git a/example/googleapis/pubspec.yaml b/example/googleapis/pubspec.yaml index 139bf224..8c7805fd 100644 --- a/example/googleapis/pubspec.yaml +++ b/example/googleapis/pubspec.yaml @@ -3,15 +3,15 @@ description: Dart gRPC client sample for Google APIs publish_to: none environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.8.0 dependencies: - async: ^2.2.0 - fixnum: + async: ^2.13.0 + fixnum: ^1.1.1 grpc: path: ../../ - protobuf: ">=3.0.0 <5.0.0" + protobuf: ^4.1.0 dev_dependencies: - lints: ^2.0.0 - test: ^1.6.4 + lints: ^6.0.0 + test: ^1.26.2 diff --git a/example/grpc-web/analysis_options.yaml b/example/grpc-web/analysis_options.yaml index d8941fc5..572dd239 100644 --- a/example/grpc-web/analysis_options.yaml +++ b/example/grpc-web/analysis_options.yaml @@ -1,5 +1 @@ -include: ../../analysis_options.yaml - -linter: - rules: - directives_ordering: false +include: package:lints/recommended.yaml diff --git a/example/grpc-web/lib/app.dart b/example/grpc-web/lib/app.dart index a9ec7b3d..17420ad2 100644 --- a/example/grpc-web/lib/app.dart +++ b/example/grpc-web/lib/app.dart @@ -39,11 +39,17 @@ class EchoApp { ..message = message ..messageCount = count ..messageInterval = 500; - _service.serverStreamingEcho(request).listen((response) { - _addRightMessage(response.message); - }, onError: (error) { - _addRightMessage(error.toString()); - }, onDone: () => print('Closed connection to server.')); + _service + .serverStreamingEcho(request) + .listen( + (response) { + _addRightMessage(response.message); + }, + onError: (error) { + _addRightMessage(error.toString()); + }, + onDone: () => print('Closed connection to server.'), + ); } void _addLeftMessage(String message) { @@ -55,13 +61,20 @@ class EchoApp { } void _addMessage(String message, String cssClass) { - document.querySelector('#first')!.after(HTMLDivElement() - ..classList.add('row') - ..append(HTMLHeadingElement.h2() - ..append(HTMLSpanElement() - ..classList.add('label') - ..classList.addAll(cssClass) - ..textContent = message))); + document + .querySelector('#first')! + .after( + HTMLDivElement() + ..classList.add('row') + ..append( + HTMLHeadingElement.h2()..append( + HTMLSpanElement() + ..classList.add('label') + ..classList.addAll(cssClass) + ..textContent = message, + ), + ), + ); } } diff --git a/example/grpc-web/pubspec.yaml b/example/grpc-web/pubspec.yaml index f48411e7..77645470 100644 --- a/example/grpc-web/pubspec.yaml +++ b/example/grpc-web/pubspec.yaml @@ -3,15 +3,15 @@ description: Dart gRPC-Web sample client publish_to: none environment: - sdk: ^3.5.0 + sdk: ^3.8.0 dependencies: grpc: path: ../../ - protobuf: ">=3.0.0 <5.0.0" - web: ^1.1.0 + protobuf: ^4.1.0 + web: ^1.1.1 dev_dependencies: - build_runner: ^2.4.13 - build_web_compilers: ^4.0.11 - lints: ^5.0.0 + build_runner: ^2.4.15 + build_web_compilers: ^4.1.5 + lints: ^6.0.0 diff --git a/example/helloworld/analysis_options.yaml b/example/helloworld/analysis_options.yaml index d8941fc5..572dd239 100644 --- a/example/helloworld/analysis_options.yaml +++ b/example/helloworld/analysis_options.yaml @@ -1,5 +1 @@ -include: ../../analysis_options.yaml - -linter: - rules: - directives_ordering: false +include: package:lints/recommended.yaml diff --git a/example/helloworld/bin/client.dart b/example/helloworld/bin/client.dart index 4ec8e8e2..d46d8c53 100644 --- a/example/helloworld/bin/client.dart +++ b/example/helloworld/bin/client.dart @@ -23,8 +23,9 @@ Future main(List args) async { port: 50051, options: ChannelOptions( credentials: ChannelCredentials.insecure(), - codecRegistry: - CodecRegistry(codecs: const [GzipCodec(), IdentityCodec()]), + codecRegistry: CodecRegistry( + codecs: const [GzipCodec(), IdentityCodec()], + ), ), ); final stub = GreeterClient(channel); diff --git a/example/helloworld/bin/unix_client.dart b/example/helloworld/bin/unix_client.dart index 0c3cacc7..7f862909 100644 --- a/example/helloworld/bin/unix_client.dart +++ b/example/helloworld/bin/unix_client.dart @@ -20,8 +20,10 @@ import 'package:helloworld/src/generated/helloworld.pbgrpc.dart'; /// Dart implementation of the gRPC helloworld.Greeter client. Future main(List args) async { - final udsAddress = - InternetAddress('localhost', type: InternetAddressType.unix); + final udsAddress = InternetAddress( + 'localhost', + type: InternetAddressType.unix, + ); final channel = ClientChannel( udsAddress, port: 0, diff --git a/example/helloworld/bin/unix_server.dart b/example/helloworld/bin/unix_server.dart index a30eaecf..6c1cbeaf 100644 --- a/example/helloworld/bin/unix_server.dart +++ b/example/helloworld/bin/unix_server.dart @@ -27,8 +27,10 @@ class GreeterService extends GreeterServiceBase { } Future main(List args) async { - final udsAddress = - InternetAddress('localhost', type: InternetAddressType.unix); + final udsAddress = InternetAddress( + 'localhost', + type: InternetAddressType.unix, + ); final server = Server.create(services: [GreeterService()]); await server.serve(address: udsAddress); print('Start UNIX Server @localhost...'); diff --git a/example/helloworld/pubspec.yaml b/example/helloworld/pubspec.yaml index 7e5da048..36874c2c 100644 --- a/example/helloworld/pubspec.yaml +++ b/example/helloworld/pubspec.yaml @@ -3,13 +3,13 @@ description: Dart gRPC sample client and server. publish_to: none environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.8.0 dependencies: - async: ^2.2.0 + async: ^2.13.0 grpc: path: ../../ - protobuf: ">=3.0.0 <5.0.0" + protobuf: ^4.1.0 dev_dependencies: - lints: ^2.0.0 + lints: ^6.0.0 diff --git a/example/metadata/analysis_options.yaml b/example/metadata/analysis_options.yaml index d8941fc5..572dd239 100644 --- a/example/metadata/analysis_options.yaml +++ b/example/metadata/analysis_options.yaml @@ -1,5 +1 @@ -include: ../../analysis_options.yaml - -linter: - rules: - directives_ordering: false +include: package:lints/recommended.yaml diff --git a/example/metadata/lib/src/client.dart b/example/metadata/lib/src/client.dart index 488a2cd8..1f91e6af 100644 --- a/example/metadata/lib/src/client.dart +++ b/example/metadata/lib/src/client.dart @@ -24,10 +24,11 @@ class Client { late MetadataClient stub; Future main(List args) async { - channel = ClientChannel('127.0.0.1', - port: 8080, - options: - const ChannelOptions(credentials: ChannelCredentials.insecure())); + channel = ClientChannel( + '127.0.0.1', + port: 8080, + options: const ChannelOptions(credentials: ChannelCredentials.insecure()), + ); stub = MetadataClient(channel); // Run all of the demos in order. await runEcho(); @@ -44,8 +45,10 @@ class Client { /// metadata. Future runEcho() async { final request = Record()..value = 'Kaj'; - final call = - stub.echo(request, options: CallOptions(metadata: {'peer': 'Verner'})); + final call = stub.echo( + request, + options: CallOptions(metadata: {'peer': 'Verner'}), + ); call.headers.then((headers) { print('Received header metadata: $headers'); }); @@ -62,11 +65,15 @@ class Client { /// well as a per-call metadata. The server will delay the response for the /// requested duration, during which the client will cancel the RPC. Future runEchoDelayCancel() async { - final stubWithCustomOptions = MetadataClient(channel, - options: CallOptions(metadata: {'peer': 'Verner'})); + final stubWithCustomOptions = MetadataClient( + channel, + options: CallOptions(metadata: {'peer': 'Verner'}), + ); final request = Record()..value = 'Kaj'; - final call = stubWithCustomOptions.echo(request, - options: CallOptions(metadata: {'delay': '1'})); + final call = stubWithCustomOptions.echo( + request, + options: CallOptions(metadata: {'delay': '1'}), + ); call.headers.then((headers) { print('Received header metadata: $headers'); }); @@ -89,8 +96,9 @@ class Client { /// receiving 3 responses. Future runAddOneCancel() async { final numbers = StreamController(); - final call = - stub.addOne(numbers.stream.map((value) => Number()..value = value)); + final call = stub.addOne( + numbers.stream.map((value) => Number()..value = value), + ); final receivedThree = Completer(); final sub = call.listen((number) { print('AddOneCancel: Received ${number.value}'); @@ -133,8 +141,10 @@ class Client { /// Call an RPC that returns a stream of Fibonacci numbers, and specify an RPC /// timeout of 2 seconds. Future runFibonacciTimeout() async { - final call = stub.fibonacci(Empty(), - options: CallOptions(timeout: Duration(seconds: 2))); + final call = stub.fibonacci( + Empty(), + options: CallOptions(timeout: Duration(seconds: 2)), + ); var count = 0; try { await for (var number in call) { diff --git a/example/metadata/pubspec.yaml b/example/metadata/pubspec.yaml index 1582aaed..a87718e2 100644 --- a/example/metadata/pubspec.yaml +++ b/example/metadata/pubspec.yaml @@ -3,14 +3,14 @@ description: Dart gRPC sample client and server. publish_to: none environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.8.0 dependencies: - async: ^2.2.0 + async: ^2.13.0 grpc: path: ../../ - protobuf: ">=3.0.0 <5.0.0" + protobuf: ^4.1.0 dev_dependencies: - lints: ^2.0.0 - test: ^1.6.0 + lints: ^6.0.0 + test: ^1.26.2 diff --git a/example/route_guide/analysis_options.yaml b/example/route_guide/analysis_options.yaml index d8941fc5..572dd239 100644 --- a/example/route_guide/analysis_options.yaml +++ b/example/route_guide/analysis_options.yaml @@ -1,5 +1 @@ -include: ../../analysis_options.yaml - -linter: - rules: - directives_ordering: false +include: package:lints/recommended.yaml diff --git a/example/route_guide/lib/src/client.dart b/example/route_guide/lib/src/client.dart index 420cc796..999b98b2 100644 --- a/example/route_guide/lib/src/client.dart +++ b/example/route_guide/lib/src/client.dart @@ -24,12 +24,15 @@ class Client { late RouteGuideClient stub; Future main(List args) async { - final channel = ClientChannel('127.0.0.1', - port: 8080, - options: - const ChannelOptions(credentials: ChannelCredentials.insecure())); - stub = RouteGuideClient(channel, - options: CallOptions(timeout: Duration(seconds: 30))); + final channel = ClientChannel( + '127.0.0.1', + port: 8080, + options: const ChannelOptions(credentials: ChannelCredentials.insecure()), + ); + stub = RouteGuideClient( + channel, + options: CallOptions(timeout: Duration(seconds: 30)), + ); // Run all of the demos in order. try { await runGetFeature(); @@ -49,7 +52,8 @@ class Client { ? 'no feature' : 'feature called "${feature.name}"'; print( - 'Found $name at ${latitude / coordFactor}, ${longitude / coordFactor}'); + 'Found $name at ${latitude / coordFactor}, ${longitude / coordFactor}', + ); } /// Run the getFeature demo. Calls getFeature with a point known to have a @@ -96,7 +100,8 @@ class Client { for (var i = 0; i < count; i++) { final point = featuresDb[random.nextInt(featuresDb.length)].location; print( - 'Visiting point ${point.latitude / coordFactor}, ${point.longitude / coordFactor}'); + 'Visiting point ${point.latitude / coordFactor}, ${point.longitude / coordFactor}', + ); yield point; await Future.delayed(Duration(milliseconds: 200 + random.nextInt(100))); } @@ -132,8 +137,10 @@ class Client { for (final note in notes) { // Short delay to simulate some other interaction. await Future.delayed(Duration(milliseconds: 10)); - print('Sending message ${note.message} at ${note.location.latitude}, ' - '${note.location.longitude}'); + print( + 'Sending message ${note.message} at ${note.location.latitude}, ' + '${note.location.longitude}', + ); yield note; } } @@ -141,7 +148,8 @@ class Client { final call = stub.routeChat(outgoingNotes()); await for (var note in call) { print( - 'Got message ${note.message} at ${note.location.latitude}, ${note.location.longitude}'); + 'Got message ${note.message} at ${note.location.latitude}, ${note.location.longitude}', + ); } } } diff --git a/example/route_guide/lib/src/server.dart b/example/route_guide/lib/src/server.dart index 8f7da679..d1664efb 100644 --- a/example/route_guide/lib/src/server.dart +++ b/example/route_guide/lib/src/server.dart @@ -28,8 +28,10 @@ class RouteGuideService extends RouteGuideServiceBase { /// The [context] object provides access to client metadata, cancellation, etc. @override Future getFeature(grpc.ServiceCall call, Point request) async { - return featuresDb.firstWhere((f) => f.location == request, - orElse: () => Feature()..location = request); + return featuresDb.firstWhere( + (f) => f.location == request, + orElse: () => Feature()..location = request, + ); } Rectangle _normalize(Rectangle r) { @@ -57,7 +59,9 @@ class RouteGuideService extends RouteGuideServiceBase { /// rectangle. @override Stream listFeatures( - grpc.ServiceCall call, Rectangle request) async* { + grpc.ServiceCall call, + Rectangle request, + ) async* { final normalizedRectangle = _normalize(request); // For each feature, check if it is in the given bounding box for (var feature in featuresDb) { @@ -74,7 +78,9 @@ class RouteGuideService extends RouteGuideServiceBase { /// total distance traveled, and total time spent. @override Future recordRoute( - grpc.ServiceCall call, Stream request) async { + grpc.ServiceCall call, + Stream request, + ) async { var pointCount = 0; var featureCount = 0; var distance = 0.0; @@ -84,8 +90,9 @@ class RouteGuideService extends RouteGuideServiceBase { await for (var location in request) { if (!timer.isRunning) timer.start(); pointCount++; - final feature = - featuresDb.firstWhereOrNull((f) => f.location == location); + final feature = featuresDb.firstWhereOrNull( + (f) => f.location == location, + ); if (feature != null) { featureCount++; } @@ -107,7 +114,9 @@ class RouteGuideService extends RouteGuideServiceBase { /// locations. @override Stream routeChat( - grpc.ServiceCall call, Stream request) async* { + grpc.ServiceCall call, + Stream request, + ) async* { await for (var note in request) { final notes = routeNotes.putIfAbsent(note.location, () => []); for (var note in notes) { @@ -134,7 +143,8 @@ class RouteGuideService extends RouteGuideServiceBase { final dLat = toRadians(lat2 - lat1); final dLon = toRadians(lon2 - lon1); - final a = sin(dLat / 2) * sin(dLat / 2) + + final a = + sin(dLat / 2) * sin(dLat / 2) + cos(phi1) * cos(phi2) * sin(dLon / 2) * sin(dLon / 2); final c = 2 * atan2(sqrt(a), sqrt(1 - a)); diff --git a/example/route_guide/pubspec.yaml b/example/route_guide/pubspec.yaml index 7f6550c1..cbf9a304 100644 --- a/example/route_guide/pubspec.yaml +++ b/example/route_guide/pubspec.yaml @@ -3,14 +3,14 @@ description: Dart gRPC sample client and server. publish_to: none environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.8.0 dependencies: - async: ^2.2.0 + async: ^2.13.0 + collection: ^1.19.1 grpc: path: ../../ - protobuf: ">=3.0.0 <5.0.0" - collection: ^1.15.0-nullsafety.4 + protobuf: ^4.1.0 dev_dependencies: - lints: ^2.0.0 + lints: ^6.0.0 diff --git a/interop/analysis_options.yaml b/interop/analysis_options.yaml new file mode 100644 index 00000000..572dd239 --- /dev/null +++ b/interop/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/interop/bin/client.dart b/interop/bin/client.dart index e713adb9..29280b02 100644 --- a/interop/bin/client.dart +++ b/interop/bin/client.dart @@ -61,50 +61,77 @@ const _serviceAccountKeyFileArgument = 'service_account_key_file'; /// checking. Future main(List args) async { final argumentParser = ArgParser(); - argumentParser.addOption(_serverHostArgument, - help: 'The server host to connect to. For example, "localhost" or ' - '"127.0.0.1".'); - argumentParser.addOption(_serverHostOverrideArgument, - help: 'The server host to claim to be connecting to, for use in TLS and ' - 'HTTP/2 :authority header. If unspecified, the value of ' - '--server_host will be used.'); - argumentParser.addOption(_serverPortArgument, - help: 'The server port to connect to. For example, "8080".'); - argumentParser.addOption(_testCaseArgument, - help: - 'The name of the test case to execute. For example, "empty_unary".'); - argumentParser.addOption(_useTLSArgument, - defaultsTo: 'false', - help: 'Whether to use a plaintext or encrypted connection.'); - argumentParser.addOption(_useTestCAArgument, - defaultsTo: 'false', - help: 'Whether to replace platform root CAs with ca.pem as the CA root.'); - argumentParser.addOption(_defaultServiceAccountArgument, - help: 'Email of the GCE default service account.'); - argumentParser.addOption(_oauthScopeArgument, - help: 'OAuth scope. For example, ' - '"https://www.googleapis.com/auth/xapi.zoo".'); - argumentParser.addOption(_serviceAccountKeyFileArgument, - help: 'The path to the service account JSON key file generated from GCE ' - 'developer console.'); + argumentParser.addOption( + _serverHostArgument, + help: + 'The server host to connect to. For example, "localhost" or ' + '"127.0.0.1".', + ); + argumentParser.addOption( + _serverHostOverrideArgument, + help: + 'The server host to claim to be connecting to, for use in TLS and ' + 'HTTP/2 :authority header. If unspecified, the value of ' + '--server_host will be used.', + ); + argumentParser.addOption( + _serverPortArgument, + help: 'The server port to connect to. For example, "8080".', + ); + argumentParser.addOption( + _testCaseArgument, + help: 'The name of the test case to execute. For example, "empty_unary".', + ); + argumentParser.addOption( + _useTLSArgument, + defaultsTo: 'false', + help: 'Whether to use a plaintext or encrypted connection.', + ); + argumentParser.addOption( + _useTestCAArgument, + defaultsTo: 'false', + help: 'Whether to replace platform root CAs with ca.pem as the CA root.', + ); + argumentParser.addOption( + _defaultServiceAccountArgument, + help: 'Email of the GCE default service account.', + ); + argumentParser.addOption( + _oauthScopeArgument, + help: + 'OAuth scope. For example, ' + '"https://www.googleapis.com/auth/xapi.zoo".', + ); + argumentParser.addOption( + _serviceAccountKeyFileArgument, + help: + 'The path to the service account JSON key file generated from GCE ' + 'developer console.', + ); final arguments = argumentParser.parse(args); late Tester testClient; try { testClient = Tester( - serverHost: arguments[_serverHostArgument] ?? - (throw 'Must specify --$_serverHostArgument'), - serverHostOverride: arguments[_serverHostOverrideArgument], - serverPort: int.tryParse(arguments[_serverPortArgument] ?? - (throw 'Must specify --$_serverPortArgument')) ?? - (throw 'Invalid port "${arguments[_serverPortArgument]}"'), - testCase: arguments[_testCaseArgument] ?? - (throw 'Must specify --$_testCaseArgument'), - useTls: arguments[_useTLSArgument] == 'true', - useTestCA: arguments[_useTestCAArgument] == 'true', - defaultServiceAccount: arguments[_defaultServiceAccountArgument], - oauthScope: arguments[_oauthScopeArgument], - serviceAccountKeyFile: arguments[_serviceAccountKeyFileArgument]); + serverHost: + arguments[_serverHostArgument] ?? + (throw 'Must specify --$_serverHostArgument'), + serverHostOverride: arguments[_serverHostOverrideArgument], + serverPort: + int.tryParse( + arguments[_serverPortArgument] ?? + (throw 'Must specify --$_serverPortArgument'), + ) ?? + (throw 'Invalid port "${arguments[_serverPortArgument]}"'), + testCase: + arguments[_testCaseArgument] ?? + (throw 'Must specify --$_testCaseArgument'), + useTls: arguments[_useTLSArgument] == 'true', + useTestCA: arguments[_useTestCAArgument] == 'true', + defaultServiceAccount: arguments[_defaultServiceAccountArgument], + oauthScope: arguments[_oauthScopeArgument], + serviceAccountKeyFile: arguments[_serviceAccountKeyFileArgument], + ); } catch (e) { print(e); print(argumentParser.usage); diff --git a/interop/bin/server.dart b/interop/bin/server.dart index 7374ed08..6a5a44a7 100644 --- a/interop/bin/server.dart +++ b/interop/bin/server.dart @@ -47,10 +47,14 @@ class TestService extends TestServiceBase { @override Future unaryCall( - ServiceCall call, SimpleRequest request) async { + ServiceCall call, + SimpleRequest request, + ) async { if (request.responseStatus.code != 0) { throw GrpcError.custom( - request.responseStatus.code, request.responseStatus.message); + request.responseStatus.code, + request.responseStatus.message, + ); } final payload = Payload()..body = List.filled(request.responseSize, 0); return SimpleResponse()..payload = payload; @@ -58,7 +62,9 @@ class TestService extends TestServiceBase { @override Future cacheableUnaryCall( - ServiceCall call, SimpleRequest request) async { + ServiceCall call, + SimpleRequest request, + ) async { final timestamp = DateTime.now().microsecond * 1000; final responsePayload = Payload()..body = ascii.encode('$timestamp'); return SimpleResponse()..payload = responsePayload; @@ -66,9 +72,13 @@ class TestService extends TestServiceBase { @override Future streamingInputCall( - ServiceCall call, Stream request) async { + ServiceCall call, + Stream request, + ) async { final aggregatedPayloadSize = await request.fold( - 0, (size, message) => size + message.payload.body.length); + 0, + (size, message) => size + message.payload.body.length, + ); return StreamingInputCallResponse() ..aggregatedPayloadSize = aggregatedPayloadSize; } @@ -78,7 +88,9 @@ class TestService extends TestServiceBase { @override Stream streamingOutputCall( - ServiceCall call, StreamingOutputCallRequest request) async* { + ServiceCall call, + StreamingOutputCallRequest request, + ) async* { for (final entry in request.responseParameters) { if (entry.intervalUs > 0) { await Future.delayed(Duration(microseconds: entry.intervalUs)); @@ -88,10 +100,13 @@ class TestService extends TestServiceBase { } StreamingOutputCallResponse _responseForRequest( - StreamingOutputCallRequest request) { + StreamingOutputCallRequest request, + ) { if (request.responseStatus.code != 0) { throw GrpcError.custom( - request.responseStatus.code, request.responseStatus.message); + request.responseStatus.code, + request.responseStatus.message, + ); } final response = StreamingOutputCallResponse(); if (request.responseParameters.isNotEmpty) { @@ -102,13 +117,17 @@ class TestService extends TestServiceBase { @override Stream fullDuplexCall( - ServiceCall call, Stream request) async* { + ServiceCall call, + Stream request, + ) async* { yield* request.map(_responseForRequest); } @override Stream halfDuplexCall( - ServiceCall call, Stream request) async* { + ServiceCall call, + Stream request, + ) async* { final bufferedResponses = await request.map(_responseForRequest).toList(); yield* Stream.fromIterable(bufferedResponses); } @@ -138,7 +157,9 @@ Future main(List args) async { final certificate = File(arguments['tls_cert_file']).readAsBytes(); final privateKey = File(arguments['tls_key_file']).readAsBytes(); tlsCredentials = ServerTlsCredentials( - certificate: await certificate, privateKey: await privateKey); + certificate: await certificate, + privateKey: await privateKey, + ); } await server.serve(port: port, security: tlsCredentials); print('Server listening on port ${server.port}...'); diff --git a/interop/lib/src/client.dart b/interop/lib/src/client.dart index a61fa817..10c48363 100644 --- a/interop/lib/src/client.dart +++ b/interop/lib/src/client.dart @@ -42,16 +42,17 @@ class Tester { final String? serviceAccountKeyFile; String? _serviceAccountJson; - Tester( - {required this.serverHost, - required this.serverHostOverride, - required this.serverPort, - required this.testCase, - required this.useTls, - required this.useTestCA, - required this.defaultServiceAccount, - required this.oauthScope, - required this.serviceAccountKeyFile}); + Tester({ + required this.serverHost, + required this.serverHostOverride, + required this.serverPort, + required this.testCase, + required this.useTls, + required this.useTestCA, + required this.defaultServiceAccount, + required this.oauthScope, + required this.serviceAccountKeyFile, + }); String get serviceAccountJson => _serviceAccountJson ??= _readServiceAccountJson(); @@ -75,7 +76,9 @@ class Tester { trustedRoot = File('ca.pem').readAsBytesSync(); } credentials = ChannelCredentials.secure( - certificates: trustedRoot, authority: serverHostOverride); + certificates: trustedRoot, + authority: serverHostOverride, + ); } else { credentials = const ChannelCredentials.insecure(); } @@ -438,14 +441,16 @@ class Tester { final request = StreamingOutputCallRequest() ..responseParameters.addAll( - expectedResponses.map((size) => ResponseParameters()..size = size)); + expectedResponses.map((size) => ResponseParameters()..size = size), + ); final responses = await client.streamingOutputCall(request).toList(); if (responses.length != 4) { throw 'Incorrect number of responses (${responses.length}).'; } - final responseLengths = - responses.map((response) => response.payload.body.length).toList(); + final responseLengths = responses + .map((response) => response.payload.body.length) + .toList(); if (!ListEquality().equals(responseLengths, expectedResponses)) { throw 'Incorrect response lengths received (${responseLengths.join(', ')} != ${expectedResponses.join(', ')})'; @@ -549,8 +554,9 @@ class Tester { final payload = Payload()..body = Uint8List(requestSizes[index]); final request = StreamingOutputCallRequest() ..payload = payload - ..responseParameters - .add(ResponseParameters()..size = expectedResponses[index]); + ..responseParameters.add( + ResponseParameters()..size = expectedResponses[index], + ); return request; } @@ -629,11 +635,16 @@ class Tester { /// zero and comparing the entire response message against a golden response Future computeEngineCreds() async { final credentials = ComputeEngineAuthenticator(); - final clientWithCredentials = - TestServiceClient(channel, options: credentials.toCallOptions); + final clientWithCredentials = TestServiceClient( + channel, + options: credentials.toCallOptions, + ); - final response = await _sendSimpleRequestForAuth(clientWithCredentials, - fillUsername: true, fillOauthScope: true); + final response = await _sendSimpleRequestForAuth( + clientWithCredentials, + fillUsername: true, + fillOauthScope: true, + ); final user = response.username; final oauth = response.oauthScope; @@ -719,11 +730,15 @@ class Tester { /// zero and comparing the entire response message against a golden response Future jwtTokenCreds() async { final credentials = JwtServiceAccountAuthenticator(serviceAccountJson); - final clientWithCredentials = - TestServiceClient(channel, options: credentials.toCallOptions); - - final response = await _sendSimpleRequestForAuth(clientWithCredentials, - fillUsername: true); + final clientWithCredentials = TestServiceClient( + channel, + options: credentials.toCallOptions, + ); + + final response = await _sendSimpleRequestForAuth( + clientWithCredentials, + fillUsername: true, + ); final username = response.username; if (username.isEmpty) { throw 'Username not received.'; @@ -773,13 +788,19 @@ class Tester { /// check against the json key file or GCE default service account email. /// * received SimpleResponse.oauth_scope is in `--oauth_scope` Future oauth2AuthToken() async { - final credentials = - ServiceAccountAuthenticator(serviceAccountJson, [oauthScope!]); - final clientWithCredentials = - TestServiceClient(channel, options: credentials.toCallOptions); - - final response = await _sendSimpleRequestForAuth(clientWithCredentials, - fillUsername: true, fillOauthScope: true); + final credentials = ServiceAccountAuthenticator(serviceAccountJson, [ + oauthScope!, + ]); + final clientWithCredentials = TestServiceClient( + channel, + options: credentials.toCallOptions, + ); + + final response = await _sendSimpleRequestForAuth( + clientWithCredentials, + fillUsername: true, + fillOauthScope: true, + ); final user = response.username; final oauth = response.oauthScope; @@ -829,13 +850,16 @@ class Tester { /// file used by the auth library. The client can optionally check the /// username matches the email address in the key file. Future perRpcCreds() async { - final credentials = - ServiceAccountAuthenticator(serviceAccountJson, [oauthScope!]); + final credentials = ServiceAccountAuthenticator(serviceAccountJson, [ + oauthScope!, + ]); - final response = await _sendSimpleRequestForAuth(client, - fillUsername: true, - fillOauthScope: true, - options: credentials.toCallOptions); + final response = await _sendSimpleRequestForAuth( + client, + fillUsername: true, + fillOauthScope: true, + options: credentials.toCallOptions, + ); final user = response.username; final oauth = response.oauthScope; @@ -855,10 +879,12 @@ class Tester { } } - Future _sendSimpleRequestForAuth(TestServiceClient client, - {bool fillUsername = false, - bool fillOauthScope = false, - CallOptions? options}) async { + Future _sendSimpleRequestForAuth( + TestServiceClient client, { + bool fillUsername = false, + bool fillOauthScope = false, + CallOptions? options, + }) async { final payload = Payload()..body = Uint8List(271828); final request = SimpleRequest() ..responseSize = 314159 @@ -922,15 +948,18 @@ class Tester { } } - final options = CallOptions(metadata: { - _headerEchoKey: _headerEchoData, - _trailerEchoKey: _trailerEchoData, - }); + final options = CallOptions( + metadata: { + _headerEchoKey: _headerEchoData, + _trailerEchoKey: _trailerEchoData, + }, + ); final unaryCall = client.unaryCall( - SimpleRequest() - ..responseSize = 314159 - ..payload = (Payload()..body = Uint8List(271828)), - options: options); + SimpleRequest() + ..responseSize = 314159 + ..payload = (Payload()..body = Uint8List(271828)), + options: options, + ); var headers = await unaryCall.headers; var trailers = await unaryCall.trailers; await unaryCall; @@ -1096,33 +1125,42 @@ class Tester { final completer = Completer(); var receivedResponse = false; - call.listen((response) { - if (receivedResponse) { - completer.completeError('Received too many responses.'); - return; - } - receivedResponse = true; - if (response.payload.body.length != 31415) { - completer.completeError('Invalid response length: ' - '${response.payload.body.length} != 31415.'); - } - call.cancel(); - }, onError: (e) { - if (e is! GrpcError) { - completer.completeError('Unexpected error: $e.'); - } else if (e.code != StatusCode.cancelled) { - completer - .completeError('Unexpected status code ${e.code}: ${e.message}.'); - } else { - completer.complete(true); - } - }, onDone: () { - if (!completer.isCompleted) completer.completeError('Expected error.'); - }); - - requests.add(StreamingOutputCallRequest() - ..responseParameters.add(ResponseParameters()..size = 31415) - ..payload = (Payload()..body = Uint8List(27182))); + call.listen( + (response) { + if (receivedResponse) { + completer.completeError('Received too many responses.'); + return; + } + receivedResponse = true; + if (response.payload.body.length != 31415) { + completer.completeError( + 'Invalid response length: ' + '${response.payload.body.length} != 31415.', + ); + } + call.cancel(); + }, + onError: (e) { + if (e is! GrpcError) { + completer.completeError('Unexpected error: $e.'); + } else if (e.code != StatusCode.cancelled) { + completer.completeError( + 'Unexpected status code ${e.code}: ${e.message}.', + ); + } else { + completer.complete(true); + } + }, + onDone: () { + if (!completer.isCompleted) completer.completeError('Expected error.'); + }, + ); + + requests.add( + StreamingOutputCallRequest() + ..responseParameters.add(ResponseParameters()..size = 31415) + ..payload = (Payload()..body = Uint8List(27182)), + ); await completer.future; requests.close(); } @@ -1145,10 +1183,14 @@ class Tester { /// * Call completed with status DEADLINE_EXCEEDED. Future timeoutOnSleepingServer() async { final requests = StreamController(); - final call = client.fullDuplexCall(requests.stream, - options: CallOptions(timeout: Duration(milliseconds: 1))); - requests.add(StreamingOutputCallRequest() - ..payload = (Payload()..body = Uint8List(27182))); + final call = client.fullDuplexCall( + requests.stream, + options: CallOptions(timeout: Duration(milliseconds: 1)), + ); + requests.add( + StreamingOutputCallRequest() + ..payload = (Payload()..body = Uint8List(27182)), + ); try { await for (final _ in call) { throw 'Unexpected response received.'; diff --git a/interop/pubspec.yaml b/interop/pubspec.yaml index b5546726..bad71168 100644 --- a/interop/pubspec.yaml +++ b/interop/pubspec.yaml @@ -3,15 +3,16 @@ description: Dart gRPC interoperability test suite. publish_to: none environment: - sdk: ^3.0.0 + sdk: ^3.8.0 dependencies: - args: ^2.0.0 - async: ^2.2.0 - collection: ^1.14.11 + args: ^2.7.0 + async: ^2.13.0 + collection: ^1.19.1 grpc: path: ../ - protobuf: ">=3.0.0 <5.0.0" + protobuf: ^4.1.0 dev_dependencies: - test: ^1.16.0 + lints: ^6.0.0 + test: ^1.26.2 diff --git a/lib/grpc_or_grpcweb.dart b/lib/grpc_or_grpcweb.dart index 049ceabf..9b579d29 100644 --- a/lib/grpc_or_grpcweb.dart +++ b/lib/grpc_or_grpcweb.dart @@ -48,23 +48,20 @@ class GrpcOrGrpcWebClientChannel extends GrpcOrGrpcWebClientChannelInternal { required super.grpcTransportSecure, required super.grpcWebPort, required super.grpcWebTransportSecure, - }) : super( - grpcHost: host, - grpcWebHost: host, - ); + }) : super(grpcHost: host, grpcWebHost: host); GrpcOrGrpcWebClientChannel.toSingleEndpoint({ required String host, required int port, required bool transportSecure, }) : super( - grpcHost: host, - grpcPort: port, - grpcTransportSecure: transportSecure, - grpcWebHost: host, - grpcWebPort: port, - grpcWebTransportSecure: transportSecure, - ); + grpcHost: host, + grpcPort: port, + grpcTransportSecure: transportSecure, + grpcWebHost: host, + grpcWebPort: port, + grpcWebTransportSecure: transportSecure, + ); GrpcOrGrpcWebClientChannel.grpc( super.host, { diff --git a/lib/src/auth/auth.dart b/lib/src/auth/auth.dart index a0059fcc..e9b5f100 100644 --- a/lib/src/auth/auth.dart +++ b/lib/src/auth/auth.dart @@ -74,7 +74,9 @@ abstract class HttpBasedAuthenticator extends BaseAuthenticator { } Future obtainCredentialsWithClient( - http.Client client, String uri); + http.Client client, + String uri, + ); } class JwtServiceAccountAuthenticator extends BaseAuthenticator { @@ -83,15 +85,17 @@ class JwtServiceAccountAuthenticator extends BaseAuthenticator { String? _keyId; JwtServiceAccountAuthenticator.fromJson( - Map serviceAccountJson) - : _serviceAccountCredentials = - auth.ServiceAccountCredentials.fromJson(serviceAccountJson), - _projectId = serviceAccountJson['project_id'], - _keyId = serviceAccountJson['private_key_id']; + Map serviceAccountJson, + ) : _serviceAccountCredentials = auth.ServiceAccountCredentials.fromJson( + serviceAccountJson, + ), + _projectId = serviceAccountJson['project_id'], + _keyId = serviceAccountJson['private_key_id']; factory JwtServiceAccountAuthenticator(String serviceAccountJsonString) => JwtServiceAccountAuthenticator.fromJson( - jsonDecode(serviceAccountJsonString)); + jsonDecode(serviceAccountJsonString), + ); String? get projectId => _projectId; @@ -103,8 +107,12 @@ class JwtServiceAccountAuthenticator extends BaseAuthenticator { // TODO(jakobr): Expose in googleapis_auth. auth.AccessToken _jwtTokenFor( - auth.ServiceAccountCredentials credentials, String? keyId, String uri, - {String? user, List? scopes}) { + auth.ServiceAccountCredentials credentials, + String? keyId, + String uri, { + String? user, + List? scopes, +}) { // Subtracting 20 seconds from current timestamp to allow for clock skew among // servers. final timestamp = @@ -121,7 +129,7 @@ auth.AccessToken _jwtTokenFor( 'aud': uri, 'exp': expiry, 'iat': timestamp, - 'sub': user ?? credentials.email + 'sub': user ?? credentials.email, }; if (scopes != null) { claims['scope'] = scopes.join(' '); @@ -135,14 +143,27 @@ auth.AccessToken _jwtTokenFor( final key = credentials.privateRSAKey; // We convert to our internal version of RSAPrivateKey. See rsa.dart for more // explanation. - final signer = RS256Signer(RSAPrivateKey( - key.n, key.e, key.d, key.p, key.q, key.dmp1, key.dmq1, key.coeff)); + final signer = RS256Signer( + RSAPrivateKey( + key.n, + key.e, + key.d, + key.p, + key.q, + key.dmp1, + key.dmq1, + key.coeff, + ), + ); final signature = signer.sign(ascii.encode(data)); final jwt = '$data.${_base64url(signature)}'; - return auth.AccessToken('Bearer', jwt, - DateTime.fromMillisecondsSinceEpoch(expiry * 1000, isUtc: true)); + return auth.AccessToken( + 'Bearer', + jwt, + DateTime.fromMillisecondsSinceEpoch(expiry * 1000, isUtc: true), + ); } String _base64url(List bytes) { diff --git a/lib/src/auth/auth_io.dart b/lib/src/auth/auth_io.dart index bb720004..d986355e 100644 --- a/lib/src/auth/auth_io.dart +++ b/lib/src/auth/auth_io.dart @@ -24,8 +24,9 @@ import 'auth.dart'; class ComputeEngineAuthenticator extends HttpBasedAuthenticator { @override Future obtainCredentialsWithClient( - http.Client client, String uri) => - auth.obtainAccessCredentialsViaMetadataServer(client); + http.Client client, + String uri, + ) => auth.obtainAccessCredentialsViaMetadataServer(client); } class ServiceAccountAuthenticator extends HttpBasedAuthenticator { @@ -34,23 +35,32 @@ class ServiceAccountAuthenticator extends HttpBasedAuthenticator { String? _projectId; ServiceAccountAuthenticator.fromJson( - Map serviceAccountJson, this._scopes) - : _serviceAccountCredentials = - auth.ServiceAccountCredentials.fromJson(serviceAccountJson), - _projectId = serviceAccountJson['project_id']; + Map serviceAccountJson, + this._scopes, + ) : _serviceAccountCredentials = auth.ServiceAccountCredentials.fromJson( + serviceAccountJson, + ), + _projectId = serviceAccountJson['project_id']; factory ServiceAccountAuthenticator( - String serviceAccountJsonString, List scopes) => - ServiceAccountAuthenticator.fromJson( - jsonDecode(serviceAccountJsonString), scopes); + String serviceAccountJsonString, + List scopes, + ) => ServiceAccountAuthenticator.fromJson( + jsonDecode(serviceAccountJsonString), + scopes, + ); String? get projectId => _projectId; @override Future obtainCredentialsWithClient( - http.Client client, String uri) => - auth.obtainAccessCredentialsViaServiceAccount( - _serviceAccountCredentials, _scopes, client); + http.Client client, + String uri, + ) => auth.obtainAccessCredentialsViaServiceAccount( + _serviceAccountCredentials, + _scopes, + client, + ); } class _CredentialsRefreshingAuthenticator extends HttpBasedAuthenticator { @@ -119,11 +129,17 @@ Future applicationDefaultCredentialsAuthenticator( // Attempt to use file created by `gcloud auth application-default login` File gcloudAdcFile; if (Platform.isWindows) { - gcloudAdcFile = File.fromUri(Uri.directory(Platform.environment['APPDATA']!) - .resolve('gcloud/application_default_credentials.json')); + gcloudAdcFile = File.fromUri( + Uri.directory( + Platform.environment['APPDATA']!, + ).resolve('gcloud/application_default_credentials.json'), + ); } else { - gcloudAdcFile = File.fromUri(Uri.directory(Platform.environment['HOME']!) - .resolve('.config/gcloud/application_default_credentials.json')); + gcloudAdcFile = File.fromUri( + Uri.directory( + Platform.environment['HOME']!, + ).resolve('.config/gcloud/application_default_credentials.json'), + ); } // Only try to load from gcloudAdcFile if it exists. if (credFile == null && await gcloudAdcFile.exists()) { @@ -137,9 +153,7 @@ Future applicationDefaultCredentialsAuthenticator( try { credentials = json.decode(await credFile.readAsString()); } on IOException { - throw Exception( - 'Failed to read credentials file from $fileSource', - ); + throw Exception('Failed to read credentials file from $fileSource'); } on FormatException { throw Exception( 'Failed to parse JSON from credentials file from $fileSource', diff --git a/lib/src/auth/rsa.dart b/lib/src/auth/rsa.dart index 0f154ee1..83019951 100644 --- a/lib/src/auth/rsa.dart +++ b/lib/src/auth/rsa.dart @@ -41,7 +41,7 @@ class RS256Signer { 0x03, 0x04, 0x02, - 0x01 + 0x01, ]; final RSAPrivateKey _rsaKey; @@ -69,7 +69,8 @@ class RS256Signer { // } var offset = 0; final digestInfo = Uint8List( - 2 + 2 + _rsaSha256AlgorithmIdentifier.length + 2 + 2 + hash.length); + 2 + 2 + _rsaSha256AlgorithmIdentifier.length + 2 + 2 + hash.length, + ); { // DigestInfo digestInfo[offset++] = ASN1Parser.sequenceTag; @@ -183,7 +184,8 @@ class ASN1Parser { return ASN1Sequence(objects); default: invalidFormat( - 'Unexpected tag $tag at offset ${offset - 1} (end: $end).'); + 'Unexpected tag $tag at offset ${offset - 1} (end: $end).', + ); } } @@ -249,7 +251,15 @@ class RSAPrivateKey { int get bitLength => n.bitLength; RSAPrivateKey( - this.n, this.e, this.d, this.p, this.q, this.dmp1, this.dmq1, this.coeff); + this.n, + this.e, + this.d, + this.p, + this.q, + this.dmp1, + this.dmq1, + this.coeff, + ); } /// Provides a [encrypt] method for encrypting messages with a [RSAPrivateKey]. @@ -261,7 +271,10 @@ abstract class RSAAlgorithm { /// The [intendedLength] argument specifies the number of bytes in which the /// result should be encoded. Zero bytes will be used for padding. static List encrypt( - RSAPrivateKey key, List bytes, int intendedLength) { + RSAPrivateKey key, + List bytes, + int intendedLength, + ) { final message = bytes2BigInt(bytes); final encryptedMessage = _encryptInteger(key, message); return integer2Bytes(encryptedMessage, intendedLength); diff --git a/lib/src/client/call.dart b/lib/src/client/call.dart index fc671e00..a776a803 100644 --- a/lib/src/client/call.dart +++ b/lib/src/client/call.dart @@ -44,8 +44,8 @@ const _reservedHeaders = [ /// by previous metadata providers) and the [uri] that is being called, and is /// expected to modify the map before returning or before completing the /// returned [Future]. -typedef MetadataProvider = FutureOr Function( - Map metadata, String uri); +typedef MetadataProvider = + FutureOr Function(Map metadata, String uri); /// Runtime options for an RPC. class CallOptions { @@ -125,26 +125,33 @@ class WebCallOptions extends CallOptions { final bool? withCredentials; // TODO(mightyvoice): add a list of extra QueryParameter for gRPC. - WebCallOptions._(Map metadata, Duration? timeout, - List metadataProviders, - {this.bypassCorsPreflight, this.withCredentials}) - : super._(metadata, timeout, metadataProviders, null); + WebCallOptions._( + Map metadata, + Duration? timeout, + List metadataProviders, { + this.bypassCorsPreflight, + this.withCredentials, + }) : super._(metadata, timeout, metadataProviders, null); /// Creates a [WebCallOptions] object. /// /// [WebCallOptions] can specify static [metadata], [timeout], /// metadata [providers] of [CallOptions], [bypassCorsPreflight] and /// [withCredentials] for CORS request. - factory WebCallOptions( - {Map? metadata, - Duration? timeout, - List? providers, - bool? bypassCorsPreflight, - bool? withCredentials}) { - return WebCallOptions._(Map.unmodifiable(metadata ?? {}), timeout, - List.unmodifiable(providers ?? []), - bypassCorsPreflight: bypassCorsPreflight ?? false, - withCredentials: withCredentials ?? false); + factory WebCallOptions({ + Map? metadata, + Duration? timeout, + List? providers, + bool? bypassCorsPreflight, + bool? withCredentials, + }) { + return WebCallOptions._( + Map.unmodifiable(metadata ?? {}), + timeout, + List.unmodifiable(providers ?? []), + bypassCorsPreflight: bypassCorsPreflight ?? false, + withCredentials: withCredentials ?? false, + ); } @override @@ -157,19 +164,25 @@ class WebCallOptions extends CallOptions { ..addAll(other.metadataProviders); if (other is! WebCallOptions) { - return WebCallOptions._(Map.unmodifiable(mergedMetadata), mergedTimeout, - List.unmodifiable(mergedProviders), - bypassCorsPreflight: bypassCorsPreflight, - withCredentials: withCredentials); + return WebCallOptions._( + Map.unmodifiable(mergedMetadata), + mergedTimeout, + List.unmodifiable(mergedProviders), + bypassCorsPreflight: bypassCorsPreflight, + withCredentials: withCredentials, + ); } final mergedBypassCorsPreflight = other.bypassCorsPreflight ?? bypassCorsPreflight; final mergedWithCredentials = other.withCredentials ?? withCredentials; - return WebCallOptions._(Map.unmodifiable(mergedMetadata), mergedTimeout, - List.unmodifiable(mergedProviders), - bypassCorsPreflight: mergedBypassCorsPreflight, - withCredentials: mergedWithCredentials); + return WebCallOptions._( + Map.unmodifiable(mergedMetadata), + mergedTimeout, + List.unmodifiable(mergedProviders), + bypassCorsPreflight: mergedBypassCorsPreflight, + withCredentials: mergedWithCredentials, + ); } } @@ -196,12 +209,19 @@ class ClientCall implements Response { final TimelineTask? _requestTimeline; TimelineTask? _responseTimeline; - ClientCall(this._method, this._requests, this.options, - [this._requestTimeline]) { - _requestTimeline?.start('gRPC Request: ${_method.path}', arguments: { - 'method': _method.path, - 'timeout': options.timeout?.toString(), - }); + ClientCall( + this._method, + this._requests, + this.options, [ + this._requestTimeline, + ]) { + _requestTimeline?.start( + 'gRPC Request: ${_method.path}', + arguments: { + 'method': _method.path, + 'timeout': options.timeout?.toString(), + }, + ); _responses.onListen = _onResponseListen; if (options.timeout != null) { _timeoutTimer = Timer(options.timeout!, _onTimedOut); @@ -213,8 +233,9 @@ class ClientCall implements Response { } void _terminateWithError(Object e) { - final error = - e is GrpcError ? e : GrpcError.unavailable('Error making call: $e'); + final error = e is GrpcError + ? e + : GrpcError.unavailable('Error making call: $e'); _finishTimelineWithError(error, _requestTimeline); _responses.addErrorIfNotClosed(error); _safeTerminate(); @@ -232,7 +253,7 @@ class ClientCall implements Response { return sanitizedMetadata; } -// TODO(sigurdm): Find out why we do this. + // TODO(sigurdm): Find out why we do this. static String audiencePath(ClientMethod method) { final lastSlashPos = method.path.lastIndexOf('/'); return lastSlashPos == -1 @@ -248,9 +269,12 @@ class ClientCall implements Response { } else { final metadata = Map.of(options.metadata); Future.forEach( - options.metadataProviders, - (MetadataProvider provider) => provider(metadata, - '${connection.scheme}://${connection.authority}${audiencePath(_method)}')) + options.metadataProviders, + (MetadataProvider provider) => provider( + metadata, + '${connection.scheme}://${connection.authority}${audiencePath(_method)}', + ), + ) .then((_) => _sendRequest(connection, _sanitizeMetadata(metadata))) .catchError(_terminateWithError); } @@ -270,22 +294,26 @@ class ClientCall implements Response { _terminateWithError(e); return; } - _requestTimeline?.instant('Request sent', arguments: { - 'metadata': metadata, - }); + _requestTimeline?.instant( + 'Request sent', + arguments: {'metadata': metadata}, + ); _requestSubscription = _requests .map((data) { - _requestTimeline?.instant('Data sent', arguments: { - 'data': data.toString(), - }); + _requestTimeline?.instant( + 'Data sent', + arguments: {'data': data.toString()}, + ); _requestTimeline?.finish(); return _method.requestSerializer(data); }) .handleError(_onRequestError) - .listen(stream.outgoingMessages.add, - onError: stream.outgoingMessages.addError, - onDone: stream.outgoingMessages.close, - cancelOnError: true); + .listen( + stream.outgoingMessages.add, + onError: stream.outgoingMessages.addError, + onDone: stream.outgoingMessages.close, + cancelOnError: true, + ); _stream = stream; // The response stream might have been listened to before _stream was ready, // so try setting up the subscription here as well. @@ -293,9 +321,7 @@ class ClientCall implements Response { } void _finishTimelineWithError(GrpcError error, TimelineTask? timeline) { - timeline?.finish(arguments: { - 'error': error.toString(), - }); + timeline?.finish(arguments: {'error': error.toString()}); } void _onTimedOut() { @@ -312,10 +338,12 @@ class ClientCall implements Response { _responses.hasListener && _responseSubscription == null) { // ignore: cancel_subscriptions - final subscription = _stream!.incomingMessages.listen(_onResponseData, - onError: _onResponseError, - onDone: _onResponseDone, - cancelOnError: true); + final subscription = _stream!.incomingMessages.listen( + _onResponseData, + onError: _onResponseError, + onDone: _onResponseDone, + cancelOnError: true, + ); if (_responses.isPaused) { subscription.pause(); } @@ -360,9 +388,10 @@ class ClientCall implements Response { } try { final decodedData = _method.responseDeserializer(data.data); - _responseTimeline?.instant('Data received', arguments: { - 'data': decodedData.toString(), - }); + _responseTimeline?.instant( + 'Data received', + arguments: {'data': decodedData.toString()}, + ); _responses.add(decodedData); _hasReceivedResponses = true; } catch (e, s) { @@ -378,9 +407,10 @@ class ClientCall implements Response { ); } _responseTimeline?.start('gRPC Response'); - _responseTimeline?.instant('Metadata received', arguments: { - 'headers': _headerMetadata.toString(), - }); + _responseTimeline?.instant( + 'Metadata received', + arguments: {'headers': _headerMetadata.toString()}, + ); _headers.complete(_headerMetadata); return; } @@ -389,9 +419,10 @@ class ClientCall implements Response { return; } final metadata = data.metadata; - _responseTimeline?.instant('Metadata received', arguments: { - 'trailers': metadata.toString(), - }); + _responseTimeline?.instant( + 'Metadata received', + arguments: {'trailers': metadata.toString()}, + ); _trailers.complete(metadata); /// Process status error if necessary diff --git a/lib/src/client/channel.dart b/lib/src/client/channel.dart index 4c5b99a1..05774985 100644 --- a/lib/src/client/channel.dart +++ b/lib/src/client/channel.dart @@ -39,7 +39,10 @@ abstract class ClientChannel { /// Initiates a new RPC on this connection. ClientCall createCall( - ClientMethod method, Stream requests, CallOptions options); + ClientMethod method, + Stream requests, + CallOptions options, + ); /// Stream of connection state changes /// @@ -59,7 +62,7 @@ abstract class ClientChannelBase implements ClientChannel { final void Function()? _channelShutdownHandler; ClientChannelBase({void Function()? channelShutdownHandler}) - : _channelShutdownHandler = channelShutdownHandler; + : _channelShutdownHandler = channelShutdownHandler; @override Future shutdown() async { @@ -104,14 +107,18 @@ abstract class ClientChannelBase implements ClientChannel { @override ClientCall createCall( - ClientMethod method, Stream requests, CallOptions options) { + ClientMethod method, + Stream requests, + CallOptions options, + ) { final call = ClientCall( - method, - requests, - options, - isTimelineLoggingEnabled - ? TimelineTask(filterKey: clientTimelineFilterKey) - : null); + method, + requests, + options, + isTimelineLoggingEnabled + ? TimelineTask(filterKey: clientTimelineFilterKey) + : null, + ); getConnection().then((connection) { if (call.isCancelled) return; connection.dispatchCall(call); diff --git a/lib/src/client/client.dart b/lib/src/client/client.dart index 1ba6d004..a790d123 100644 --- a/lib/src/client/client.dart +++ b/lib/src/client/client.dart @@ -26,10 +26,12 @@ class Client { final List _interceptors; /// Interceptors will be applied in direct order before making a request. - Client(this._channel, - {CallOptions? options, Iterable? interceptors}) - : _options = options ?? CallOptions(), - _interceptors = List.unmodifiable(interceptors ?? Iterable.empty()); + Client( + this._channel, { + CallOptions? options, + Iterable? interceptors, + }) : _options = options ?? CallOptions(), + _interceptors = List.unmodifiable(interceptors ?? Iterable.empty()); @Deprecated(r'''This method does not invoke interceptors and is superseded by $createStreamingCall and $createUnaryCall which invoke interceptors. @@ -38,15 +40,21 @@ If you are getting this warning in autogenerated protobuf client stubs, regenerate these stubs using protobuf compiler plugin version 19.2.0 or newer. ''') ClientCall $createCall( - ClientMethod method, Stream requests, - {CallOptions? options}) { + ClientMethod method, + Stream requests, { + CallOptions? options, + }) { return _channel.createCall(method, requests, _options.mergedWith(options)); } - ResponseFuture $createUnaryCall(ClientMethod method, Q request, - {CallOptions? options}) { + ResponseFuture $createUnaryCall( + ClientMethod method, + Q request, { + CallOptions? options, + }) { var invoker = (method, request, options) => ResponseFuture( - _channel.createCall(method, Stream.value(request), options)); + _channel.createCall(method, Stream.value(request), options), + ); for (final interceptor in _interceptors.reversed) { final delegate = invoker; @@ -58,8 +66,10 @@ regenerate these stubs using protobuf compiler plugin version 19.2.0 or newer. } ResponseStream $createStreamingCall( - ClientMethod method, Stream requests, - {CallOptions? options}) { + ClientMethod method, + Stream requests, { + CallOptions? options, + }) { var invoker = (method, requests, options) => ResponseStream(_channel.createCall(method, requests, options)); diff --git a/lib/src/client/client_keepalive.dart b/lib/src/client/client_keepalive.dart index c088e59a..e9e9ad95 100644 --- a/lib/src/client/client_keepalive.dart +++ b/lib/src/client/client_keepalive.dart @@ -61,8 +61,8 @@ final class Idle extends KeepAliveState { final Stopwatch timeSinceFrame; Idle([this.pingTimer, Stopwatch? stopwatch]) - : timeSinceFrame = stopwatch ?? clock.stopwatch() - ..start(); + : timeSinceFrame = stopwatch ?? clock.stopwatch() + ..start(); @override KeepAliveState? onEvent(KeepAliveEvent event, ClientKeepAlive manager) { @@ -71,9 +71,12 @@ final class Idle extends KeepAliveState { // When the transport goes active, we do not reset the nextKeepaliveTime. // This allows us to quickly check whether the connection is still // working. - final timer = pingTimer ?? - Timer(manager._pingInterval - timeSinceFrame.elapsed, - manager.sendPing); + final timer = + pingTimer ?? + Timer( + manager._pingInterval - timeSinceFrame.elapsed, + manager.sendPing, + ); return PingScheduled(timer, timeSinceFrame); default: return null; @@ -91,8 +94,8 @@ final class PingScheduled extends KeepAliveState { final Stopwatch timeSinceFrame; PingScheduled(this.pingTimer, [Stopwatch? stopwatch]) - : timeSinceFrame = stopwatch ?? clock.stopwatch() - ..start(); + : timeSinceFrame = stopwatch ?? clock.stopwatch() + ..start(); @override KeepAliveState? onEvent(KeepAliveEvent event, ClientKeepAlive manager) { diff --git a/lib/src/client/common.dart b/lib/src/client/common.dart index 5060fc18..1da48510 100644 --- a/lib/src/client/common.dart +++ b/lib/src/client/common.dart @@ -60,9 +60,11 @@ class ResponseFuture extends DelegatingFuture } ResponseFuture(this._call) - : super(_call.response + : super( + _call.response .fold(null, _ensureOnlyOneResponse) - .then(_ensureOneResponse)); + .then(_ensureOneResponse), + ); } /// A gRPC response producing a stream of values. diff --git a/lib/src/client/connection.dart b/lib/src/client/connection.dart index 460438af..2f973dc9 100644 --- a/lib/src/client/connection.dart +++ b/lib/src/client/connection.dart @@ -31,7 +31,7 @@ enum ConnectionState { idle, /// Shutting down, no further RPCs allowed. - shutdown + shutdown, } abstract class ClientConnection { @@ -42,9 +42,13 @@ abstract class ClientConnection { void dispatchCall(ClientCall call); /// Start a request for [path] with [metadata]. - GrpcTransportStream makeRequest(String path, Duration? timeout, - Map metadata, ErrorHandler onRequestFailure, - {required CallOptions callOptions}); + GrpcTransportStream makeRequest( + String path, + Duration? timeout, + Map metadata, + ErrorHandler onRequestFailure, { + required CallOptions callOptions, + }); /// Shuts down this connection. /// diff --git a/lib/src/client/grpc_or_grpcweb_channel_grpc.dart b/lib/src/client/grpc_or_grpcweb_channel_grpc.dart index 53a6d11c..971c4dbc 100644 --- a/lib/src/client/grpc_or_grpcweb_channel_grpc.dart +++ b/lib/src/client/grpc_or_grpcweb_channel_grpc.dart @@ -26,14 +26,14 @@ class GrpcOrGrpcWebClientChannelInternal extends ClientChannel { required int grpcWebPort, required bool grpcWebTransportSecure, }) : super( - grpcHost, - port: grpcPort, - options: ChannelOptions( - credentials: grpcTransportSecure - ? ChannelCredentials.secure() - : ChannelCredentials.insecure(), - ), - ); + grpcHost, + port: grpcPort, + options: ChannelOptions( + credentials: grpcTransportSecure + ? ChannelCredentials.secure() + : ChannelCredentials.insecure(), + ), + ); GrpcOrGrpcWebClientChannelInternal.grpc( super.host, { diff --git a/lib/src/client/grpc_or_grpcweb_channel_web.dart b/lib/src/client/grpc_or_grpcweb_channel_web.dart index 858b0ef7..78fd704a 100644 --- a/lib/src/client/grpc_or_grpcweb_channel_web.dart +++ b/lib/src/client/grpc_or_grpcweb_channel_web.dart @@ -24,22 +24,25 @@ class GrpcOrGrpcWebClientChannelInternal extends GrpcWebClientChannel { required String grpcWebHost, required int grpcWebPort, required bool grpcWebTransportSecure, - }) : super.xhr(Uri( - host: grpcWebHost, - port: grpcWebPort, - scheme: grpcWebTransportSecure ? 'https' : 'http', - )); + }) : super.xhr( + Uri( + host: grpcWebHost, + port: grpcWebPort, + scheme: grpcWebTransportSecure ? 'https' : 'http', + ), + ); GrpcOrGrpcWebClientChannelInternal.grpc( Object host, { required int port, required ChannelOptions options, }) : super.xhr( - Uri( - host: host.toString(), - port: port, - scheme: options.credentials.isSecure ? 'https' : 'http'), - ) { + Uri( + host: host.toString(), + port: port, + scheme: options.credentials.isSecure ? 'https' : 'http', + ), + ) { // Do not silently ignore options as caller may expect them to have effects. throw UnsupportedError('not supported by gRPC-web'); } diff --git a/lib/src/client/http2_channel.dart b/lib/src/client/http2_channel.dart index 2657eb06..b6268c4b 100644 --- a/lib/src/client/http2_channel.dart +++ b/lib/src/client/http2_channel.dart @@ -48,11 +48,15 @@ class ClientTransportConnectorChannel extends ClientChannelBase { final ClientTransportConnector transportConnector; final ChannelOptions options; - ClientTransportConnectorChannel(this.transportConnector, - {this.options = const ChannelOptions()}); + ClientTransportConnectorChannel( + this.transportConnector, { + this.options = const ChannelOptions(), + }); @override ClientConnection createConnection() => Http2ClientConnection.fromClientTransportConnector( - transportConnector, options); + transportConnector, + options, + ); } diff --git a/lib/src/client/http2_connection.dart b/lib/src/client/http2_connection.dart index 115f5082..bc3c75b5 100644 --- a/lib/src/client/http2_connection.dart +++ b/lib/src/client/http2_connection.dart @@ -37,8 +37,10 @@ class Http2ClientConnection implements connection.ClientConnection { static final _methodPost = Header.ascii(':method', 'POST'); static final _schemeHttp = Header.ascii(':scheme', 'http'); static final _schemeHttps = Header.ascii(':scheme', 'https'); - static final _contentTypeGrpc = - Header.ascii('content-type', 'application/grpc'); + static final _contentTypeGrpc = Header.ascii( + 'content-type', + 'application/grpc', + ); static final _teTrailers = Header.ascii('te', 'trailers'); final ChannelOptions options; @@ -63,10 +65,12 @@ class Http2ClientConnection implements connection.ClientConnection { ClientKeepAlive? keepAliveManager; Http2ClientConnection(Object host, int port, this.options) - : _transportConnector = SocketTransportConnector(host, port, options); + : _transportConnector = SocketTransportConnector(host, port, options); Http2ClientConnection.fromClientTransportConnector( - this._transportConnector, this.options); + this._transportConnector, + this.options, + ); ChannelCredentials get credentials => options.credentials; @@ -102,35 +106,38 @@ class Http2ClientConnection implements connection.ClientConnection { return; } _setState(ConnectionState.connecting); - connectTransport().then((transport) async { - _currentReconnectDelay = null; - _transportConnection = transport; - if (options.keepAlive.shouldSendPings) { - keepAliveManager = ClientKeepAlive( - options: options.keepAlive, - ping: () { - if (transport.isOpen) { - transport.ping(); - } - }, - onPingTimeout: () => transport.finish(), - ); - transport.onFrameReceived - .listen((_) => keepAliveManager?.onFrameReceived()); - } - _connectionLifeTimer - ..reset() - ..start(); - transport.onActiveStateChanged = _handleActiveStateChanged; - _setState(ConnectionState.ready); - - if (_hasPendingCalls()) { - // Take all pending calls out, and reschedule. - final pendingCalls = _pendingCalls.toList(); - _pendingCalls.clear(); - pendingCalls.forEach(dispatchCall); - } - }).catchError(_handleConnectionFailure); + connectTransport() + .then((transport) async { + _currentReconnectDelay = null; + _transportConnection = transport; + if (options.keepAlive.shouldSendPings) { + keepAliveManager = ClientKeepAlive( + options: options.keepAlive, + ping: () { + if (transport.isOpen) { + transport.ping(); + } + }, + onPingTimeout: () => transport.finish(), + ); + transport.onFrameReceived.listen( + (_) => keepAliveManager?.onFrameReceived(), + ); + } + _connectionLifeTimer + ..reset() + ..start(); + transport.onActiveStateChanged = _handleActiveStateChanged; + _setState(ConnectionState.ready); + + if (_hasPendingCalls()) { + // Take all pending calls out, and reschedule. + final pendingCalls = _pendingCalls.toList(); + _pendingCalls.clear(); + pendingCalls.forEach(dispatchCall); + } + }) + .catchError(_handleConnectionFailure); } /// Abandons the current connection if it is unhealthy or has been open for @@ -171,9 +178,13 @@ class Http2ClientConnection implements connection.ClientConnection { } @override - GrpcTransportStream makeRequest(String path, Duration? timeout, - Map metadata, ErrorHandler onRequestFailure, - {CallOptions? callOptions}) { + GrpcTransportStream makeRequest( + String path, + Duration? timeout, + Map metadata, + ErrorHandler onRequestFailure, { + CallOptions? callOptions, + }) { final compressionCodec = callOptions?.compression; final headers = createCallHeaders( credentials.isSecure, @@ -185,7 +196,7 @@ class Http2ClientConnection implements connection.ClientConnection { userAgent: options.userAgent, grpcAcceptEncodings: (callOptions?.metadata ?? const {})['grpc-accept-encoding'] ?? - options.codecRegistry?.supportedEncodings, + options.codecRegistry?.supportedEncodings, ); final stream = _transportConnection!.makeRequest(headers); return Http2TransportStream( @@ -240,9 +251,9 @@ class Http2ClientConnection implements connection.ClientConnection { void _handleIdleTimeout() { if (_timer == null || _state != ConnectionState.ready) return; _cancelTimer(); - _transportConnection - ?.finish() - .catchError((_) {}); // TODO(jakobr): Log error. + _transportConnection?.finish().catchError( + (_) {}, + ); // TODO(jakobr): Log error. keepAliveManager?.onTransportTermination(); _disconnect(); _setState(ConnectionState.idle); @@ -344,7 +355,7 @@ class Http2ClientConnection implements connection.ClientConnection { if (grpcAcceptEncodings != null) Header.ascii('grpc-accept-encoding', grpcAcceptEncodings), if (compressionCodec != null) - Header.ascii('grpc-encoding', compressionCodec.encodingName) + Header.ascii('grpc-encoding', compressionCodec.encodingName), ]; metadata?.forEach((key, value) { headers.add(Header(ascii.encode(key), utf8.encode(value))); @@ -365,7 +376,7 @@ class SocketTransportConnector implements ClientTransportConnector { int get port => proxy == null ? _port : proxy!.port; SocketTransportConnector(this._host, this._port, this._options) - : assert(_host is InternetAddress || _host is String); + : assert(_host is InternetAddress || _host is String); @override Future connect() async { @@ -500,7 +511,8 @@ class SocketTransportConnector implements ClientTransportConnector { completer.complete(); } else { throw TransportException( - 'Error establishing proxy connection: $response'); + 'Error establishing proxy connection: $response', + ); } } } diff --git a/lib/src/client/interceptor.dart b/lib/src/client/interceptor.dart index 5318725f..e9494908 100644 --- a/lib/src/client/interceptor.dart +++ b/lib/src/client/interceptor.dart @@ -17,11 +17,19 @@ import 'call.dart'; import 'common.dart'; import 'method.dart'; -typedef ClientUnaryInvoker = ResponseFuture Function( - ClientMethod method, Q request, CallOptions options); +typedef ClientUnaryInvoker = + ResponseFuture Function( + ClientMethod method, + Q request, + CallOptions options, + ); -typedef ClientStreamingInvoker = ResponseStream Function( - ClientMethod method, Stream requests, CallOptions options); +typedef ClientStreamingInvoker = + ResponseStream Function( + ClientMethod method, + Stream requests, + CallOptions options, + ); /// ClientInterceptors intercepts client calls before they are executed. /// @@ -30,18 +38,23 @@ typedef ClientStreamingInvoker = ResponseStream Function( abstract class ClientInterceptor { // Intercept unary call. // This method is called when client sends single request and receives single response. - ResponseFuture interceptUnary(ClientMethod method, Q request, - CallOptions options, ClientUnaryInvoker invoker) { + ResponseFuture interceptUnary( + ClientMethod method, + Q request, + CallOptions options, + ClientUnaryInvoker invoker, + ) { return invoker(method, request, options); } // Intercept streaming call. // This method is called when client sends either request or response stream. ResponseStream interceptStreaming( - ClientMethod method, - Stream requests, - CallOptions options, - ClientStreamingInvoker invoker) { + ClientMethod method, + Stream requests, + CallOptions options, + ClientStreamingInvoker invoker, + ) { return invoker(method, requests, options); } } diff --git a/lib/src/client/query_parameter.dart b/lib/src/client/query_parameter.dart index 857de71f..fed18571 100644 --- a/lib/src/client/query_parameter.dart +++ b/lib/src/client/query_parameter.dart @@ -42,7 +42,7 @@ class QueryParameter implements Comparable { /// This is not the default constructor since the single-value case is the /// most common. QueryParameter.multi(this.key, List values) - : values = values..sort() { + : values = values..sort() { ArgumentError.checkNotNull(key); if (key.trim().isEmpty) { throw ArgumentError(key); diff --git a/lib/src/client/transport/cors.dart b/lib/src/client/transport/cors.dart index 1b7e4697..dafb2323 100644 --- a/lib/src/client/transport/cors.dart +++ b/lib/src/client/transport/cors.dart @@ -40,8 +40,9 @@ Uri moveHttpHeadersToQueryParam(Map metadata, Uri requestUri) { final paramValue = _generateHttpHeadersOverwriteParam(metadata); metadata.clear(); return requestUri.replace( - queryParameters: Map.of(requestUri.queryParameters) - ..[_httpHeadersParamName] = paramValue); + queryParameters: Map.of(requestUri.queryParameters) + ..[_httpHeadersParamName] = paramValue, + ); } /// Generates the URL parameter value with custom headers encoded as diff --git a/lib/src/client/transport/http2_credentials.dart b/lib/src/client/transport/http2_credentials.dart index f2b76690..0f0c5c7f 100644 --- a/lib/src/client/transport/http2_credentials.dart +++ b/lib/src/client/transport/http2_credentials.dart @@ -21,8 +21,8 @@ import '../../shared/security.dart'; /// returns `true`, the bad certificate is allowed, and the TLS handshake can /// continue. If the handler returns `false`, the TLS handshake fails, and the /// connection is aborted. -typedef BadCertificateHandler = bool Function( - X509Certificate certificate, String host); +typedef BadCertificateHandler = + bool Function(X509Certificate certificate, String host); /// Bad certificate handler that disables all certificate checks. /// DO NOT USE IN PRODUCTION! @@ -38,28 +38,34 @@ class ChannelCredentials { final String? _certificatePassword; final BadCertificateHandler? onBadCertificate; - const ChannelCredentials._(this.isSecure, this._certificateBytes, - this._certificatePassword, this.authority, this.onBadCertificate); + const ChannelCredentials._( + this.isSecure, + this._certificateBytes, + this._certificatePassword, + this.authority, + this.onBadCertificate, + ); /// Disable TLS. RPCs are sent in clear text. const ChannelCredentials.insecure({String? authority}) - : this._(false, null, null, authority, null); + : this._(false, null, null, authority, null); /// Enable TLS and optionally specify the [certificates] to trust. If /// [certificates] is not provided, the default trust store is used. - const ChannelCredentials.secure( - {List? certificates, - String? password, - String? authority, - BadCertificateHandler? onBadCertificate}) - : this._(true, certificates, password, authority, onBadCertificate); + const ChannelCredentials.secure({ + List? certificates, + String? password, + String? authority, + BadCertificateHandler? onBadCertificate, + }) : this._(true, certificates, password, authority, onBadCertificate); SecurityContext? get securityContext { if (!isSecure) return null; if (_certificateBytes != null) { - return createSecurityContext(false) - ..setTrustedCertificatesBytes(_certificateBytes, - password: _certificatePassword); + return createSecurityContext(false)..setTrustedCertificatesBytes( + _certificateBytes, + password: _certificatePassword, + ); } final context = SecurityContext(withTrustedRoots: true); context.setAlpnProtocols(supportedAlpnProtocols, false); diff --git a/lib/src/client/transport/http2_transport.dart b/lib/src/client/transport/http2_transport.dart index a55c5396..137545c7 100644 --- a/lib/src/client/transport/http2_transport.dart +++ b/lib/src/client/transport/http2_transport.dart @@ -39,16 +39,18 @@ class Http2TransportStream extends GrpcTransportStream { CodecRegistry? codecRegistry, Codec? compression, ) : incomingMessages = _transportStream.incomingMessages - .transform(GrpcHttpDecoder(forResponse: true)) - .transform(grpcDecompressor(codecRegistry: codecRegistry)) { + .transform(GrpcHttpDecoder(forResponse: true)) + .transform(grpcDecompressor(codecRegistry: codecRegistry)) { _outgoingMessages.stream .map((payload) => frame(payload, compression)) .map((bytes) => DataStreamMessage(bytes)) .handleError(_onError) - .listen(_transportStream.outgoingMessages.add, - onError: _transportStream.outgoingMessages.addError, - onDone: _transportStream.outgoingMessages.close, - cancelOnError: true); + .listen( + _transportStream.outgoingMessages.add, + onError: _transportStream.outgoingMessages.addError, + onDone: _transportStream.outgoingMessages.close, + cancelOnError: true, + ); } @override diff --git a/lib/src/client/transport/web_streams.dart b/lib/src/client/transport/web_streams.dart index d4100add..107403cf 100644 --- a/lib/src/client/transport/web_streams.dart +++ b/lib/src/client/transport/web_streams.dart @@ -71,7 +71,11 @@ class _GrpcWebConversionSink implements ChunkedConversionSink { final chunkRemaining = chunkLength - _chunkOffset; final toCopy = min(headerRemaining, chunkRemaining); _dataHeader.setRange( - _dataOffset, _dataOffset + toCopy, chunkData, _chunkOffset); + _dataOffset, + _dataOffset + toCopy, + chunkData, + _chunkOffset, + ); _dataOffset += toCopy; _chunkOffset += toCopy; if (_dataOffset == _dataHeader.lengthInBytes) { @@ -91,8 +95,12 @@ class _GrpcWebConversionSink implements ChunkedConversionSink { if (dataRemaining > 0) { final chunkRemaining = chunkData.length - _chunkOffset; final toCopy = min(dataRemaining, chunkRemaining); - _data! - .setRange(_dataOffset, _dataOffset + toCopy, chunkData, _chunkOffset); + _data!.setRange( + _dataOffset, + _dataOffset + toCopy, + chunkData, + _chunkOffset, + ); _dataOffset += toCopy; _chunkOffset += toCopy; } diff --git a/lib/src/client/transport/xhr_transport.dart b/lib/src/client/transport/xhr_transport.dart index 693088c2..c156d72f 100644 --- a/lib/src/client/transport/xhr_transport.dart +++ b/lib/src/client/transport/xhr_transport.dart @@ -46,14 +46,19 @@ class XhrTransportStream implements GrpcTransportStream { @override StreamSink> get outgoingMessages => _outgoingMessages.sink; - XhrTransportStream(this._request, - {required ErrorHandler onError, required onDone}) - : _onError = onError, - _onDone = onDone { - _outgoingMessages.stream.map(frame).listen( - (data) => _request.send(Uint8List.fromList(data).toJS), - cancelOnError: true, - onError: _onError); + XhrTransportStream( + this._request, { + required ErrorHandler onError, + required onDone, + }) : _onError = onError, + _onDone = onDone { + _outgoingMessages.stream + .map(frame) + .listen( + (data) => _request.send(Uint8List.fromList(data).toJS), + cancelOnError: true, + onError: _onError, + ); _request.onReadyStateChange.listen((_) { if (_incomingProcessor.isClosed) { @@ -74,8 +79,10 @@ class XhrTransportStream implements GrpcTransportStream { if (_incomingProcessor.isClosed) { return; } - _onError(GrpcError.unavailable('XhrConnection connection-error'), - StackTrace.current); + _onError( + GrpcError.unavailable('XhrConnection connection-error'), + StackTrace.current, + ); terminate(); }); @@ -85,8 +92,8 @@ class XhrTransportStream implements GrpcTransportStream { } final responseText = _request.responseText; final bytes = Uint8List.fromList( - responseText.substring(_requestBytesRead).codeUnits) - .buffer; + responseText.substring(_requestBytesRead).codeUnits, + ).buffer; _requestBytesRead = responseText.length; _incomingProcessor.add(bytes); }); @@ -94,15 +101,20 @@ class XhrTransportStream implements GrpcTransportStream { _incomingProcessor.stream .transform(GrpcWebDecoder()) .transform(grpcDecompressor()) - .listen(_incomingMessages.add, - onError: _onError, onDone: _incomingMessages.close); + .listen( + _incomingMessages.add, + onError: _onError, + onDone: _incomingMessages.close, + ); } bool _validateResponseState() { try { validateHttpStatusAndContentType( - _request.status, _request.responseHeaders, - rawResponse: _request.responseText); + _request.status, + _request.responseHeaders, + rawResponse: _request.responseText, + ); return true; } catch (e, st) { _onError(e, st); @@ -124,11 +136,13 @@ class XhrTransportStream implements GrpcTransportStream { } if (_request.status != 200) { _onError( - GrpcError.unavailable( - 'Request failed with status: ${_request.status}', - null, - _request.responseText), - StackTrace.current); + GrpcError.unavailable( + 'Request failed with status: ${_request.status}', + null, + _request.responseText, + ), + StackTrace.current, + ); return; } } @@ -264,7 +278,9 @@ class XhrClientConnection implements ClientConnection { String get scheme => uri.scheme; void _initializeRequest( - IXMLHttpRequest request, Map metadata) { + IXMLHttpRequest request, + Map metadata, + ) { metadata.forEach(request.setRequestHeader); // Overriding the mimetype allows us to stream and parse the data request.overrideMimeType('text/plain; charset=x-user-defined'); @@ -275,9 +291,13 @@ class XhrClientConnection implements ClientConnection { IXMLHttpRequest createHttpRequest() => XMLHttpRequestImpl(); @override - GrpcTransportStream makeRequest(String path, Duration? timeout, - Map metadata, ErrorHandler onError, - {CallOptions? callOptions}) { + GrpcTransportStream makeRequest( + String path, + Duration? timeout, + Map metadata, + ErrorHandler onError, { + CallOptions? callOptions, + }) { // gRPC-web headers. if (_getContentTypeHeader(metadata) == null) { metadata['Content-Type'] = 'application/grpc-web+proto'; @@ -299,14 +319,20 @@ class XhrClientConnection implements ClientConnection { // Must set headers after calling open(). _initializeRequest(request, metadata); - final transportStream = - _createXhrTransportStream(request, onError, _removeStream); + final transportStream = _createXhrTransportStream( + request, + onError, + _removeStream, + ); _requests.add(transportStream); return transportStream; } - XhrTransportStream _createXhrTransportStream(IXMLHttpRequest request, - ErrorHandler onError, void Function(XhrTransportStream stream) onDone) { + XhrTransportStream _createXhrTransportStream( + IXMLHttpRequest request, + ErrorHandler onError, + void Function(XhrTransportStream stream) onDone, + ) { return XhrTransportStream(request, onError: onError, onDone: onDone); } diff --git a/lib/src/server/handler.dart b/lib/src/server/handler.dart index fb285c6d..92717ec2 100644 --- a/lib/src/server/handler.dart +++ b/lib/src/server/handler.dart @@ -90,14 +90,14 @@ class ServerHandler extends ServiceCall { InternetAddress? remoteAddress, GrpcErrorHandler? errorHandler, this.onDataReceived, - }) : _stream = stream, - _serviceLookup = serviceLookup, - _interceptors = interceptors, - _codecRegistry = codecRegistry, - _clientCertificate = clientCertificate, - _remoteAddress = remoteAddress, - _errorHandler = errorHandler, - _serverInterceptors = serverInterceptors; + }) : _stream = stream, + _serviceLookup = serviceLookup, + _interceptors = interceptors, + _codecRegistry = codecRegistry, + _clientCertificate = clientCertificate, + _remoteAddress = remoteAddress, + _errorHandler = errorHandler, + _serverInterceptors = serverInterceptors; @override DateTime? get deadline => _deadline; @@ -244,10 +244,12 @@ class ServerHandler extends ServiceCall { _responses = _descriptor.handle(this, requests.stream, _serverInterceptors); - _responseSubscription = _responses.listen(_onResponse, - onError: _onResponseError, - onDone: _onResponseDone, - cancelOnError: true); + _responseSubscription = _responses.listen( + _onResponse, + onError: _onResponseError, + onDone: _onResponseDone, + cancelOnError: true, + ); _incomingSubscription!.onData(_onDataActive); _incomingSubscription!.onDone(_onDoneExpected); @@ -298,8 +300,9 @@ class ServerHandler extends ServiceCall { try { request = _descriptor.deserialize(data.data); } catch (error, trace) { - final grpcError = - GrpcError.internal('Error deserializing request: $error'); + final grpcError = GrpcError.internal( + 'Error deserializing request: $error', + ); _sendError(grpcError, trace); _requests! ..addError(grpcError, trace) @@ -364,8 +367,10 @@ class ServerHandler extends ServiceCall { _customHeaders = null; final outgoingHeaders =
[]; - outgoingHeadersMap.forEach((key, value) => - outgoingHeaders.add(Header(ascii.encode(key), utf8.encode(value)))); + outgoingHeadersMap.forEach( + (key, value) => + outgoingHeaders.add(Header(ascii.encode(key), utf8.encode(value))), + ); _stream.sendHeaders(outgoingHeaders); _headersSent = true; } @@ -398,16 +403,19 @@ class ServerHandler extends ServiceCall { _customTrailers = null; outgoingTrailersMap['grpc-status'] = status.toString(); if (message != null) { - outgoingTrailersMap['grpc-message'] = - Uri.encodeFull(message).replaceAll('%20', ' '); + outgoingTrailersMap['grpc-message'] = Uri.encodeFull( + message, + ).replaceAll('%20', ' '); } if (errorTrailers != null) { outgoingTrailersMap.addAll(errorTrailers); } final outgoingTrailers =
[]; - outgoingTrailersMap.forEach((key, value) => - outgoingTrailers.add(Header(ascii.encode(key), utf8.encode(value)))); + outgoingTrailersMap.forEach( + (key, value) => + outgoingTrailers.add(Header(ascii.encode(key), utf8.encode(value))), + ); _stream.sendHeaders(outgoingTrailers, endStream: true); // We're done! _cancelResponseSubscription(); diff --git a/lib/src/server/interceptor.dart b/lib/src/server/interceptor.dart index 2a8e74aa..7de012dd 100644 --- a/lib/src/server/interceptor.dart +++ b/lib/src/server/interceptor.dart @@ -25,11 +25,15 @@ import 'service.dart'; /// If the interceptor returns a [GrpcError], the error will be returned as a response and [ServiceMethod] wouldn't be called. /// If the interceptor throws [Exception], [GrpcError.internal] with exception.toString() will be returned. /// If the interceptor returns null, the corresponding [ServiceMethod] of [Service] will be called. -typedef Interceptor = FutureOr Function( - ServiceCall call, ServiceMethod method); +typedef Interceptor = + FutureOr Function(ServiceCall call, ServiceMethod method); -typedef ServerStreamingInvoker = Stream Function( - ServiceCall call, ServiceMethod method, Stream requests); +typedef ServerStreamingInvoker = + Stream Function( + ServiceCall call, + ServiceMethod method, + Stream requests, + ); /// A gRPC Interceptor. /// @@ -37,8 +41,12 @@ typedef ServerStreamingInvoker = Stream Function( /// If the interceptor throws [GrpcError], the error will be returned as a response. [ServiceMethod] wouldn't be called if the error is thrown before calling the invoker. /// If the interceptor modifies the provided stream, the invocation will continue with the provided stream. abstract class ServerInterceptor { - Stream intercept(ServiceCall call, ServiceMethod method, - Stream requests, ServerStreamingInvoker invoker) { + Stream intercept( + ServiceCall call, + ServiceMethod method, + Stream requests, + ServerStreamingInvoker invoker, + ) { return invoker(call, method, requests); } } diff --git a/lib/src/server/server.dart b/lib/src/server/server.dart index a58a3bdb..9d3e40d8 100644 --- a/lib/src/server/server.dart +++ b/lib/src/server/server.dart @@ -57,11 +57,12 @@ class ServerTlsCredentials extends ServerCredentials { /// /// If the [certificate] or [privateKey] is encrypted, the password must also /// be provided. - ServerTlsCredentials( - {this.certificate, - this.certificatePassword, - this.privateKey, - this.privateKeyPassword}); + ServerTlsCredentials({ + this.certificate, + this.certificatePassword, + this.privateKey, + this.privateKeyPassword, + }); @override SecurityContext get securityContext { @@ -70,8 +71,10 @@ class ServerTlsCredentials extends ServerCredentials { context.usePrivateKeyBytes(privateKey!, password: privateKeyPassword); } if (certificate != null) { - context.useCertificateChainBytes(certificate!, - password: certificatePassword); + context.useCertificateChainBytes( + certificate!, + password: certificatePassword, + ); } return context; } @@ -105,10 +108,10 @@ class ConnectionServer { CodecRegistry? codecRegistry, GrpcErrorHandler? errorHandler, this._keepAliveOptions = const ServerKeepAliveOptions(), - ]) : _codecRegistry = codecRegistry, - _interceptors = interceptors, - _serverInterceptors = serverInterceptors, - _errorHandler = errorHandler { + ]) : _codecRegistry = codecRegistry, + _interceptors = interceptors, + _serverInterceptors = serverInterceptors, + _errorHandler = errorHandler { for (final service in services) { _services[service.$name] = service; } @@ -133,31 +136,35 @@ class ConnectionServer { pingNotifier: connection.onPingReceived, dataNotifier: onDataReceivedController.stream, ).handle(); - connection.incomingStreams.listen((stream) { - final handler = serveStream_( - stream: stream, - clientCertificate: clientCertificate, - remoteAddress: remoteAddress, - onDataReceived: onDataReceivedController.sink, - ); - handler.onCanceled.then((_) => handlers[connection]?.remove(handler)); - handlers[connection]!.add(handler); - }, onError: (error, stackTrace) { - if (error is Error) { - Zone.current.handleUncaughtError(error, stackTrace); - } - }, onDone: () async { - // TODO(sigurdm): This is not correct behavior in the presence of - // half-closed tcp streams. - // Half-closed streams seems to not be fully supported by package:http2. - // https://github.com/dart-lang/http2/issues/42 - for (var handler in handlers[connection]!) { - handler.cancel(); - } - _connections.remove(connection); - handlers.remove(connection); - await onDataReceivedController.close(); - }); + connection.incomingStreams.listen( + (stream) { + final handler = serveStream_( + stream: stream, + clientCertificate: clientCertificate, + remoteAddress: remoteAddress, + onDataReceived: onDataReceivedController.sink, + ); + handler.onCanceled.then((_) => handlers[connection]?.remove(handler)); + handlers[connection]!.add(handler); + }, + onError: (error, stackTrace) { + if (error is Error) { + Zone.current.handleUncaughtError(error, stackTrace); + } + }, + onDone: () async { + // TODO(sigurdm): This is not correct behavior in the presence of + // half-closed tcp streams. + // Half-closed streams seems to not be fully supported by package:http2. + // https://github.com/dart-lang/http2/issues/42 + for (var handler in handlers[connection]!) { + handler.cancel(); + } + _connections.remove(connection); + handlers.remove(connection); + await onDataReceivedController.close(); + }, + ); } @visibleForTesting @@ -168,18 +175,18 @@ class ConnectionServer { Sink? onDataReceived, }) { return ServerHandler( - stream: stream, - serviceLookup: lookupService, - interceptors: _interceptors, - serverInterceptors: _serverInterceptors, - codecRegistry: _codecRegistry, - // ignore: unnecessary_cast - clientCertificate: clientCertificate as io_bits.X509Certificate?, - // ignore: unnecessary_cast - remoteAddress: remoteAddress as io_bits.InternetAddress?, - errorHandler: _errorHandler, - onDataReceived: onDataReceived) - ..handle(); + stream: stream, + serviceLookup: lookupService, + interceptors: _interceptors, + serverInterceptors: _serverInterceptors, + codecRegistry: _codecRegistry, + // ignore: unnecessary_cast + clientCertificate: clientCertificate as io_bits.X509Certificate?, + // ignore: unnecessary_cast + remoteAddress: remoteAddress as io_bits.InternetAddress?, + errorHandler: _errorHandler, + onDataReceived: onDataReceived, + )..handle(); } } @@ -209,13 +216,13 @@ class Server extends ConnectionServer { CodecRegistry? codecRegistry, GrpcErrorHandler? errorHandler, }) : super( - services, - interceptors, - serverInterceptors, - codecRegistry, - errorHandler, - keepAliveOptions, - ); + services, + interceptors, + serverInterceptors, + codecRegistry, + errorHandler, + keepAliveOptions, + ); /// The port that the server is listening on, or `null` if the server is not /// active. @@ -273,33 +280,36 @@ class Server extends ConnectionServer { _insecureServer = _server; server = _server; } - server.listen((socket) { - // Don't wait for io buffers to fill up before sending requests. - if (socket.address.type != InternetAddressType.unix) { - socket.setOption(SocketOption.tcpNoDelay, true); - } - - X509Certificate? clientCertificate; - - if (socket is SecureSocket) { - clientCertificate = socket.peerCertificate; - } - - final connection = ServerTransportConnection.viaSocket( - socket, - settings: http2ServerSettings, - ); + server.listen( + (socket) { + // Don't wait for io buffers to fill up before sending requests. + if (socket.address.type != InternetAddressType.unix) { + socket.setOption(SocketOption.tcpNoDelay, true); + } + + X509Certificate? clientCertificate; + + if (socket is SecureSocket) { + clientCertificate = socket.peerCertificate; + } + + final connection = ServerTransportConnection.viaSocket( + socket, + settings: http2ServerSettings, + ); - serveConnection( - connection: connection, - clientCertificate: clientCertificate, - remoteAddress: socket.remoteAddressOrNull, - ); - }, onError: (error, stackTrace) { - if (error is Error) { - Zone.current.handleUncaughtError(error, stackTrace); - } - }); + serveConnection( + connection: connection, + clientCertificate: clientCertificate, + remoteAddress: socket.remoteAddressOrNull, + ); + }, + onError: (error, stackTrace) { + if (error is Error) { + Zone.current.handleUncaughtError(error, stackTrace); + } + }, + ); } @override @@ -326,7 +336,8 @@ class Server extends ConnectionServer { } @Deprecated( - 'This is internal functionality, and will be removed in next major version.') + 'This is internal functionality, and will be removed in next major version.', + ) void serveStream(ServerTransportStream stream) { serveStream_(stream: stream); } diff --git a/lib/src/server/service.dart b/lib/src/server/service.dart index 9f1b2fa8..48e72909 100644 --- a/lib/src/server/service.dart +++ b/lib/src/server/service.dart @@ -32,28 +32,27 @@ class ServiceMethod { final Function handler; ServiceMethod( - this.name, - this.handler, - this.streamingRequest, - this.streamingResponse, - this.requestDeserializer, - this.responseSerializer); + this.name, + this.handler, + this.streamingRequest, + this.streamingResponse, + this.requestDeserializer, + this.responseSerializer, + ); StreamController createRequestStream(StreamSubscription incoming) => StreamController( - onListen: incoming.resume, - onPause: incoming.pause, - onResume: incoming.resume); + onListen: incoming.resume, + onPause: incoming.pause, + onResume: incoming.resume, + ); Q deserialize(List data) => requestDeserializer(data); List serialize(dynamic response) => responseSerializer(response as R); - ServerStreamingInvoker _createCall() => (( - ServiceCall call, - ServiceMethod method, - Stream requests, - ) { + ServerStreamingInvoker _createCall() => + ((ServiceCall call, ServiceMethod method, Stream requests) { if (streamingResponse) { if (streamingRequest) { return handler(call, requests); @@ -100,8 +99,9 @@ class ServiceMethod { return value; } - final future = - stream.fold(null, ensureOnlyOneRequest).then(ensureOneRequest); + final future = stream + .fold(null, ensureOnlyOneRequest) + .then(ensureOneRequest); // Make sure errors on the future aren't unhandled, but return the original // future so the request handler can also get the error. _awaitAndCatch(future); diff --git a/lib/src/shared/codec_registry.dart b/lib/src/shared/codec_registry.dart index 73c05581..8e701548 100644 --- a/lib/src/shared/codec_registry.dart +++ b/lib/src/shared/codec_registry.dart @@ -18,17 +18,25 @@ import 'codec.dart'; /// Encloses classes related to the compression and decompression of messages. class CodecRegistry { CodecRegistry({List codecs = const [IdentityCodec()]}) - : _codecs = {for (var codec in codecs) codec.encodingName: codec}, - _supportedEncodings = codecs.map((c) { - if (c.encodingName.contains(',')) { - throw ArgumentError.value(c.encodingName, 'codecs', - 'contains entries with names containing ","'); - } - return c.encodingName; - }).join(',') { + : _codecs = {for (var codec in codecs) codec.encodingName: codec}, + _supportedEncodings = codecs + .map((c) { + if (c.encodingName.contains(',')) { + throw ArgumentError.value( + c.encodingName, + 'codecs', + 'contains entries with names containing ","', + ); + } + return c.encodingName; + }) + .join(',') { if (_codecs.length != codecs.length) { throw ArgumentError.value( - codecs, 'codecs', 'contains multiple entries with the same name'); + codecs, + 'codecs', + 'contains multiple entries with the same name', + ); } } diff --git a/lib/src/shared/message.dart b/lib/src/shared/message.dart index 0533c555..a9652e4f 100644 --- a/lib/src/shared/message.dart +++ b/lib/src/shared/message.dart @@ -63,13 +63,16 @@ class GrpcMessageSink implements Sink { } List frame(List rawPayload, [Codec? codec]) { - final compressedPayload = - codec == null ? rawPayload : codec.compress(rawPayload); + final compressedPayload = codec == null + ? rawPayload + : codec.compress(rawPayload); final payloadLength = compressedPayload.length; final bytes = Uint8List(payloadLength + 5); final header = bytes.buffer.asByteData(0, 5); header.setUint8( - 0, (codec == null || codec.encodingName == 'identity') ? 0 : 1); + 0, + (codec == null || codec.encodingName == 'identity') ? 0 : 1, + ); header.setUint32(1, payloadLength); bytes.setRange(5, bytes.length, compressedPayload); return bytes; @@ -80,21 +83,23 @@ StreamTransformer grpcDecompressor({ }) { Codec? codec; return StreamTransformer.fromHandlers( - handleData: (GrpcMessage value, EventSink sink) { - if (value is GrpcData && value.isCompressed) { - if (codec == null) { - sink.addError( - GrpcError.unimplemented('Compression mechanism not supported'), - ); + handleData: (GrpcMessage value, EventSink sink) { + if (value is GrpcData && value.isCompressed) { + if (codec == null) { + sink.addError( + GrpcError.unimplemented('Compression mechanism not supported'), + ); + return; + } + sink.add(GrpcData(codec!.decompress(value.data), isCompressed: false)); return; } - sink.add(GrpcData(codec!.decompress(value.data), isCompressed: false)); - return; - } - if (value is GrpcMetadata && value.metadata.containsKey('grpc-encoding')) { - codec = codecRegistry?.lookup(value.metadata['grpc-encoding']!); - } - sink.add(value); - }); + if (value is GrpcMetadata && + value.metadata.containsKey('grpc-encoding')) { + codec = codecRegistry?.lookup(value.metadata['grpc-encoding']!); + } + sink.add(value); + }, + ); } diff --git a/lib/src/shared/status.dart b/lib/src/shared/status.dart index cb662326..df4e31b9 100644 --- a/lib/src/shared/status.dart +++ b/lib/src/shared/status.dart @@ -153,25 +153,25 @@ class StatusCode { /// Creates a string from a gRPC status code. static String? name(int status) => switch (status) { - ok => 'OK', - cancelled => 'CANCELLED', - unknown => 'UNKNOWN', - invalidArgument => 'INVALID_ARGUMENT', - deadlineExceeded => 'DEADLINE_EXCEEDED', - notFound => 'NOT_FOUND', - alreadyExists => 'ALREADY_EXISTS', - permissionDenied => 'PERMISSION_DENIED', - resourceExhausted => 'RESOURCE_EXHAUSTED', - failedPrecondition => 'FAILED_PRECONDITION', - aborted => 'ABORTED', - outOfRange => 'OUT_OF_RANGE', - unimplemented => 'UNIMPLEMENTED', - internal => 'INTERNAL', - unavailable => 'UNAVAILABLE', - dataLoss => 'DATA_LOSS', - unauthenticated => 'UNAUTHENTICATED', - int() => null, - }; + ok => 'OK', + cancelled => 'CANCELLED', + unknown => 'UNKNOWN', + invalidArgument => 'INVALID_ARGUMENT', + deadlineExceeded => 'DEADLINE_EXCEEDED', + notFound => 'NOT_FOUND', + alreadyExists => 'ALREADY_EXISTS', + permissionDenied => 'PERMISSION_DENIED', + resourceExhausted => 'RESOURCE_EXHAUSTED', + failedPrecondition => 'FAILED_PRECONDITION', + aborted => 'ABORTED', + outOfRange => 'OUT_OF_RANGE', + unimplemented => 'UNIMPLEMENTED', + internal => 'INTERNAL', + unavailable => 'UNAVAILABLE', + dataLoss => 'DATA_LOSS', + unauthenticated => 'UNAUTHENTICATED', + int() => null, + }; } class GrpcError implements Exception { @@ -182,73 +182,86 @@ class GrpcError implements Exception { final List? details; /// Custom error code. - const GrpcError.custom(this.code, - [this.message, this.details, this.rawResponse, this.trailers = const {}]); + const GrpcError.custom( + this.code, [ + this.message, + this.details, + this.rawResponse, + this.trailers = const {}, + ]); /// The operation completed successfully. const GrpcError.ok([this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.ok; + : trailers = const {}, + code = StatusCode.ok; /// The operation was cancelled (typically by the caller). const GrpcError.cancelled([this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.cancelled; + : trailers = const {}, + code = StatusCode.cancelled; /// Unknown error. An example of where this error may be returned is if a /// Status value received from another address space belongs to an error-space /// that is not known in this address space. Also errors raised by APIs that /// do not return enough error information may be converted to this error. const GrpcError.unknown([this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.unknown; + : trailers = const {}, + code = StatusCode.unknown; /// Client specified an invalid argument. Note that this differs from /// [failedPrecondition]. [invalidArgument] indicates arguments that are /// problematic regardless of the state of the system (e.g., a malformed file /// name). - const GrpcError.invalidArgument( - [this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.invalidArgument; + const GrpcError.invalidArgument([ + this.message, + this.details, + this.rawResponse, + ]) : trailers = const {}, + code = StatusCode.invalidArgument; /// Deadline expired before operation could complete. For operations that /// change the state of the system, this error may be returned even if the /// operation has completed successfully. For example, a successful response /// from a server could have been delayed long enough for the deadline to /// expire. - const GrpcError.deadlineExceeded( - [this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.deadlineExceeded; + const GrpcError.deadlineExceeded([ + this.message, + this.details, + this.rawResponse, + ]) : trailers = const {}, + code = StatusCode.deadlineExceeded; /// Some requested entity (e.g., file or directory) was not found. const GrpcError.notFound([this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.notFound; + : trailers = const {}, + code = StatusCode.notFound; /// Some entity that we attempted to create (e.g., file or directory) already /// exists. const GrpcError.alreadyExists([this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.alreadyExists; + : trailers = const {}, + code = StatusCode.alreadyExists; /// The caller does not have permission to execute the specified operation. /// [permissionDenied] must not be used for rejections caused by exhausting /// some resource (use [resourceExhausted] instead for those errors). /// [permissionDenied] must not be used if the caller cannot be identified /// (use [unauthenticated] instead for those errors). - const GrpcError.permissionDenied( - [this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.permissionDenied; + const GrpcError.permissionDenied([ + this.message, + this.details, + this.rawResponse, + ]) : trailers = const {}, + code = StatusCode.permissionDenied; /// Some resource has been exhausted, perhaps a per-user quota, or perhaps the /// entire file system is out of space. - const GrpcError.resourceExhausted( - [this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.resourceExhausted; + const GrpcError.resourceExhausted([ + this.message, + this.details, + this.rawResponse, + ]) : trailers = const {}, + code = StatusCode.resourceExhausted; /// Operation was rejected because the system is not in a state required for /// the operation's execution. For example, directory to be deleted may be @@ -264,10 +277,12 @@ class GrpcError implements Exception { /// because the directory is non-empty, [failedPrecondition] should be /// returned since the client should not retry unless they have first /// fixed up the directory by deleting files from it. - const GrpcError.failedPrecondition( - [this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.failedPrecondition; + const GrpcError.failedPrecondition([ + this.message, + this.details, + this.rawResponse, + ]) : trailers = const {}, + code = StatusCode.failedPrecondition; /// The operation was aborted, typically due to a concurrency issue like /// sequencer check failures, transaction aborts, etc. @@ -275,8 +290,8 @@ class GrpcError implements Exception { /// See litmus test above for deciding between [failedPrecondition], /// [aborted], and [unavailable]. const GrpcError.aborted([this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.aborted; + : trailers = const {}, + code = StatusCode.aborted; /// Operation was attempted past the valid range. E.g., seeking or reading /// past end of file. @@ -292,20 +307,23 @@ class GrpcError implements Exception { /// when it applies so that callers who are iterating through a space can /// easily look for an [outOfRange] error to detect when they are done. const GrpcError.outOfRange([this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.outOfRange; + : trailers = const {}, + code = StatusCode.outOfRange; /// Operation is not implemented or not supported/enabled in this service. const GrpcError.unimplemented([this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.unimplemented; + : trailers = const {}, + code = StatusCode.unimplemented; /// Internal errors. Means some invariants expected by underlying system has /// been broken. If you see one of these errors, something is very broken. // TODO(sigurdm): This should probably not be an [Exception]. - const GrpcError.internal( - [this.message, this.details, this.rawResponse, this.trailers]) - : code = StatusCode.internal; + const GrpcError.internal([ + this.message, + this.details, + this.rawResponse, + this.trailers, + ]) : code = StatusCode.internal; /// The service is currently unavailable. This is a most likely a transient /// condition and may be corrected by retrying with a backoff. @@ -313,20 +331,22 @@ class GrpcError implements Exception { /// See litmus test above for deciding between [failedPrecondition], /// [aborted], and [unavailable]. const GrpcError.unavailable([this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.unavailable; + : trailers = const {}, + code = StatusCode.unavailable; /// Unrecoverable data loss or corruption. const GrpcError.dataLoss([this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.dataLoss; + : trailers = const {}, + code = StatusCode.dataLoss; /// The request does not have valid authentication credentials for the /// operation. - const GrpcError.unauthenticated( - [this.message, this.details, this.rawResponse]) - : trailers = const {}, - code = StatusCode.unauthenticated; + const GrpcError.unauthenticated([ + this.message, + this.details, + this.rawResponse, + ]) : trailers = const {}, + code = StatusCode.unauthenticated; /// Given a status code, return the name String get codeName => @@ -405,18 +425,24 @@ GeneratedMessage parseErrorDetailsFromAny(Any any) { /// occurs. /// void validateHttpStatusAndContentType( - int? httpStatus, Map headers, - {Object? rawResponse}) { + int? httpStatus, + Map headers, { + Object? rawResponse, +}) { if (httpStatus == null) { throw GrpcError.unknown( - 'HTTP response status is unknown', null, rawResponse); + 'HTTP response status is unknown', + null, + rawResponse, + ); } if (httpStatus == 0) { throw GrpcError.unknown( - 'HTTP request completed without a status (potential CORS issue)', - null, - rawResponse); + 'HTTP request completed without a status (potential CORS issue)', + null, + rawResponse, + ); } final status = StatusCode.fromHttpStatus(httpStatus); @@ -448,7 +474,10 @@ void validateHttpStatusAndContentType( // Check if content-type header indicates a supported format. if (!_validContentTypePrefix.any(contentType.startsWith)) { throw GrpcError.unknown( - 'unsupported content-type ($contentType)', null, rawResponse); + 'unsupported content-type ($contentType)', + null, + rawResponse, + ); } } @@ -489,7 +518,7 @@ const _statusDetailsHeader = 'grpc-status-details-bin'; const _validContentTypePrefix = [ 'application/grpc', 'application/json+protobuf', - 'application/x-protobuf' + 'application/x-protobuf', ]; /// Given a string of base64url data, attempt to parse a Status object from it. @@ -505,7 +534,8 @@ const _validContentTypePrefix = [ List decodeStatusDetails(String data) { try { final parsedStatus = Status.fromBuffer( - base64Url.decode(data.padRight((data.length + 3) & ~3, '='))); + base64Url.decode(data.padRight((data.length + 3) & ~3, '=')), + ); return parsedStatus.details.map(parseErrorDetailsFromAny).toList(); } catch (e) { return []; diff --git a/lib/src/shared/streams.dart b/lib/src/shared/streams.dart index 6c4469e4..1f6146ab 100644 --- a/lib/src/shared/streams.dart +++ b/lib/src/shared/streams.dart @@ -84,7 +84,11 @@ class _GrpcMessageConversionSink final chunkRemaining = chunkLength - chunkReadOffset; final toCopy = min(headerRemaining, chunkRemaining); _dataHeader.setRange( - _dataOffset, _dataOffset + toCopy, chunkData, chunkReadOffset); + _dataOffset, + _dataOffset + toCopy, + chunkData, + chunkReadOffset, + ); _dataOffset += toCopy; chunkReadOffset += toCopy; if (_dataOffset == _dataHeader.lengthInBytes) { @@ -101,13 +105,21 @@ class _GrpcMessageConversionSink final chunkRemaining = chunkLength - chunkReadOffset; final toCopy = min(dataRemaining, chunkRemaining); _data!.setRange( - _dataOffset, _dataOffset + toCopy, chunkData, chunkReadOffset); + _dataOffset, + _dataOffset + toCopy, + chunkData, + chunkReadOffset, + ); _dataOffset += toCopy; chunkReadOffset += toCopy; } if (_dataOffset == _data!.lengthInBytes) { - _out.add(GrpcData(_data!, - isCompressed: _dataHeader.buffer.asByteData().getUint8(0) != 0)); + _out.add( + GrpcData( + _data!, + isCompressed: _dataHeader.buffer.asByteData().getUint8(0) != 0, + ), + ); _data = null; _dataOffset = 0; } diff --git a/pubspec.yaml b/pubspec.yaml index 9f9c8f6d..1078be8a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: grpc -version: 4.1.0 +version: 4.1.1-wip description: Dart implementation of gRPC, a high performance, open-source universal RPC framework. repository: https://github.com/grpc/grpc-dart @@ -9,31 +9,31 @@ topics: - rpc environment: - sdk: ^3.5.0 + sdk: ^3.8.0 dependencies: - async: ^2.5.0 - crypto: ^3.0.0 - fixnum: ^1.0.0 - googleapis_auth: ">=1.1.0 <3.0.0" - meta: ^1.3.0 - http: ">=0.13.0 <2.0.0" - http2: ^2.2.0 - protobuf: ">=2.0.0 <5.0.0" - clock: ^1.1.1 - web: ^1.1.0 + async: ^2.13.0 + crypto: ^3.0.6 + fixnum: ^1.1.1 + googleapis_auth: ^2.0.0 + meta: ^1.17.0 + http: ^1.4.0 + http2: ^2.3.1 + protobuf: ^4.1.0 + clock: ^1.1.2 + web: ^1.1.1 dev_dependencies: - build_runner: ^2.0.0 - build_test: ^2.0.0 - lints: ^5.0.0 - mockito: ^5.0.0 - path: ^1.8.0 - test: ^1.16.0 - stream_channel: ^2.1.0 - stream_transform: ^2.0.0 - vm_service: ">=11.6.0 <16.0.0" - fake_async: ^1.3.1 + build_runner: ^2.4.15 + build_test: ^2.2.3 + lints: ^6.0.0 + mockito: ^5.4.6 + path: ^1.9.1 + test: ^1.26.2 + stream_channel: ^2.1.4 + stream_transform: ^2.1.1 + vm_service: ^15.0.2 + fake_async: ^1.3.3 false_secrets: - interop/server1.key diff --git a/test/client_certificate_test.dart b/test/client_certificate_test.dart index 70bb2b98..c6a5f32d 100644 --- a/test/client_certificate_test.dart +++ b/test/client_certificate_test.dart @@ -35,7 +35,9 @@ class EchoService extends EchoServiceBase { @override Stream serverStreamingEcho( - ServiceCall call, ServerStreamingEchoRequest request) { + ServiceCall call, + ServerStreamingEchoRequest request, + ) { // TODO: implement serverStreamingEcho throw UnimplementedError(); } @@ -53,13 +55,17 @@ Future main() async { SecurityContextChannelCredentials.baseSecurityContext(); channelContext.useCertificateChain('test/data/localhost.crt'); channelContext.usePrivateKey('test/data/localhost.key'); - final channelCredentials = SecurityContextChannelCredentials(channelContext, - onBadCertificate: (cert, s) { - return true; - }); - final channel = ClientChannel(address, - port: server.port ?? 443, - options: ChannelOptions(credentials: channelCredentials)); + final channelCredentials = SecurityContextChannelCredentials( + channelContext, + onBadCertificate: (cert, s) { + return true; + }, + ); + final channel = ClientChannel( + address, + port: server.port ?? 443, + options: ChannelOptions(credentials: channelCredentials), + ); final client = EchoServiceClient(channel); // Test @@ -79,13 +85,17 @@ Future main() async { SecurityContextChannelCredentials.baseSecurityContext(); channelContext.useCertificateChain('test/data/localhost.crt'); channelContext.usePrivateKey('test/data/localhost.key'); - final channelCredentials = SecurityContextChannelCredentials(channelContext, - onBadCertificate: (cert, s) { - return true; - }); - final channel = ClientChannel(address, - port: server.port ?? 443, - options: ChannelOptions(credentials: channelCredentials)); + final channelCredentials = SecurityContextChannelCredentials( + channelContext, + onBadCertificate: (cert, s) { + return true; + }, + ); + final channel = ClientChannel( + address, + port: server.port ?? 443, + options: ChannelOptions(credentials: channelCredentials), + ); final client = EchoServiceClient(channel); // Test @@ -103,23 +113,27 @@ Future _setUpServer([bool requireClientCertificate = false]) async { serverContext.useCertificateChain('test/data/localhost.crt'); serverContext.usePrivateKey('test/data/localhost.key'); serverContext.setTrustedCertificates('test/data/localhost.crt'); - final ServerCredentials serverCredentials = - SecurityContextServerCredentials(serverContext); + final ServerCredentials serverCredentials = SecurityContextServerCredentials( + serverContext, + ); await server.serve( - address: address, - port: 0, - security: serverCredentials, - requireClientCertificate: requireClientCertificate); + address: address, + port: 0, + security: serverCredentials, + requireClientCertificate: requireClientCertificate, + ); return server; } class SecurityContextChannelCredentials extends ChannelCredentials { final SecurityContext _securityContext; - SecurityContextChannelCredentials(SecurityContext securityContext, - {super.authority, super.onBadCertificate}) - : _securityContext = securityContext, - super.secure(); + SecurityContextChannelCredentials( + SecurityContext securityContext, { + super.authority, + super.onBadCertificate, + }) : _securityContext = securityContext, + super.secure(); @override SecurityContext get securityContext => _securityContext; @@ -133,8 +147,8 @@ class SecurityContextServerCredentials extends ServerTlsCredentials { final SecurityContext _securityContext; SecurityContextServerCredentials(SecurityContext securityContext) - : _securityContext = securityContext, - super(); + : _securityContext = securityContext, + super(); @override SecurityContext get securityContext => _securityContext; diff --git a/test/client_handles_bad_connections_test.dart b/test/client_handles_bad_connections_test.dart index 75114d7d..1cb8d47c 100644 --- a/test/client_handles_bad_connections_test.dart +++ b/test/client_handles_bad_connections_test.dart @@ -29,15 +29,19 @@ import 'common.dart'; class TestClient extends grpc.Client { static final _$stream = grpc.ClientMethod( - '/test.TestService/stream', - (int value) => [value], - (List value) => value[0]); + '/test.TestService/stream', + (int value) => [value], + (List value) => value[0], + ); TestClient(super.channel); grpc.ResponseStream stream(int request, {grpc.CallOptions? options}) { - return $createStreamingCall(_$stream, Stream.value(request), - options: options); + return $createStreamingCall( + _$stream, + Stream.value(request), + options: options, + ); } } @@ -46,8 +50,16 @@ class TestService extends grpc.Service { String get $name => 'test.TestService'; TestService() { - $addMethod(grpc.ServiceMethod('stream', stream, false, true, - (List value) => value[0], (int value) => [value])); + $addMethod( + grpc.ServiceMethod( + 'stream', + stream, + false, + true, + (List value) => value[0], + (int value) => [value], + ), + ); } Stream stream(grpc.ServiceCall call, Future request) async* { @@ -72,29 +84,34 @@ class FixedConnectionClientChannel extends ClientChannelBase { } Future main() async { - testTcpAndUds('client reconnects after the connection gets old', - (address) async { + testTcpAndUds('client reconnects after the connection gets old', ( + address, + ) async { // client reconnect after a short delay. final server = grpc.Server.create(services: [TestService()]); await server.serve(address: address, port: 0); - final channel = FixedConnectionClientChannel(Http2ClientConnection( - address, - server.port!, - grpc.ChannelOptions( - idleTimeout: Duration(minutes: 1), - // Short delay to test that it will time out. - connectionTimeout: Duration(milliseconds: 100), - credentials: grpc.ChannelCredentials.insecure(), + final channel = FixedConnectionClientChannel( + Http2ClientConnection( + address, + server.port!, + grpc.ChannelOptions( + idleTimeout: Duration(minutes: 1), + // Short delay to test that it will time out. + connectionTimeout: Duration(milliseconds: 100), + credentials: grpc.ChannelCredentials.insecure(), + ), ), - )); + ); final testClient = TestClient(channel); expect(await testClient.stream(1).toList(), [1, 2, 3]); await Future.delayed(Duration(milliseconds: 200)); expect(await testClient.stream(1).toList(), [1, 2, 3]); expect( - channel.states.where((x) => x == grpc.ConnectionState.ready).length, 2); + channel.states.where((x) => x == grpc.ConnectionState.ready).length, + 2, + ); server.shutdown(); }); @@ -102,14 +119,18 @@ Future main() async { // client reconnect after setting stream limit. final server = grpc.Server.create(services: [TestService()]); await server.serve( - address: address, - port: 0, - http2ServerSettings: ServerSettings(concurrentStreamLimit: 2)); + address: address, + port: 0, + http2ServerSettings: ServerSettings(concurrentStreamLimit: 2), + ); - final channel = FixedConnectionClientChannel(Http2ClientConnection( + final channel = FixedConnectionClientChannel( + Http2ClientConnection( address, server.port!, - grpc.ChannelOptions(credentials: grpc.ChannelCredentials.insecure()))); + grpc.ChannelOptions(credentials: grpc.ChannelCredentials.insecure()), + ), + ); final states = []; channel.onConnectionStateChanged.listen((state) { states.add(state); diff --git a/test/client_tests/call_test.dart b/test/client_tests/call_test.dart index 001fd897..8c60ff82 100644 --- a/test/client_tests/call_test.dart +++ b/test/client_tests/call_test.dart @@ -34,8 +34,10 @@ void main() { }); test('WebCallOptions mergeWith CallOptions returns WebCallOptions', () { - final options1 = - WebCallOptions(bypassCorsPreflight: true, withCredentials: true); + final options1 = WebCallOptions( + bypassCorsPreflight: true, + withCredentials: true, + ); final metadata = {'test': '42'}; final options2 = CallOptions(metadata: metadata); final mergedOptions1 = options1.mergedWith(options2) as WebCallOptions; @@ -50,56 +52,45 @@ void main() { expect(mergedOptions2.withCredentials, true); }); - test( - 'Cancelling a call correctly complete headers future', - () async { - final clientCall = harness.client.unary(dummyValue); - - Future.delayed( - Duration(milliseconds: cancelDurationMillis), - ).then((_) => clientCall.cancel()); - - expect(await clientCall.headers, isEmpty); - - await expectLater( - clientCall, - throwsA( - isA().having( - (e) => e.codeName, - 'Test codename', - contains('CANCELLED'), - ), + test('Cancelling a call correctly complete headers future', () async { + final clientCall = harness.client.unary(dummyValue); + + Future.delayed( + Duration(milliseconds: cancelDurationMillis), + ).then((_) => clientCall.cancel()); + + expect(await clientCall.headers, isEmpty); + + await expectLater( + clientCall, + throwsA( + isA().having( + (e) => e.codeName, + 'Test codename', + contains('CANCELLED'), ), - ); - }, - ); - - test( - 'Cancelling a call correctly complete trailers futures', - () async { - final clientCall = harness.client.unary(dummyValue); - - Future.delayed( - Duration(milliseconds: cancelDurationMillis), - ).then((_) { - clientCall.cancel(); - }); - - expect( - await clientCall.trailers, - isEmpty, - ); - - await expectLater( - clientCall, - throwsA( - isA().having( - (e) => e.codeName, - 'Test codename', - contains('CANCELLED'), - ), + ), + ); + }); + + test('Cancelling a call correctly complete trailers futures', () async { + final clientCall = harness.client.unary(dummyValue); + + Future.delayed(Duration(milliseconds: cancelDurationMillis)).then((_) { + clientCall.cancel(); + }); + + expect(await clientCall.trailers, isEmpty); + + await expectLater( + clientCall, + throwsA( + isA().having( + (e) => e.codeName, + 'Test codename', + contains('CANCELLED'), ), - ); - }, - ); + ), + ); + }); } diff --git a/test/client_tests/client_interceptor_test.dart b/test/client_tests/client_interceptor_test.dart index b1763d5c..0ea27b69 100644 --- a/test/client_tests/client_interceptor_test.dart +++ b/test/client_tests/client_interceptor_test.dart @@ -45,8 +45,12 @@ class FakeInterceptor implements ClientInterceptor { FakeInterceptor(this._id); @override - ResponseFuture interceptUnary(ClientMethod method, Q request, - CallOptions options, ClientUnaryInvoker invoker) { + ResponseFuture interceptUnary( + ClientMethod method, + Q request, + CallOptions options, + ClientUnaryInvoker invoker, + ) { _invocations.add(InterceptorInvocation(_id, ++_unary, _streaming)); return invoker(method, request, _inject(options)); @@ -54,10 +58,11 @@ class FakeInterceptor implements ClientInterceptor { @override ResponseStream interceptStreaming( - ClientMethod method, - Stream requests, - CallOptions options, - ClientStreamingInvoker invoker) { + ClientMethod method, + Stream requests, + CallOptions options, + ClientStreamingInvoker invoker, + ) { _invocations.add(InterceptorInvocation(_id, _unary, ++_streaming)); final requestStream = _id > 10 @@ -68,9 +73,13 @@ class FakeInterceptor implements ClientInterceptor { } CallOptions _inject(CallOptions options) { - return options.mergedWith(CallOptions(metadata: { - 'x-interceptor': _invocations.map((i) => i.toString()).join(', '), - })); + return options.mergedWith( + CallOptions( + metadata: { + 'x-interceptor': _invocations.map((i) => i.toString()).join(', '), + }, + ), + ); } static void tearDown() { @@ -102,7 +111,7 @@ void main() { expectedResult: responseValue, expectedPath: '/Test/Unary', expectedCustomHeaders: { - 'x-interceptor': '{id: 1, unary: 1, streaming: 0}' + 'x-interceptor': '{id: 1, unary: 1, streaming: 0}', }, serverHandlers: [handleRequest], ); @@ -135,7 +144,7 @@ void main() { expectedPath: '/Test/Unary', expectedCustomHeaders: { 'x-interceptor': - '{id: 1, unary: 1, streaming: 0}, {id: 2, unary: 1, streaming: 0}' + '{id: 1, unary: 1, streaming: 0}, {id: 2, unary: 1, streaming: 0}', }, serverHandlers: [handleRequest], ); @@ -170,12 +179,13 @@ void main() { } await harness.runTest( - clientCall: - harness.client.bidirectional(Stream.fromIterable(requests)).toList(), + clientCall: harness.client + .bidirectional(Stream.fromIterable(requests)) + .toList(), expectedResult: responses, expectedPath: '/Test/Bidirectional', expectedCustomHeaders: { - 'x-interceptor': '{id: 1, unary: 0, streaming: 1}' + 'x-interceptor': '{id: 1, unary: 0, streaming: 1}', }, serverHandlers: [handleRequest, handleRequest, handleRequest], doneHandler: handleDone, @@ -211,13 +221,14 @@ void main() { } await harness.runTest( - clientCall: - harness.client.bidirectional(Stream.fromIterable(requests)).toList(), + clientCall: harness.client + .bidirectional(Stream.fromIterable(requests)) + .toList(), expectedResult: responses, expectedPath: '/Test/Bidirectional', expectedCustomHeaders: { 'x-interceptor': - '{id: 1, unary: 0, streaming: 1}, {id: 2, unary: 0, streaming: 1}' + '{id: 1, unary: 0, streaming: 1}, {id: 2, unary: 0, streaming: 1}', }, serverHandlers: [handleRequest, handleRequest, handleRequest], doneHandler: handleDone, @@ -254,8 +265,9 @@ void main() { } await harness.runTest( - clientCall: - harness.client.bidirectional(Stream.fromIterable(requests)).toList(), + clientCall: harness.client + .bidirectional(Stream.fromIterable(requests)) + .toList(), expectedResult: responses, expectedPath: '/Test/Bidirectional', serverHandlers: [handleRequest, handleRequest, handleRequest], diff --git a/test/client_tests/client_keepalive_manager_test.dart b/test/client_tests/client_keepalive_manager_test.dart index c260dc91..aed098a1 100644 --- a/test/client_tests/client_keepalive_manager_test.dart +++ b/test/client_tests/client_keepalive_manager_test.dart @@ -50,7 +50,8 @@ void main() { void initKeepAliveManager([ClientKeepAliveOptions? opt]) { reset(pinger); - final options = opt ?? + final options = + opt ?? ClientKeepAliveOptions( pingInterval: pingInterval, timeout: timeout, @@ -172,11 +173,13 @@ void main() { test('transportGoesIdle_doesntCauseIdleWhenEnabled', () { FakeAsync().run((async) { keepAliveManager.onTransportTermination(); - initKeepAliveManager(ClientKeepAliveOptions( - pingInterval: pingInterval, - timeout: timeout, - permitWithoutCalls: true, - )); + initKeepAliveManager( + ClientKeepAliveOptions( + pingInterval: pingInterval, + timeout: timeout, + permitWithoutCalls: true, + ), + ); keepAliveManager.onTransportStarted(); // Keepalive scheduling should have started immediately. diff --git a/test/client_tests/client_test.dart b/test/client_tests/client_test.dart index 4a434834..be5f73f6 100644 --- a/test/client_tests/client_test.dart +++ b/test/client_tests/client_test.dart @@ -78,8 +78,10 @@ void main() { } await harness.runTest( - clientCall: harness.client.unary(requestValue, - options: CallOptions(metadata: {'grpc-accept-encoding': 'gzip'})), + clientCall: harness.client.unary( + requestValue, + options: CallOptions(metadata: {'grpc-accept-encoding': 'gzip'}), + ), expectedResult: responseValue, expectedCustomHeaders: {'grpc-accept-encoding': 'gzip'}, expectedPath: '/Test/Unary', @@ -157,8 +159,9 @@ void main() { } await harness.runTest( - clientCall: - harness.client.bidirectional(Stream.fromIterable(requests)).toList(), + clientCall: harness.client + .bidirectional(Stream.fromIterable(requests)) + .toList(), expectedResult: responses, expectedPath: '/Test/Bidirectional', serverHandlers: [handleRequest, handleRequest, handleRequest], @@ -189,8 +192,9 @@ void main() { await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), - expectedException: - GrpcError.unimplemented('More than one response received'), + expectedException: GrpcError.unimplemented( + 'More than one response received', + ), serverHandlers: [handleRequest], ); }); @@ -229,8 +233,9 @@ void main() { await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), - expectedException: - GrpcError.unimplemented('Received data before headers'), + expectedException: GrpcError.unimplemented( + 'Received data before headers', + ), serverHandlers: [handleRequest], ); }); @@ -245,8 +250,9 @@ void main() { await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), - expectedException: - GrpcError.unimplemented('Received data after trailers'), + expectedException: GrpcError.unimplemented( + 'Received data after trailers', + ), serverHandlers: [handleRequest], ); }); @@ -271,50 +277,60 @@ void main() { const customStatusMessage = 'Custom message'; void handleRequest(_) { - harness.toClient.add(HeadersStreamMessage([ - Header.ascii(':status', '200'), - Header.ascii('content-type', 'application/grpc'), - Header.ascii('grpc-status', '$customStatusCode'), - Header.ascii('grpc-message', customStatusMessage) - ], endStream: true)); + harness.toClient.add( + HeadersStreamMessage([ + Header.ascii(':status', '200'), + Header.ascii('content-type', 'application/grpc'), + Header.ascii('grpc-status', '$customStatusCode'), + Header.ascii('grpc-message', customStatusMessage), + ], endStream: true), + ); harness.toClient.close(); } await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), - expectedException: - GrpcError.custom(customStatusCode, customStatusMessage), + expectedException: GrpcError.custom( + customStatusCode, + customStatusMessage, + ), serverHandlers: [handleRequest], ); }); test('Call throws if HTTP status indicates an error', () async { void handleRequest(_) { - harness.toClient.add(HeadersStreamMessage([ - Header.ascii(':status', HttpStatus.serviceUnavailable.toString()), - Header.ascii('content-type', 'application/grpc'), - ])); + harness.toClient.add( + HeadersStreamMessage([ + Header.ascii(':status', HttpStatus.serviceUnavailable.toString()), + Header.ascii('content-type', 'application/grpc'), + ]), + ); // Send a frame that might be misinterpreted as a length-prefixed proto // message and cause OOM. - harness.toClient - .add(DataStreamMessage([0, 0xFF, 0xFF, 0xFF, 0xFF], endStream: true)); + harness.toClient.add( + DataStreamMessage([0, 0xFF, 0xFF, 0xFF, 0xFF], endStream: true), + ); harness.toClient.close(); } await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), expectedException: GrpcError.unavailable( - 'HTTP connection completed with 503 instead of 200'), + 'HTTP connection completed with 503 instead of 200', + ), serverHandlers: [handleRequest], ); }); test('Call throws if content-type indicates an error', () async { void handleRequest(_) { - harness.toClient.add(HeadersStreamMessage([ - Header.ascii(':status', '200'), - Header.ascii('content-type', 'text/html'), - ])); + harness.toClient.add( + HeadersStreamMessage([ + Header.ascii(':status', '200'), + Header.ascii('content-type', 'text/html'), + ]), + ); // Send a frame that might be misinterpreted as a length-prefixed proto // message and cause OOM. harness.toClient.add(DataStreamMessage([0, 0xFF, 0xFF, 0xFF, 0xFF])); @@ -323,15 +339,16 @@ void main() { await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), - expectedException: - GrpcError.unknown('unsupported content-type (text/html)'), + expectedException: GrpcError.unknown( + 'unsupported content-type (text/html)', + ), serverHandlers: [handleRequest], ); }); for (var contentType in [ 'application/json+protobuf', - 'application/x-protobuf' + 'application/x-protobuf', ]) { test('$contentType content type is accepted', () async { const requestValue = 17; @@ -342,10 +359,12 @@ void main() { expect(mockDecode(data.data), requestValue); harness - ..toClient.add(HeadersStreamMessage([ - Header.ascii(':status', '200'), - Header.ascii('content-type', contentType), - ])) + ..toClient.add( + HeadersStreamMessage([ + Header.ascii(':status', '200'), + Header.ascii('content-type', contentType), + ]), + ) ..sendResponseValue(responseValue) ..sendResponseTrailer(); } @@ -365,19 +384,23 @@ void main() { const encodedCustomStatusMessage = '%E3%82%A8%E3%83%A9%E3%83%BC'; void handleRequest(_) { - harness.toClient.add(HeadersStreamMessage([ - Header.ascii(':status', '200'), - Header.ascii('content-type', 'application/grpc'), - Header.ascii('grpc-status', '$customStatusCode'), - Header.ascii('grpc-message', encodedCustomStatusMessage) - ], endStream: true)); + harness.toClient.add( + HeadersStreamMessage([ + Header.ascii(':status', '200'), + Header.ascii('content-type', 'application/grpc'), + Header.ascii('grpc-status', '$customStatusCode'), + Header.ascii('grpc-message', encodedCustomStatusMessage), + ], endStream: true), + ); harness.toClient.close(); } await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), - expectedException: - GrpcError.custom(customStatusCode, customStatusMessage), + expectedException: GrpcError.custom( + customStatusCode, + customStatusMessage, + ), serverHandlers: [handleRequest], ); }); @@ -404,9 +427,12 @@ void main() { ..sendResponseTrailer(); } - harness.client = TestClient(harness.channel, decode: (bytes) { - throw 'error decoding'; - }); + harness.client = TestClient( + harness.channel, + decode: (bytes) { + throw 'error decoding'; + }, + ); await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), @@ -474,15 +500,18 @@ void main() { test('Connection states are reported', () async { final connectionStates = []; - harness.channel.onConnectionStateChanged.listen((state) { - connectionStates.add(state); - }, onDone: () { - expect(connectionStates, [ - ConnectionState.connecting, - ConnectionState.ready, - ConnectionState.shutdown - ]); - }); + harness.channel.onConnectionStateChanged.listen( + (state) { + connectionStates.add(state); + }, + onDone: () { + expect(connectionStates, [ + ConnectionState.connecting, + ConnectionState.ready, + ConnectionState.shutdown, + ]); + }, + ); await makeUnaryCall(); }); @@ -490,36 +519,49 @@ void main() { test('Connection errors are reported', () async { final connectionStates = []; harness.connection!.connectionError = 'Connection error'; - harness.channel.onConnectionStateChanged.listen((state) { - connectionStates.add(state); - }, onDone: () { - expect( - connectionStates, [ConnectionState.connecting, ConnectionState.idle]); - }); + harness.channel.onConnectionStateChanged.listen( + (state) { + connectionStates.add(state); + }, + onDone: () { + expect(connectionStates, [ + ConnectionState.connecting, + ConnectionState.idle, + ]); + }, + ); - final expectedException = - GrpcError.unavailable('Error connecting: Connection error'); + final expectedException = GrpcError.unavailable( + 'Error connecting: Connection error', + ); await harness.expectThrows( - harness.client.unary(dummyValue), expectedException); + harness.client.unary(dummyValue), + expectedException, + ); }); test('Connections time out if idle', () async { final done = Completer(); final connectionStates = []; - harness.channel.onConnectionStateChanged.listen((state) { - connectionStates.add(state); - if (state == ConnectionState.idle) done.complete(); - }, onDone: () async { - expect(connectionStates, - [ConnectionState.connecting, ConnectionState.ready]); - await done.future; - expect(connectionStates, [ - ConnectionState.connecting, - ConnectionState.ready, - ConnectionState.idle - ]); - }); + harness.channel.onConnectionStateChanged.listen( + (state) { + connectionStates.add(state); + if (state == ConnectionState.idle) done.complete(); + }, + onDone: () async { + expect(connectionStates, [ + ConnectionState.connecting, + ConnectionState.ready, + ]); + await done.future; + expect(connectionStates, [ + ConnectionState.connecting, + ConnectionState.ready, + ConnectionState.idle, + ]); + }, + ); harness.channelOptions.idleTimeout = const Duration(microseconds: 10); @@ -545,38 +587,53 @@ void main() { test('authority is computed correctly', () { final emptyOptions = ChannelOptions(); - expect(Http2ClientConnection('localhost', 8080, emptyOptions).authority, - 'localhost:8080'); - expect(Http2ClientConnection('localhost', 443, emptyOptions).authority, - 'localhost'); + expect( + Http2ClientConnection('localhost', 8080, emptyOptions).authority, + 'localhost:8080', + ); + expect( + Http2ClientConnection('localhost', 443, emptyOptions).authority, + 'localhost', + ); final channelOptions = ChannelOptions( - credentials: ChannelCredentials.insecure(authority: 'myauthority.com')); - expect(Http2ClientConnection('localhost', 8080, channelOptions).authority, - 'myauthority.com'); - expect(Http2ClientConnection('localhost', 443, channelOptions).authority, - 'myauthority.com'); + credentials: ChannelCredentials.insecure(authority: 'myauthority.com'), + ); + expect( + Http2ClientConnection('localhost', 8080, channelOptions).authority, + 'myauthority.com', + ); + expect( + Http2ClientConnection('localhost', 443, channelOptions).authority, + 'myauthority.com', + ); }); test( - 'decodeStatusDetails should decode details into a List if base64 present', - () { - final decodedDetails = decodeStatusDetails( - 'CAMSEGFtb3VudCB0b28gc21hbGwafgopdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUucnBjLkJhZFJlcXVlc3QSUQpPCgZhbW91bnQSRVRoZSByZXF1aXJlZCBjdXJyZW5jeSBjb252ZXJzaW9uIHdvdWxkIHJlc3VsdCBpbiBhIHplcm8gdmFsdWUgcGF5bWVudA'); - expect(decodedDetails, isA>()); - expect(decodedDetails.length, 1); - }); + 'decodeStatusDetails should decode details into a List if base64 present', + () { + final decodedDetails = decodeStatusDetails( + 'CAMSEGFtb3VudCB0b28gc21hbGwafgopdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUucnBjLkJhZFJlcXVlc3QSUQpPCgZhbW91bnQSRVRoZSByZXF1aXJlZCBjdXJyZW5jeSBjb252ZXJzaW9uIHdvdWxkIHJlc3VsdCBpbiBhIHplcm8gdmFsdWUgcGF5bWVudA', + ); + expect(decodedDetails, isA>()); + expect(decodedDetails.length, 1); + }, + ); test( - 'decodeStatusDetails should decode details into an empty list for an invalid base64 string', - () { - final decodedDetails = decodeStatusDetails('xxxxxxxxxxxxxxxxxxxxxx'); - expect(decodedDetails, isA>()); - expect(decodedDetails.length, 0); - }); + 'decodeStatusDetails should decode details into an empty list for an invalid base64 string', + () { + final decodedDetails = decodeStatusDetails('xxxxxxxxxxxxxxxxxxxxxx'); + expect(decodedDetails, isA>()); + expect(decodedDetails.length, 0); + }, + ); test('parseGeneratedMessage should parse out a valid Any type', () { - final status = Status.fromBuffer(base64Url.decode( - 'CAMSEGFtb3VudCB0b28gc21hbGwafgopdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUucnBjLkJhZFJlcXVlc3QSUQpPCgZhbW91bnQSRVRoZSByZXF1aXJlZCBjdXJyZW5jeSBjb252ZXJzaW9uIHdvdWxkIHJlc3VsdCBpbiBhIHplcm8gdmFsdWUgcGF5bWVudA==')); + final status = Status.fromBuffer( + base64Url.decode( + 'CAMSEGFtb3VudCB0b28gc21hbGwafgopdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUucnBjLkJhZFJlcXVlc3QSUQpPCgZhbW91bnQSRVRoZSByZXF1aXJlZCBjdXJyZW5jeSBjb252ZXJzaW9uIHdvdWxkIHJlc3VsdCBpbiBhIHplcm8gdmFsdWUgcGF5bWVudA==', + ), + ); expect(status.details, isNotEmpty); final detailItem = status.details.first; @@ -586,8 +643,10 @@ void main() { final castedResult = parsedResult as BadRequest; expect(castedResult.fieldViolations, isNotEmpty); expect(castedResult.fieldViolations.first.field_1, 'amount'); - expect(castedResult.fieldViolations.first.description, - 'The required currency conversion would result in a zero value payment'); + expect( + castedResult.fieldViolations.first.description, + 'The required currency conversion would result in a zero value payment', + ); }); test('Call should throw details embedded in the headers', () async { @@ -597,13 +656,15 @@ void main() { 'CAMSEGFtb3VudCB0b28gc21hbGwafgopdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUucnBjLkJhZFJlcXVlc3QSUQpPCgZhbW91bnQSRVRoZSByZXF1aXJlZCBjdXJyZW5jeSBjb252ZXJzaW9uIHdvdWxkIHJlc3VsdCBpbiBhIHplcm8gdmFsdWUgcGF5bWVudA'; void handleRequest(_) { - harness.toClient.add(HeadersStreamMessage([ - Header.ascii(':status', '200'), - Header.ascii('content-type', 'application/grpc'), - Header.ascii('grpc-status', code.toString()), - Header.ascii('grpc-message', message), - Header.ascii('grpc-status-details-bin', details), - ], endStream: true)); + harness.toClient.add( + HeadersStreamMessage([ + Header.ascii(':status', '200'), + Header.ascii('content-type', 'application/grpc'), + Header.ascii('grpc-status', code.toString()), + Header.ascii('grpc-message', message), + Header.ascii('grpc-status-details-bin', details), + ], endStream: true), + ); harness.toClient.close(); } @@ -625,24 +686,21 @@ void main() { final customVal = 'some custom value'; final customTrailers = {customKey: customVal}; void handleRequest(_) { - harness.toClient.add(HeadersStreamMessage([ - Header.ascii(':status', '200'), - Header.ascii('content-type', 'application/grpc'), - Header.ascii('grpc-status', code.toString()), - Header.ascii('grpc-message', message), - Header.ascii(customKey, customVal), - ], endStream: true)); + harness.toClient.add( + HeadersStreamMessage([ + Header.ascii(':status', '200'), + Header.ascii('content-type', 'application/grpc'), + Header.ascii('grpc-status', code.toString()), + Header.ascii('grpc-message', message), + Header.ascii(customKey, customVal), + ], endStream: true), + ); harness.toClient.close(); } await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), - expectedException: GrpcError.custom( - code, - message, - [], - customTrailers, - ), + expectedException: GrpcError.custom(code, message, [], customTrailers), expectedCustomTrailers: customTrailers, serverHandlers: [handleRequest], ); diff --git a/test/client_tests/client_transport_connector_test.dart b/test/client_tests/client_transport_connector_test.dart index d2fa8872..09444e6c 100644 --- a/test/client_tests/client_transport_connector_test.dart +++ b/test/client_tests/client_transport_connector_test.dart @@ -128,8 +128,9 @@ void main() { } await harness.runTest( - clientCall: - harness.client.bidirectional(Stream.fromIterable(requests)).toList(), + clientCall: harness.client + .bidirectional(Stream.fromIterable(requests)) + .toList(), expectedResult: responses, expectedPath: '/Test/Bidirectional', serverHandlers: [handleRequest, handleRequest, handleRequest], @@ -160,8 +161,9 @@ void main() { await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), - expectedException: - GrpcError.unimplemented('More than one response received'), + expectedException: GrpcError.unimplemented( + 'More than one response received', + ), serverHandlers: [handleRequest], ); }); @@ -200,8 +202,9 @@ void main() { await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), - expectedException: - GrpcError.unimplemented('Received data before headers'), + expectedException: GrpcError.unimplemented( + 'Received data before headers', + ), serverHandlers: [handleRequest], ); }); @@ -216,8 +219,9 @@ void main() { await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), - expectedException: - GrpcError.unimplemented('Received data after trailers'), + expectedException: GrpcError.unimplemented( + 'Received data after trailers', + ), serverHandlers: [handleRequest], ); }); @@ -242,19 +246,23 @@ void main() { const customStatusMessage = 'Custom message'; void handleRequest(_) { - harness.toClient.add(HeadersStreamMessage([ - Header.ascii(':status', '200'), - Header.ascii('content-type', 'application/grpc'), - Header.ascii('grpc-status', '$customStatusCode'), - Header.ascii('grpc-message', customStatusMessage) - ], endStream: true)); + harness.toClient.add( + HeadersStreamMessage([ + Header.ascii(':status', '200'), + Header.ascii('content-type', 'application/grpc'), + Header.ascii('grpc-status', '$customStatusCode'), + Header.ascii('grpc-message', customStatusMessage), + ], endStream: true), + ); harness.toClient.close(); } await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), - expectedException: - GrpcError.custom(customStatusCode, customStatusMessage), + expectedException: GrpcError.custom( + customStatusCode, + customStatusMessage, + ), serverHandlers: [handleRequest], ); }); @@ -281,9 +289,12 @@ void main() { ..sendResponseTrailer(); } - harness.client = TestClient(harness.channel, decode: (bytes) { - throw 'error decoding'; - }); + harness.client = TestClient( + harness.channel, + decode: (bytes) { + throw 'error decoding'; + }, + ); await harness.runFailureTest( clientCall: harness.client.unary(dummyValue), @@ -351,36 +362,49 @@ void main() { test('Connection errors are reported', () async { final connectionStates = []; - final expectedException = - GrpcError.unavailable('Error connecting: Connection error'); + final expectedException = GrpcError.unavailable( + 'Error connecting: Connection error', + ); harness.connection!.connectionError = 'Connection error'; - harness.channel.onConnectionStateChanged.listen((state) { - connectionStates.add(state); - }, onDone: () async { - await harness.expectThrows( - harness.client.unary(dummyValue), expectedException); - - expect( - connectionStates, [ConnectionState.connecting, ConnectionState.idle]); - }); + harness.channel.onConnectionStateChanged.listen( + (state) { + connectionStates.add(state); + }, + onDone: () async { + await harness.expectThrows( + harness.client.unary(dummyValue), + expectedException, + ); + + expect(connectionStates, [ + ConnectionState.connecting, + ConnectionState.idle, + ]); + }, + ); }); test('Connections time out if idle', () async { final done = Completer(); final connectionStates = []; - harness.channel.onConnectionStateChanged.listen((state) { - connectionStates.add(state); - if (state == ConnectionState.idle) done.complete(); - }, onDone: () async { - expect(connectionStates, - [ConnectionState.connecting, ConnectionState.ready]); - await done.future; - expect(connectionStates, [ - ConnectionState.connecting, - ConnectionState.ready, - ConnectionState.idle - ]); - }); + harness.channel.onConnectionStateChanged.listen( + (state) { + connectionStates.add(state); + if (state == ConnectionState.idle) done.complete(); + }, + onDone: () async { + expect(connectionStates, [ + ConnectionState.connecting, + ConnectionState.ready, + ]); + await done.future; + expect(connectionStates, [ + ConnectionState.connecting, + ConnectionState.ready, + ConnectionState.idle, + ]); + }, + ); harness.channelOptions.idleTimeout = const Duration(microseconds: 10); @@ -406,15 +430,24 @@ void main() { test('authority is computed correctly', () { final emptyOptions = ChannelOptions(); - expect(Http2ClientConnection('localhost', 8080, emptyOptions).authority, - 'localhost:8080'); - expect(Http2ClientConnection('localhost', 443, emptyOptions).authority, - 'localhost'); + expect( + Http2ClientConnection('localhost', 8080, emptyOptions).authority, + 'localhost:8080', + ); + expect( + Http2ClientConnection('localhost', 443, emptyOptions).authority, + 'localhost', + ); final channelOptions = ChannelOptions( - credentials: ChannelCredentials.insecure(authority: 'myauthority.com')); - expect(Http2ClientConnection('localhost', 8080, channelOptions).authority, - 'myauthority.com'); - expect(Http2ClientConnection('localhost', 443, channelOptions).authority, - 'myauthority.com'); + credentials: ChannelCredentials.insecure(authority: 'myauthority.com'), + ); + expect( + Http2ClientConnection('localhost', 8080, channelOptions).authority, + 'myauthority.com', + ); + expect( + Http2ClientConnection('localhost', 443, channelOptions).authority, + 'myauthority.com', + ); }); } diff --git a/test/client_tests/client_xhr_transport_test.dart b/test/client_tests/client_xhr_transport_test.dart index 84c805ec..98eea0a4 100644 --- a/test/client_tests/client_xhr_transport_test.dart +++ b/test/client_tests/client_xhr_transport_test.dart @@ -29,8 +29,10 @@ import 'package:stream_transform/stream_transform.dart'; import 'package:test/test.dart'; import 'package:web/web.dart'; -final readyStateChangeEvent = - Event('readystatechange', EventInit(bubbles: false, cancelable: false)); +final readyStateChangeEvent = Event( + 'readystatechange', + EventInit(bubbles: false, cancelable: false), +); final progressEvent = ProgressEvent('onloadstart'); class MockHttpRequest extends Mock implements IXMLHttpRequest { @@ -63,15 +65,16 @@ class MockHttpRequest extends Mock implements IXMLHttpRequest { super.noSuchMethod(Invocation.getter(#readyState), returnValue: -1); @override - Map get responseHeaders => - super.noSuchMethod(Invocation.getter(#responseHeaders), - returnValue: {}); + Map get responseHeaders => super.noSuchMethod( + Invocation.getter(#responseHeaders), + returnValue: {}, + ); } class MockXhrClientConnection extends XhrClientConnection { MockXhrClientConnection({int? code}) - : _statusCode = code ?? 200, - super(Uri.parse('test:0')); + : _statusCode = code ?? 200, + super(Uri.parse('test:0')); late MockHttpRequest latestRequest; final int _statusCode; @@ -88,54 +91,84 @@ void main() { test('Make request sends correct headers', () async { final metadata = { 'parameter_1': 'value_1', - 'parameter_2': 'value_2' + 'parameter_2': 'value_2', }; final connection = MockXhrClientConnection(); - connection.makeRequest('path', Duration(seconds: 10), metadata, - (error, _) => fail(error.toString())); + connection.makeRequest( + 'path', + Duration(seconds: 10), + metadata, + (error, _) => fail(error.toString()), + ); - verify(connection.latestRequest - .setRequestHeader('Content-Type', 'application/grpc-web+proto')); - verify(connection.latestRequest - .setRequestHeader('X-User-Agent', 'grpc-web-dart/0.1')); + verify( + connection.latestRequest.setRequestHeader( + 'Content-Type', + 'application/grpc-web+proto', + ), + ); + verify( + connection.latestRequest.setRequestHeader( + 'X-User-Agent', + 'grpc-web-dart/0.1', + ), + ); verify(connection.latestRequest.setRequestHeader('X-Grpc-Web', '1')); - verify(connection.latestRequest - .overrideMimeType('text/plain; charset=x-user-defined')); + verify( + connection.latestRequest.overrideMimeType( + 'text/plain; charset=x-user-defined', + ), + ); verify(connection.latestRequest.responseType = 'text'); }); test( - 'Make request sends correct headers and path if bypassCorsPreflight=true', - () async { - final metadata = {'header_1': 'value_1', 'header_2': 'value_2'}; - final connection = MockXhrClientConnection(); - - connection.makeRequest('path', Duration(seconds: 10), metadata, + 'Make request sends correct headers and path if bypassCorsPreflight=true', + () async { + final metadata = {'header_1': 'value_1', 'header_2': 'value_2'}; + final connection = MockXhrClientConnection(); + + connection.makeRequest( + 'path', + Duration(seconds: 10), + metadata, (error, _) => fail(error.toString()), - callOptions: WebCallOptions(bypassCorsPreflight: true)); - - expect(metadata, isEmpty); - verify(connection.latestRequest.open('POST', - 'test:path?%24httpHeaders=header_1%3Avalue_1%0D%0Aheader_2%3Avalue_2%0D%0AContent-Type%3Aapplication%2Fgrpc-web%2Bproto%0D%0AX-User-Agent%3Agrpc-web-dart%2F0.1%0D%0AX-Grpc-Web%3A1%0D%0A')); - verify(connection.latestRequest - .overrideMimeType('text/plain; charset=x-user-defined')); - verify(connection.latestRequest.responseType = 'text'); - }); - - test( - 'Make request sends correct headers if call options already have ' + callOptions: WebCallOptions(bypassCorsPreflight: true), + ); + + expect(metadata, isEmpty); + verify( + connection.latestRequest.open( + 'POST', + 'test:path?%24httpHeaders=header_1%3Avalue_1%0D%0Aheader_2%3Avalue_2%0D%0AContent-Type%3Aapplication%2Fgrpc-web%2Bproto%0D%0AX-User-Agent%3Agrpc-web-dart%2F0.1%0D%0AX-Grpc-Web%3A1%0D%0A', + ), + ); + verify( + connection.latestRequest.overrideMimeType( + 'text/plain; charset=x-user-defined', + ), + ); + verify(connection.latestRequest.responseType = 'text'); + }, + ); + + test('Make request sends correct headers if call options already have ' 'Content-Type header', () async { final metadata = { 'header_1': 'value_1', 'header_2': 'value_2', - 'Content-Type': 'application/json+protobuf' + 'Content-Type': 'application/json+protobuf', }; final connection = MockXhrClientConnection(); - connection.makeRequest('/path', Duration(seconds: 10), metadata, - (error, _) => fail(error.toString())); + connection.makeRequest( + '/path', + Duration(seconds: 10), + metadata, + (error, _) => fail(error.toString()), + ); expect(metadata, { 'header_1': 'value_1', @@ -147,12 +180,16 @@ void main() { test('Content-Type header case insensitivity', () async { final metadata = { 'header_1': 'value_1', - 'CONTENT-TYPE': 'application/json+protobuf' + 'CONTENT-TYPE': 'application/json+protobuf', }; final connection = MockXhrClientConnection(); - connection.makeRequest('/path', Duration(seconds: 10), metadata, - (error, _) => fail(error.toString())); + connection.makeRequest( + '/path', + Duration(seconds: 10), + metadata, + (error, _) => fail(error.toString()), + ); expect(metadata, { 'header_1': 'value_1', 'CONTENT-TYPE': 'application/json+protobuf', @@ -160,54 +197,79 @@ void main() { final lowerMetadata = { 'header_1': 'value_1', - 'content-type': 'application/json+protobuf' + 'content-type': 'application/json+protobuf', }; - connection.makeRequest('/path', Duration(seconds: 10), lowerMetadata, - (error, _) => fail(error.toString())); + connection.makeRequest( + '/path', + Duration(seconds: 10), + lowerMetadata, + (error, _) => fail(error.toString()), + ); expect(lowerMetadata, { 'header_1': 'value_1', 'content-type': 'application/json+protobuf', }); }); - test('Make request sends correct headers path if only withCredentials=true', - () async { - final metadata = {'header_1': 'value_1', 'header_2': 'value_2'}; - final connection = MockXhrClientConnection(); - - connection.makeRequest('path', Duration(seconds: 10), metadata, + test( + 'Make request sends correct headers path if only withCredentials=true', + () async { + final metadata = {'header_1': 'value_1', 'header_2': 'value_2'}; + final connection = MockXhrClientConnection(); + + connection.makeRequest( + 'path', + Duration(seconds: 10), + metadata, (error, _) => fail(error.toString()), - callOptions: WebCallOptions(withCredentials: true)); - - expect(metadata, { - 'header_1': 'value_1', - 'header_2': 'value_2', - 'Content-Type': 'application/grpc-web+proto', - 'X-User-Agent': 'grpc-web-dart/0.1', - 'X-Grpc-Web': '1' - }); - verify(connection.latestRequest - .setRequestHeader('Content-Type', 'application/grpc-web+proto')); - verify(connection.latestRequest - .setRequestHeader('X-User-Agent', 'grpc-web-dart/0.1')); - verify(connection.latestRequest.setRequestHeader('X-Grpc-Web', '1')); - verify(connection.latestRequest.open('POST', 'test:path')); - verify(connection.latestRequest.withCredentials = true); - verify(connection.latestRequest - .overrideMimeType('text/plain; charset=x-user-defined')); - verify(connection.latestRequest.responseType = 'text'); - }); + callOptions: WebCallOptions(withCredentials: true), + ); + + expect(metadata, { + 'header_1': 'value_1', + 'header_2': 'value_2', + 'Content-Type': 'application/grpc-web+proto', + 'X-User-Agent': 'grpc-web-dart/0.1', + 'X-Grpc-Web': '1', + }); + verify( + connection.latestRequest.setRequestHeader( + 'Content-Type', + 'application/grpc-web+proto', + ), + ); + verify( + connection.latestRequest.setRequestHeader( + 'X-User-Agent', + 'grpc-web-dart/0.1', + ), + ); + verify(connection.latestRequest.setRequestHeader('X-Grpc-Web', '1')); + verify(connection.latestRequest.open('POST', 'test:path')); + verify(connection.latestRequest.withCredentials = true); + verify( + connection.latestRequest.overrideMimeType( + 'text/plain; charset=x-user-defined', + ), + ); + verify(connection.latestRequest.responseType = 'text'); + }, + ); test('Sent data converted to stream properly', () async { final metadata = { 'parameter_1': 'value_1', - 'parameter_2': 'value_2' + 'parameter_2': 'value_2', }; final connection = MockXhrClientConnection(); - final stream = connection.makeRequest('path', Duration(seconds: 10), - metadata, (error, _) => fail(error.toString())); + final stream = connection.makeRequest( + 'path', + Duration(seconds: 10), + metadata, + (error, _) => fail(error.toString()), + ); final data = List.filled(10, 0); stream.outgoingMessages.add(data); @@ -215,7 +277,8 @@ void main() { final expectedData = frame(data); verify( - connection.latestRequest.send(Uint8List.fromList(expectedData).toJS)); + connection.latestRequest.send(Uint8List.fromList(expectedData).toJS), + ); }); test('Stream handles headers properly', () async { @@ -227,21 +290,28 @@ void main() { final transport = MockXhrClientConnection(); - final stream = transport.makeRequest('test_path', Duration(seconds: 10), {}, - (error, _) => fail(error.toString())); + final stream = transport.makeRequest( + 'test_path', + Duration(seconds: 10), + {}, + (error, _) => fail(error.toString()), + ); when(transport.latestRequest.responseHeaders).thenReturn(responseHeaders); - when(transport.latestRequest.responseText) - .thenReturn(String.fromCharCodes(frame([]))); + when( + transport.latestRequest.responseText, + ).thenReturn(String.fromCharCodes(frame([]))); // Set expectation for request readyState and generate two readyStateChange // events, so that incomingMessages stream completes. final readyStates = [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]; when(transport.latestRequest.readyState).thenReturnInOrder(readyStates); - transport.latestRequest.readyStateChangeController - .add(readyStateChangeEvent); - transport.latestRequest.readyStateChangeController - .add(readyStateChangeEvent); + transport.latestRequest.readyStateChangeController.add( + readyStateChangeEvent, + ); + transport.latestRequest.readyStateChangeController.add( + readyStateChangeEvent, + ); // Should be only one metadata message with headers augmented with :status // field. @@ -261,13 +331,19 @@ void main() { final connection = MockXhrClientConnection(); - final stream = connection.makeRequest('test_path', Duration(seconds: 10), - requestHeaders, (error, _) => fail(error.toString())); - - final encodedTrailers = frame(responseTrailers.entries - .map((e) => '${e.key}:${e.value}') - .join('\r\n') - .codeUnits); + final stream = connection.makeRequest( + 'test_path', + Duration(seconds: 10), + requestHeaders, + (error, _) => fail(error.toString()), + ); + + final encodedTrailers = frame( + responseTrailers.entries + .map((e) => '${e.key}:${e.value}') + .join('\r\n') + .codeUnits, + ); encodedTrailers[0] = 0x80; // Mark this frame as trailers. final encodedString = String.fromCharCodes(encodedTrailers); @@ -276,31 +352,37 @@ void main() { // Set expectation for request readyState and generate events so that // incomingMessages stream completes. - when(connection.latestRequest.readyState).thenReturnInOrder( - [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]); - connection.latestRequest.readyStateChangeController - .add(readyStateChangeEvent); + when( + connection.latestRequest.readyState, + ).thenReturnInOrder([XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]); + connection.latestRequest.readyStateChangeController.add( + readyStateChangeEvent, + ); connection.latestRequest.progressController.add(progressEvent); - connection.latestRequest.readyStateChangeController - .add(readyStateChangeEvent); + connection.latestRequest.readyStateChangeController.add( + readyStateChangeEvent, + ); // Should be two metadata messages: headers and trailers. - final messages = - await stream.incomingMessages.whereType().toList(); + final messages = await stream.incomingMessages + .whereType() + .toList(); expect(messages.length, 2); expect(messages.first.metadata, requestHeaders); expect(messages.last.metadata, responseTrailers); }); test('Stream handles empty trailers properly', () async { - final requestHeaders = { - 'content-type': 'application/grpc+proto', - }; + final requestHeaders = {'content-type': 'application/grpc+proto'}; final connection = MockXhrClientConnection(); - final stream = connection.makeRequest('test_path', Duration(seconds: 10), - {}, (error, _) => fail(error.toString())); + final stream = connection.makeRequest( + 'test_path', + Duration(seconds: 10), + {}, + (error, _) => fail(error.toString()), + ); final encoded = frame(''.codeUnits); encoded[0] = 0x80; // Mark this frame as trailers. @@ -310,17 +392,21 @@ void main() { when(connection.latestRequest.responseText).thenReturn(encodedString); // Set expectation for request readyState and generate events so that // incomingMessages stream completes. - when(connection.latestRequest.readyState).thenReturnInOrder( - [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]); - connection.latestRequest.readyStateChangeController - .add(readyStateChangeEvent); + when( + connection.latestRequest.readyState, + ).thenReturnInOrder([XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]); + connection.latestRequest.readyStateChangeController.add( + readyStateChangeEvent, + ); connection.latestRequest.progressController.add(progressEvent); - connection.latestRequest.readyStateChangeController - .add(readyStateChangeEvent); + connection.latestRequest.readyStateChangeController.add( + readyStateChangeEvent, + ); // Should be two metadata messages: headers and trailers. - final messages = - await stream.incomingMessages.whereType().toList(); + final messages = await stream.incomingMessages + .whereType() + .toList(); expect(messages.length, 2); expect(messages.first.metadata, requestHeaders); expect(messages.last.metadata, isEmpty); @@ -335,22 +421,30 @@ void main() { final connection = MockXhrClientConnection(); - final stream = connection.makeRequest('test_path', Duration(seconds: 10), - requestHeaders, (error, _) => fail(error.toString())); + final stream = connection.makeRequest( + 'test_path', + Duration(seconds: 10), + requestHeaders, + (error, _) => fail(error.toString()), + ); final data = List.filled(10, 224); when(connection.latestRequest.responseHeaders).thenReturn(requestHeaders); - when(connection.latestRequest.responseText) - .thenReturn(String.fromCharCodes(frame(data))); + when( + connection.latestRequest.responseText, + ).thenReturn(String.fromCharCodes(frame(data))); // Set expectation for request readyState and generate events, so that // incomingMessages stream completes. - when(connection.latestRequest.readyState).thenReturnInOrder( - [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]); - connection.latestRequest.readyStateChangeController - .add(readyStateChangeEvent); + when( + connection.latestRequest.readyState, + ).thenReturnInOrder([XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]); + connection.latestRequest.readyStateChangeController.add( + readyStateChangeEvent, + ); connection.latestRequest.progressController.add(progressEvent); - connection.latestRequest.readyStateChangeController - .add(readyStateChangeEvent); + connection.latestRequest.readyStateChangeController.add( + readyStateChangeEvent, + ); // Expect a single data message. final message = await stream.incomingMessages.whereType().single; @@ -368,12 +462,14 @@ void main() { errors.add(e as GrpcError); }); const errorDetails = 'error details'; - when(connection.latestRequest.responseHeaders) - .thenReturn({'content-type': 'application/grpc+proto'}); + when( + connection.latestRequest.responseHeaders, + ).thenReturn({'content-type': 'application/grpc+proto'}); when(connection.latestRequest.readyState).thenReturn(XMLHttpRequest.DONE); when(connection.latestRequest.responseText).thenReturn(errorDetails); - connection.latestRequest.readyStateChangeController - .add(readyStateChangeEvent); + connection.latestRequest.readyStateChangeController.add( + readyStateChangeEvent, + ); await errorReceived.future; expect(errors.single.rawResponse, errorDetails); }); @@ -387,19 +483,25 @@ void main() { final connection = MockXhrClientConnection(); - final stream = connection.makeRequest('test_path', Duration(seconds: 10), - metadata, (error, _) => fail(error.toString())); + final stream = connection.makeRequest( + 'test_path', + Duration(seconds: 10), + metadata, + (error, _) => fail(error.toString()), + ); final data = >[ List.filled(10, 224), - List.filled(5, 124) + List.filled(5, 124), ]; - final encodedStrings = - data.map((d) => String.fromCharCodes(frame(d))).toList(); + final encodedStrings = data + .map((d) => String.fromCharCodes(frame(d))) + .toList(); when(connection.latestRequest.responseHeaders).thenReturn(metadata); - when(connection.latestRequest.readyState) - .thenReturn(XMLHttpRequest.HEADERS_RECEIVED); + when( + connection.latestRequest.readyState, + ).thenReturn(XMLHttpRequest.HEADERS_RECEIVED); // At first invocation the response should be the the first message, after // that first + last messages. @@ -413,13 +515,15 @@ void main() { }); final readyStates = [XMLHttpRequest.HEADERS_RECEIVED, XMLHttpRequest.DONE]; - when(connection.latestRequest.readyState) - .thenAnswer((_) => readyStates.removeAt(0)); + when( + connection.latestRequest.readyState, + ).thenAnswer((_) => readyStates.removeAt(0)); final queue = StreamQueue(stream.incomingMessages); // Headers. - connection.latestRequest.readyStateChangeController - .add(readyStateChangeEvent); + connection.latestRequest.readyStateChangeController.add( + readyStateChangeEvent, + ); expect(((await queue.next) as GrpcMetadata).metadata, metadata); // Data 1. connection.latestRequest.progressController.add(progressEvent); @@ -428,8 +532,9 @@ void main() { connection.latestRequest.progressController.add(progressEvent); expect(((await queue.next) as GrpcData).data, data[1]); // Done. - connection.latestRequest.readyStateChangeController - .add(readyStateChangeEvent); + connection.latestRequest.readyStateChangeController.add( + readyStateChangeEvent, + ); expect(await queue.hasNext, isFalse); }); } diff --git a/test/client_tests/grpc_or_grpcweb_channel_web_test.dart b/test/client_tests/grpc_or_grpcweb_channel_web_test.dart index 8382219e..32da65c1 100644 --- a/test/client_tests/grpc_or_grpcweb_channel_web_test.dart +++ b/test/client_tests/grpc_or_grpcweb_channel_web_test.dart @@ -32,11 +32,15 @@ void main() { expect(channel is GrpcWebClientChannel, isTrue); final webChannel = channel as GrpcWebClientChannel; expect( - webChannel.uri, equals(Uri(host: host, port: port, scheme: 'https'))); + webChannel.uri, + equals(Uri(host: host, port: port, scheme: 'https')), + ); }); test('Constructor grpc on web throws UnsupportedError', () { - expect(() => GrpcOrGrpcWebClientChannel.grpc(host, port: port), - throwsUnsupportedError); + expect( + () => GrpcOrGrpcWebClientChannel.grpc(host, port: port), + throwsUnsupportedError, + ); }); } diff --git a/test/common.dart b/test/common.dart index c685761d..a51d4ea6 100644 --- a/test/common.dart +++ b/test/common.dart @@ -24,8 +24,10 @@ void testUds(String name, FutureOr Function(InternetAddress) testCase) { test(name, () async { final tempDir = await Directory.systemTemp.createTemp(); - final address = InternetAddress('${tempDir.path}/socket', - type: InternetAddressType.unix); + final address = InternetAddress( + '${tempDir.path}/socket', + type: InternetAddressType.unix, + ); addTearDown(() => tempDir.delete(recursive: true)); await testCase(address); }); @@ -33,8 +35,10 @@ void testUds(String name, FutureOr Function(InternetAddress) testCase) { /// Test functionality for both TCP and Unix domain sockets. void testTcpAndUds( - String name, FutureOr Function(InternetAddress) testCase, - {String host = 'localhost'}) { + String name, + FutureOr Function(InternetAddress) testCase, { + String host = 'localhost', +}) { test(name, () async { final address = await InternetAddress.lookup(host); await testCase(address.first); diff --git a/test/connection_server_test.dart b/test/connection_server_test.dart index d4357736..79182589 100644 --- a/test/connection_server_test.dart +++ b/test/connection_server_test.dart @@ -112,7 +112,9 @@ void main() { test('Server returns error on unimplemented path', () async { harness ..expectErrorResponse( - StatusCode.unimplemented, 'Path /Test/NotFound not found') + StatusCode.unimplemented, + 'Path /Test/NotFound not found', + ) ..sendRequestHeader('/Test/NotFound'); await harness.fromServer.done; }); @@ -120,7 +122,8 @@ void main() { /// Returns a service method handler that verifies that awaiting the request /// throws a specific error. Future Function(ServiceCall call, Future request) expectError( - expectedError) { + expectedError, + ) { return expectAsync2((ServiceCall call, Future request) async { try { final result = await request; @@ -140,7 +143,7 @@ void main() { /// Returns a service method handler that verifies that awaiting the request /// stream throws a specific error. Stream Function(ServiceCall call, Stream request) - expectErrorStreaming(expectedError) { + expectErrorStreaming(expectedError) { return (ServiceCall call, Stream request) async* { try { await for (var entry in request) { @@ -160,30 +163,35 @@ void main() { test('Server returns error on missing request for unary call', () async { harness - ..service.unaryHandler = - expectError(GrpcError.unimplemented('No request received')) + ..service.unaryHandler = expectError( + GrpcError.unimplemented('No request received'), + ) ..expectErrorResponse(StatusCode.unimplemented, 'No request received') ..sendRequestHeader('/Test/Unary') ..toServer.close(); await harness.fromServer.done; }); - test('Server returns error if multiple headers are received for unary call', - () async { - harness - ..service.unaryHandler = - expectError(GrpcError.unimplemented('Expected request')) - ..expectErrorResponse(StatusCode.unimplemented, 'Expected request') - ..sendRequestHeader('/Test/Unary') - ..toServer.add(HeadersStreamMessage([])) - ..toServer.close(); - await harness.fromServer.done; - }); + test( + 'Server returns error if multiple headers are received for unary call', + () async { + harness + ..service.unaryHandler = expectError( + GrpcError.unimplemented('Expected request'), + ) + ..expectErrorResponse(StatusCode.unimplemented, 'Expected request') + ..sendRequestHeader('/Test/Unary') + ..toServer.add(HeadersStreamMessage([])) + ..toServer.close(); + await harness.fromServer.done; + }, + ); test('Server returns error on too many requests for unary call', () async { harness - ..service.unaryHandler = - expectError(GrpcError.unimplemented('Too many requests')) + ..service.unaryHandler = expectError( + GrpcError.unimplemented('Too many requests'), + ) ..expectErrorResponse(StatusCode.unimplemented, 'Too many requests') ..sendRequestHeader('/Test/Unary') ..sendData(dummyValue) @@ -195,9 +203,12 @@ void main() { test('Server returns request deserialization errors', () async { harness ..service.bidirectionalHandler = expectErrorStreaming( - GrpcError.internal('Error deserializing request: Failed')) + GrpcError.internal('Error deserializing request: Failed'), + ) ..expectErrorResponse( - StatusCode.internal, 'Error deserializing request: Failed') + StatusCode.internal, + 'Error deserializing request: Failed', + ) ..sendRequestHeader('/Test/RequestError') ..sendData(dummyValue) ..toServer.close(); @@ -207,9 +218,12 @@ void main() { test('Server returns response serialization errors', () async { harness ..service.bidirectionalHandler = expectErrorStreaming( - GrpcError.internal('Error sending response: Failed')) + GrpcError.internal('Error sending response: Failed'), + ) ..expectErrorResponse( - StatusCode.internal, 'Error sending response: Failed') + StatusCode.internal, + 'Error sending response: Failed', + ) ..sendRequestHeader('/Test/ResponseError') ..sendData(dummyValue) ..sendData(dummyValue) @@ -257,11 +271,13 @@ void main() { harness ..service.unaryHandler = methodHandler - ..fromServer.stream.listen(expectAsync1((_) {}, count: 0), - onError: expectAsync1((dynamic error) { - expect(error, 'TERMINATED'); - }, count: 1), - onDone: expectAsync0(() {}, count: 1)) + ..fromServer.stream.listen( + expectAsync1((_) {}, count: 0), + onError: expectAsync1((dynamic error) { + expect(error, 'TERMINATED'); + }, count: 1), + onDone: expectAsync0(() {}, count: 1), + ) ..sendRequestHeader('/Test/Unary') ..toServer.addError('CANCEL'); @@ -271,14 +287,17 @@ void main() { }); test( - 'Server returns error if request stream is closed before sending anything', - () async { - harness - ..expectErrorResponse( - StatusCode.unavailable, 'Request stream closed unexpectedly') - ..toServer.close(); - await harness.fromServer.done; - }); + 'Server returns error if request stream is closed before sending anything', + () async { + harness + ..expectErrorResponse( + StatusCode.unavailable, + 'Request stream closed unexpectedly', + ) + ..toServer.close(); + await harness.fromServer.done; + }, + ); group('Server with interceptor', () { group('processes calls if interceptor allows request', () { @@ -306,8 +325,10 @@ void main() { } test('with sync interceptor', () => doTest(interceptor)); - test('with async interceptor', - () => doTest((call, method) async => interceptor(call, method))); + test( + 'with async interceptor', + () => doTest((call, method) async => interceptor(call, method)), + ); }); group('returns error if interceptor blocks request', () { @@ -322,15 +343,19 @@ void main() { harness ..interceptor.handler = handler ..expectErrorResponse( - StatusCode.unauthenticated, 'Request is unauthenticated') + StatusCode.unauthenticated, + 'Request is unauthenticated', + ) ..sendRequestHeader('/Test/Unary'); await harness.fromServer.done; } test('with sync interceptor', () => doTest(interceptor)); - test('with async interceptor', - () => doTest((call, method) async => interceptor(call, method))); + test( + 'with async interceptor', + () => doTest((call, method) async => interceptor(call, method)), + ); }); group('returns internal error if interceptor throws exception', () { @@ -342,15 +367,19 @@ void main() { harness ..interceptor.handler = handler ..expectErrorResponse( - StatusCode.internal, 'Exception: Reason is unknown') + StatusCode.internal, + 'Exception: Reason is unknown', + ) ..sendRequestHeader('/Test/Unary'); await harness.fromServer.done; } test('with sync interceptor', () => doTest(interceptor)); - test('with async interceptor', - () => doTest((call, method) async => interceptor(call, method))); + test( + 'with async interceptor', + () => doTest((call, method) async => interceptor(call, method)), + ); }); test("don't fail if interceptor await 2 times", () async { @@ -363,7 +392,9 @@ void main() { harness ..interceptor.handler = interceptor ..expectErrorResponse( - StatusCode.internal, 'Exception: Reason is unknown') + StatusCode.internal, + 'Exception: Reason is unknown', + ) ..sendRequestHeader('/Test/Unary') ..sendData(1); diff --git a/test/grpc_web_decoding_test.dart b/test/grpc_web_decoding_test.dart index 96d985aa..68f96e5c 100644 --- a/test/grpc_web_decoding_test.dart +++ b/test/grpc_web_decoding_test.dart @@ -22,11 +22,15 @@ import 'package:test/test.dart'; void main() { test('decoding an empty repeated', () async { - final data = await GrpcWebDecoder() - .bind(Stream.fromIterable([ - Uint8List.fromList([0, 0, 0, 0, 0]).buffer - ])) - .first as GrpcData; + final data = + await GrpcWebDecoder() + .bind( + Stream.fromIterable([ + Uint8List.fromList([0, 0, 0, 0, 0]).buffer, + ]), + ) + .first + as GrpcData; expect(data.data, []); }); } diff --git a/test/grpc_web_server.dart b/test/grpc_web_server.dart index a1098eb0..0e930c81 100644 --- a/test/grpc_web_server.dart +++ b/test/grpc_web_server.dart @@ -36,7 +36,9 @@ class EchoService extends EchoServiceBase { @override Stream serverStreamingEcho( - ServiceCall call, ServerStreamingEchoRequest request) async* { + ServiceCall call, + ServerStreamingEchoRequest request, + ) async* { for (var i = 0; i < request.messageCount; i++) { yield ServerStreamingEchoResponse()..message = request.message; if (i < request.messageCount - 1) { @@ -132,7 +134,8 @@ Future hybridMain(StreamChannel channel) async { final tempDir = await Directory.systemTemp.createTemp(); final config = p.join(tempDir.path, 'config.yaml'); await File(config).writeAsString( - envoyConfig.replaceAll('%TARGET_PORT%', server.port.toString())); + envoyConfig.replaceAll('%TARGET_PORT%', server.port.toString()), + ); // Spawn a proxy that would translate gRPC-web protocol into gRPC protocol // for us. We use envoy proxy. See CONTRIBUTING.md for setup. @@ -152,24 +155,25 @@ if you are running tests locally. // Parse output of the proxy process looking for a port it selected. final portRe = RegExp( - r'Set listener listener_0 socket factory local address to 0.0.0.0:(\d+)'); + r'Set listener listener_0 socket factory local address to 0.0.0.0:(\d+)', + ); - proxy.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen((line) { + proxy.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen(( + line, + ) { _info('envoy|stderr] $line'); final m = portRe.firstMatch(line); if (m != null) { - channel.sink - .add({'grpcPort': int.parse(m[1]!), 'httpPort': httpServer.port}); + channel.sink.add({ + 'grpcPort': int.parse(m[1]!), + 'httpPort': httpServer.port, + }); } }); - proxy.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen((line) { + proxy.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen(( + line, + ) { _info('envoy|stdout] $line'); }); @@ -228,9 +232,11 @@ Future startHttpServer() async { server.defaultResponseHeaders.add('Access-Control-Allow-Origin', '*'); server.listen((request) async { _info('${request.method} ${request.requestedUri} ${request.headers}'); - final message = await GrpcHttpDecoder() - .bind(request.map((list) => DataStreamMessage(list))) - .first as GrpcData; + final message = + await GrpcHttpDecoder() + .bind(request.map((list) => DataStreamMessage(list))) + .first + as GrpcData; final echoRequest = EchoRequest.fromBuffer(message.data); (testCases[echoRequest.message] ?? defaultHandler)(request.response); }); diff --git a/test/grpc_web_test.dart b/test/grpc_web_test.dart index dd5fccb6..72963031 100644 --- a/test/grpc_web_test.dart +++ b/test/grpc_web_test.dart @@ -55,16 +55,19 @@ void main() { // in one go). final sw = Stopwatch()..start(); final timings = await service - .serverStreamingEcho(ServerStreamingEchoRequest() - ..message = testMessage - ..messageCount = 20 - ..messageInterval = 100) + .serverStreamingEcho( + ServerStreamingEchoRequest() + ..message = testMessage + ..messageCount = 20 + ..messageInterval = 100, + ) .map((response) { - expect(response.message, equals(testMessage)); - final timing = sw.elapsedMilliseconds; - sw.reset(); - return timing; - }).toList(); + expect(response.message, equals(testMessage)); + final timing = sw.elapsedMilliseconds; + sw.reset(); + return timing; + }) + .toList(); final maxDelay = timings.reduce(math.max); expect(maxDelay, lessThan(500)); }); @@ -84,26 +87,36 @@ void main() { var terminated = false; service - .serverStreamingEcho(ServerStreamingEchoRequest() - ..message = testMessage - ..messageCount = 20 - ..messageInterval = 100) - .listen((response) { - expect(response.message, equals(testMessage)); - }, onError: (e) { - expect(terminated, isTrue); - }); + .serverStreamingEcho( + ServerStreamingEchoRequest() + ..message = testMessage + ..messageCount = 20 + ..messageInterval = 100, + ) + .listen( + (response) { + expect(response.message, equals(testMessage)); + }, + onError: (e) { + expect(terminated, isTrue); + }, + ); service - .serverStreamingEcho(ServerStreamingEchoRequest() - ..message = testMessage - ..messageCount = 20 - ..messageInterval = 100) - .listen((response) { - expect(response.message, equals(testMessage)); - }, onError: (e) { - expect(terminated, isTrue); - }); + .serverStreamingEcho( + ServerStreamingEchoRequest() + ..message = testMessage + ..messageCount = 20 + ..messageInterval = 100, + ) + .listen( + (response) { + expect(response.message, equals(testMessage)); + }, + onError: (e) { + expect(terminated, isTrue); + }, + ); await Future.delayed(Duration(milliseconds: 500)); terminated = true; @@ -118,13 +131,15 @@ void main() { const testMessage = 'hello from gRPC-web'; final stream = service - .serverStreamingEcho(ServerStreamingEchoRequest() - ..message = testMessage - ..messageCount = 20 - ..messageInterval = 100) + .serverStreamingEcho( + ServerStreamingEchoRequest() + ..message = testMessage + ..messageCount = 20 + ..messageInterval = 100, + ) .listen((response) { - expect(response.message, equals(testMessage)); - }); + expect(response.message, equals(testMessage)); + }); await Future.delayed(Duration(milliseconds: 500)); await stream.cancel(); @@ -134,11 +149,14 @@ void main() { final invalidResponseTests = { 'cors': GrpcError.unknown( - 'HTTP request completed without a status (potential CORS issue)'), + 'HTTP request completed without a status (potential CORS issue)', + ), 'status-503': GrpcError.unavailable( - 'HTTP connection completed with 503 instead of 200'), - 'bad-content-type': - GrpcError.unknown('unsupported content-type (text/html)'), + 'HTTP connection completed with 503 instead of 200', + ), + 'bad-content-type': GrpcError.unknown( + 'unsupported content-type (text/html)', + ), }; for (var entry in invalidResponseTests.entries) { @@ -150,10 +168,14 @@ void main() { // See [startHttpServer] in [grpc_web_server.dart] for the server part. test('invalid response: ${entry.key}', () async { final channel = GrpcWebClientChannel.xhr(server.httpUri); - final service = EchoServiceClient(channel, - options: WebCallOptions(bypassCorsPreflight: true)); - expect(() => service.echo(EchoRequest()..message = 'test:${entry.key}'), - throwsA(entry.value)); + final service = EchoServiceClient( + channel, + options: WebCallOptions(bypassCorsPreflight: true), + ); + expect( + () => service.echo(EchoRequest()..message = 'test:${entry.key}'), + throwsA(entry.value), + ); }); } } @@ -181,23 +203,28 @@ class GrpcWebServer { static Future start() async { // Spawn the server code on the server side, it will send us back port // number we should be talking to. - final serverChannel = - spawnHybridUri('grpc_web_server.dart', stayAlive: true); + final serverChannel = spawnHybridUri( + 'grpc_web_server.dart', + stayAlive: true, + ); final portCompleter = Completer(); final exitCompleter = Completer(); - serverChannel.stream.listen((event) { - if (!portCompleter.isCompleted) { - portCompleter.complete(event); - } else if (event == 'EXITED') { - exitCompleter.complete(); - } - }, onError: (e) { - if (!portCompleter.isCompleted) { - portCompleter.completeError(e); - } else if (!exitCompleter.isCompleted) { - exitCompleter.completeError(e); - } - }); + serverChannel.stream.listen( + (event) { + if (!portCompleter.isCompleted) { + portCompleter.complete(event); + } else if (event == 'EXITED') { + exitCompleter.complete(); + } + }, + onError: (e) { + if (!portCompleter.isCompleted) { + portCompleter.completeError(e); + } else if (!exitCompleter.isCompleted) { + exitCompleter.completeError(e); + } + }, + ); final ports = await portCompleter.future; @@ -208,9 +235,10 @@ class GrpcWebServer { // because browsers like chrome don't trust self-signed certificates by // default. return GrpcWebServer( - serverChannel, - exitCompleter.future, - Uri.parse('http://localhost:$grpcPort'), - Uri.parse('http://localhost:$httpPort')); + serverChannel, + exitCompleter.future, + Uri.parse('http://localhost:$grpcPort'), + Uri.parse('http://localhost:$httpPort'), + ); } } diff --git a/test/keepalive_test.dart b/test/keepalive_test.dart index bf48f9b5..cd314ada 100644 --- a/test/keepalive_test.dart +++ b/test/keepalive_test.dart @@ -80,40 +80,46 @@ void main() { await server.shutdown(); }); - test('Server terminates connection after too many pings without data', - () async { - await fakeClient.echo(EchoRequest()); - await Future.delayed(timeout * maxBadPings * 2); - await fakeClient.echo(EchoRequest()); - // Check that the server closed the connection, the next request then has - // to build a new one. - expect(fakeChannel.newConnectionCounter, 2); - }); - - test('Server doesnt terminate connection after pings, as data is sent', - () async { - for (var i = 0; i < 10; i++) { + test( + 'Server terminates connection after too many pings without data', + () async { await fakeClient.echo(EchoRequest()); - await Future.delayed(timeout * 0.2); - } - - // Check that the server never closed the connection - expect(fakeChannel.newConnectionCounter, 1); - }); - - test('Server doesnt ack the ping, making the client shutdown the transport', - () async { - //Send a first request, get a connection - await unresponsiveClient.echo(EchoRequest()); - expect(unresponsiveChannel.newConnectionCounter, 1); - - //Ping is not being acked on time - await Future.delayed(timeout * 2); - - //A second request gets a new connection - await unresponsiveClient.echo(EchoRequest()); - expect(unresponsiveChannel.newConnectionCounter, 2); - }); + await Future.delayed(timeout * maxBadPings * 2); + await fakeClient.echo(EchoRequest()); + // Check that the server closed the connection, the next request then has + // to build a new one. + expect(fakeChannel.newConnectionCounter, 2); + }, + ); + + test( + 'Server doesnt terminate connection after pings, as data is sent', + () async { + for (var i = 0; i < 10; i++) { + await fakeClient.echo(EchoRequest()); + await Future.delayed(timeout * 0.2); + } + + // Check that the server never closed the connection + expect(fakeChannel.newConnectionCounter, 1); + }, + ); + + test( + 'Server doesnt ack the ping, making the client shutdown the transport', + () async { + //Send a first request, get a connection + await unresponsiveClient.echo(EchoRequest()); + expect(unresponsiveChannel.newConnectionCounter, 1); + + //Ping is not being acked on time + await Future.delayed(timeout * 2); + + //A second request gets a new connection + await unresponsiveClient.echo(EchoRequest()); + expect(unresponsiveChannel.newConnectionCounter, 2); + }, + ); } /// A wrapper around a [FakeHttp2ClientConnection] @@ -160,8 +166,11 @@ class UnresponsiveClientChannel extends FakeClientChannel { @override ClientConnection createConnection() { - fakeHttp2ClientConnection = - UnresponsiveHttp2ClientConnection(host, port, options); + fakeHttp2ClientConnection = UnresponsiveHttp2ClientConnection( + host, + port, + options, + ); return fakeHttp2ClientConnection!; } } @@ -182,10 +191,11 @@ class UnresponsiveHttp2ClientConnection extends FakeHttp2ClientConnection { } class FakeClientKeepAlive extends ClientKeepAlive { - FakeClientKeepAlive( - {required super.options, - required super.ping, - required super.onPingTimeout}); + FakeClientKeepAlive({ + required super.options, + required super.ping, + required super.onPingTimeout, + }); @override void onFrameReceived() { @@ -200,6 +210,7 @@ class FakeEchoService extends EchoServiceBase { @override Stream serverStreamingEcho( - ServiceCall call, ServerStreamingEchoRequest request) => - throw UnsupportedError('Not used in this test'); + ServiceCall call, + ServerStreamingEchoRequest request, + ) => throw UnsupportedError('Not used in this test'); } diff --git a/test/options_test.dart b/test/options_test.dart index edb3a243..c536162f 100644 --- a/test/options_test.dart +++ b/test/options_test.dart @@ -27,16 +27,21 @@ void main() { test('report password errors correctly', () async { final certificates = await File('test/data/certstore.p12').readAsBytes(); - final missingPassword = - ChannelCredentials.secure(certificates: certificates); + final missingPassword = ChannelCredentials.secure( + certificates: certificates, + ); expect(() => missingPassword.securityContext, throwsA(isTlsException)); final wrongPassword = ChannelCredentials.secure( - certificates: certificates, password: 'wrong'); + certificates: certificates, + password: 'wrong', + ); expect(() => wrongPassword.securityContext, throwsA(isTlsException)); final correctPassword = ChannelCredentials.secure( - certificates: certificates, password: 'correct'); + certificates: certificates, + password: 'correct', + ); expect(correctPassword.securityContext, isNotNull); }); }); diff --git a/test/proxy_secure_test.dart b/test/proxy_secure_test.dart index 806913b2..eb061281 100644 --- a/test/proxy_secure_test.dart +++ b/test/proxy_secure_test.dart @@ -86,6 +86,7 @@ class FakeEchoService extends EchoServiceBase { @override Stream serverStreamingEcho( - ServiceCall call, ServerStreamingEchoRequest request) => - throw UnimplementedError(); + ServiceCall call, + ServerStreamingEchoRequest request, + ) => throw UnimplementedError(); } diff --git a/test/proxy_test.dart b/test/proxy_test.dart index 6fc30720..5b5e60cf 100644 --- a/test/proxy_test.dart +++ b/test/proxy_test.dart @@ -68,6 +68,7 @@ class FakeEchoService extends EchoServiceBase { @override Stream serverStreamingEcho( - ServiceCall call, ServerStreamingEchoRequest request) => - throw UnimplementedError(); + ServiceCall call, + ServerStreamingEchoRequest request, + ) => throw UnimplementedError(); } diff --git a/test/round_trip_test.dart b/test/round_trip_test.dart index 5d07fc89..ab3addb7 100644 --- a/test/round_trip_test.dart +++ b/test/round_trip_test.dart @@ -28,14 +28,20 @@ import 'package:test/test.dart'; import 'common.dart'; class TestClient extends Client { - static final _$stream = ClientMethod('/test.TestService/stream', - (int value) => [value], (List value) => value[0]); + static final _$stream = ClientMethod( + '/test.TestService/stream', + (int value) => [value], + (List value) => value[0], + ); TestClient(super.channel); ResponseStream stream(int request, {CallOptions? options}) { - return $createStreamingCall(_$stream, Stream.value(request), - options: options); + return $createStreamingCall( + _$stream, + Stream.value(request), + options: options, + ); } } @@ -46,8 +52,16 @@ class TestService extends Service { String get $name => 'test.TestService'; TestService({this.expectedAuthority}) { - $addMethod(ServiceMethod('stream', stream, false, true, - (List value) => value[0], (int value) => [value])); + $addMethod( + ServiceMethod( + 'stream', + stream, + false, + true, + (List value) => value[0], + (int value) => [value], + ), + ); } static const requestFiniteStream = 1; @@ -85,10 +99,7 @@ class TestServiceWithGrpcError extends TestService { 'This error should contain trailers', null, null, - { - 'key1': 'value1', - 'key2': 'value2', - }, + {'key1': 'value1', 'key2': 'value2'}, ); } } @@ -111,57 +122,74 @@ Future main() async { final server = Server.create(services: [TestService()]); await server.serve(address: address, port: 0); - final channel = FixedConnectionClientChannel(Http2ClientConnection( - address, - server.port!, - ChannelOptions(credentials: ChannelCredentials.insecure()), - )); + final channel = FixedConnectionClientChannel( + Http2ClientConnection( + address, + server.port!, + ChannelOptions(credentials: ChannelCredentials.insecure()), + ), + ); final testClient = TestClient(channel); - expect(await testClient.stream(TestService.requestFiniteStream).toList(), - [1, 2, 3]); + expect(await testClient.stream(TestService.requestFiniteStream).toList(), [ + 1, + 2, + 3, + ]); server.shutdown(); }); testUds('UDS provides valid default authority', (address) async { // round trip test of insecure connection. - final server = - Server.create(services: [TestService(expectedAuthority: 'localhost')]); + final server = Server.create( + services: [TestService(expectedAuthority: 'localhost')], + ); await server.serve(address: address, port: 0); - final channel = FixedConnectionClientChannel(Http2ClientConnection( - address, - server.port!, - ChannelOptions(credentials: ChannelCredentials.insecure()), - )); + final channel = FixedConnectionClientChannel( + Http2ClientConnection( + address, + server.port!, + ChannelOptions(credentials: ChannelCredentials.insecure()), + ), + ); final testClient = TestClient(channel); - expect(await testClient.stream(TestService.requestFiniteStream).toList(), - [1, 2, 3]); + expect(await testClient.stream(TestService.requestFiniteStream).toList(), [ + 1, + 2, + 3, + ]); server.shutdown(); }); - testTcpAndUds('round trip with outgoing and incoming compression', - (address) async { + testTcpAndUds('round trip with outgoing and incoming compression', ( + address, + ) async { final server = Server.create( services: [TestService()], codecRegistry: CodecRegistry(codecs: const [GzipCodec()]), ); await server.serve(address: address, port: 0); - final channel = FixedConnectionClientChannel(Http2ClientConnection( - address, - server.port!, - ChannelOptions( - credentials: ChannelCredentials.insecure(), - codecRegistry: CodecRegistry(codecs: const [GzipCodec()]), + final channel = FixedConnectionClientChannel( + Http2ClientConnection( + address, + server.port!, + ChannelOptions( + credentials: ChannelCredentials.insecure(), + codecRegistry: CodecRegistry(codecs: const [GzipCodec()]), + ), ), - )); + ); final testClient = TestClient(channel); expect( - await testClient - .stream(TestService.requestFiniteStream, - options: CallOptions(compression: const GzipCodec())) - .toList(), - [1, 2, 3]); + await testClient + .stream( + TestService.requestFiniteStream, + options: CallOptions(compression: const GzipCodec()), + ) + .toList(), + [1, 2, 3], + ); await server.shutdown(); }); @@ -169,40 +197,53 @@ Future main() async { // round trip test of secure connection. final server = Server.create(services: [TestService()]); await server.serve( - address: address, - port: 0, - security: ServerTlsCredentials( - certificate: File('test/data/localhost.crt').readAsBytesSync(), - privateKey: File('test/data/localhost.key').readAsBytesSync())); - - final channel = FixedConnectionClientChannel(Http2ClientConnection( - address, - server.port!, - ChannelOptions( + address: address, + port: 0, + security: ServerTlsCredentials( + certificate: File('test/data/localhost.crt').readAsBytesSync(), + privateKey: File('test/data/localhost.key').readAsBytesSync(), + ), + ); + + final channel = FixedConnectionClientChannel( + Http2ClientConnection( + address, + server.port!, + ChannelOptions( credentials: ChannelCredentials.secure( - certificates: File('test/data/localhost.crt').readAsBytesSync(), - authority: 'localhost')), - )); + certificates: File('test/data/localhost.crt').readAsBytesSync(), + authority: 'localhost', + ), + ), + ), + ); final testClient = TestClient(channel); - expect(await testClient.stream(TestService.requestFiniteStream).toList(), - [1, 2, 3]); + expect(await testClient.stream(TestService.requestFiniteStream).toList(), [ + 1, + 2, + 3, + ]); server.shutdown(); }); test('exception in onMetadataException', () async { - final server = - Server.create(services: [TestServiceWithOnMetadataException()]); + final server = Server.create( + services: [TestServiceWithOnMetadataException()], + ); await server.serve(address: 'localhost', port: 0); - final channel = FixedConnectionClientChannel(Http2ClientConnection( - 'localhost', - server.port!, - ChannelOptions(credentials: ChannelCredentials.insecure()), - )); + final channel = FixedConnectionClientChannel( + Http2ClientConnection( + 'localhost', + server.port!, + ChannelOptions(credentials: ChannelCredentials.insecure()), + ), + ); final testClient = TestClient(channel); await expectLater( - testClient.stream(TestService.requestFiniteStream).toList(), - throwsA(isA())); + testClient.stream(TestService.requestFiniteStream).toList(), + throwsA(isA()), + ); await server.shutdown(); }); @@ -210,11 +251,13 @@ Future main() async { final server = Server.create(services: [TestService()]); await server.serve(address: 'localhost', port: 0); - final channel = FixedConnectionClientChannel(Http2ClientConnection( - 'localhost', - server.port!, - ChannelOptions(credentials: ChannelCredentials.insecure()), - )); + final channel = FixedConnectionClientChannel( + Http2ClientConnection( + 'localhost', + server.port!, + ChannelOptions(credentials: ChannelCredentials.insecure()), + ), + ); final testClient = TestClient(channel); expect(await testClient.stream(TestService.requestInfiniteStream).first, 1); await channel.shutdown(); @@ -225,24 +268,29 @@ Future main() async { final server = Server.create(services: [TestServiceWithGrpcError()]); await server.serve(address: 'localhost', port: 0); - final channel = FixedConnectionClientChannel(Http2ClientConnection( - 'localhost', - server.port!, - ChannelOptions(credentials: ChannelCredentials.insecure()), - )); + final channel = FixedConnectionClientChannel( + Http2ClientConnection( + 'localhost', + server.port!, + ChannelOptions(credentials: ChannelCredentials.insecure()), + ), + ); final testClient = TestClient(channel); await expectLater( testClient.stream(TestService.requestFiniteStream).toList(), - throwsA(predicate((e) { - final trailers = e.trailers; - if (trailers == null || trailers.length != 2) return false; - final entries = trailers.entries.toList(); - final isOk = entries[0].key == 'key1' && - entries[0].value == 'value1' && - entries[1].key == 'key2' && - entries[1].value == 'value2'; - return isOk; - })), + throwsA( + predicate((e) { + final trailers = e.trailers; + if (trailers == null || trailers.length != 2) return false; + final entries = trailers.entries.toList(); + final isOk = + entries[0].key == 'key1' && + entries[0].value == 'value1' && + entries[1].key == 'key2' && + entries[1].value == 'value2'; + return isOk; + }), + ), ); await server.shutdown(); }); diff --git a/test/server_cancellation_test.dart b/test/server_cancellation_test.dart index ab119bb2..f021d5fd 100644 --- a/test/server_cancellation_test.dart +++ b/test/server_cancellation_test.dart @@ -28,7 +28,9 @@ class EchoService extends EchoServiceBase { @override Stream serverStreamingEcho( - ServiceCall call, ServerStreamingEchoRequest request) async* { + ServiceCall call, + ServerStreamingEchoRequest request, + ) async* { for (var i = 0; i < request.messageCount; i++) { yield ServerStreamingEchoResponse(message: '$i'); await Future.delayed(Duration(milliseconds: request.messageInterval)); @@ -44,9 +46,7 @@ void main() { server.handlers.entries.firstOrNull?.value.length ?? 0; setUp(() async { - server = Server.create( - services: [EchoService()], - ); + server = Server.create(services: [EchoService()]); await server.serve(address: 'localhost', port: 0); channel = ClientChannel( 'localhost', @@ -65,12 +65,12 @@ void main() { messageCount: 5, messageInterval: 5, ); - final stream1 = EchoServiceClient(channel) - .serverStreamingEcho(request) - .asBroadcastStream(); - final stream2 = EchoServiceClient(channel) - .serverStreamingEcho(request) - .asBroadcastStream(); + final stream1 = EchoServiceClient( + channel, + ).serverStreamingEcho(request).asBroadcastStream(); + final stream2 = EchoServiceClient( + channel, + ).serverStreamingEcho(request).asBroadcastStream(); expect(numberHandlers(), 0); diff --git a/test/server_handles_broken_connection_test.dart b/test/server_handles_broken_connection_test.dart index 689b5256..28e88bbe 100644 --- a/test/server_handles_broken_connection_test.dart +++ b/test/server_handles_broken_connection_test.dart @@ -27,16 +27,22 @@ import 'common.dart'; class TestClient extends grpc.Client { static final _$infiniteStream = grpc.ClientMethod( - '/test.TestService/infiniteStream', - (int value) => [value], - (List value) => value[0]); + '/test.TestService/infiniteStream', + (int value) => [value], + (List value) => value[0], + ); TestClient(grpc.ClientChannel super.channel); - grpc.ResponseStream infiniteStream(int request, - {grpc.CallOptions? options}) { - return $createStreamingCall(_$infiniteStream, Stream.value(request), - options: options); + grpc.ResponseStream infiniteStream( + int request, { + grpc.CallOptions? options, + }) { + return $createStreamingCall( + _$infiniteStream, + Stream.value(request), + options: options, + ); } } @@ -46,12 +52,22 @@ class TestService extends grpc.Service { final void Function() finallyCallback; TestService({required this.finallyCallback}) { - $addMethod(grpc.ServiceMethod('infiniteStream', infiniteStream, - false, true, (List value) => value[0], (int value) => [value])); + $addMethod( + grpc.ServiceMethod( + 'infiniteStream', + infiniteStream, + false, + true, + (List value) => value[0], + (int value) => [value], + ), + ); } Stream infiniteStream( - grpc.ServiceCall call, Future request) async* { + grpc.ServiceCall call, + Future request, + ) async* { var count = await request; try { while (true) { @@ -73,8 +89,11 @@ class ClientData { final int port; final SendPort sendPort; - ClientData( - {required this.address, required this.port, required this.sendPort}); + ClientData({ + required this.address, + required this.port, + required this.sendPort, + }); } void client(ClientData clientData) async { @@ -85,37 +104,52 @@ void client(ClientData clientData) async { credentials: grpc.ChannelCredentials.insecure(), ), ); - TestClient(channel).infiniteStream(1).listen((count) async { - await channel.terminate(); - }, onError: (e) { - clientData.sendPort.send(e); - }); + TestClient(channel) + .infiniteStream(1) + .listen( + (count) async { + await channel.terminate(); + }, + onError: (e) { + clientData.sendPort.send(e); + }, + ); } Future main() async { testTcpAndUds( - 'the client interrupting the connection does not crash the server', - (address) async { - // interrrupt the connect of client, the server does not crash. - late grpc.Server server; - server = grpc.Server.create(services: [ - TestService( - finallyCallback: expectAsync0(() { - expect(server.shutdown(), completes); - }, reason: 'the producer should get cancelled'), - ) - ]); - await server.serve(address: address, port: 0); - final receivePort = ReceivePort(); - Isolate.spawn( + 'the client interrupting the connection does not crash the server', + (address) async { + // interrrupt the connect of client, the server does not crash. + late grpc.Server server; + server = grpc.Server.create( + services: [ + TestService( + finallyCallback: expectAsync0(() { + expect(server.shutdown(), completes); + }, reason: 'the producer should get cancelled'), + ), + ], + ); + await server.serve(address: address, port: 0); + final receivePort = ReceivePort(); + Isolate.spawn( client, ClientData( - address: address, - port: server.port!, - sendPort: receivePort.sendPort)); - receivePort.listen(expectAsync1((e) { - expect(e, isA()); - receivePort.close(); - }, reason: 'the client should send an error from the destroyed channel')); - }); + address: address, + port: server.port!, + sendPort: receivePort.sendPort, + ), + ); + receivePort.listen( + expectAsync1( + (e) { + expect(e, isA()); + receivePort.close(); + }, + reason: 'the client should send an error from the destroyed channel', + ), + ); + }, + ); } diff --git a/test/server_keepalive_manager_test.dart b/test/server_keepalive_manager_test.dart index 94ab257c..9463905e 100644 --- a/test/server_keepalive_manager_test.dart +++ b/test/server_keepalive_manager_test.dart @@ -27,15 +27,16 @@ void main() { var goAway = false; void initServer([ServerKeepAliveOptions? options]) => ServerKeepAlive( - options: options ?? - ServerKeepAliveOptions( - maxBadPings: maxBadPings, - minIntervalBetweenPingsWithoutData: Duration(milliseconds: 5), - ), - pingNotifier: pingStream.stream, - dataNotifier: dataStream.stream, - tooManyBadPings: () async => goAway = true, - ).handle(); + options: + options ?? + ServerKeepAliveOptions( + maxBadPings: maxBadPings, + minIntervalBetweenPingsWithoutData: Duration(milliseconds: 5), + ), + pingNotifier: pingStream.stream, + dataNotifier: dataStream.stream, + tooManyBadPings: () async => goAway = true, + ).handle(); setUp(() { pingStream = StreamController(); @@ -72,25 +73,28 @@ void main() { }); }); test( - 'Sending too many pings without data doesn`t kill connection if the server doesn`t care', - () async { - FakeAsync().run((async) { - initServer(ServerKeepAliveOptions( - maxBadPings: null, - minIntervalBetweenPingsWithoutData: Duration(milliseconds: 5), - )); - // Send good ping - pingStream.sink.add(null); - async.elapse(timeAfterPing); - - // Send a lot of bad pings, that's still ok. - for (var i = 0; i < 50; i++) { + 'Sending too many pings without data doesn`t kill connection if the server doesn`t care', + () async { + FakeAsync().run((async) { + initServer( + ServerKeepAliveOptions( + maxBadPings: null, + minIntervalBetweenPingsWithoutData: Duration(milliseconds: 5), + ), + ); + // Send good ping pingStream.sink.add(null); - } - async.elapse(timeAfterPing); - expect(goAway, false); - }); - }); + async.elapse(timeAfterPing); + + // Send a lot of bad pings, that's still ok. + for (var i = 0; i < 50; i++) { + pingStream.sink.add(null); + } + async.elapse(timeAfterPing); + expect(goAway, false); + }); + }, + ); test('Sending many pings with data doesn`t kill connection', () async { FakeAsync().run((async) { diff --git a/test/server_test.dart b/test/server_test.dart index 3f1a388d..07bd2c99 100644 --- a/test/server_test.dart +++ b/test/server_test.dart @@ -112,7 +112,9 @@ void main() { test('Server returns error on unimplemented path', () async { harness ..expectErrorResponse( - StatusCode.unimplemented, 'Path /Test/NotFound not found') + StatusCode.unimplemented, + 'Path /Test/NotFound not found', + ) ..sendRequestHeader('/Test/NotFound'); await harness.fromServer.done; }); @@ -120,7 +122,8 @@ void main() { /// Returns a service method handler that verifies that awaiting the request /// throws a specific error. Future Function(ServiceCall call, Future request) expectError( - expectedError) { + expectedError, + ) { return expectAsync2((ServiceCall call, Future request) async { try { final result = await request; @@ -140,7 +143,7 @@ void main() { /// Returns a service method handler that verifies that awaiting the request /// stream throws a specific error. Stream Function(ServiceCall call, Stream request) - expectErrorStreaming(expectedError) { + expectErrorStreaming(expectedError) { return (ServiceCall call, Stream request) async* { try { await for (var entry in request) { @@ -160,8 +163,9 @@ void main() { test('Server returns error on missing request for unary call', () async { harness - ..service.unaryHandler = - expectError(GrpcError.unimplemented('No request received')) + ..service.unaryHandler = expectError( + GrpcError.unimplemented('No request received'), + ) ..expectErrorResponse(StatusCode.unimplemented, 'No request received') ..sendRequestHeader('/Test/Unary') ..toServer.close(); @@ -182,22 +186,26 @@ void main() { await harness.fromServer.done; }); - test('Server returns error if multiple headers are received for unary call', - () async { - harness - ..service.unaryHandler = - expectError(GrpcError.unimplemented('Expected request')) - ..expectErrorResponse(StatusCode.unimplemented, 'Expected request') - ..sendRequestHeader('/Test/Unary') - ..toServer.add(HeadersStreamMessage([])) - ..toServer.close(); - await harness.fromServer.done; - }); + test( + 'Server returns error if multiple headers are received for unary call', + () async { + harness + ..service.unaryHandler = expectError( + GrpcError.unimplemented('Expected request'), + ) + ..expectErrorResponse(StatusCode.unimplemented, 'Expected request') + ..sendRequestHeader('/Test/Unary') + ..toServer.add(HeadersStreamMessage([])) + ..toServer.close(); + await harness.fromServer.done; + }, + ); test('Server returns error on too many requests for unary call', () async { harness - ..service.unaryHandler = - expectError(GrpcError.unimplemented('Too many requests')) + ..service.unaryHandler = expectError( + GrpcError.unimplemented('Too many requests'), + ) ..expectErrorResponse(StatusCode.unimplemented, 'Too many requests') ..sendRequestHeader('/Test/Unary') ..sendData(dummyValue) @@ -209,9 +217,12 @@ void main() { test('Server returns request deserialization errors', () async { harness ..service.bidirectionalHandler = expectErrorStreaming( - GrpcError.internal('Error deserializing request: Failed')) + GrpcError.internal('Error deserializing request: Failed'), + ) ..expectErrorResponse( - StatusCode.internal, 'Error deserializing request: Failed') + StatusCode.internal, + 'Error deserializing request: Failed', + ) ..sendRequestHeader('/Test/RequestError') ..sendData(dummyValue) ..toServer.close(); @@ -221,9 +232,12 @@ void main() { test('Server returns response serialization errors', () async { harness ..service.bidirectionalHandler = expectErrorStreaming( - GrpcError.internal('Error sending response: Failed')) + GrpcError.internal('Error sending response: Failed'), + ) ..expectErrorResponse( - StatusCode.internal, 'Error sending response: Failed') + StatusCode.internal, + 'Error sending response: Failed', + ) ..sendRequestHeader('/Test/ResponseError') ..sendData(dummyValue) ..sendData(dummyValue) @@ -271,11 +285,13 @@ void main() { harness ..service.unaryHandler = methodHandler - ..fromServer.stream.listen(expectAsync1((_) {}, count: 0), - onError: expectAsync1((dynamic error) { - expect(error, 'TERMINATED'); - }, count: 1), - onDone: expectAsync0(() {}, count: 1)) + ..fromServer.stream.listen( + expectAsync1((_) {}, count: 0), + onError: expectAsync1((dynamic error) { + expect(error, 'TERMINATED'); + }, count: 1), + onDone: expectAsync0(() {}, count: 1), + ) ..sendRequestHeader('/Test/Unary') ..toServer.addError('CANCEL'); @@ -285,14 +301,17 @@ void main() { }); test( - 'Server returns error if request stream is closed before sending anything', - () async { - harness - ..expectErrorResponse( - StatusCode.unavailable, 'Request stream closed unexpectedly') - ..toServer.close(); - await harness.fromServer.done; - }); + 'Server returns error if request stream is closed before sending anything', + () async { + harness + ..expectErrorResponse( + StatusCode.unavailable, + 'Request stream closed unexpectedly', + ) + ..toServer.close(); + await harness.fromServer.done; + }, + ); group('Server with interceptor', () { group('processes calls if interceptor allows request', () { @@ -320,8 +339,10 @@ void main() { } test('with sync interceptor', () => doTest(interceptor)); - test('with async interceptor', - () => doTest((call, method) async => interceptor(call, method))); + test( + 'with async interceptor', + () => doTest((call, method) async => interceptor(call, method)), + ); }); group('returns error if interceptor blocks request', () { @@ -336,15 +357,19 @@ void main() { harness ..interceptor.handler = handler ..expectErrorResponse( - StatusCode.unauthenticated, 'Request is unauthenticated') + StatusCode.unauthenticated, + 'Request is unauthenticated', + ) ..sendRequestHeader('/Test/Unary'); await harness.fromServer.done; } test('with sync interceptor', () => doTest(interceptor)); - test('with async interceptor', - () => doTest((call, method) async => interceptor(call, method))); + test( + 'with async interceptor', + () => doTest((call, method) async => interceptor(call, method)), + ); }); group('returns internal error if interceptor throws exception', () { @@ -356,15 +381,19 @@ void main() { harness ..interceptor.handler = handler ..expectErrorResponse( - StatusCode.internal, 'Exception: Reason is unknown') + StatusCode.internal, + 'Exception: Reason is unknown', + ) ..sendRequestHeader('/Test/Unary'); await harness.fromServer.done; } test('with sync interceptor', () => doTest(interceptor)); - test('with async interceptor', - () => doTest((call, method) async => interceptor(call, method))); + test( + 'with async interceptor', + () => doTest((call, method) async => interceptor(call, method)), + ); }); test("don't fail if interceptor await 2 times", () async { @@ -377,7 +406,9 @@ void main() { harness ..interceptor.handler = interceptor ..expectErrorResponse( - StatusCode.internal, 'Exception: Reason is unknown') + StatusCode.internal, + 'Exception: Reason is unknown', + ) ..sendRequestHeader('/Test/Unary') ..sendData(1); @@ -412,9 +443,11 @@ void main() { test('with sync interceptor', () => doTest(interceptor)); test( - 'with async interceptor', - () => doTest((call, method, requests) async => - interceptor(call, method, requests))); + 'with async interceptor', + () => doTest( + (call, method, requests) async => interceptor(call, method, requests), + ), + ); }); group('returns error if interceptor blocks request', () { @@ -429,7 +462,9 @@ void main() { harness ..serverInterceptor.onStart = handler ..expectErrorResponse( - StatusCode.unauthenticated, 'Request is unauthenticated') + StatusCode.unauthenticated, + 'Request is unauthenticated', + ) ..sendRequestHeader('/Test/Unary'); await harness.fromServer.done; @@ -437,9 +472,11 @@ void main() { test('with sync interceptor', () => doTest(interceptor)); test( - 'with async interceptor', - () => doTest((call, method, request) async => - interceptor(call, method, request))); + 'with async interceptor', + () => doTest( + (call, method, request) async => interceptor(call, method, request), + ), + ); }); test("don't fail if interceptor await 2 times", () async { @@ -490,7 +527,7 @@ void main() { onFinish: (call, method, requests) { invocationsOrderRecords.add('Done'); }, - ) + ), ]); expect(invocationsOrderRecords, equals(['Start', 'Data [7]', 'Done'])); @@ -521,19 +558,20 @@ void main() { onFinish: (call, method, requests) { invocationsOrderRecords.add('Done 2'); }, - ) + ), ]); expect( - invocationsOrderRecords, - equals([ - 'Start 1', - 'Start 2', - 'Data 2 [7]', - 'Data 1 [7]', - 'Done 2', - 'Done 1', - ])); + invocationsOrderRecords, + equals([ + 'Start 1', + 'Start 2', + 'Data 2 [7]', + 'Data 1 [7]', + 'Done 2', + 'Done 1', + ]), + ); }); }); @@ -556,13 +594,15 @@ void main() { invocationsOrderRecords.add('Done 1'); }, ), - TestServerInterruptingInterceptor(transform: (value) { - if (value is int) { - return value * 2 as R; - } + TestServerInterruptingInterceptor( + transform: (value) { + if (value is int) { + return value * 2 as R; + } - return value; - }), + return value; + }, + ), TestServerInterceptor( onStart: (call, method, requests) { invocationsOrderRecords.add('Start 2'); @@ -573,7 +613,7 @@ void main() { onFinish: (call, method, requests) { invocationsOrderRecords.add('Done 2'); }, - ) + ), ]; Future methodHandler(ServiceCall call, Future request) async { @@ -590,15 +630,16 @@ void main() { await harness.fromServer.done; expect( - invocationsOrderRecords, - equals([ - 'Start 1', - 'Start 2', - 'Data 2 [7]', - 'Data 1 [14]', - 'Done 2', - 'Done 1', - ])); + invocationsOrderRecords, + equals([ + 'Start 1', + 'Start 2', + 'Data 2 [7]', + 'Data 1 [14]', + 'Done 2', + 'Done 1', + ]), + ); }); }); } diff --git a/test/src/client_utils.dart b/test/src/client_utils.dart index fd5cd388..5ac3f2ea 100644 --- a/test/src/client_utils.dart +++ b/test/src/client_utils.dart @@ -35,7 +35,7 @@ class FakeConnection extends Http2ClientConnection { Object? connectionError; FakeConnection(String host, this.transport, ChannelOptions options) - : super(host, 443, options); + : super(host, 443, options); @override Future connectTransport() async { @@ -50,7 +50,7 @@ class FakeClientTransportConnection extends Http2ClientConnection { Object? connectionError; FakeClientTransportConnection(this.connector, ChannelOptions options) - : super.fromClientTransportConnector(connector, options); + : super.fromClientTransportConnector(connector, options); @override Future connectTransport() async { @@ -91,7 +91,7 @@ class FakeChannel extends ClientChannel { FakeChannelOptions get options => super.options as FakeChannelOptions; FakeChannel(String super.host, this.connection, FakeChannelOptions options) - : super(options: options); + : super(options: options); @override Future getConnection() async => connection; @@ -104,8 +104,10 @@ class FakeClientConnectorChannel extends ClientTransportConnectorChannel { FakeChannelOptions get options => super.options as FakeChannelOptions; FakeClientConnectorChannel( - super.connector, this.connection, FakeChannelOptions options) - : super(options: options); + super.connector, + this.connection, + FakeChannelOptions options, + ) : super(options: options); @override Future getConnection() async => connection; @@ -121,34 +123,57 @@ class TestClient extends Client { final int Function(List value) decode; - TestClient(super.channel, - {super.options, super.interceptors, this.decode = mockDecode}) { + TestClient( + super.channel, { + super.options, + super.interceptors, + this.decode = mockDecode, + }) { _$unary = ClientMethod('/Test/Unary', mockEncode, decode); - _$clientStreaming = - ClientMethod('/Test/ClientStreaming', mockEncode, decode); - _$serverStreaming = - ClientMethod('/Test/ServerStreaming', mockEncode, decode); - _$bidirectional = - ClientMethod('/Test/Bidirectional', mockEncode, decode); + _$clientStreaming = ClientMethod( + '/Test/ClientStreaming', + mockEncode, + decode, + ); + _$serverStreaming = ClientMethod( + '/Test/ServerStreaming', + mockEncode, + decode, + ); + _$bidirectional = ClientMethod( + '/Test/Bidirectional', + mockEncode, + decode, + ); } ResponseFuture unary(int request, {CallOptions? options}) { return $createUnaryCall(_$unary, request, options: options); } - ResponseFuture clientStreaming(Stream request, - {CallOptions? options}) { - return $createStreamingCall(_$clientStreaming, request, options: options) - .single; + ResponseFuture clientStreaming( + Stream request, { + CallOptions? options, + }) { + return $createStreamingCall( + _$clientStreaming, + request, + options: options, + ).single; } ResponseStream serverStreaming(int request, {CallOptions? options}) { - return $createStreamingCall(_$serverStreaming, Stream.value(request), - options: options); + return $createStreamingCall( + _$serverStreaming, + Stream.value(request), + options: options, + ); } - ResponseStream bidirectional(Stream request, - {CallOptions? options}) { + ResponseStream bidirectional( + Stream request, { + CallOptions? options, + }) { return $createStreamingCall(_$bidirectional, request, options: options); } } @@ -226,8 +251,9 @@ abstract class _Harness { stream = MockClientTransportStream(); fromClient = StreamController(); toClient = StreamController(); - when(transport.makeRequest(any, endStream: anyNamed('endStream'))) - .thenReturn(stream); + when( + transport.makeRequest(any, endStream: anyNamed('endStream')), + ).thenReturn(stream); when(transport.onActiveStateChanged = captureAny).thenReturn(null); when(transport.isOpen).thenReturn(true); when(stream.outgoingMessages).thenReturn(fromClient.sink); @@ -247,9 +273,7 @@ abstract class _Harness { Header.ascii('content-type', 'application/grpc'), ]; - static final _defaultTrailers = [ - Header.ascii('grpc-status', '0'), - ]; + static final _defaultTrailers = [Header.ascii('grpc-status', '0')]; void sendResponseHeader() { assert(!headersWereSent); @@ -262,54 +286,66 @@ abstract class _Harness { } void sendResponseTrailer({bool closeStream = true}) { - toClient.add(HeadersStreamMessage([ - if (!headersWereSent) ..._defaultHeaders, - ..._defaultTrailers, - ], endStream: true)); + toClient.add( + HeadersStreamMessage([ + if (!headersWereSent) ..._defaultHeaders, + ..._defaultTrailers, + ], endStream: true), + ); if (closeStream) toClient.close(); } void signalIdle() { - final ActiveStateHandler handler = - verify(transport.onActiveStateChanged = captureAny).captured.single; + final ActiveStateHandler handler = verify( + transport.onActiveStateChanged = captureAny, + ).captured.single; expect(handler, isNotNull); handler(false); } - Future runTest( - {Future? clientCall, - dynamic expectedResult, - String? expectedPath, - Duration? expectedTimeout, - Map? expectedCustomHeaders, - List serverHandlers = const [], - void Function()? doneHandler, - bool expectDone = true}) async { + Future runTest({ + Future? clientCall, + dynamic expectedResult, + String? expectedPath, + Duration? expectedTimeout, + Map? expectedCustomHeaders, + List serverHandlers = const [], + void Function()? doneHandler, + bool expectDone = true, + }) async { var serverHandlerIndex = 0; void handleServerMessage(StreamMessage message) { serverHandlers[serverHandlerIndex++](message); } final clientSubscription = fromClient.stream.listen( - expectAsync1(handleServerMessage, count: serverHandlers.length), - onError: expectAsync1((dynamic _) {}, count: 0), - onDone: expectAsync0(doneHandler ?? () {}, count: expectDone ? 1 : 0)); + expectAsync1(handleServerMessage, count: serverHandlers.length), + onError: expectAsync1((dynamic _) {}, count: 0), + onDone: expectAsync0(doneHandler ?? () {}, count: expectDone ? 1 : 0), + ); final result = await clientCall; if (expectedResult != null) { expect(result, expectedResult); } - final List
capturedHeaders = - verify(transport.makeRequest(captureAny)).captured.single; + final List
capturedHeaders = verify( + transport.makeRequest(captureAny), + ).captured.single; validateRequestHeaders( - Map.fromEntries(capturedHeaders.map((header) => - MapEntry(utf8.decode(header.name), utf8.decode(header.value)))), - path: expectedPath, - authority: expectedAuthority, - timeout: - expectedTimeout == null ? null : toTimeoutString(expectedTimeout), - customHeaders: expectedCustomHeaders); + Map.fromEntries( + capturedHeaders.map( + (header) => + MapEntry(utf8.decode(header.name), utf8.decode(header.value)), + ), + ), + path: expectedPath, + authority: expectedAuthority, + timeout: expectedTimeout == null + ? null + : toTimeoutString(expectedTimeout), + customHeaders: expectedCustomHeaders, + ); await clientSubscription.cancel(); } @@ -335,15 +371,16 @@ abstract class _Harness { } } - Future runFailureTest( - {Future? clientCall, - dynamic expectedException, - String? expectedPath, - Duration? expectedTimeout, - Map? expectedCustomHeaders, - Map? expectedCustomTrailers, - List serverHandlers = const [], - bool expectDone = true}) async { + Future runFailureTest({ + Future? clientCall, + dynamic expectedException, + String? expectedPath, + Duration? expectedTimeout, + Map? expectedCustomHeaders, + Map? expectedCustomTrailers, + List serverHandlers = const [], + bool expectDone = true, + }) async { return runTest( clientCall: expectThrows( clientCall, diff --git a/test/src/server_utils.dart b/test/src/server_utils.dart index fa43749b..6a14209e 100644 --- a/test/src/server_utils.dart +++ b/test/src/server_utils.dart @@ -29,24 +29,53 @@ class TestService extends Service { Future Function(ServiceCall call, Future request)? unaryHandler; Future Function(ServiceCall call, Stream request)? - clientStreamingHandler; + clientStreamingHandler; Stream Function(ServiceCall call, Future request)? - serverStreamingHandler; + serverStreamingHandler; Stream Function(ServiceCall call, Stream request)? - bidirectionalHandler; + bidirectionalHandler; TestService() { $addMethod(ServerHarness.createMethod('Unary', _unary, false, false)); - $addMethod(ServerHarness.createMethod( - 'ClientStreaming', _clientStreaming, true, false)); - $addMethod(ServerHarness.createMethod( - 'ServerStreaming', _serverStreaming, false, true)); - $addMethod(ServerHarness.createMethod( - 'Bidirectional', _bidirectional, true, true)); - $addMethod(ServiceMethod('RequestError', _bidirectional, true, - true, (List value) => throw 'Failed', mockEncode)); - $addMethod(ServiceMethod('ResponseError', _bidirectional, true, - true, mockDecode, (int value) => throw 'Failed')); + $addMethod( + ServerHarness.createMethod( + 'ClientStreaming', + _clientStreaming, + true, + false, + ), + ); + $addMethod( + ServerHarness.createMethod( + 'ServerStreaming', + _serverStreaming, + false, + true, + ), + ); + $addMethod( + ServerHarness.createMethod('Bidirectional', _bidirectional, true, true), + ); + $addMethod( + ServiceMethod( + 'RequestError', + _bidirectional, + true, + true, + (List value) => throw 'Failed', + mockEncode, + ), + ); + $addMethod( + ServiceMethod( + 'ResponseError', + _bidirectional, + true, + true, + mockDecode, + (int value) => throw 'Failed', + ), + ); } Future _unary(ServiceCall call, Future request) { @@ -90,12 +119,17 @@ class TestInterceptor { } } -typedef TestServerInterceptorOnStart = Function( - ServiceCall call, ServiceMethod method, Stream requests); -typedef TestServerInterceptorOnData = Function( - ServiceCall call, ServiceMethod method, Stream requests, dynamic data); -typedef TestServerInterceptorOnFinish = Function( - ServiceCall call, ServiceMethod method, Stream requests); +typedef TestServerInterceptorOnStart = + Function(ServiceCall call, ServiceMethod method, Stream requests); +typedef TestServerInterceptorOnData = + Function( + ServiceCall call, + ServiceMethod method, + Stream requests, + dynamic data, + ); +typedef TestServerInterceptorOnFinish = + Function(ServiceCall call, ServiceMethod method, Stream requests); class TestServerInterceptor extends ServerInterceptor { TestServerInterceptorOnStart? onStart; @@ -105,12 +139,20 @@ class TestServerInterceptor extends ServerInterceptor { TestServerInterceptor({this.onStart, this.onData, this.onFinish}); @override - Stream intercept(ServiceCall call, ServiceMethod method, - Stream requests, ServerStreamingInvoker invoker) async* { + Stream intercept( + ServiceCall call, + ServiceMethod method, + Stream requests, + ServerStreamingInvoker invoker, + ) async* { await onStart?.call(call, method, requests); - await for (final chunk - in super.intercept(call, method, requests, invoker)) { + await for (final chunk in super.intercept( + call, + method, + requests, + invoker, + )) { await onData?.call(call, method, requests, chunk); yield chunk; } @@ -125,8 +167,12 @@ class TestServerInterruptingInterceptor extends ServerInterceptor { TestServerInterruptingInterceptor({required this.transform}); @override - Stream intercept(ServiceCall call, ServiceMethod method, - Stream requests, ServerStreamingInvoker invoker) async* { + Stream intercept( + ServiceCall call, + ServiceMethod method, + Stream requests, + ServerStreamingInvoker invoker, + ) async* { yield* super.intercept(call, method, requests, invoker).map(transform); } } @@ -162,24 +208,32 @@ class TestServerStream extends ServerTransportStream { class ServerHarness extends _Harness { @override ConnectionServer createServer() => Server.create( - services: [service], - interceptors: [interceptor.call], - serverInterceptors: serverInterceptors..insert(0, serverInterceptor), - ); - - static ServiceMethod createMethod(String name, - Function methodHandler, bool clientStreaming, bool serverStreaming) { - return ServiceMethod(name, methodHandler, clientStreaming, - serverStreaming, mockDecode, mockEncode); + services: [service], + interceptors: [interceptor.call], + serverInterceptors: serverInterceptors..insert(0, serverInterceptor), + ); + + static ServiceMethod createMethod( + String name, + Function methodHandler, + bool clientStreaming, + bool serverStreaming, + ) { + return ServiceMethod( + name, + methodHandler, + clientStreaming, + serverStreaming, + mockDecode, + mockEncode, + ); } } class ConnectionServerHarness extends _Harness { @override - ConnectionServer createServer() => ConnectionServer( - [service], - [interceptor.call], - ); + ConnectionServer createServer() => + ConnectionServer([service], [interceptor.call]); static ServiceMethod createMethod( String name, @@ -230,9 +284,10 @@ abstract class _Harness { } fromServer.stream.listen( - expectAsync1(handleMessages, count: handlers.length), - onError: expectAsync1((dynamic _) {}, count: 0), - onDone: expectAsync0(() {}, count: 1)); + expectAsync1(handleMessages, count: handlers.length), + onError: expectAsync1((dynamic _) {}, count: 0), + onDone: expectAsync0(() {}, count: 1), + ); } void expectErrorResponse(int status, String message) { @@ -242,17 +297,25 @@ abstract class _Harness { void expectTrailingErrorResponse(int status, String message) { setupTest([ headerValidator(), - errorTrailerValidator(status, message, validateHeader: false) + errorTrailerValidator(status, message, validateHeader: false), ]); } - void sendRequestHeader(String path, - {String authority = 'test', - Map? metadata, - Duration? timeout}) { + void sendRequestHeader( + String path, { + String authority = 'test', + Map? metadata, + Duration? timeout, + }) { final headers = Http2ClientConnection.createCallHeaders( - true, authority, path, timeout, metadata, null, - userAgent: 'dart-grpc/1.0.0 test'); + true, + authority, + path, + timeout, + metadata, + null, + userAgent: 'dart-grpc/1.0.0 test', + ); toServer.add(HeadersStreamMessage(headers)); } diff --git a/test/src/utils.dart b/test/src/utils.dart index 35e631db..7359b0df 100644 --- a/test/src/utils.dart +++ b/test/src/utils.dart @@ -26,14 +26,17 @@ List mockEncode(int value) => List.filled(value, 0); int mockDecode(List value) => value.length; -Map headersToMap(List
headers) => - {for (var h in headers) ascii.decode(h.name): ascii.decode(h.value)}; - -void validateRequestHeaders(Map headers, - {String? path, - String authority = 'test', - String? timeout, - Map? customHeaders}) { +Map headersToMap(List
headers) => { + for (var h in headers) ascii.decode(h.name): ascii.decode(h.value), +}; + +void validateRequestHeaders( + Map headers, { + String? path, + String authority = 'test', + String? timeout, + Map? customHeaders, +}) { expect(headers[':method'], 'POST'); expect(headers[':scheme'], 'https'); if (path != null) { @@ -50,10 +53,12 @@ void validateRequestHeaders(Map headers, }); } -void validateResponseHeaders(Map headers, - {int status = 200, - bool allowTrailers = false, - Map? customHeaders}) { +void validateResponseHeaders( + Map headers, { + int status = 200, + bool allowTrailers = false, + Map? customHeaders, +}) { expect(headers[':status'], '200'); expect(headers['content-type'], startsWith('application/grpc')); if (!allowTrailers) { @@ -65,8 +70,12 @@ void validateResponseHeaders(Map headers, }); } -void validateResponseTrailers(Map trailers, - {int status = 0, String? message, Map? customTrailers}) { +void validateResponseTrailers( + Map trailers, { + int status = 0, + String? message, + Map? customTrailers, +}) { expect(trailers['grpc-status'], '$status'); if (message != null) { expect(trailers['grpc-message'], message); @@ -76,8 +85,10 @@ void validateResponseTrailers(Map trailers, }); } -GrpcMetadata validateMetadataMessage(StreamMessage message, - {bool endStream = false}) { +GrpcMetadata validateMetadataMessage( + StreamMessage message, { + bool endStream = false, +}) { expect(message, TypeMatcher()); expect(message.endStream, endStream); @@ -103,14 +114,19 @@ void Function(StreamMessage message) headerValidator() { } void Function(StreamMessage message) errorTrailerValidator( - int status, String statusMessage, - {bool validateHeader = false}) { + int status, + String statusMessage, { + bool validateHeader = false, +}) { return (StreamMessage message) { final trailer = validateMetadataMessage(message, endStream: true); if (validateHeader) { validateResponseHeaders(trailer.metadata, allowTrailers: true); } - validateResponseTrailers(trailer.metadata, - status: status, message: statusMessage); + validateResponseTrailers( + trailer.metadata, + status: status, + message: statusMessage, + ); }; } diff --git a/test/stream_test.dart b/test/stream_test.dart index bc9b7468..caf744e5 100644 --- a/test/stream_test.dart +++ b/test/stream_test.dart @@ -37,8 +37,26 @@ void main() { ..add(DataStreamMessage([0, 0, 10, 48, 49])) ..add(DataStreamMessage([50, 51, 52, 53])) ..add(DataStreamMessage([54, 55, 56, 57, 0, 0, 0])) - ..add(DataStreamMessage( - [0, 4, 97, 98, 99, 100, 0, 0, 0, 0, 1, 65, 0, 0, 0, 0])) + ..add( + DataStreamMessage([ + 0, + 4, + 97, + 98, + 99, + 100, + 0, + 0, + 0, + 0, + 1, + 65, + 0, + 0, + 0, + 0, + ]), + ) ..add(DataStreamMessage([4, 48, 49, 50, 51, 1, 0, 0, 1, 0])) ..add(DataStreamMessage(List.filled(256, 90))); input.close(); @@ -50,29 +68,41 @@ void main() { } expect(converted[0], TypeMatcher()); - verify( - converted[1] as GrpcData, [48, 49, 50, 51, 52, 53, 54, 55, 56, 57]); + verify(converted[1] as GrpcData, [ + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + ]); verify(converted[2] as GrpcData, [97, 98, 99, 100]); verify(converted[3] as GrpcData, [65]); verify(converted[4] as GrpcData, [48, 49, 50, 51]); verify(converted[5] as GrpcData, List.filled(256, 90)); }); - test('throws error if input is closed while receiving data header', - () async { - final result = output.toList(); - input - ..add(HeadersStreamMessage([])) - ..add(DataStreamMessage([0, 0, 0])) - ..close(); - try { - await result; - fail('Did not throw'); - } on GrpcError catch (e) { - expect(e.code, StatusCode.unavailable); - expect(e.message, 'Closed in non-idle state'); - } - }); + test( + 'throws error if input is closed while receiving data header', + () async { + final result = output.toList(); + input + ..add(HeadersStreamMessage([])) + ..add(DataStreamMessage([0, 0, 0])) + ..close(); + try { + await result; + fail('Did not throw'); + } on GrpcError catch (e) { + expect(e.code, StatusCode.unavailable); + expect(e.message, 'Closed in non-idle state'); + } + }, + ); test('throws error if input is closed while receiving data', () async { final result = output.toList(); @@ -89,22 +119,24 @@ void main() { } }); - test('throws error if receiving metadata while reading data header', - () async { - final result = output.toList(); - input - ..add(HeadersStreamMessage([])) - ..add(DataStreamMessage([0, 0, 0, 0])) - ..add(HeadersStreamMessage([])) - ..close(); - try { - await result; - fail('Did not throw'); - } on GrpcError catch (e) { - expect(e.code, StatusCode.unimplemented); - expect(e.message, 'Received header while reading data'); - } - }); + test( + 'throws error if receiving metadata while reading data header', + () async { + final result = output.toList(); + input + ..add(HeadersStreamMessage([])) + ..add(DataStreamMessage([0, 0, 0, 0])) + ..add(HeadersStreamMessage([])) + ..close(); + try { + await result; + fail('Did not throw'); + } on GrpcError catch (e) { + expect(e.code, StatusCode.unimplemented); + expect(e.message, 'Received header while reading data'); + } + }, + ); test('throws error if receiving metadata while reading data', () async { final result = output.toList(); diff --git a/test/timeline_test.dart b/test/timeline_test.dart index b0e936db..05d7039c 100644 --- a/test/timeline_test.dart +++ b/test/timeline_test.dart @@ -15,7 +15,8 @@ @TestOn('vm') @Skip( - 'Run only as `dart run --enable-vm-service --timeline-streams=Dart test/timeline_test.dart`') + 'Run only as `dart run --enable-vm-service --timeline-streams=Dart test/timeline_test.dart`', +) library; import 'dart:async'; @@ -33,12 +34,18 @@ const String path = '/test.TestService/stream'; class TestClient extends Client { static final _$stream = ClientMethod( - path, (int value) => [value], (List value) => value[0]); + path, + (int value) => [value], + (List value) => value[0], + ); TestClient(super.channel); ResponseStream stream(int request, {CallOptions? options}) { - return $createStreamingCall(_$stream, Stream.fromIterable([request]), - options: options); + return $createStreamingCall( + _$stream, + Stream.fromIterable([request]), + options: options, + ); } } @@ -47,8 +54,16 @@ class TestService extends Service { String get $name => 'test.TestService'; TestService() { - $addMethod(ServiceMethod('stream', stream, false, true, - (List value) => value[0], (int value) => [value])); + $addMethod( + ServiceMethod( + 'stream', + stream, + false, + true, + (List value) => value[0], + (int value) => [value], + ), + ); } Stream stream(ServiceCall call, Future request) async* { @@ -75,11 +90,13 @@ Future testee() async { final vmService = await vmServiceConnectUri(uri.toString()); final server = Server.create(services: [TestService()]); await server.serve(address: 'localhost', port: 0); - final channel = FixedConnectionClientChannel(Http2ClientConnection( - 'localhost', - server.port!, - ChannelOptions(credentials: ChannelCredentials.insecure()), - )); + final channel = FixedConnectionClientChannel( + Http2ClientConnection( + 'localhost', + server.port!, + ChannelOptions(credentials: ChannelCredentials.insecure()), + ), + ); final testClient = TestClient(channel); await testClient.stream(1).toList(); await server.shutdown(); diff --git a/test/timeout_test.dart b/test/timeout_test.dart index 95deaa6d..83b6dc89 100644 --- a/test/timeout_test.dart +++ b/test/timeout_test.dart @@ -89,8 +89,10 @@ void main() { final timeout = Duration(microseconds: 1); await harness.runFailureTest( - clientCall: harness.client - .unary(dummyValue, options: CallOptions(timeout: timeout)), + clientCall: harness.client.unary( + dummyValue, + options: CallOptions(timeout: timeout), + ), expectedException: GrpcError.deadlineExceeded('Deadline exceeded'), expectedPath: '/Test/Unary', expectedTimeout: timeout, diff --git a/test/tools/http2_client.dart b/test/tools/http2_client.dart index 9bd40045..af70a0d4 100644 --- a/test/tools/http2_client.dart +++ b/test/tools/http2_client.dart @@ -23,8 +23,9 @@ Future main(List args) async { final serverPort = 0; final proxyPort = int.tryParse(args.first); - final proxy = - proxyPort != null ? Proxy(host: 'localhost', port: proxyPort) : null; + final proxy = proxyPort != null + ? Proxy(host: 'localhost', port: proxyPort) + : null; final port = proxyPort ?? serverPort; @@ -34,24 +35,24 @@ Future main(List args) async { ChannelOptions(proxy: proxy), ); await connector.initSocket('localhost', port); - final incoming = - proxy == null ? connector.socket : await connector.connectToProxy(proxy); + final incoming = proxy == null + ? connector.socket + : await connector.connectToProxy(proxy); final uri = Uri.parse('http://localhost:0'); - final transport = - ClientTransportConnection.viaStreams(incoming, connector.socket); - - final request = transport.makeRequest( - [ - Header.ascii(':method', 'GET'), - Header.ascii(':path', uri.path), - Header.ascii(':scheme', uri.scheme), - Header.ascii(':authority', uri.host), - ], - endStream: true, + final transport = ClientTransportConnection.viaStreams( + incoming, + connector.socket, ); + final request = transport.makeRequest([ + Header.ascii(':method', 'GET'), + Header.ascii(':path', uri.path), + Header.ascii(':scheme', uri.scheme), + Header.ascii(':authority', uri.host), + ], endStream: true); + await for (var message in request.incomingMessages) { if (message is HeadersStreamMessage) { for (var header in message.headers) {