Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Not compatible with a supported function shape" when using RequestContext parameter #464

Closed
jimmyff opened this issue May 16, 2024 · 10 comments
Labels
kind/enhancement New feature or request

Comments

@jimmyff
Copy link

jimmyff commented May 16, 2024

Hey, I've recently updated this package (0.4.3+1) and I'm having difficulty getting my async pub sub endpoints working again.

The issue seems to be with using async modifier, giving me Not compatible with a supported function shape error, this is my function decleration:

@CloudFunction()
Future<void> function(PubSub pubSub, RequestContext context) async {

Where as this works, but then I can't use all the async conveniences.

@CloudFunction()
FutureOr<void> function(PubSub pubSub, RequestContext context) {

I'm sure I am missing something simple here? 😅 (apologies in advance...)

Error

[SEVERE] functions_framework_builder:function_framework_builder on lib/functions.dart:

Not compatible with a supported function shape:
  HandlerWithLogger [FutureOr<Response> Function(Request, RequestLogger)] from package:functions_framework/functions_framework.dart
  Handler [FutureOr<Response> Function(Request)] from package:shelf/shelf.dart
  CloudEventWithContextHandler [FutureOr<void> Function(CloudEvent, RequestContext)] from package:functions_framework/functions_framework.dart
  CloudEventHandler [FutureOr<void> Function(CloudEvent)] from package:functions_framework/functions_framework.dart
  JsonHandler [FutureOr<ResponseType> Function(RequestType request, RequestContext context)] from package:functions_framework/functions_framework.dart
  JsonHandler [FutureOr<ResponseType> Function(RequestType request)] from package:functions_framework/functions_framework.dart

package:test_pubsub/functions.dart:10:14
   |
   |  Future<void> function(PubSub pubSub, RequestContext context) async {
   |             ^^^^^^^^
   |
[INFO] Running build completed, took 21.1s

[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 165ms

[SEVERE] Failed after 21.3s

bin/server.dart
import 'package:functions_framework/serve.dart';
import 'package:test_pubsub/functions.dart' as function_library;

Future<void> main(List<String> args) async {
  await serve(args, _nameToFunctionTarget);
}

FunctionTarget? _nameToFunctionTarget(String name) => switch (name) {
      'function' => JsonWithContextFunctionTarget.voidResult(
          function_library.function,
          (json) {
            if (json is Map<String, dynamic>) {
              try {
                return function_library.PubSub.fromJson(json);
              } catch (e, stack) {
                throw BadRequestException(
                  400,
                  'There was an error parsing the provided JSON data.',
                  innerError: e,
                  innerStack: stack,
                );
              }
            }
            throw BadRequestException(
              400,
              'The provided JSON is not the expected type '
              '`Map<String, dynamic>`.',
            );
          },
        ),
      _ => null
    };
lib/functions.dart
@CloudFunction()
Future<void> function(PubSub pubSub, RequestContext context) async {
  await Future.delayed(Duration(milliseconds: 1));
  context.logger.info('[Pub Sub] subscription: ${pubSub.subscription}');
  context.logger.info('[Pub Sub] message: ${pubSub.message.dataDecoded()}');
  context.responseHeaders['subscription'] = pubSub.subscription;
}

Thanks

@jimmyff
Copy link
Author

jimmyff commented May 16, 2024

Just been digging through the source and I can't understand why JsonWithContextFunctionTarget<PubSub, void> isn't working for me. build_runner is correctly generating bin/server.dart, it's only when it's trying to deploy to cloud functions that it throws this error.

Wondering if I need to make my own FunctionTarget but I think there should be a simple way to get JsonWithContextFunctionTarget working?

@kevmoo
Copy link
Collaborator

kevmoo commented May 16, 2024

Does PubSub have a fromJson constructor and a toJson function?

@kevmoo
Copy link
Collaborator

kevmoo commented May 16, 2024

And/or have you tried typing your function FutureOr and marking it async?

@jimmyff
Copy link
Author

jimmyff commented May 17, 2024

Hey @kevmoo thanks for the suggestions. Yeah pub sub class should be fine, this is it:

pub_sub_types.dart
import 'dart:convert';
import 'dart:typed_data';
import 'package:json_annotation/json_annotation.dart';

part 'pub_sub_types.g.dart';

@JsonSerializable()
class PubSub {
  final PubSubMessage message;
  final String subscription;
  PubSub(this.message, this.subscription);
  factory PubSub.fromJson(Map<String, dynamic> json) {
    try {
      return _$PubSubFromJson(json);
    } catch (e, s) {
      print('PubSub: Failed to parse json: $json');
      rethrow;
    }
  }
  Map<String, dynamic> toJson() => _$PubSubToJson(this);
}

@JsonSerializable()
class PubSubMessage {
  final String? data;
  final Map<String, String>? attributes;
  final String messageId;
  final DateTime publishTime;

  bool hasData() => data != null;
  Map<String, dynamic> jsonDecoded() => json.decode(dataDecoded());
  String dataDecoded() => utf8.decode(dataBytes());
  Uint8List dataBytes() =>
      data != null ? base64Decode(data!) : throw Exception('Data excected');

  PubSubMessage(this.data, this.messageId, this.publishTime, this.attributes);
  factory PubSubMessage.fromJson(Map<String, dynamic> json) {
    try {
      return _$PubSubMessageFromJson(json);
    } catch (e, s) {
      print('PubSubMessage: Failed to parse json: $json');
      rethrow;
    }
  }
  Map<String, dynamic> toJson() => _$PubSubMessageToJson(this);
}

// This was a test to see if returning this instead of void would help, it didn't
@JsonSerializable()
class PubSubResponse {
  final bool success;
  PubSubResponse(this.success);
  factory PubSubResponse.fromJson(Map<String, dynamic> json) {
    try {
      return _$PubSubResponseFromJson(json);
    } catch (e, s) {
      print('PubSubResponse: Failed to parse json: $json');
      rethrow;
    }
  }
  Map<String, dynamic> toJson() => _$PubSubResponseToJson(this);
}

I've tried every combination return types and async keyword but I get the not compatible shape error if I use any sort of Future / async. I tried creating a PubSubResponse class for ResponseType but that hasn't helped.

Should I look at registering my own function target? If so is there any advice where to start with that? I've had a quick scout around but I couldn't see where they're registered.

Thanks

@jimmyff
Copy link
Author

jimmyff commented May 20, 2024

A little progress, it doesn't seem to be related to the Future/async. If I alter the package example to include the RequestContext parameter then it fails to deploy too:

@CloudFunction()
GreetingResponse function(GreetingRequest request, RequestContext context) {
  final name = request.name ?? 'World';
  final json = GreetingResponse(salutation: 'Hello', name: name);
  return json;
}

With the same error:

Not compatible with a supported function shape:
  HandlerWithLogger [FutureOr<Response> Function(Request, RequestLogger)] from package:functions_framework/functions_framework.dart
  Handler [FutureOr<Response> Function(Request)] from package:shelf/shelf.dart
  CloudEventWithContextHandler [FutureOr<void> Function(CloudEvent, RequestContext)] from package:functions_framework/functions_framework.dart
  CloudEventHandler [FutureOr<void> Function(CloudEvent)] from package:functions_framework/functions_framework.dart
  JsonHandler [FutureOr<ResponseType> Function(RequestType request, RequestContext context)] from package:functions_framework/functions_framework.dart
  JsonHandler [FutureOr<ResponseType> Function(RequestType request)] from package:functions_framework/functions_framework.dart

package:test_pubsub/functions.dart:55:18
   ╷
55 │ GreetingResponse function(GreetingRequest request, RequestContext context) {
   │                  ^^^^^^^^
   ╵
[INFO] Running build completed, took 15.9s

My function & the package example both work without RequestContext with Future return type and async keyword. They also work with a FutureOr<void> return type.

Why it builds on my local environment yet fails on docker & cloud run remains a mystery for now. All I can think was a dependency version discrepancy but I've pretty much locked all the dependency versions down to mirror cloud run and local.

@kevmoo
Copy link
Collaborator

kevmoo commented May 20, 2024

have you tried printing out the pubspec.lock file in CI? Comparing the versions of Dart?

The only example I can think of is if the analyzer version has changed...

@kevmoo
Copy link
Collaborator

kevmoo commented May 20, 2024

diff --git a/examples/json/lib/functions.dart b/examples/json/lib/functions.dart
index 4213df4..802a158 100644
--- a/examples/json/lib/functions.dart
+++ b/examples/json/lib/functions.dart
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.

+import 'dart:async';
+
 import 'package:functions_framework/functions_framework.dart';
 import 'package:json_annotation/json_annotation.dart';

@@ -59,7 +61,7 @@ class GreetingResponse {
 }

 @CloudFunction()
-GreetingResponse function(GreetingRequest request) {
+Future<GreetingResponse> function(GreetingRequest request) async {
   final name = request.name ?? 'World';
   final json = GreetingResponse(salutation: 'Hello', name: name);
   return json;

I tried changing up the example in the repro to make it work...and it's fine.

I might could change this to support FutureOr but that doesn't seem to be blocking you

@jimmyff
Copy link
Author

jimmyff commented May 27, 2024

Hey @kevmoo the issue only occurs when adding a RequestContext context parameter.

I've since modified my code not to require the context so this issue is not blocking me. I left the issue open because as far as I can tell, you should be able to use RequestContext context (Expected it to get picked up by the FunctionTarget: JsonHandler [FutureOr<ResponseType> Function(RequestType request, RequestContext context)]). The example code demonstrates the issue too with the additional RequestContext parameter.

Happy for this issue to be closed or left open.

@jimmyff jimmyff changed the title "Not compatible with a supported function shape" when using async modifier "Not compatible with a supported function shape" when using RequestContext parameter May 27, 2024
@kevmoo
Copy link
Collaborator

kevmoo commented May 28, 2024

Yep. Let's call it an enhancement!

@kevmoo kevmoo added the kind/enhancement New feature or request label May 28, 2024
@kevmoo
Copy link
Collaborator

kevmoo commented May 30, 2024

I tried modifying the integration test file and it works perfectly.

I'm going to close this out.

If you can modify the integartion_test/lib/src/json_handlers.dart to reproduce the error, let me know.

diff --git a/integration_test/bin/server.dart b/integration_test/bin/server.dart
index 6b155d0..ad074d1 100644
--- a/integration_test/bin/server.dart
+++ b/integration_test/bin/server.dart
@@ -61,6 +61,29 @@ FunctionTarget? _nameToFunctionTarget(String name) => switch (name) {
             );
           },
         ),
+      'pubSubHandlerWithReplyAndContext' =>
+        JsonWithContextFunctionTarget.voidResult(
+          function_library.pubSubHandlerWithReplyAndContext,
+          (json) {
+            if (json is Map<String, dynamic>) {
+              try {
+                return function_library.PubSub.fromJson(json);
+              } catch (e, stack) {
+                throw BadRequestException(
+                  400,
+                  'There was an error parsing the provided JSON data.',
+                  innerError: e,
+                  innerStack: stack,
+                );
+              }
+            }
+            throw BadRequestException(
+              400,
+              'The provided JSON is not the expected type '
+              '`Map<String, dynamic>`.',
+            );
+          },
+        ),
       'jsonHandler' => JsonWithContextFunctionTarget(
           function_library.jsonHandler,
           (json) {
diff --git a/integration_test/lib/src/json_handlers.dart b/integration_test/lib/src/json_handlers.dart
index 7e7e9db..016d5d6 100644
--- a/integration_test/lib/src/json_handlers.dart
+++ b/integration_test/lib/src/json_handlers.dart
@@ -24,6 +24,17 @@ void pubSubHandler(PubSub pubSub, RequestContext context) {
   context.responseHeaders['multi'] = ['item1', 'item2'];
 }

+@CloudFunction()
+Future<void> pubSubHandlerWithReplyAndContext(
+  PubSub pubSub,
+  RequestContext context,
+) async {
+  print('subscription: ${pubSub.subscription}');
+  context.logger.info('subscription: ${pubSub.subscription}');
+  context.responseHeaders['subscription'] = pubSub.subscription;
+  context.responseHeaders['multi'] = ['item1', 'item2'];
+}
+
 @CloudFunction()
 FutureOr<bool> jsonHandler(
   Map<String, dynamic> request,

@kevmoo kevmoo closed this as completed May 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants