Skip to content

Commit 85612ee

Browse files
fix local development with the Pub/Sub emulator (GoogleCloudPlatform#98)
* unblock local development with the pubsub emulator Currently, the functions frameworks are dependent on some private dataplane event marshalling logic in order to correctly pass PubSub events to background functions. This commit implements the same marshalling logic in the FF in order to enable local development using the PubSub emulator. We have already made this change in other languages: GoogleCloudPlatform/functions-framework-nodejs#272 GoogleCloudPlatform/functions-framework-ruby#100 GoogleCloudPlatform/functions-framework-python#121 GoogleCloudPlatform/functions-framework-go#70 * fix code style issues
1 parent 7b6246b commit 85612ee

File tree

4 files changed

+117
-4
lines changed

4 files changed

+117
-4
lines changed

invoker/core/src/main/java/com/google/cloud/functions/invoker/Event.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import com.google.gson.JsonObject;
2222
import com.google.gson.JsonParseException;
2323
import java.lang.reflect.Type;
24+
import java.time.OffsetDateTime;
25+
import java.time.format.DateTimeFormatter;
2426

2527
/**
2628
* Represents an event that should be handled by a background function. This is an internal format
@@ -53,6 +55,34 @@ public Event deserialize(
5355
context =
5456
jsonDeserializationContext.deserialize(
5557
adjustContextResource(contextCopy), CloudFunctionsContext.class);
58+
} else if (isPubSubEmulatorPayload(root)) {
59+
JsonObject message = root.getAsJsonObject("message");
60+
61+
String timestampString =
62+
message.has("publishTime")
63+
? message.get("publishTime").getAsString()
64+
: DateTimeFormatter.ISO_INSTANT.format(OffsetDateTime.now());
65+
66+
context =
67+
CloudFunctionsContext.builder()
68+
.setEventType("google.pubsub.topic.publish")
69+
.setTimestamp(timestampString)
70+
.setEventId(message.get("messageId").getAsString())
71+
.setResource(
72+
"{"
73+
+ "\"name\":null,"
74+
+ "\"service\":\"pubsub.googleapis.com\","
75+
+ "\"type\":\"type.googleapis.com/google.pubsub.v1.PubsubMessage\""
76+
+ "}")
77+
.build();
78+
79+
JsonObject marshalledData = new JsonObject();
80+
marshalledData.addProperty("@type", "type.googleapis.com/google.pubsub.v1.PubsubMessage");
81+
marshalledData.add("data", message.get("data"));
82+
if (message.has("attributes")) {
83+
marshalledData.add("attributes", message.get("attributes"));
84+
}
85+
data = marshalledData;
5686
} else {
5787
JsonObject rootCopy = root.deepCopy();
5888
rootCopy.remove("data");
@@ -63,6 +93,14 @@ public Event deserialize(
6393
return Event.of(data, context);
6494
}
6595

96+
private boolean isPubSubEmulatorPayload(JsonObject root) {
97+
if (root.has("subscription") && root.has("message") && root.get("message").isJsonObject()) {
98+
JsonObject message = root.getAsJsonObject("message");
99+
return message.has("data") && message.has("messageId");
100+
}
101+
return false;
102+
}
103+
66104
/**
67105
* Replaces 'resource' member from context JSON with its string equivalent. The original
68106
* 'resource' member can be a JSON object itself while {@link CloudFunctionsContext} requires it

invoker/core/src/test/java/com/google/cloud/functions/invoker/BackgroundFunctionExecutorTest.java

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package com.google.cloud.functions.invoker;
22

33
import static com.google.cloud.functions.invoker.BackgroundFunctionExecutor.backgroundFunctionTypeArgument;
4+
import static com.google.common.truth.Truth.assertThat;
45
import static com.google.common.truth.Truth8.assertThat;
56

67
import com.google.cloud.functions.BackgroundFunction;
78
import com.google.cloud.functions.Context;
9+
import com.google.gson.JsonObject;
10+
import java.io.IOException;
11+
import java.io.InputStreamReader;
12+
import java.io.Reader;
813
import java.util.Map;
914
import org.junit.Test;
1015
import org.junit.runner.RunWith;
@@ -20,7 +25,8 @@ private static class PubSubMessage {
2025
}
2126

2227
private static class PubSubFunction implements BackgroundFunction<PubSubMessage> {
23-
@Override public void accept(PubSubMessage payload, Context context) {}
28+
@Override
29+
public void accept(PubSubMessage payload, Context context) {}
2430
}
2531

2632
@Test
@@ -31,7 +37,8 @@ public void backgroundFunctionTypeArgument_simple() {
3137
private abstract static class Parent implements BackgroundFunction<PubSubMessage> {}
3238

3339
private static class Child extends Parent {
34-
@Override public void accept(PubSubMessage payload, Context context) {}
40+
@Override
41+
public void accept(PubSubMessage payload, Context context) {}
3542
}
3643

3744
@Test
@@ -42,7 +49,8 @@ public void backgroundFunctionTypeArgument_superclass() {
4249
private interface GenericParent<T> extends BackgroundFunction<T> {}
4350

4451
private static class GenericChild implements GenericParent<PubSubMessage> {
45-
@Override public void accept(PubSubMessage payload, Context context) {}
52+
@Override
53+
public void accept(PubSubMessage payload, Context context) {}
4654
}
4755

4856
@Test
@@ -52,7 +60,8 @@ public void backgroundFunctionTypeArgument_genericInterface() {
5260

5361
@SuppressWarnings("rawtypes")
5462
private static class ForgotTypeParameter implements BackgroundFunction {
55-
@Override public void accept(Object payload, Context context) {}
63+
@Override
64+
public void accept(Object payload, Context context) {}
5665
}
5766

5867
@Test
@@ -62,4 +71,41 @@ public void backgroundFunctionTypeArgument_raw() {
6271
(Class<? extends BackgroundFunction<?>>) (Class<?>) ForgotTypeParameter.class;
6372
assertThat(backgroundFunctionTypeArgument(c)).isEmpty();
6473
}
74+
75+
@Test
76+
public void parseLegacyEventPubSub() throws IOException {
77+
try (Reader reader =
78+
new InputStreamReader(getClass().getResourceAsStream("/pubsub_background.json"))) {
79+
Event event = BackgroundFunctionExecutor.parseLegacyEvent(reader);
80+
81+
Context context = event.getContext();
82+
assertThat(context.eventType()).isEqualTo("google.pubsub.topic.publish");
83+
assertThat(context.eventId()).isEqualTo("1");
84+
assertThat(context.timestamp()).isEqualTo("2021-06-28T05:46:32.390Z");
85+
86+
JsonObject data = event.getData().getAsJsonObject();
87+
assertThat(data.get("data").getAsString()).isEqualTo("eyJmb28iOiJiYXIifQ==");
88+
String attr = data.get("attributes").getAsJsonObject().get("test").getAsString();
89+
assertThat(attr).isEqualTo("123");
90+
}
91+
}
92+
93+
@Test
94+
public void parseLegacyEventPubSubEmulator() throws IOException {
95+
try (Reader reader =
96+
new InputStreamReader(getClass().getResourceAsStream("/pubsub_emulator.json"))) {
97+
Event event = BackgroundFunctionExecutor.parseLegacyEvent(reader);
98+
99+
Context context = event.getContext();
100+
assertThat(context.eventType()).isEqualTo("google.pubsub.topic.publish");
101+
assertThat(context.eventId()).isEqualTo("1");
102+
assertThat(context.timestamp()).isNotNull();
103+
;
104+
105+
JsonObject data = event.getData().getAsJsonObject();
106+
assertThat(data.get("data").getAsString()).isEqualTo("eyJmb28iOiJiYXIifQ==");
107+
String attr = data.get("attributes").getAsJsonObject().get("test").getAsString();
108+
assertThat(attr).isEqualTo("123");
109+
}
110+
}
65111
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"data": {
3+
"@type": "type.googleapis.com/google.pubsub.v1.PubsubMessage",
4+
"data": "eyJmb28iOiJiYXIifQ==",
5+
"attributes": {
6+
"test": "123"
7+
}
8+
},
9+
"context": {
10+
"eventId": "1",
11+
"eventType": "google.pubsub.topic.publish",
12+
"resource": {
13+
"name": "projects/FOO/topics/BAR_TOPIC",
14+
"service": "pubsub.googleapis.com",
15+
"type": "type.googleapis.com/google.pubsub.v1.PubsubMessage"
16+
},
17+
"timestamp": "2021-06-28T05:46:32.390Z"
18+
}
19+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"subscription": "projects/FOO/subscriptions/BAR_SUB",
3+
"message": {
4+
"data": "eyJmb28iOiJiYXIifQ==",
5+
"messageId": "1",
6+
"attributes": {
7+
"test": "123"
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)