From 6ba8c2420bcc12c1848885889a8d451b4fe2ded4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20B=C3=B6cker?= Date: Tue, 8 Sep 2020 14:47:49 +0200 Subject: [PATCH 1/5] Refactoring - Allow multiple handlers by fetching correct name from environment - Allow parallel execution by not locking loop after each invocation - Enable analysis by correcting analysis_options.yaml file name - Bump Pedantic - Fix analyzer errors - Allow logging - Correct error invocation body - Removed need for InvocationResult class --- .gitignore | 4 +- ...lysis_option.yaml => analysis_options.yaml | 0 example/lib/main.dart | 7 +- lib/client/client.dart | 110 ++++++++++-------- lib/events/alb_event.dart | 33 +++--- lib/events/alexa_event.dart | 3 +- lib/events/apigateway_event.dart | 62 +++++----- lib/events/appsync_event.dart | 9 +- lib/events/cloudwatch_event.dart | 5 +- lib/events/cloudwatch_log_event.dart | 3 +- lib/events/cognito_event.dart | 3 +- lib/events/dynamodb_event.dart | 13 ++- lib/events/kinesis_data_firehose_event.dart | 3 +- lib/events/kinesis_data_stream_event.dart | 7 +- lib/events/s3_event.dart | 5 +- lib/events/sqs_event.dart | 7 +- lib/runtime/context.dart | 70 ++++++----- lib/runtime/event.dart | 26 ++--- lib/runtime/exception.dart | 2 +- lib/runtime/runtime.dart | 94 ++++++++------- pubspec.yaml | 7 +- test/alb_event_test.dart | 36 +++--- test/apigateway_event_test.dart | 44 +++---- test/client_test.dart | 16 +-- test/cloudwatch_event_test.dart | 20 ++-- test/cloudwatch_log_event_test.dart | 14 +-- test/cognito_event_test.dart | 20 ++-- test/common.dart | 49 -------- test/context_test.dart | 63 +++++----- test/dynamodb_event_test.dart | 10 +- test/event_test.dart | 8 +- test/exception_test.dart | 14 +-- test/kinesis_data_firehose_event.dart | 20 ++-- test/kinesis_data_stream_event_test.dart | 28 ++--- test/runtime_test.dart | 29 +++-- test/s3_event.dart | 30 ++--- test/sqs_event_test.dart | 24 ++-- test/testing.dart | 3 - 38 files changed, 446 insertions(+), 455 deletions(-) rename .analysis_option.yaml => analysis_options.yaml (100%) delete mode 100644 test/common.dart delete mode 100644 test/testing.dart diff --git a/.gitignore b/.gitignore index 78e819ce..ca9c29d0 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,6 @@ doc/api/ # Further artifacts *.exe *.zip -bootstrap \ No newline at end of file +bootstrap + +.idea \ No newline at end of file diff --git a/.analysis_option.yaml b/analysis_options.yaml similarity index 100% rename from .analysis_option.yaml rename to analysis_options.yaml diff --git a/example/lib/main.dart b/example/lib/main.dart index bb26dafb..57da9948 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,12 +2,11 @@ import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; void main() async { /// This demo's handling an API Gateway request. - final Handler helloApiGateway = (context, event) async { + final Handler helloApiGateway = (context, event) async { final response = {"message": "hello ${context.requestId}"}; - /// it returns an encoded response to the gateway - return InvocationResult( - context.requestId, AwsApiGatewayResponse.fromJson(response)); + /// it returns an response to the gateway + return AwsApiGatewayResponse.fromJson(response); }; /// The Runtime is a singleton. You can define the handlers as you wish. diff --git a/lib/client/client.dart b/lib/client/client.dart index b8f7b509..72db259f 100644 --- a/lib/client/client.dart +++ b/lib/client/client.dart @@ -2,6 +2,13 @@ import 'dart:io'; import 'dart:async'; import 'dart:convert'; +import 'package:http/io_client.dart'; +import 'package:http_extensions/http_extensions.dart'; +import 'package:http_extensions_log/http_extensions_log.dart'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:http/http.dart' as http; + /// Next invocation data wraps the data from the /// invocation endpoint of the Lambda Runtime Interface. class NextInvocation { @@ -35,28 +42,26 @@ class NextInvocation { final String cognitoIdentity; /// Digesting a [HttpClientResponse] into a [NextInvocation]. - static Future fromResponse( - HttpClientResponse response) async { - return NextInvocation( - response: json - .decode((await response.transform(Utf8Decoder()).toList()).first), - requestId: response.headers.value(runtimeRequestId), - deadlineMs: response.headers.value(runtimeDeadlineMs), - invokedFunctionArn: response.headers.value(runtimeInvokedFunctionArn), - traceId: response.headers.value(runtimeTraceId), - clientContext: response.headers.value(runtimeClientContext), - cognitoIdentity: response.headers.value(runtimeCognitoIdentity)); - } - - const NextInvocation( - {this.requestId, - this.deadlineMs, - this.traceId, - this.clientContext, - this.cognitoIdentity, - this.invokedFunctionArn, - this.response}) - : assert(requestId != null); + static Future fromResponse(http.Response response) async => + NextInvocation( + response: (json.decode(utf8.decode(response.bodyBytes)) as Map) + .cast(), + requestId: response.headers[runtimeRequestId], + deadlineMs: response.headers[runtimeDeadlineMs], + invokedFunctionArn: response.headers[runtimeInvokedFunctionArn], + traceId: response.headers[runtimeTraceId], + clientContext: response.headers[runtimeClientContext], + cognitoIdentity: response.headers[runtimeCognitoIdentity]); + + const NextInvocation({ + @required this.requestId, + this.deadlineMs, + this.traceId, + this.clientContext, + this.cognitoIdentity, + this.invokedFunctionArn, + this.response, + }) : assert(requestId != null); } /// Invocation result is the result that the invoked handler @@ -92,8 +97,7 @@ class InvocationError { /// representation for the Runtime Interface. Map toJson() => { 'errorMessage': error.toString(), - 'errorType': "InvocationError", - 'stackTrace': this.stackTrace.toString() + 'errorType': 'InvocationError', }; const InvocationError(this.error, this.stackTrace); @@ -103,7 +107,7 @@ class InvocationError { /// It is implemented as a singleton whereby [Client.instance] /// always returns the already instantiated client. class Client { - HttpClient _client; + http.BaseClient _client; static final Client _singleton = Client._internal(); @@ -112,53 +116,59 @@ class Client { } Client._internal() { - _client = HttpClient(); + _client = ExtendedClient( + inner: IOClient(), + extensions: [ + LogExtension( + logger: Logger('HTTP'), + defaultOptions: LogOptions( + isEnabled: true, + logContent: true, + logHeaders: true, + ), + ), + ], + ); } static const runtimeApiVersion = '2018-06-01'; - static final runtimeApi = Platform.environment["AWS_LAMBDA_RUNTIME_API"]; - /// Get the next inovation from the AWS Lambda Runtime Interface (see https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). + static String get runtimeApi => + Platform.environment['AWS_LAMBDA_RUNTIME_API']; + + static String get handlerName => Platform.environment['_HANDLER']; + + /// Get the next invocation from the AWS Lambda Runtime Interface (see https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). Future getNextInvocation() async { - final request = await _client.getUrl(Uri.parse( + final response = await _client.get(Uri.parse( 'http://${runtimeApi}/${runtimeApiVersion}/runtime/invocation/next')); - final response = await request.close(); return NextInvocation.fromResponse(response); } /// Post the invocation response to the AWS Lambda Runtime Interface. - Future postInvocationResponse( - InvocationResult result) async { - final request = await _client.postUrl( + Future postInvocationResponse( + String requestId, dynamic payload) async { + final body = jsonEncode(payload); + print(body); + return await _client.post( Uri.parse( - 'http://${runtimeApi}/${runtimeApiVersion}/runtime/invocation/${result.requestId}/response', + 'http://${runtimeApi}/${runtimeApiVersion}/runtime/invocation/$requestId/response', ), + body: body, ); - request.add( - utf8.encode( - json.encode(result.body), - ), - ); - - return await request.close(); } /// Post an invocation error to the AWS Lambda Runtime Interface. /// It takes in an [InvocationError] and the [requestId]. The [requestId] /// is used to map the error to the execution. - Future postInvocationError( + Future postInvocationError( String requestId, InvocationError err) async { - final request = await _client.postUrl( + return await _client.post( Uri.parse( 'http://${runtimeApi}/${runtimeApiVersion}/runtime/invocation/$requestId/error', ), + body: jsonEncode(err), + headers: {'Content-type': 'application/json'}, ); - request.add( - utf8.encode( - json.encode(err) - ) - ); - - return await request.close(); } } diff --git a/lib/events/alb_event.dart b/lib/events/alb_event.dart index 05a4f963..fccc5cad 100644 --- a/lib/events/alb_event.dart +++ b/lib/events/alb_event.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 'alb_event.g.dart'; @@ -7,7 +8,7 @@ part 'alb_event.g.dart'; /// Event send by an Application Load Balancer to the /// invocation to the Lambda. @JsonSerializable() -class AwsALBEvent { +class AwsALBEvent extends Event { /// Request context in which this request is executed. /// For the ELB this is the ARN of the target group. @JsonKey() @@ -43,14 +44,15 @@ class AwsALBEvent { Map toJson() => _$AwsALBEventToJson(this); - const AwsALBEvent( - {this.context, - this.httpMethod, - this.path, - this.headers, - this.queryStringParameters, - this.body, - this.isBase64Encoded}); + const AwsALBEvent({ + this.context, + this.httpMethod, + this.path, + this.headers, + this.queryStringParameters, + this.body, + this.isBase64Encoded, + }); } /// Response for a request from an Application Load Balancer. @@ -100,13 +102,18 @@ class AwsALBResponse { /// The Response that should be returned to the Application Load Balancer. /// It is constructed with some default values for the optional parameters. - AwsALBResponse( - {body, headers, isBase64Encoded, statusCode, statusDescription}) { + AwsALBResponse({ + String body, + Map headers, + bool isBase64Encoded, + int statusCode, + String statusDescription, + }) { this.body = body ?? ''; this.isBase64Encoded = isBase64Encoded ?? false; - this.headers = headers ?? {"Content-Type": "text/html; charset=utf-8"}; + this.headers = headers ?? {'Content-Type': 'text/html; charset=utf-8'}; this.statusCode = statusCode ?? HttpStatus.ok; - this.statusDescription = statusDescription ?? "200 OK"; + this.statusDescription = statusDescription ?? '200 OK'; } } diff --git a/lib/events/alexa_event.dart b/lib/events/alexa_event.dart index 5bf63110..0fdf513b 100644 --- a/lib/events/alexa_event.dart +++ b/lib/events/alexa_event.dart @@ -1,3 +1,4 @@ +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 'alexa_event.g.dart'; @@ -28,7 +29,7 @@ class AwsAlexaEventHeader { /// Event send by an Application Load Balancer to the /// invocation to the Lambda. @JsonSerializable() -class AwsAlexaEvent { +class AwsAlexaEvent extends Event { /// Meta information about the event. @JsonKey() final AwsAlexaEventHeader header; diff --git a/lib/events/apigateway_event.dart b/lib/events/apigateway_event.dart index 83845f2b..be6019ae 100644 --- a/lib/events/apigateway_event.dart +++ b/lib/events/apigateway_event.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'dart:convert'; +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 'apigateway_event.g.dart'; @@ -25,22 +26,25 @@ class AwsApiGatewayResponse { /// Returns the JSON representation of the response. This is called by /// the JSON encoder to produce the response. Map toJson() => { - 'body': body, + if (body != null) 'body': body, 'isBase64Encoded': isBase64Encoded, 'statusCode': statusCode, - 'headers': headers + if (headers != null) 'headers': headers }; /// The factory creates a new [AwsApiGatewayResponse] from JSON. /// It optionally accepts the Base64 encoded flag and a HTTP Status Code /// for the response. factory AwsApiGatewayResponse.fromJson(Map body, - {bool isBase64Encoded, int statusCode, Map headers}) { + {bool isBase64Encoded = false, + int statusCode = HttpStatus.ok, + Map headers}) { return AwsApiGatewayResponse( - body: json.encode(body), - isBase64Encoded: isBase64Encoded, - headers: headers, - statusCode: statusCode); + body: json.encode(body), + isBase64Encoded: isBase64Encoded, + headers: headers, + statusCode: statusCode, + ); } /// The Response that should be returned by the API Gateway for the @@ -49,20 +53,20 @@ class AwsApiGatewayResponse { /// of the response is. AwsApiGatewayResponse({ String body, - bool isBase64Encoded, + bool isBase64Encoded = false, Map headers, int statusCode, }) { - this.body = body ?? ''; + this.body = body; this.isBase64Encoded = isBase64Encoded ?? false; - this.headers = headers ?? {"Content-Type": "application/json"}; + this.headers = headers ?? {'Content-Type': 'application/json'}; this.statusCode = statusCode ?? HttpStatus.ok; } } /// API Gateway Event ... @JsonSerializable() -class AwsApiGatewayEvent { +class AwsApiGatewayEvent extends Event { /// URL Path ... @JsonKey() final String path; @@ -118,58 +122,58 @@ class AwsApiGatewayEvent { /// API Gateway Event Headers ... @JsonSerializable() class AwsApiGatewayEventHeaders { - @JsonKey(name: "Accept") + @JsonKey(name: 'Accept') final String accept; - @JsonKey(name: "Accept-Encoding") + @JsonKey(name: 'Accept-Encoding') final String acceptEncoding; - @JsonKey(name: "CloudFront-Forwarded-Proto") + @JsonKey(name: 'CloudFront-Forwarded-Proto') final String cloudfrontForwardProto; - @JsonKey(name: "CloudFront-Is-Desktop-Viewer") + @JsonKey(name: 'CloudFront-Is-Desktop-Viewer') final String cloudfrontIsDesktopViewer; - @JsonKey(name: "CloudFront-Is-Mobile-Viewer") + @JsonKey(name: 'CloudFront-Is-Mobile-Viewer') final String cloudfrontIsMobileViewer; - @JsonKey(name: "CloudFront-Is-SmartTV-Viewer") + @JsonKey(name: 'CloudFront-Is-SmartTV-Viewer') final String cloudfrontIsSmartTvViewer; - @JsonKey(name: "CloudFront-Is-Tablet-Viewer") + @JsonKey(name: 'CloudFront-Is-Tablet-Viewer') final String cloudfrontIsTabletViewer; - @JsonKey(name: "CloudFront-Viewer-Country") + @JsonKey(name: 'CloudFront-Viewer-Country') final String cloudfrontViewerCountry; - @JsonKey(name: "Host") + @JsonKey(name: 'Host') final String host; - @JsonKey(name: "Upgrade-Insecure-Requests") + @JsonKey(name: 'Upgrade-Insecure-Requests') final String upgradeInsecureRequests; - @JsonKey(name: "User-Agent") + @JsonKey(name: 'User-Agent') final String userAgent; - @JsonKey(name: "Via") + @JsonKey(name: 'Via') final String via; - @JsonKey(name: "X-Amz-Cf-Id") + @JsonKey(name: 'X-Amz-Cf-Id') final String xAmzCfId; - @JsonKey(name: "X-Forwarded-For") + @JsonKey(name: 'X-Forwarded-For') final String xForwardedFor; - @JsonKey(name: "X-Forwarded-Port") + @JsonKey(name: 'X-Forwarded-Port') final String xForwardedPort; - @JsonKey(name: "X-Forwarded-Proto") + @JsonKey(name: 'X-Forwarded-Proto') final String xForwardedProto; - @JsonKey(name: "Cache-Control") + @JsonKey(name: 'Cache-Control') final String cacheControl; - @JsonKey(name: "X-Amzn-Trace-Id") + @JsonKey(name: 'X-Amzn-Trace-Id') final String xAmznTraceId; @JsonKey(ignore: true) diff --git a/lib/events/appsync_event.dart b/lib/events/appsync_event.dart index 3223f6be..115640e5 100644 --- a/lib/events/appsync_event.dart +++ b/lib/events/appsync_event.dart @@ -1,17 +1,18 @@ +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 'appsync_event.g.dart'; /// App Sync Event ... @JsonSerializable() -class AwsAppSyncEvent { - @JsonKey(name: "version") +class AwsAppSyncEvent extends Event { + @JsonKey(name: 'version') final String version; - @JsonKey(name: "operation") + @JsonKey(name: 'operation') final String operation; - @JsonKey(name: "payload") + @JsonKey(name: 'payload') final String payload; factory AwsAppSyncEvent.fromJson(Map json) => diff --git a/lib/events/cloudwatch_event.dart b/lib/events/cloudwatch_event.dart index 28aa9013..04a69189 100644 --- a/lib/events/cloudwatch_event.dart +++ b/lib/events/cloudwatch_event.dart @@ -1,3 +1,4 @@ +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 'cloudwatch_event.g.dart'; @@ -22,7 +23,7 @@ part 'cloudwatch_event.g.dart'; /// } /// ``` @JsonSerializable() -class AwsCloudwatchEvent { +class AwsCloudwatchEvent extends Event { /// Resources ... @JsonKey() final List resources; @@ -44,7 +45,7 @@ class AwsCloudwatchEvent { final String account; /// Data Type ... - @JsonKey(name: "detail-type") + @JsonKey(name: 'detail-type') final String detailType; /// Detail ... diff --git a/lib/events/cloudwatch_log_event.dart b/lib/events/cloudwatch_log_event.dart index ea7ab929..ff3ce943 100644 --- a/lib/events/cloudwatch_log_event.dart +++ b/lib/events/cloudwatch_log_event.dart @@ -1,3 +1,4 @@ +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 'cloudwatch_log_event.g.dart'; @@ -10,7 +11,7 @@ part 'cloudwatch_log_event.g.dart'; /// Cloudwatch Log Event ... @JsonSerializable() -class AwsCloudwatchLogEvent { +class AwsCloudwatchLogEvent extends Event { /// awslogs ... @JsonKey() final Map awslogs; diff --git a/lib/events/cognito_event.dart b/lib/events/cognito_event.dart index 4c8862f3..a334b5fa 100644 --- a/lib/events/cognito_event.dart +++ b/lib/events/cognito_event.dart @@ -1,9 +1,10 @@ +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 'cognito_event.g.dart'; @JsonSerializable() -class AwsCognitoEvent { +class AwsCognitoEvent extends Event { @JsonKey() final int version; diff --git a/lib/events/dynamodb_event.dart b/lib/events/dynamodb_event.dart index 8ca4e06c..47c552c2 100644 --- a/lib/events/dynamodb_event.dart +++ b/lib/events/dynamodb_event.dart @@ -1,3 +1,4 @@ +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 'dynamodb_event.g.dart'; @@ -5,17 +6,17 @@ part 'dynamodb_event.g.dart'; /// Event send by a DynamoDB stream that contains /// the updated records in the DynamoDB table. @JsonSerializable() -class AwsDynamoDBUpdateRecord { +class AwsDynamoDBUpdateRecord extends Event { /// Keys ... - @JsonKey(name: "Keys") + @JsonKey(name: 'Keys') final Map keys; /// New Image ... - @JsonKey(name: "NewImage") + @JsonKey(name: 'NewImage') final Map oldImage; /// Old Image .... - @JsonKey(name: "OldImage") + @JsonKey(name: 'OldImage') final Map newImage; factory AwsDynamoDBUpdateRecord.fromJson(Map json) => @@ -69,9 +70,9 @@ class AwsDynamoDBUpdateEventRecord { /// DynamoDB Update Event ... @JsonSerializable() -class AwsDynamoDBUpdateEvent { +class AwsDynamoDBUpdateEvent extends Event { /// awslogs ... - @JsonKey(name: "Records") + @JsonKey(name: 'Records') final List records; factory AwsDynamoDBUpdateEvent.fromJson(Map json) => diff --git a/lib/events/kinesis_data_firehose_event.dart b/lib/events/kinesis_data_firehose_event.dart index fcfd8fdb..e58607ef 100644 --- a/lib/events/kinesis_data_firehose_event.dart +++ b/lib/events/kinesis_data_firehose_event.dart @@ -1,10 +1,11 @@ +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 'kinesis_data_firehose_event.g.dart'; /// Kinesis ..... @JsonSerializable() -class AwsKinesisFirehoseData { +class AwsKinesisFirehoseData extends Event { /// Record ID ... @JsonKey() final String recordId; diff --git a/lib/events/kinesis_data_stream_event.dart b/lib/events/kinesis_data_stream_event.dart index c83a0865..f362c7b7 100644 --- a/lib/events/kinesis_data_stream_event.dart +++ b/lib/events/kinesis_data_stream_event.dart @@ -1,10 +1,11 @@ +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 'kinesis_data_stream_event.g.dart'; /// Kinesis ..... @JsonSerializable() -class AwsKinesisDataStream { +class AwsKinesisDataStream extends Event { /// Partition Key ... @JsonKey() final String partitionKey; @@ -93,9 +94,9 @@ class AwsKinesisDataStreamRecord { /// Kinesis Event ... @JsonSerializable() -class AwsKinesisDataStreamEvent { +class AwsKinesisDataStreamEvent extends Event { /// The SQS message records that have been send with the event. - @JsonKey(name: "Records") + @JsonKey(name: 'Records') final List records; factory AwsKinesisDataStreamEvent.fromJson(Map json) { diff --git a/lib/events/s3_event.dart b/lib/events/s3_event.dart index 26af55bf..716081fc 100644 --- a/lib/events/s3_event.dart +++ b/lib/events/s3_event.dart @@ -1,3 +1,4 @@ +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 's3_event.g.dart'; @@ -5,8 +6,8 @@ part 's3_event.g.dart'; /// Representing a recorded S3 Event send to the Lambda. /// This can be send in batches of operations. @JsonSerializable() -class AwsS3Event { - @JsonKey(name: "Records") +class AwsS3Event extends Event { + @JsonKey(name: 'Records') final List records; const AwsS3Event({this.records}); diff --git a/lib/events/sqs_event.dart b/lib/events/sqs_event.dart index b3cc57c8..645845eb 100644 --- a/lib/events/sqs_event.dart +++ b/lib/events/sqs_event.dart @@ -1,10 +1,11 @@ +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; import 'package:json_annotation/json_annotation.dart'; part 'sqs_event.g.dart'; /// SQS Event Record that is send via [AwsSQSEvent]. @JsonSerializable() -class AwsSQSEventRecord { +class AwsSQSEventRecord extends Event { /// Id of the SQS message. @JsonKey() final String messageId; @@ -62,9 +63,9 @@ class AwsSQSEventRecord { /// Event that is send via SQS to trigger for an innovation /// of a Lambda. @JsonSerializable() -class AwsSQSEvent { +class AwsSQSEvent extends Event { /// The SQS message records that have been send with the event. - @JsonKey(name: "Records") + @JsonKey(name: 'Records') final List records; factory AwsSQSEvent.fromJson(Map json) { diff --git a/lib/runtime/context.dart b/lib/runtime/context.dart index 231eaafb..48b8308a 100644 --- a/lib/runtime/context.dart +++ b/lib/runtime/context.dart @@ -1,4 +1,6 @@ import 'dart:io' show Platform; +import 'package:meta/meta.dart'; + import '../client/client.dart'; /// Context contains the Lambda execution context information. @@ -24,10 +26,12 @@ class Context { /// Creates a new [Context] from [NextInvocation] which is the data /// from the Lambda Runtime Interface for the next [Handler] invocation. - static fromNextInvocation(NextInvocation nextInvocation) { + static Context fromNextInvocation(NextInvocation nextInvocation) { return Context( - requestId: nextInvocation.requestId, - invokedFunction: nextInvocation.invokedFunctionArn); + handler: Client.handlerName, + requestId: nextInvocation.requestId, + invokedFunction: nextInvocation.invokedFunctionArn, + ); } /// Handler that is used for the invocation of the function @@ -70,26 +74,27 @@ class Context { /// The ARN to identify the function. String invokedFunctionArn; - Context( - {String handler, - String functionName, - String functionMemorySize, - String logGroupName, - String logStreamName, - String requestId, - String invokedFunction, - String region, - String executionEnv, - String accessKey, - String secretAccessKey, - String sessionToken}) { + Context({ + @required String handler, + String functionName, + String functionMemorySize, + String logGroupName, + String logStreamName, + @required String requestId, + String invokedFunction, + String region, + String executionEnv, + String accessKey, + String secretAccessKey, + String sessionToken, + }) { assert(requestId != null); assert(handler != null); this.handler = handler ?? Platform.environment[_kAWSLambdaHandler]; this.functionName = functionName ?? Platform.environment[_kAWSLambdaFunctionName]; - this.functionVersion = + functionVersion = functionVersion ?? Platform.environment[_kAWSLambdaFunctionVersion]; this.functionMemorySize = functionMemorySize ?? Platform.environment[_kAWSLambdaFunctionMemorySize]; @@ -98,7 +103,7 @@ class Context { this.logStreamName = logStreamName ?? Platform.environment[_kAWSLambdaLogStreamName]; this.requestId = requestId; - this.invokedFunctionArn = invokedFunctionArn; + invokedFunctionArn = invokedFunctionArn; this.region = region ?? Platform.environment[_kAWSLambdaRegion]; this.executionEnv = executionEnv ?? Platform.environment[_kAWSLambdaExecutionEnv]; @@ -110,19 +115,20 @@ class Context { } /// Allows to copy a created [Context] over with some new settings. - Context copyWith( - {String handler, - String functionName, - String functionMemorySize, - String logGroupName, - String logStreamName, - String requestId, - String invokedFunction, - String region, - String executionEnv, - String accessKey, - String secretAccessKey, - String sessionToken}) { + Context copyWith({ + String handler, + String functionName, + String functionMemorySize, + String logGroupName, + String logStreamName, + String requestId, + String invokedFunction, + String region, + String executionEnv, + String accessKey, + String secretAccessKey, + String sessionToken, + }) { return Context( handler: handler ?? this.handler, functionName: functionName ?? this.functionName, @@ -130,7 +136,7 @@ class Context { logGroupName: logGroupName ?? this.logGroupName, logStreamName: logStreamName ?? this.logStreamName, requestId: requestId ?? this.requestId, - invokedFunction: invokedFunction ?? this.invokedFunctionArn, + invokedFunction: invokedFunction ?? invokedFunctionArn, region: region ?? this.region, executionEnv: executionEnv ?? this.executionEnv, accessKey: accessKey ?? this.accessKey, diff --git a/lib/runtime/event.dart b/lib/runtime/event.dart index 3802e2cb..3174f5a0 100644 --- a/lib/runtime/event.dart +++ b/lib/runtime/event.dart @@ -1,3 +1,5 @@ +import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; + import '../events/alb_event.dart'; import '../events/alexa_event.dart'; import '../events/apigateway_event.dart'; @@ -16,7 +18,8 @@ import '../events/kinesis_data_stream_event.dart'; /// Note is currently not supported to register your /// own events here. abstract class Event { - static final _registry = { + const Event(); + static final Map)> _registry = { AwsCognitoEvent: (Map json) => AwsCognitoEvent.fromJson(json), AwsS3Event: (Map json) => AwsS3Event.fromJson(json), @@ -38,28 +41,23 @@ abstract class Event { }; /// Checks if a type of event is already registered. - static exists() { + static bool exists() { return Event._registry.containsKey(T); } /// Returs the value of a registered event. It is [null] /// if no such event has been registered. - static value() { - return Event._registry[T]; - } + static T Function(Map) value() => + Event._registry[T] as T Function(Map); /// Registers an event. - static registerEvent(func) { - Event._registry[T] = func; - } + static void registerEvent(T Function(Map) func) => + Event._registry[T] = func; /// Deregisters an event. - static deregisterEvent() { - Event._registry.remove(T); - } + static void deregisterEvent() => Event._registry.remove(T); /// Creates a new event from a handler type with the [NextInvocation.response]. - static fromHandler(Type type, Map json) { - return _registry[type](json); - } + static dynamic fromHandler(Type type, Map json) => + _registry[type](json); } diff --git a/lib/runtime/exception.dart b/lib/runtime/exception.dart index b655b0ad..b774f1e1 100644 --- a/lib/runtime/exception.dart +++ b/lib/runtime/exception.dart @@ -7,5 +7,5 @@ class RuntimeException implements Exception { final String cause; @override - String toString() => "RuntimeException: $cause"; + String toString() => 'RuntimeException: $cause'; } diff --git a/lib/runtime/runtime.dart b/lib/runtime/runtime.dart index 106d7474..5b0c4922 100644 --- a/lib/runtime/runtime.dart +++ b/lib/runtime/runtime.dart @@ -1,15 +1,17 @@ import 'dart:async'; -import '../client/client.dart'; -import 'event.dart'; -import 'context.dart'; -import 'exception.dart'; +import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; +import 'package:logging/logging.dart'; + +import 'package:aws_lambda_dart_runtime/client/client.dart'; +import 'package:aws_lambda_dart_runtime/runtime/event.dart'; +import 'package:aws_lambda_dart_runtime/runtime/context.dart'; +import 'package:aws_lambda_dart_runtime/runtime/exception.dart'; /// A function which ingests and Event and a [Context] /// and returns a [InvocationResult]. The result is ecoded /// by the [Runtime] and posted to the Lambda API. -typedef Handler = Future Function( - Context context, T event); +typedef Handler = Future Function(Context context, E event); class _RuntimeHandler { final Type type; @@ -37,8 +39,9 @@ class _RuntimeHandler { /// ``` /// /// Note: You can register an -class Runtime { +class Runtime { Client _client; + final Logger _logger = Logger('Runtime'); static final Runtime _singleton = Runtime._internal(); final Map _handlers = {}; @@ -49,12 +52,13 @@ class Runtime { Runtime._internal() { _client = Client(); + _logger.finest('Created Client for handler: ${Client.handlerName}'); } /// Lists the registered handlers by name. /// The name is a simple [String] which reflects /// the name of the trigger in the Lambda Execution API. - List get handlers => _handlers.keys; + List get handlers => _handlers.keys.toList(); /// Checks if a specific handlers has been registered /// with the runtime. @@ -62,27 +66,24 @@ class Runtime { /// Register a handler function [Handler] with [name] /// which digests an event [T]. - Handler registerHandler(String name, Handler handler) { - _handlers[name] = _RuntimeHandler(T, handler); + Handler registerHandler(String name, Handler handler) { + _handlers[name] = _RuntimeHandler(E, handler); - return _handlers[name].handler; + return handler; } - /// Unregister a handler function [Handler] with [name]. - Handler deregisterHandler(String name) => - _handlers.remove(name).handler; + /// Unregister a handler function [Handler] with [name]. + Handler deregisterHandler(String name) => + _handlers.remove(name).handler as Handler; /// Register an new event to be ingested by a handler. /// The type should reflect your type in your handler definition [Handler]. - void registerEvent(func) { - Event.registerEvent(func); - } + void registerEvent(T Function(Map) func) => + Event.registerEvent(func); /// Deregister an new event to be ingested by a handler. /// The type should reflect your type in your handler definition [Handler]. - void deregisterEvent() { - Event.deregisterEvent(); - } + void deregisterEvent() => Event.deregisterEvent(); /// Run the [Runtime] in loop and digest events that are /// fetched from the AWS Lambda API Interface. The events are processed @@ -90,34 +91,41 @@ class Runtime { /// /// If the invocation of an event was successful the function /// sends the [InvocationResult] via [_client.postInvocationResponse(result)] to the API. - /// If there is an error during the execution. The execption gets catched + /// If there is an error during the execution. The execution gets caught /// and the error is posted via [_client.postInvocationError(err)] to the API. void invoke() async { - do { - NextInvocation nextInvocation; - + _logger.finest('Invoked'); + while (true) { try { - // get the next invocation - nextInvocation = await _client.getNextInvocation(); - - // creating the new context - final context = Context.fromNextInvocation(nextInvocation); - - final func = _handlers[context.handler]; - if(func == null) { - throw RuntimeException('No handler with name "${context.handler}" registered in runtime!'); - } - final event = - Event.fromHandler(func.type, await nextInvocation.response); - final result = await func.handler(context, event); - - await _client.postInvocationResponse(result); + _logger.finest('Getting next invocation'); + _handleInvocation(await _client.getNextInvocation()); } on Exception catch (error, stacktrace) { - await _client.postInvocationError( - nextInvocation.requestId, InvocationError(error, stacktrace)); + // inner try-catch didn't succeed posting invocation error + // without throwing, all hope is lost, log and crash + _logger.severe('Invocation handling failed', error, stacktrace); + rethrow; } + } + } - nextInvocation = null; - } while (true); + void _handleInvocation(NextInvocation nextInvocation) async { + try { + // creating the new context + final context = Context.fromNextInvocation(nextInvocation); + + final func = _handlers[context.handler]; + if (func == null) { + throw RuntimeException( + 'No handler with name "${context.handler}" registered in runtime!'); + } + final event = Event.fromHandler(func.type, await nextInvocation.response); + final result = await func.handler(context, event); + + await _client.postInvocationResponse(context.requestId, result); + } catch (error, stacktrace) { + _logger.severe('Error in invocation', error, stacktrace); + await _client.postInvocationError( + nextInvocation?.requestId, InvocationError(error, stacktrace)); + } } } diff --git a/pubspec.yaml b/pubspec.yaml index a2ea236f..259fc16b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,10 +10,15 @@ environment: dependencies: json_annotation: ^3.0.0 + meta: ^1.2.3 + http: ^0.12.2 + http_extensions: ^0.1.2 + http_extensions_log: ^0.1.2 + logging: ^0.11.4 dev_dependencies: json_serializable: ^3.2.3 build_runner: ^1.7.2 test: ^1.9.4 mockito: ^4.1.1 - pedantic: ^1.0.0 + pedantic: ^1.9.2 diff --git a/test/alb_event_test.dart b/test/alb_event_test.dart index 5917753c..9ce66c04 100644 --- a/test/alb_event_test.dart +++ b/test/alb_event_test.dart @@ -1,46 +1,46 @@ import 'dart:io'; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; -import "package:test/test.dart"; +import 'package:test/test.dart'; void main() { - group("albevent_default", () { - test("factory creates response with default values", () { - final response = AwsALBResponse.fromString("SUCCESS"); + group('albevent_default', () { + test('factory creates response with default values', () { + final response = AwsALBResponse.fromString('SUCCESS'); - expect(response.body, equals("SUCCESS")); + expect(response.body, equals('SUCCESS')); expect(response.statusCode, equals(200)); expect(response.isBase64Encoded, equals(false)); }); - test("factory creates a response with HTTP Status 400", () { - final response = AwsALBResponse.fromString("SUCCESS", + test('factory creates a response with HTTP Status 400', () { + final response = AwsALBResponse.fromString('SUCCESS', statusCode: HttpStatus.badRequest); - expect(response.body, equals("SUCCESS")); + expect(response.body, equals('SUCCESS')); expect(response.statusCode, equals(HttpStatus.badRequest)); expect(response.isBase64Encoded, equals(false)); }); - test("factory creates an event with html/text Header", () { - final response = AwsALBResponse.fromString(""); + test('factory creates an event with html/text Header', () { + final response = AwsALBResponse.fromString(''); expect(response.headers, - equals({"Content-Type": "text/html; charset=utf-8"})); + equals({'Content-Type': 'text/html; charset=utf-8'})); }); - test("factory creates an event with JSON Header", () { - final response = AwsALBResponse.fromString("", - headers: {"Content-Type": "application/json"}); + test('factory creates an event with JSON Header', () { + final response = AwsALBResponse.fromString('', + headers: {'Content-Type': 'application/json'}); - expect(response.headers, equals({"Content-Type": "application/json"})); + expect(response.headers, equals({'Content-Type': 'application/json'})); }); - test("factory creates response which is base64 encoded", () { + test('factory creates response which is base64 encoded', () { final response = - AwsALBResponse.fromString("SUCCESS", isBase64Encoded: true); + AwsALBResponse.fromString('SUCCESS', isBase64Encoded: true); - expect(response.body, equals("SUCCESS")); + expect(response.body, equals('SUCCESS')); expect(response.statusCode, equals(HttpStatus.ok)); expect(response.isBase64Encoded, equals(true)); }); diff --git a/test/apigateway_event_test.dart b/test/apigateway_event_test.dart index 09d7dc02..2670e6c7 100644 --- a/test/apigateway_event_test.dart +++ b/test/apigateway_event_test.dart @@ -3,32 +3,32 @@ import 'dart:io' show File; import 'dart:convert'; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; -import "package:test/test.dart"; +import 'package:test/test.dart'; final file = 'data/apigateway_event.json'; -final String contents = new File(file).readAsStringSync(); -final Map json = jsonDecode(contents); +final String contents = File(file).readAsStringSync(); +final Map json = jsonDecode(contents) as Map; void main() { - group("apigateway_default", () { - test("json got parsed and creates an event", () async { + group('apigateway_default', () { + test('json got parsed and creates an event', () async { final event = AwsApiGatewayEvent.fromJson(json); - expect(event.body, equals(jsonEncode({"foo": "bar"}))); - expect(event.path, equals("/test/hello")); + expect(event.body, equals(jsonEncode({'foo': 'bar'}))); + expect(event.path, equals('/test/hello')); expect(event.headers.acceptEncoding, - equals("gzip, deflate, lzma, sdch, br")); - expect(event.requestContext.httpMethod, equals("POST")); - expect(event.requestContext.accountId, equals("123456789012")); + equals('gzip, deflate, lzma, sdch, br')); + expect(event.requestContext.httpMethod, equals('POST')); + expect(event.requestContext.accountId, equals('123456789012')); expect(event.requestContext.requestId, - equals("41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9")); - expect(event.queryStringParameters, equals({"name": "me"})); - expect(event.requestContext.resourcePath, equals("/{proxy+}")); + equals('41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9')); + expect(event.queryStringParameters, equals({'name': 'me'})); + expect(event.requestContext.resourcePath, equals('/{proxy+}')); expect(event.headers.raw['Accept-Encoding'], - equals("gzip, deflate, lzma, sdch, br")); + equals('gzip, deflate, lzma, sdch, br')); }); - test("factory creates event with default values", () { + test('factory creates event with default values', () { final response = AwsApiGatewayResponse.fromJson({}); expect(response.body, equals({}.toString())); @@ -36,7 +36,7 @@ void main() { expect(response.isBase64Encoded, equals(false)); }); - test("factory creates an event with HTTP Status 400", () { + test('factory creates an event with HTTP Status 400', () { final response = AwsApiGatewayResponse.fromJson({}, statusCode: HttpStatus.badRequest); @@ -45,21 +45,21 @@ void main() { expect(response.isBase64Encoded, equals(false)); }); - test("factory creates an event with JSON Header", () { + test('factory creates an event with JSON Header', () { final response = AwsApiGatewayResponse.fromJson({}); - expect(response.headers, equals({"Content-Type": "application/json"})); + expect(response.headers, equals({'Content-Type': 'application/json'})); }); - test("factory creates an event with text/html Header", () { + test('factory creates an event with text/html Header', () { final response = AwsApiGatewayResponse.fromJson({}, - headers: {"Content-Type": "text/html; charset=utf-8"}); + headers: {'Content-Type': 'text/html; charset=utf-8'}); expect(response.headers, - equals({"Content-Type": "text/html; charset=utf-8"})); + equals({'Content-Type': 'text/html; charset=utf-8'})); }); - test("factory creates an event with is based 64 encoded", () { + test('factory creates an event with is based 64 encoded', () { final response = AwsApiGatewayResponse.fromJson({}, isBase64Encoded: true); diff --git a/test/client_test.dart b/test/client_test.dart index 6552fe6f..1a3d2f5e 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -1,20 +1,20 @@ -import "package:test/test.dart"; +import 'package:test/test.dart'; import 'package:aws_lambda_dart_runtime/client/client.dart'; void main() { group('invocation', () { - test("invocation result gets populated", () { - final result = InvocationResult("1234567890", "SUCCESS"); + test('invocation result gets populated', () { + final result = InvocationResult('1234567890', 'SUCCESS'); - expect(result.requestId, "1234567890"); - expect(result.body, "SUCCESS"); + expect(result.requestId, '1234567890'); + expect(result.body, 'SUCCESS'); }); - test("invocation error gets populated", () { - final stateError = new StateError("foo"); + test('invocation error gets populated', () { + final stateError = StateError('foo'); final invocationError = - InvocationError(stateError, new StackTrace.fromString("")); + InvocationError(stateError, StackTrace.fromString('')); expect(invocationError.error, stateError); }); diff --git a/test/cloudwatch_event_test.dart b/test/cloudwatch_event_test.dart index 42717a24..61e261f5 100644 --- a/test/cloudwatch_event_test.dart +++ b/test/cloudwatch_event_test.dart @@ -2,24 +2,24 @@ import 'dart:convert'; import 'dart:io' show File; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; -import "package:test/test.dart"; +import 'package:test/test.dart'; final file = 'data/cloudwatch_event.json'; -final String contents = new File(file).readAsStringSync(); -final Map json = jsonDecode(contents); +final String contents = File(file).readAsStringSync(); +final json = jsonDecode(contents) as Map; void main() { - group("cloudwatch_default", () { - test("json got parsed and creates an event", () async { + group('cloudwatch_default', () { + test('json got parsed and creates an event', () async { final event = AwsCloudwatchEvent.fromJson(json); - expect(event.account, equals("1234567890")); - expect(event.region, equals("eu-west-1")); - expect(event.detailType, equals("Scheduled Event")); - expect(event.id, equals("cdc73f9d-aea9-11e3-9d5a-835b769c0d9c")); + expect(event.account, equals('1234567890')); + expect(event.region, equals('eu-west-1')); + expect(event.detailType, equals('Scheduled Event')); + expect(event.id, equals('cdc73f9d-aea9-11e3-9d5a-835b769c0d9c')); expect(event.detail, equals({})); - expect(event.time, equals(DateTime.parse("1970-01-01T00:00:00Z"))); + expect(event.time, equals(DateTime.parse('1970-01-01T00:00:00Z'))); }); }); } diff --git a/test/cloudwatch_log_event_test.dart b/test/cloudwatch_log_event_test.dart index 316e4c5f..2aa28377 100644 --- a/test/cloudwatch_log_event_test.dart +++ b/test/cloudwatch_log_event_test.dart @@ -2,23 +2,23 @@ import 'dart:convert'; import 'dart:io' show File; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; -import "package:test/test.dart"; +import 'package:test/test.dart'; final file = 'data/cloudwatch_log_event.json'; -final String contents = new File(file).readAsStringSync(); -final Map json = jsonDecode(contents); +final String contents = File(file).readAsStringSync(); +final json = jsonDecode(contents) as Map; void main() { - group("cloudwatch_log_default", () { - test("json got parsed and creates an event", () async { + group('cloudwatch_log_default', () { + test('json got parsed and creates an event', () async { final event = AwsCloudwatchLogEvent.fromJson(json); expect( event.awslogs, equals({ - "data": - "H4sIAAAAAAAAAHWPwQqCQBCGX0Xm7EFtK+smZBEUgXoLCdMhFtKV3akI8d0bLYmibvPPN3wz00CJxmQnTO41whwWQRIctmEcB6sQbFC3CjW3XW8kxpOpP+OC22d1Wml1qZkQGtoMsScxaczKN3plG8zlaHIta5KqWsozoTYw3/djzwhpLwivWFGHGpAFe7DL68JlBUk+l7KSN7tCOEJ4M3/qOI49vMHj+zCKdlFqLaU2ZHV2a4Ct/an0/ivdX8oYc1UVX860fQDQiMdxRQEAAA==" + 'data': + 'H4sIAAAAAAAAAHWPwQqCQBCGX0Xm7EFtK+smZBEUgXoLCdMhFtKV3akI8d0bLYmibvPPN3wz00CJxmQnTO41whwWQRIctmEcB6sQbFC3CjW3XW8kxpOpP+OC22d1Wml1qZkQGtoMsScxaczKN3plG8zlaHIta5KqWsozoTYw3/djzwhpLwivWFGHGpAFe7DL68JlBUk+l7KSN7tCOEJ4M3/qOI49vMHj+zCKdlFqLaU2ZHV2a4Ct/an0/ivdX8oYc1UVX860fQDQiMdxRQEAAA==' })); }); }); diff --git a/test/cognito_event_test.dart b/test/cognito_event_test.dart index 24ba6966..2ebc2aec 100644 --- a/test/cognito_event_test.dart +++ b/test/cognito_event_test.dart @@ -2,24 +2,24 @@ import 'dart:convert'; import 'dart:io' show File; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; -import "package:test/test.dart"; +import 'package:test/test.dart'; final file = 'data/cognito_event.json'; -final String contents = new File(file).readAsStringSync(); -final Map json = jsonDecode(contents); +final String contents = File(file).readAsStringSync(); +final json = jsonDecode(contents) as Map; void main() { - group("cognito_default", () { - test("json got parsed and creates an event", () async { + group('cognito_default', () { + test('json got parsed and creates an event', () async { final event = AwsCognitoEvent.fromJson(json); expect(event.version, equals(1)); - expect(event.userPoolId, equals("1234567")); - expect(event.userName, equals("foo")); - expect(event.response.smsMessage, equals("foo")); - expect(event.response.emailSubject, equals("foo")); - expect(event.response.emailMessage, equals("bar")); + expect(event.userPoolId, equals('1234567')); + expect(event.userName, equals('foo')); + expect(event.response.smsMessage, equals('foo')); + expect(event.response.emailSubject, equals('foo')); + expect(event.response.emailMessage, equals('bar')); }); }); } diff --git a/test/common.dart b/test/common.dart deleted file mode 100644 index 291d16c5..00000000 --- a/test/common.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'dart:io'; - -import 'package:mockito/mockito.dart'; - -class MockHttpClientResponse extends Mock implements HttpClientResponse { - MockHttpClientResponse(this.statusCode, - {this.result, this.headers, this.body}); - - @override - final int statusCode; - - final String result; - - @override - final HttpHeaders headers; - - // encode the response body as bytes. - final List body; -} - -/// A mocked [HttpHeaders] that ignores all writes. -class MockHttpHeaders extends HttpHeaders { - @override - List operator [](String name) => []; - - @override - void add(String name, Object value, {bool preserveHeaderCase = false}) {} - - @override - void clear() {} - - @override - void forEach(void Function(String name, List values) f) {} - - @override - void noFolding(String name) {} - - @override - void remove(String name, Object value) {} - - @override - void removeAll(String name) {} - - @override - void set(String name, Object value, {bool preserveHeaderCase = false}) {} - - @override - String value(String name) => null; -} diff --git a/test/context_test.dart b/test/context_test.dart index d6bfaad8..d587bd76 100644 --- a/test/context_test.dart +++ b/test/context_test.dart @@ -1,48 +1,43 @@ -import "dart:convert"; - -import "package:test/test.dart"; +import 'package:http/http.dart'; +import 'package:test/test.dart'; import 'package:aws_lambda_dart_runtime/runtime/context.dart'; import 'package:aws_lambda_dart_runtime/client/client.dart'; -import 'common.dart'; void main() { group('context', () { - test("Context gets initialized with a Platform.environment", () async { - final Map environment = { - "_HANDLER": "foo", - "AWS_LAMBDA_FUNCTION_NAME": "bar", - "AWS_LAMBDA_FUNCTION_VERSION": "1", - "AWS_LAMBDA_LOG_GROUP_NAME": "foo", - "AWS_LAMBDA_LOG_STREAM_NAME": "foo", - "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "128", - "AWS_REGION": "eu-west-1", - "AWS_EXECUTION_ENV": "foo", - "AWS_ACCESS_KEY_ID": "secret", - "AWS_SECRET_ACCESS_KEY": "key", - "AWS_SESSION_TOKEN": "1234567890" + test('Context gets initialized with a Platform.environment', () async { + final environment = { + '_HANDLER': 'foo', + 'AWS_LAMBDA_FUNCTION_NAME': 'bar', + 'AWS_LAMBDA_FUNCTION_VERSION': '1', + 'AWS_LAMBDA_LOG_GROUP_NAME': 'foo', + 'AWS_LAMBDA_LOG_STREAM_NAME': 'foo', + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE': '128', + 'AWS_REGION': 'eu-west-1', + 'AWS_EXECUTION_ENV': 'foo', + 'AWS_ACCESS_KEY_ID': 'secret', + 'AWS_SECRET_ACCESS_KEY': 'key', + 'AWS_SESSION_TOKEN': '1234567890' }; - final headers = MockHttpHeaders(); - - final response = MockHttpClientResponse(200, - result: "", headers: headers, body: utf8.encode("{}")); - final nextInvocation = await NextInvocation.fromResponse(response); + final nextInvocation = + await NextInvocation.fromResponse(Response('{}', 200)); final ctx = Context.fromNextInvocation(nextInvocation); - expect(ctx.handler, environment["_HANDLER"]); - expect(ctx.functionName, environment["AWS_LAMBDA_FUNCTION_NAME"]); - expect(ctx.functionVersion, environment["AWS_LAMBDA_FUNCTION_VERSION"]); - expect(ctx.logGroupName, environment["AWS_LAMBDA_LOG_GROUP_NAME"]); - expect(ctx.logStreamName, environment["AWS_LAMBDA_LOG_STREAM_NAME"]); + expect(ctx.handler, environment['_HANDLER']); + expect(ctx.functionName, environment['AWS_LAMBDA_FUNCTION_NAME']); + expect(ctx.functionVersion, environment['AWS_LAMBDA_FUNCTION_VERSION']); + expect(ctx.logGroupName, environment['AWS_LAMBDA_LOG_GROUP_NAME']); + expect(ctx.logStreamName, environment['AWS_LAMBDA_LOG_STREAM_NAME']); expect(ctx.functionMemorySize, - environment["AWS_LAMBDA_FUNCTION_MEMORY_SIZE"]); - expect(ctx.region, environment["AWS_REGION"]); - expect(ctx.executionEnv, environment["AWS_EXECUTION_ENV"]); - expect(ctx.accessKey, environment["AWS_ACCESS_KEY_ID"]); - expect(ctx.secretAccessKey, environment["AWS_SECRET_ACCESS_KEY"]); - expect(ctx.sessionToken, environment["AWS_SESSION_TOKEN"]); + environment['AWS_LAMBDA_FUNCTION_MEMORY_SIZE']); + expect(ctx.region, environment['AWS_REGION']); + expect(ctx.executionEnv, environment['AWS_EXECUTION_ENV']); + expect(ctx.accessKey, environment['AWS_ACCESS_KEY_ID']); + expect(ctx.secretAccessKey, environment['AWS_SECRET_ACCESS_KEY']); + expect(ctx.sessionToken, environment['AWS_SESSION_TOKEN']); }); - }, skip: "the tests are not quite ready"); + }, skip: 'the tests are not quite ready'); } diff --git a/test/dynamodb_event_test.dart b/test/dynamodb_event_test.dart index 31ebb878..cce51a4f 100644 --- a/test/dynamodb_event_test.dart +++ b/test/dynamodb_event_test.dart @@ -2,16 +2,16 @@ import 'dart:convert'; import 'dart:io' show File; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; -import "package:test/test.dart"; +import 'package:test/test.dart'; final file = 'data/dynamodb_event.json'; -final String contents = new File(file).readAsStringSync(); -final Map json = jsonDecode(contents); +final String contents = File(file).readAsStringSync(); +final json = jsonDecode(contents) as Map; void main() { - group("dynamodb_default", () { - test("json got parsed and creates an event", () async { + group('dynamodb_default', () { + test('json got parsed and creates an event', () async { final event = AwsDynamoDBUpdateEvent.fromJson(json); expect(event.records.length, equals(1)); diff --git a/test/event_test.dart b/test/event_test.dart index f7f7c416..a79c2d88 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -1,8 +1,8 @@ -import "package:test/test.dart"; +import 'package:test/test.dart'; import 'package:aws_lambda_dart_runtime/runtime/event.dart'; -class CustomTestEvent { +class CustomTestEvent extends Event { factory CustomTestEvent.fromJson(Map json) { return CustomTestEvent(); } @@ -12,14 +12,14 @@ class CustomTestEvent { void main() { group('runtime', () { - test("Custom event is add to the events", () { + test('Custom event is add to the events', () { Event.registerEvent( (Map json) => CustomTestEvent.fromJson({})); expect(Event.exists(), true); }); - test("Custom event is deregistered", () { + test('Custom event is deregistered', () { Event.registerEvent( (Map json) => CustomTestEvent.fromJson({})); diff --git a/test/exception_test.dart b/test/exception_test.dart index 9d620fbd..e98457c0 100644 --- a/test/exception_test.dart +++ b/test/exception_test.dart @@ -1,17 +1,17 @@ -import "package:test/test.dart"; +import 'package:test/test.dart'; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; void main() { - group("runtime exception", () { - test("return the cause a string", () { - final exception = RuntimeException("missing handler"); - expect(exception.toString(), equals("RuntimeException: missing handler")); + group('runtime exception', () { + test('return the cause a string', () { + final exception = RuntimeException('missing handler'); + expect(exception.toString(), equals('RuntimeException: missing handler')); }); - test("catch exception with cause", () { + test('catch exception with cause', () { try { - throw RuntimeException("missing handler"); + throw RuntimeException('missing handler'); } on RuntimeException catch (e) { expect(e.cause, 'missing handler'); return; diff --git a/test/kinesis_data_firehose_event.dart b/test/kinesis_data_firehose_event.dart index 34a7f702..61130c7a 100644 --- a/test/kinesis_data_firehose_event.dart +++ b/test/kinesis_data_firehose_event.dart @@ -2,28 +2,28 @@ import 'dart:convert'; import 'dart:io' show File; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; -import "package:test/test.dart"; +import 'package:test/test.dart'; final file = 'data/kinesis_data_firehose_event.json'; -final String contents = new File(file).readAsStringSync(); -final Map json = jsonDecode(contents); +final String contents = File(file).readAsStringSync(); +final json = jsonDecode(contents) as Map; void main() { - group("kinesis_firehose_default", () { - test("json got parsed and creates an event", () async { + group('kinesis_firehose_default', () { + test('json got parsed and creates an event', () async { final event = AwsKinesisFirehoseDataEvent.fromJson(json); expect(event.records.length, equals(1)); - expect(event.invocationId, equals("invocationIdExample")); - expect(event.deliveryStreamArn, equals("arn:aws:kinesis:EXAMPLE")); - expect(event.region, equals("eu-west-1")); + expect(event.invocationId, equals('invocationIdExample')); + expect(event.deliveryStreamArn, equals('arn:aws:kinesis:EXAMPLE')); + expect(event.region, equals('eu-west-1')); expect(event.records[0].recordId, - equals("49546986683135544286507457936321625675700192471156785154")); + equals('49546986683135544286507457936321625675700192471156785154')); expect( event.records[0].approximateArrivalTimestamp, equals(1495072949453)); expect(event.records[0].data, - equals("SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=")); + equals('SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=')); }); }); } diff --git a/test/kinesis_data_stream_event_test.dart b/test/kinesis_data_stream_event_test.dart index dc082785..e36b4df7 100644 --- a/test/kinesis_data_stream_event_test.dart +++ b/test/kinesis_data_stream_event_test.dart @@ -2,35 +2,35 @@ import 'dart:convert'; import 'dart:io' show File; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; -import "package:test/test.dart"; +import 'package:test/test.dart'; final file = 'data/kinesis_data_stream_event.json'; -final String contents = new File(file).readAsStringSync(); -final Map json = jsonDecode(contents); +final String contents = File(file).readAsStringSync(); +final json = jsonDecode(contents) as Map; void main() { - group("kinesis_default", () { - test("json got parsed and creates an event", () async { + group('kinesis_default', () { + test('json got parsed and creates an event', () async { final event = AwsKinesisDataStreamEvent.fromJson(json); expect(event.records.length, equals(1)); - expect(event.records[0].eventSource, equals("aws:kinesis")); + expect(event.records[0].eventSource, equals('aws:kinesis')); expect( event.records[0].eventID, equals( - "shardId-000000000000:49545115243490985018280067714973144582180062593244200961")); + 'shardId-000000000000:49545115243490985018280067714973144582180062593244200961')); expect( - event.records[0].eventSourceARN, equals("arn:aws:kinesis:EXAMPLE")); - expect(event.records[0].awsRegion, equals("eu-west-1")); - expect(event.records[0].eventVersion, equals("1.0")); + event.records[0].eventSourceARN, equals('arn:aws:kinesis:EXAMPLE')); + expect(event.records[0].awsRegion, equals('eu-west-1')); + expect(event.records[0].eventVersion, equals('1.0')); expect( - event.records[0].invokeIdentityArn, equals("arn:aws:iam::EXAMPLE")); - expect(event.records[0].kinesis.partitionKey, equals("partitionKey-03")); + event.records[0].invokeIdentityArn, equals('arn:aws:iam::EXAMPLE')); + expect(event.records[0].kinesis.partitionKey, equals('partitionKey-03')); expect(event.records[0].kinesis.data, - equals("SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=")); + equals('SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=')); expect(event.records[0].kinesis.sequenceNumber, - equals("49545115243490985018280067714973144582180062593244200961")); + equals('49545115243490985018280067714973144582180062593244200961')); }); }); } diff --git a/test/runtime_test.dart b/test/runtime_test.dart index ecc614ae..ec99735b 100644 --- a/test/runtime_test.dart +++ b/test/runtime_test.dart @@ -1,44 +1,43 @@ -import "package:test/test.dart"; +import 'package:test/test.dart'; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; void main() { group('runtime', () { - test("instance is created without error", () { + test('instance is created without error', () { expect(() => Runtime(), returnsNormally); }); - test("instance is same accross invocation", () async { + test('instance is same across invocation', () async { final runtime = await Runtime(); expect(runtime, await Runtime()); }); - test("successfully add a handler to runtime", () async { + test('successfully add a handler to runtime', () async { final runtime = await Runtime(); - final Handler testHandler = (context, event) async { - return new InvocationResult(context.requestId, "HELLO WORLD"); + final Handler testHandler = (context, event) async { + return null; }; - final addHandler = runtime.registerHandler("test.handler", testHandler); + final addHandler = runtime.registerHandler('test.handler', testHandler); - expect(runtime.handlerExists("test.handler"), equals(true)); + expect(runtime.handlerExists('test.handler'), equals(true)); expect(addHandler, equals(testHandler)); }); - test("successfully deregister a handler to runtime", () async { + test('successfully deregister a handler to runtime', () async { final runtime = await Runtime(); - final Handler testHandler = (context, event) async { - return new InvocationResult(context.requestId, "HELLO WORLD"); + final testHandler = (context, event) async { + return null; }; - runtime.registerHandler("test.handler", testHandler); - final removedHandler = - runtime.deregisterHandler("test.handler"); + runtime.registerHandler('test.handler', testHandler); + final removedHandler = runtime.deregisterHandler('test.handler'); - expect(runtime.handlerExists("test.handler"), equals(false)); + expect(runtime.handlerExists('test.handler'), equals(false)); expect(removedHandler, equals(testHandler)); }); }); diff --git a/test/s3_event.dart b/test/s3_event.dart index 3f5a3d41..26bd867f 100644 --- a/test/s3_event.dart +++ b/test/s3_event.dart @@ -2,29 +2,29 @@ import 'dart:convert'; import 'dart:io' show File; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; -import "package:test/test.dart"; +import 'package:test/test.dart'; final file = 'data/s3_event.json'; -final String contents = new File(file).readAsStringSync(); -final Map json = jsonDecode(contents); +final String contents = File(file).readAsStringSync(); +final json = jsonDecode(contents) as Map; void main() { - group("s3_default", () { - test("json got parsed and creates an event", () async { + group('s3_default', () { + test('json got parsed and creates an event', () async { final event = AwsS3Event.fromJson(json); - expect(event.records[0].eventVersion, equals("2.0")); - expect(event.records[0].eventSource, equals("aws:s3")); - expect(event.records[0].awsRegion, equals("eu-west-1")); + expect(event.records[0].eventVersion, equals('2.0')); + expect(event.records[0].eventSource, equals('aws:s3')); + expect(event.records[0].awsRegion, equals('eu-west-1')); expect(event.records[0].eventTime, - equals(DateTime.parse("1970-01-01T00:00:00.000Z"))); - expect(event.records[0].eventName, equals("ObjectCreated:Put")); - expect(event.records[0].userIdentity.principalId, equals("EXAMPLE")); - expect(event.records[0].requestParameters["sourceIPAddress"], - equals("127.0.0.1")); - expect(event.records[0].s3.s3SchemaVersion, equals("1.0")); - expect(event.records[0].s3.configurationId, equals("testConfigRule")); + equals(DateTime.parse('1970-01-01T00:00:00.000Z'))); + expect(event.records[0].eventName, equals('ObjectCreated:Put')); + expect(event.records[0].userIdentity.principalId, equals('EXAMPLE')); + expect(event.records[0].requestParameters['sourceIPAddress'], + equals('127.0.0.1')); + expect(event.records[0].s3.s3SchemaVersion, equals('1.0')); + expect(event.records[0].s3.configurationId, equals('testConfigRule')); }); }); } diff --git a/test/sqs_event_test.dart b/test/sqs_event_test.dart index 64f5f088..a7c14918 100644 --- a/test/sqs_event_test.dart +++ b/test/sqs_event_test.dart @@ -2,29 +2,29 @@ import 'dart:convert'; import 'dart:io' show File; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; -import "package:test/test.dart"; +import 'package:test/test.dart'; final file = 'data/sqs_event.json'; -final String contents = new File(file).readAsStringSync(); -final Map json = jsonDecode(contents); +final String contents = File(file).readAsStringSync(); +final json = jsonDecode(contents) as Map; void main() { - group("sqs_default", () { - test("json got parsed and creates an event", () async { + group('sqs_default', () { + test('json got parsed and creates an event', () async { final event = AwsSQSEvent.fromJson(json); expect(event.records.length, equals(1)); expect(event.records[0].md5OfBody, - equals("7b270e59b47ff90a553787216d55d91d")); - expect(event.records[0].eventSource, equals("aws:sqs")); + equals('7b270e59b47ff90a553787216d55d91d')); + expect(event.records[0].eventSource, equals('aws:sqs')); expect(event.records[0].eventSourceARN, - equals("arn:aws:sqs:eu-west-1:123456789012:MyQueue")); - expect(event.records[0].awsRegion, equals("eu-west-1")); - expect(event.records[0].body, equals("Hello from SQS!")); + equals('arn:aws:sqs:eu-west-1:123456789012:MyQueue')); + expect(event.records[0].awsRegion, equals('eu-west-1')); + expect(event.records[0].body, equals('Hello from SQS!')); expect(event.records[0].messageId, - equals("19dd0b57-b21e-4ac1-bd88-01bbb068cb78")); - expect(event.records[0].receiptHandle, equals("MessageReceiptHandle")); + equals('19dd0b57-b21e-4ac1-bd88-01bbb068cb78')); + expect(event.records[0].receiptHandle, equals('MessageReceiptHandle')); }); }); } diff --git a/test/testing.dart b/test/testing.dart deleted file mode 100644 index 0b27728f..00000000 --- a/test/testing.dart +++ /dev/null @@ -1,3 +0,0 @@ -library testing; - -export 'common.dart'; From 34824b6e1282f7a4378d841b204f1653ebba65a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20B=C3=B6cker?= Date: Tue, 8 Sep 2020 14:54:02 +0200 Subject: [PATCH 2/5] Remove forgotten print statement --- lib/client/client.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/client/client.dart b/lib/client/client.dart index 72db259f..6e38b9bb 100644 --- a/lib/client/client.dart +++ b/lib/client/client.dart @@ -148,13 +148,11 @@ class Client { /// Post the invocation response to the AWS Lambda Runtime Interface. Future postInvocationResponse( String requestId, dynamic payload) async { - final body = jsonEncode(payload); - print(body); return await _client.post( Uri.parse( 'http://${runtimeApi}/${runtimeApiVersion}/runtime/invocation/$requestId/response', ), - body: body, + body: jsonEncode(payload), ); } From f15d56ad9e362a5b315138251f0941042e98bfe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20B=C3=B6cker?= Date: Fri, 11 Sep 2020 08:59:33 +0200 Subject: [PATCH 3/5] Remove Logger, disallow parallel execution by awaiting invocation handle --- example/lib/main.dart | 6 +++--- lib/client/client.dart | 20 ++------------------ lib/runtime/runtime.dart | 15 +-------------- pubspec.yaml | 3 --- 4 files changed, 6 insertions(+), 38 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 57da9948..0438f132 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,8 +2,8 @@ import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; void main() async { /// This demo's handling an API Gateway request. - final Handler helloApiGateway = (context, event) async { - final response = {"message": "hello ${context.requestId}"}; + final helloApiGateway = (context, event) async { + final response = {'message': 'hello ${context.requestId}'}; /// it returns an response to the gateway return AwsApiGatewayResponse.fromJson(response); @@ -11,6 +11,6 @@ void main() async { /// The Runtime is a singleton. You can define the handlers as you wish. Runtime() - ..registerHandler("hello.apigateway", helloApiGateway) + ..registerHandler('hello.apigateway', helloApiGateway) ..invoke(); } diff --git a/lib/client/client.dart b/lib/client/client.dart index 6e38b9bb..25caa344 100644 --- a/lib/client/client.dart +++ b/lib/client/client.dart @@ -2,10 +2,6 @@ import 'dart:io'; import 'dart:async'; import 'dart:convert'; -import 'package:http/io_client.dart'; -import 'package:http_extensions/http_extensions.dart'; -import 'package:http_extensions_log/http_extensions_log.dart'; -import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:http/http.dart' as http; @@ -107,7 +103,7 @@ class InvocationError { /// It is implemented as a singleton whereby [Client.instance] /// always returns the already instantiated client. class Client { - http.BaseClient _client; + http.Client _client; static final Client _singleton = Client._internal(); @@ -116,19 +112,7 @@ class Client { } Client._internal() { - _client = ExtendedClient( - inner: IOClient(), - extensions: [ - LogExtension( - logger: Logger('HTTP'), - defaultOptions: LogOptions( - isEnabled: true, - logContent: true, - logHeaders: true, - ), - ), - ], - ); + _client = http.Client(); } static const runtimeApiVersion = '2018-06-01'; diff --git a/lib/runtime/runtime.dart b/lib/runtime/runtime.dart index 5b0c4922..e3808278 100644 --- a/lib/runtime/runtime.dart +++ b/lib/runtime/runtime.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:aws_lambda_dart_runtime/aws_lambda_dart_runtime.dart'; -import 'package:logging/logging.dart'; import 'package:aws_lambda_dart_runtime/client/client.dart'; import 'package:aws_lambda_dart_runtime/runtime/event.dart'; @@ -41,7 +40,6 @@ class _RuntimeHandler { /// Note: You can register an class Runtime { Client _client; - final Logger _logger = Logger('Runtime'); static final Runtime _singleton = Runtime._internal(); final Map _handlers = {}; @@ -52,7 +50,6 @@ class Runtime { Runtime._internal() { _client = Client(); - _logger.finest('Created Client for handler: ${Client.handlerName}'); } /// Lists the registered handlers by name. @@ -94,17 +91,8 @@ class Runtime { /// If there is an error during the execution. The execution gets caught /// and the error is posted via [_client.postInvocationError(err)] to the API. void invoke() async { - _logger.finest('Invoked'); while (true) { - try { - _logger.finest('Getting next invocation'); - _handleInvocation(await _client.getNextInvocation()); - } on Exception catch (error, stacktrace) { - // inner try-catch didn't succeed posting invocation error - // without throwing, all hope is lost, log and crash - _logger.severe('Invocation handling failed', error, stacktrace); - rethrow; - } + await _handleInvocation(await _client.getNextInvocation()); } } @@ -123,7 +111,6 @@ class Runtime { await _client.postInvocationResponse(context.requestId, result); } catch (error, stacktrace) { - _logger.severe('Error in invocation', error, stacktrace); await _client.postInvocationError( nextInvocation?.requestId, InvocationError(error, stacktrace)); } diff --git a/pubspec.yaml b/pubspec.yaml index 259fc16b..abec3ccd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,9 +12,6 @@ dependencies: json_annotation: ^3.0.0 meta: ^1.2.3 http: ^0.12.2 - http_extensions: ^0.1.2 - http_extensions_log: ^0.1.2 - logging: ^0.11.4 dev_dependencies: json_serializable: ^3.2.3 From d0fd83122407b6b067223a7f453f89998651a7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20B=C3=B6cker?= Date: Fri, 11 Sep 2020 09:12:15 +0200 Subject: [PATCH 4/5] Fix: take argument instead of null self attribute in context --- lib/runtime/context.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/runtime/context.dart b/lib/runtime/context.dart index 48b8308a..daeba3ec 100644 --- a/lib/runtime/context.dart +++ b/lib/runtime/context.dart @@ -103,7 +103,7 @@ class Context { this.logStreamName = logStreamName ?? Platform.environment[_kAWSLambdaLogStreamName]; this.requestId = requestId; - invokedFunctionArn = invokedFunctionArn; + invokedFunctionArn = invokedFunction; this.region = region ?? Platform.environment[_kAWSLambdaRegion]; this.executionEnv = executionEnv ?? Platform.environment[_kAWSLambdaExecutionEnv]; From 3fc1e6cb69368318733f1cc6eaf1a614c1449ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20B=C3=B6cker?= Date: Fri, 11 Sep 2020 09:14:18 +0200 Subject: [PATCH 5/5] Fix wrong wording in documentation --- lib/runtime/runtime.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/runtime/runtime.dart b/lib/runtime/runtime.dart index e3808278..c3ec2650 100644 --- a/lib/runtime/runtime.dart +++ b/lib/runtime/runtime.dart @@ -88,7 +88,7 @@ class Runtime { /// /// If the invocation of an event was successful the function /// sends the [InvocationResult] via [_client.postInvocationResponse(result)] to the API. - /// If there is an error during the execution. The execution gets caught + /// If there is an error during the execution. The exception gets caught /// and the error is posted via [_client.postInvocationError(err)] to the API. void invoke() async { while (true) {