Skip to content

Commit 26055b3

Browse files
authoredJun 21, 2023
feature: support protobuf cloud events (#408)
Closes #391 Also improve efficiency of JSON decoding
1 parent 4918f34 commit 26055b3

File tree

6 files changed

+68
-27
lines changed

6 files changed

+68
-27
lines changed
 

‎functions_framework/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.4.3-wip
2+
3+
- Support `application/protobuf` event encoding.
4+
15
## 0.4.2
26

37
- Requires Dart `3.0.0`.

‎functions_framework/lib/src/json_request_utils.dart

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ import 'package:gcp/gcp.dart';
1818
import 'package:http_parser/http_parser.dart';
1919
import 'package:shelf/shelf.dart';
2020

21-
MediaType mediaTypeFromRequest(Request request) {
21+
MediaType mediaTypeFromRequest(Request request, {String? requiredMimeType}) {
2222
final contentType = request.headers[contentTypeHeader];
2323
if (contentType == null) {
2424
throw BadRequestException(400, '$contentTypeHeader header is required.');
2525
}
26+
final MediaType value;
2627
try {
27-
return MediaType.parse(contentType);
28+
value = MediaType.parse(contentType);
2829
} catch (e, stack) {
2930
throw BadRequestException(
3031
400,
@@ -33,23 +34,23 @@ MediaType mediaTypeFromRequest(Request request) {
3334
innerStack: stack,
3435
);
3536
}
36-
}
3737

38-
void mustBeJson(MediaType type) {
39-
if (type.mimeType != jsonContentType) {
40-
// https://github.com/GoogleCloudPlatform/functions-framework#http-status-codes
41-
throw BadRequestException(
42-
400,
43-
'Unsupported encoding "${type.toString()}". '
44-
'Only "$jsonContentType" is supported.',
45-
);
38+
if (requiredMimeType != null) {
39+
if (value.mimeType != requiredMimeType) {
40+
// https://github.com/GoogleCloudPlatform/functions-framework#http-status-codes
41+
throw BadRequestException(
42+
400,
43+
'Unsupported encoding "${value.mimeType.toString()}". '
44+
'Only "$requiredMimeType" is supported.',
45+
);
46+
}
4647
}
48+
return value;
4749
}
4850

49-
Future<Object?> decodeJson(Request request) async {
50-
final content = await request.readAsString();
51+
Future<Object?> decodeJson(Stream<List<int>> data) async {
5152
try {
52-
final value = jsonDecode(content);
53+
final value = await utf8.decoder.bind(data).transform(json.decoder).single;
5354
return value;
5455
} on FormatException catch (e, stackTrace) {
5556
// https://github.com/GoogleCloudPlatform/functions-framework#http-status-codes
@@ -64,4 +65,44 @@ Future<Object?> decodeJson(Request request) async {
6465

6566
const jsonContentType = 'application/json';
6667

68+
enum SupportedContentTypes {
69+
json(jsonContentType),
70+
protobuf('application/protobuf');
71+
72+
const SupportedContentTypes(this.value);
73+
74+
final String value;
75+
76+
static Future<({MediaType mimeType, Object? data})> decode(
77+
Request request,
78+
) async {
79+
final type = mediaTypeFromRequest(request);
80+
final supportedType = SupportedContentTypes.values.singleWhere(
81+
(element) => element.value == type.mimeType,
82+
orElse: () => throw BadRequestException(
83+
400,
84+
'Unsupported encoding "$type". '
85+
'Supported types: '
86+
'${SupportedContentTypes.values.map((e) => '"${e.value}"').join(', ')}',
87+
),
88+
);
89+
90+
return (
91+
mimeType: type,
92+
data: await supportedType._decode(request.read()),
93+
);
94+
}
95+
96+
Future<Object?> _decode(
97+
Stream<List<int>> data,
98+
) async =>
99+
switch (this) {
100+
json => await decodeJson(data),
101+
protobuf => await data.fold<List<int>>(
102+
<int>[],
103+
(previous, element) => previous..addAll(element),
104+
),
105+
};
106+
}
107+
67108
const contentTypeHeader = 'Content-Type';

‎functions_framework/lib/src/targets/cloud_event_targets.dart

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,8 @@ Future<CloudEvent> _eventFromRequest(Request request) =>
6767
: _decodeStructured(request);
6868

6969
Future<CloudEvent> _decodeStructured(Request request) async {
70-
final type = mediaTypeFromRequest(request);
71-
72-
mustBeJson(type);
73-
var jsonObject = await decodeJson(request) as Map<String, dynamic>;
70+
final type = mediaTypeFromRequest(request, requiredMimeType: jsonContentType);
71+
var jsonObject = await decodeJson(request.read()) as Map<String, dynamic>;
7472

7573
if (!jsonObject.containsKey('datacontenttype')) {
7674
jsonObject = {
@@ -86,15 +84,14 @@ const _cloudEventPrefix = 'ce-';
8684
const _clientEventPrefixLength = _cloudEventPrefix.length;
8785

8886
Future<CloudEvent> _decodeBinary(Request request) async {
89-
final type = mediaTypeFromRequest(request);
90-
mustBeJson(type);
87+
final data = await SupportedContentTypes.decode(request);
9188

9289
final map = <String, Object?>{
9390
for (var e in request.headers.entries
9491
.where((element) => element.key.startsWith(_cloudEventPrefix)))
9592
e.key.substring(_clientEventPrefixLength): e.value,
96-
'datacontenttype': type.toString(),
97-
'data': await decodeJson(request),
93+
'datacontenttype': data.mimeType.toString(),
94+
'data': data.data,
9895
};
9996

10097
return _decodeValidCloudEvent(map, 'binary-mode message');

‎functions_framework/lib/src/targets/json_targets.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@ abstract class _JsonFunctionTargetBase<RequestType> extends FunctionTarget {
2828
_JsonFunctionTargetBase(this._fromJson);
2929

3030
Future<RequestType> _toRequestType(Request request) async {
31-
final type = mediaTypeFromRequest(request);
32-
mustBeJson(type);
33-
final jsonObject = await decodeJson(request);
31+
mediaTypeFromRequest(request, requiredMimeType: jsonContentType);
32+
final jsonObject = await decodeJson(request.read());
3433
return _fromJson(jsonObject);
3534
}
3635
}

‎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.4.2
2+
version: 0.4.3-wip
33
description: >-
44
FaaS (Function as a service) framework for writing portable Dart functions
55
repository: https://github.com/GoogleCloudPlatform/functions-framework-dart

‎integration_test/test/custom_type_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ void main() {
6868
);
6969
await expectInvalid(
7070
response,
71-
'Unsupported encoding "application/text; charset=utf-8". '
71+
'Unsupported encoding "application/text". '
7272
'Only "application/json" is supported.',
7373
);
7474
});

0 commit comments

Comments
 (0)
Please sign in to comment.