Skip to content

Commit 6afafcf

Browse files
authored
Terminate if the configured signature doesn't match the function (#71)
Towards #1 Related to GoogleCloudPlatform/functions-framework-conformance#58
1 parent 35c5286 commit 6afafcf

File tree

14 files changed

+175
-63
lines changed

14 files changed

+175
-63
lines changed

.github/workflows/conformance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ jobs:
5555
functionType: 'cloudevent'
5656
useBuildpacks: false
5757
# Coordinate changes here with the documentation in `CONTRIBUTING.MD`
58-
cmd: '"dart test/hello/bin/server.dart --target conformanceCloudEvent"'
58+
cmd: '"dart test/hello/bin/server.dart --target conformanceCloudEvent --signature-type cloudevent"'
5959
validateMapping: false
6060
startDelay: 3

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ $ $FUNCTION_FRAMEWORK_CONFORMANCE_PATH/client/client -buildpacks=false -start-de
6565
#### Cloud events
6666

6767
```console
68-
$ $FUNCTION_FRAMEWORK_CONFORMANCE_PATH/client/client -buildpacks=false -start-delay 3 -type=cloudevent -cmd="dart test/hello/bin/server.dart --target conformanceCloudEvent" --validate-mapping=false
68+
$ $FUNCTION_FRAMEWORK_CONFORMANCE_PATH/client/client -buildpacks=false -start-delay 3 -type=cloudevent -cmd="dart test/hello/bin/server.dart --target conformanceCloudEvent --signature-type cloudevent" --validate-mapping=false
6969
```
7070

7171
This corespondes to the configuration in `.github/workflows/conformance.yml`.

functions_framework/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 0.2.1-dev
1+
# 0.3.0-dev
22

33
- Added support for basic Cloud Events.
44
- Added `CloudEvent` type.

functions_framework/lib/functions_framework.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@
1313
// limitations under the License.
1414

1515
export 'src/cloud_event.dart';
16-
export 'src/cloud_event_wrapper.dart';
16+
export 'src/cloud_event_wrapper.dart' show CloudEventHandler;
1717
export 'src/cloud_function.dart';

functions_framework/lib/serve.dart

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,21 @@ import 'package:shelf/shelf.dart';
2222
import 'src/bad_configuration.dart';
2323
import 'src/cloud_metadata.dart';
2424
import 'src/function_config.dart';
25+
import 'src/function_endpoint.dart';
2526
import 'src/logging.dart';
2627
import 'src/run.dart';
2728

28-
export 'src/cloud_event_wrapper.dart' show wrapCloudEventHandler;
29+
export 'src/function_endpoint.dart' show FunctionEndpoint;
2930

3031
/// If there is an invalid configuration, [BadConfigurationException] will be
3132
/// thrown.
3233
///
3334
/// If there are no configuration errors, this function will not return until
3435
/// the process has received signal [ProcessSignal.sigterm] or
3536
/// [ProcessSignal.sigint].
36-
Future<void> serve(List<String> args, Map<String, Handler> handlers) async {
37+
Future<void> serve(List<String> args, Set<FunctionEndpoint> functions) async {
3738
try {
38-
await _serve(args, handlers);
39+
await _serve(args, functions);
3940
} on BadConfigurationException catch (e) {
4041
stderr.writeln(red.wrap(e.message));
4142
if (e.details != null) {
@@ -45,21 +46,32 @@ Future<void> serve(List<String> args, Map<String, Handler> handlers) async {
4546
}
4647
}
4748

48-
Future<void> _serve(List<String> args, Map<String, Handler> handlers) async {
49+
Future<void> _serve(List<String> args, Set<FunctionEndpoint> functions) async {
4950
final configFromEnvironment = FunctionConfig.fromEnv();
5051

5152
final config = FunctionConfig.fromArgs(
5253
args,
5354
defaults: configFromEnvironment,
5455
);
5556

56-
final handler = handlers[config.target];
57-
58-
if (handler == null) {
59-
throw BadConfigurationException(
57+
final function = functions.singleWhere(
58+
(element) => element.target == config.target,
59+
orElse: () => throw BadConfigurationException(
6060
'There is no handler configured for '
61-
'FUNCTION_TARGET `${config.target}`.',
61+
'$environmentKeyFunctionTarget `${config.target}`.',
62+
),
63+
);
64+
65+
if (function.functionType == FunctionType.cloudevent &&
66+
config.functionType == FunctionType.http) {
67+
// See https://github.com/GoogleCloudPlatform/functions-framework-conformance/issues/58
68+
stderr.writeln(
69+
'The configured $environmentKeyFunctionTarget `${config.target}` has a '
70+
'function type of `cloudevent` which is not compatible with the '
71+
'configured $environmentKeyFunctionSignatureType of `http`.',
6272
);
73+
exitCode = ExitCode.usage.code;
74+
return;
6375
}
6476

6577
final projectId = await CloudMetadata.projectId();
@@ -89,5 +101,5 @@ Future<void> _serve(List<String> args, Map<String, Handler> handlers) async {
89101
sigIntSub = ProcessSignal.sigint.watch().listen(signalHandler);
90102
sigTermSub = ProcessSignal.sigterm.watch().listen(signalHandler);
91103

92-
await run(config.port, handler, completer.future, loggingMiddleware);
104+
await run(config.port, function.handler, completer.future, loggingMiddleware);
93105
}

functions_framework/lib/src/cloud_event_wrapper.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import 'cloud_event.dart';
2424

2525
typedef CloudEventHandler = FutureOr<void> Function(CloudEvent request);
2626

27-
Handler wrapCloudEventHandler(CloudEventHandler handler) => (request) async {
27+
Handler wrapCloudEventFunction(CloudEventHandler handler) => (request) async {
2828
CloudEvent event;
2929

3030
try {

functions_framework/lib/src/function_config.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ const defaultPort = 8080;
2222
const defaultFunctionType = FunctionType.http;
2323
const defaultFunctionTarget = 'function';
2424

25+
const environmentKeyFunctionTarget = 'FUNCTION_TARGET';
26+
const environmentKeyFunctionSignatureType = 'FUNCTION_SIGNATURE_TYPE';
27+
2528
const _portOpt = 'port';
2629
const _targetOpt = 'target';
2730
const _functionTypeOpt = 'signature-type';
@@ -62,7 +65,8 @@ class FunctionConfig {
6265

6366
return FunctionConfig(
6467
port: port,
65-
target: Platform.environment['FUNCTION_TARGET'] ?? defaultFunctionTarget,
68+
target: Platform.environment[environmentKeyFunctionTarget] ??
69+
defaultFunctionTarget,
6670
functionType: _parseFunctionType(
6771
Platform.environment['FUNCTION_SIGNATURE_TYPE'] ??
6872
_enumValue(FunctionType.http),
@@ -87,7 +91,7 @@ class FunctionConfig {
8791
_targetOpt,
8892
help: 'The name of the exported function to be invoked in response to '
8993
'requests.\n'
90-
'Overrides the FUNCTION_TARGET environment variable.',
94+
'Overrides the $environmentKeyFunctionTarget environment variable.',
9195
)
9296
..addOption(
9397
_functionTypeOpt,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import 'package:shelf/shelf.dart';
16+
17+
import 'cloud_event_wrapper.dart';
18+
import 'function_config.dart';
19+
20+
abstract class FunctionEndpoint {
21+
final String target;
22+
23+
FunctionType get functionType;
24+
25+
Handler get handler;
26+
27+
const FunctionEndpoint(this.target);
28+
29+
const factory FunctionEndpoint.http(String target, Handler function) =
30+
HttpFunctionEndPoint;
31+
32+
const factory FunctionEndpoint.cloudEvent(
33+
String target, CloudEventHandler function) = CloudEventFunctionEndPoint;
34+
}
35+
36+
class HttpFunctionEndPoint extends FunctionEndpoint {
37+
final Handler function;
38+
39+
@override
40+
FunctionType get functionType => FunctionType.http;
41+
42+
@override
43+
Handler get handler => function;
44+
45+
const HttpFunctionEndPoint(String target, this.function) : super(target);
46+
}
47+
48+
class CloudEventFunctionEndPoint extends FunctionEndpoint {
49+
final CloudEventHandler function;
50+
51+
@override
52+
FunctionType get functionType => FunctionType.cloudevent;
53+
54+
@override
55+
Handler get handler => wrapCloudEventFunction(function);
56+
57+
const CloudEventFunctionEndPoint(String target, this.function)
58+
: super(target);
59+
}

functions_framework/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: functions_framework
2-
version: 0.2.1-dev
2+
version: 0.3.0-dev
33
description: >-
44
FaaS (Function as a service) framework for writing portable Dart functions
55
repository: https://github.com/GoogleCloudPlatform/functions-framework-dart

functions_framework_builder/lib/builder.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ class _FunctionsFrameworkBuilder implements Builder {
6363

6464
final function = element as FunctionElement;
6565

66-
final invokeExpression = validator.validate(function);
67-
6866
final targetReader = annotatedElement.annotation.read('target');
6967

7068
final targetName =
@@ -77,6 +75,8 @@ class _FunctionsFrameworkBuilder implements Builder {
7775
);
7876
}
7977

78+
final invokeExpression = validator.validate(targetName, function);
79+
8080
entries[targetName] = invokeExpression;
8181
}
8282

@@ -97,16 +97,14 @@ class _FunctionsFrameworkBuilder implements Builder {
9797
// limitations under the License.
9898
9999
import 'package:functions_framework/serve.dart';
100-
import 'package:shelf/shelf.dart';
101-
102100
import '${input.uri}' as $functionsLibraryPrefix;
103101
104102
Future<void> main(List<String> args) async {
105103
await serve(args, _functions);
106104
}
107105
108-
final _functions = <String, Handler>{
109-
${entries.entries.map((e) => " '${e.key}': ${e.value},").join('\n')}
106+
const _functions = <FunctionEndpoint>{
107+
${entries.values.map((e) => " $e,").join('\n')}
110108
};
111109
''';
112110

functions_framework_builder/lib/src/supported_function_types.dart

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ class SupportedFunctionTypes {
2525

2626
SupportedFunctionTypes._(this._types);
2727

28-
String validate(FunctionElement element) {
28+
String validate(String targetName, FunctionElement element) {
2929
for (var type in _types) {
3030
if (type.compatible(element)) {
31-
return type.createReference(element);
31+
return type.createReference(targetName, element);
3232
}
3333
}
3434
throw InvalidGenerationSourceError(
@@ -47,12 +47,13 @@ ${_types.map((e) => ' ${e.name} [${e.typeDescription}] from ${e.library}').join
4747
resolver,
4848
'package:shelf/shelf.dart',
4949
'Handler',
50+
'FunctionEndpoint.http',
5051
),
5152
await _SupportedFunctionType.create(
5253
resolver,
5354
'package:functions_framework/functions_framework.dart',
5455
'CloudEventHandler',
55-
wrapperFunction: 'wrapCloudEventHandler',
56+
'FunctionEndpoint.cloudEvent',
5657
),
5758
],
5859
);
@@ -63,21 +64,21 @@ class _SupportedFunctionType {
6364
final String name;
6465
final FunctionType type;
6566
final String typeDescription;
66-
final String wrapperFunction;
67+
final String constructor;
6768

6869
_SupportedFunctionType(
6970
this.library,
7071
this.name,
7172
this.type, {
72-
this.wrapperFunction,
73+
this.constructor,
7374
}) : typeDescription = type.getDisplayString(withNullability: false);
7475

7576
static Future<_SupportedFunctionType> create(
7677
Resolver resolver,
7778
String libraryUri,
78-
String typeDefName, {
79-
String wrapperFunction,
80-
}) async {
79+
String typeDefName,
80+
String constructor,
81+
) async {
8182
final lib = await resolver.libraryFor(
8283
AssetId.resolve(libraryUri),
8384
);
@@ -94,7 +95,7 @@ class _SupportedFunctionType {
9495
libraryUri,
9596
typeDefName,
9697
functionType,
97-
wrapperFunction: wrapperFunction,
98+
constructor: constructor,
9899
);
99100
}
100101

@@ -104,13 +105,6 @@ class _SupportedFunctionType {
104105
type,
105106
);
106107

107-
String createReference(FunctionElement element) {
108-
var expression = '$functionsLibraryPrefix.${element.name}';
109-
110-
if (wrapperFunction != null) {
111-
expression = '$wrapperFunction($expression)';
112-
}
113-
114-
return expression;
115-
}
108+
String createReference(String targetName, FunctionElement element) =>
109+
"$constructor('$targetName', $functionsLibraryPrefix.${element.name},)";
116110
}

functions_framework_builder/test/builder_test.dart

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ Response handleGet(Request request) => Response.ok('Hello, World!');
3939
''',
4040
'''
4141
$_outputHeader
42-
final _functions = <String, Handler>{
43-
'handleGet': function_library.handleGet,
42+
const _functions = <FunctionEndpoint>{
43+
FunctionEndpoint.http(
44+
'handleGet',
45+
function_library.handleGet,
46+
),
4447
};
4548
''',
4649
);
@@ -57,8 +60,11 @@ Response handleGet(Request request) => Response.ok('Hello, World!');
5760
''',
5861
'''
5962
$_outputHeader
60-
final _functions = <String, Handler>{
61-
'some function': function_library.handleGet,
63+
const _functions = <FunctionEndpoint>{
64+
FunctionEndpoint.http(
65+
'some function',
66+
function_library.handleGet,
67+
),
6268
};
6369
''',
6470
);
@@ -77,12 +83,16 @@ final _functions = <String, Handler>{
7783
'customResponse',
7884
'customResponseAsync',
7985
'customResponseFutureOr',
80-
].map((e) => " '$e': function_library.$e,").join('\n');
86+
].map((e) => """
87+
FunctionEndpoint.http(
88+
'$e',
89+
function_library.$e,
90+
),""").join('\n');
8191
await _generateTest(
8292
file.readAsStringSync(),
8393
'''
8494
$_outputHeader
85-
final _functions = <String, Handler>{
95+
const _functions = <FunctionEndpoint>{
8696
$lines
8797
};
8898
''',
@@ -98,14 +108,16 @@ $lines
98108
'futureOrFunction',
99109
'optionalParam',
100110
'objectParam',
101-
]
102-
.map((e) => " '$e': wrapCloudEventHandler(function_library.$e),")
103-
.join('\n');
111+
].map((e) => """
112+
FunctionEndpoint.cloudEvent(
113+
'$e',
114+
function_library.$e,
115+
),""").join('\n');
104116
await _generateTest(
105117
file.readAsStringSync(),
106118
'''
107119
$_outputHeader
108-
final _functions = <String, Handler>{
120+
const _functions = <FunctionEndpoint>{
109121
$lines
110122
};
111123
''',
@@ -265,8 +277,6 @@ String get _outputHeader => '''
265277
// limitations under the License.
266278
267279
import 'package:functions_framework/serve.dart';
268-
import 'package:shelf/shelf.dart';
269-
270280
import 'package:$_pkgName/functions.dart' as function_library;
271281
272282
Future<void> main(List<String> args) async {

0 commit comments

Comments
 (0)