Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: GoogleCloudPlatform/functions-framework-php
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.8.0
Choose a base ref
...
head repository: GoogleCloudPlatform/functions-framework-php
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.0.0
Choose a head ref
  • 2 commits
  • 4 files changed
  • 2 contributors

Commits on Aug 28, 2021

  1. fix: Fix local dev with PubSub emulator (#106)

    * fix: Fix local dev with PubSub emulator
    
    Adds ability to detect messages from the PubSub emulator,
    and properly convert them to CloudEvent events.
    MontyCarter authored Aug 28, 2021

    Verified

    This commit was signed with the committer’s verified signature.
    nostrorom nostro
    Copy the full SHA
    351852f View commit details

Commits on Sep 9, 2021

  1. Verified

    This commit was signed with the committer’s verified signature.
    nostrorom nostro
    Copy the full SHA
    494730f View commit details
Showing with 168 additions and 13 deletions.
  1. +9 −1 CONTRIBUTING.md
  2. +1 −1 src/CloudEventFunctionWrapper.php
  3. +70 −3 src/LegacyEventMapper.php
  4. +88 −8 tests/LegacyEventMapperTest.php
10 changes: 9 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -25,4 +25,12 @@ information on using pull requests.
## Community Guidelines

This project follows [Google's Open Source Community
Guidelines](https://opensource.google.com/conduct/).
Guidelines](https://opensource.google.com/conduct/).

## Releasing

This library is released automatically to Packagist when a
[GitHub release](https://github.com/GoogleCloudPlatform/functions-framework-php/releases)
is created. Name the release with the version appropriate for the changes
included in that release, following semantic versioning. The package will
automatically be published to Packagist within a few minutes.
2 changes: 1 addition & 1 deletion src/CloudEventFunctionWrapper.php
Original file line number Diff line number Diff line change
@@ -72,7 +72,7 @@ public function execute(ServerRequestInterface $request): ResponseInterface
switch ($this->getEventType($request)) {
case self::TYPE_LEGACY:
$mapper = new LegacyEventMapper();
$cloudevent = $mapper->fromJsonData($data);
$cloudevent = $mapper->fromJsonData($data, $request->getUri()->getPath());
break;

case self::TYPE_STRUCTURED:
73 changes: 70 additions & 3 deletions src/LegacyEventMapper.php
Original file line number Diff line number Diff line change
@@ -45,6 +45,10 @@ class LegacyEventMapper
'providers/cloud.storage/eventTypes/object.change' => 'google.cloud.storage.object.v1.finalized',
];

// Constants for Legacy Pubsub Conversion
private const PUBSUB_CE_EVENT_TYPE = 'google.pubsub.topic.publish';
private const LEGACY_PUBSUB_MESSAGE_TYPE = 'type.googleapis.com/google.pubsub.v1.PubsubMessage';

// CloudEvent service names.
private const FIREBASE_AUTH_CE_SERVICE = 'firebaseauth.googleapis.com';
private const FIREBASE_CE_SERVICE = 'firebase.googleapis.com';
@@ -81,9 +85,9 @@ class LegacyEventMapper
'lastSignedInAt' => 'lastSignInTime',
];

public function fromJsonData(array $jsonData): CloudEvent
public function fromJsonData(array $jsonData, string $requestUriPath): CloudEvent
{
[$context, $data] = $this->getLegacyEventContextAndData($jsonData);
[$context, $data] = $this->getLegacyEventContextAndData($jsonData, $requestUriPath);

$eventType = $context->getEventType();
$resourceName = $context->getResourceName();
@@ -142,8 +146,12 @@ public function fromJsonData(array $jsonData): CloudEvent
]);
}

private function getLegacyEventContextAndData(array $jsonData): array
private function getLegacyEventContextAndData(array $jsonData, string $requestUriPath): array
{
if ($this->isRawPubsubPayload($jsonData)) {
$jsonData = $this->convertRawPubsubPayload($jsonData, $requestUriPath);
}

$data = $jsonData['data'] ?? null;

if (array_key_exists('context', $jsonData)) {
@@ -158,6 +166,55 @@ private function getLegacyEventContextAndData(array $jsonData): array
return [$context, $data];
}

private function isRawPubsubPayload(array $jsonData): bool
{
return (!is_null($jsonData) &&
!array_key_exists('context', $jsonData) &&
array_key_exists('subscription', $jsonData) &&
array_key_exists('message', $jsonData) &&
array_key_exists('data', $jsonData['message']) &&
array_key_exists('messageId', $jsonData['message']));
}

private function convertRawPubsubPayload(array $jsonData, string $requestUriPath): array
{
$path_match = preg_match('#projects/[^/?]+/topics/[^/?]+#', $requestUriPath, $matches);
if ($path_match) {
$topic = $matches[0];
} else {
$topic = 'UNKNOWN_PUBSUB_TOPIC';
$this->stderrStructuredWarn('Failed to extract the topic name from the URL path.');
$this->stderrStructuredWarn(
'Configure your subscription\'s push endpoint to use the following path: ' .
'projects/PROJECT_NAME/topics/TOPIC_NAME'
);
}

if (array_key_exists('publishTime', $jsonData['message'])) {
$timestamp = $jsonData['message']['publishTime'];
} else {
$timestamp = gmdate('%Y-%m-%dT%H:%M:%S.%6NZ');
}

return [
'context' => [
'eventId' => $jsonData['message']['messageId'],
'timestamp' => $timestamp,
'eventType' => self::PUBSUB_CE_EVENT_TYPE,
'resource' => [
'service' => self::PUBSUB_CE_SERVICE,
'type' => self::LEGACY_PUBSUB_MESSAGE_TYPE,
'name' => $topic,
],
],
'data' => [
'@type' => self::LEGACY_PUBSUB_MESSAGE_TYPE,
'data' => $jsonData['message']['data'],
'attributes' => $jsonData['message']['attributes'],
],
];
}

private function ceType(string $eventType): string
{
if (isset(self::$ceTypeMap[$eventType])) {
@@ -209,4 +266,14 @@ private function ceResourceAndSubject(string $ceService, string $resource, ?stri

return [$matches[1], $matches[2]];
}

private function stderrStructuredWarn(string $msg)
{
$stderr = fopen('php://stderr', 'wb');
fwrite($stderr, json_encode([
'message' => $msg,
'severity' => 'WARNING'
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
fclose($stderr);
}
}
96 changes: 88 additions & 8 deletions tests/LegacyEventMapperTest.php
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ public function testWithContextProperty(): void
],
]
];
$cloudevent = $mapper->fromJsonData($jsonData);
$cloudevent = $mapper->fromJsonData($jsonData, '');

$this->assertSame('1413058901901494', $cloudevent->getId());
$this->assertSame(
@@ -78,7 +78,7 @@ public function testWithoutContextProperty(): void
'service' => 'pubsub.googleapis.com'
],
];
$cloudevent = $mapper->fromJsonData($jsonData);
$cloudevent = $mapper->fromJsonData($jsonData, '');

$this->assertSame('1413058901901494', $cloudevent->getId());
$this->assertSame(
@@ -102,6 +102,86 @@ public function testWithoutContextProperty(): void
$this->assertSame($jsonData['timestamp'], $message['publishTime']);
}

public function testRawPubsubNoPath(): void
{
$mapper = new LegacyEventMapper();
$jsonData = [
'subscription' => 'projects/sample-project/subscriptions/gcf-test-sub',
'message' => [
'data' => 'eyJmb28iOiJiYXIifQ==',
'messageId' => '1215011316659232',
'attributes' => ['test' => '123']
],
];

$cloudevent = $mapper->fromJsonData($jsonData, '');

$this->assertSame('1.0', $cloudevent->getSpecVersion());
$this->assertSame('1215011316659232', $cloudevent->getId());
$this->assertSame(
'//pubsub.googleapis.com/UNKNOWN_PUBSUB_TOPIC',
$cloudevent->getSource()
);
$this->assertSame(
'google.cloud.pubsub.topic.v1.messagePublished',
$cloudevent->getType()
);
$this->assertNull($cloudevent->getSubject());
$this->assertEqualsWithDelta(
strtotime(gmdate('%Y-%m-%dT%H:%M:%S.%6NZ')),
strtotime($cloudevent->getTime()),
1
);
$this->assertSame(
'123',
$cloudevent->getData()['message']['attributes']['test']
);
$this->assertSame(
'eyJmb28iOiJiYXIifQ==',
$cloudevent->getData()['message']['data']
);
}

public function testRawPubsubWithPath(): void
{
$mapper = new LegacyEventMapper();
$jsonData = [
'subscription' => 'projects/sample-project/subscriptions/gcf-test-sub',
'message' => [
'data' => 'eyJmb28iOiJiYXIifQ==',
'messageId' => '1215011316659232',
'attributes' => ['test' => '123']
],
];

$cloudevent = $mapper->fromJsonData($jsonData, '/projects/sample-project/topics/gcf-test?pubsub_trigger=true');

$this->assertSame('1.0', $cloudevent->getSpecVersion());
$this->assertSame('1215011316659232', $cloudevent->getId());
$this->assertSame(
'//pubsub.googleapis.com/projects/sample-project/topics/gcf-test',
$cloudevent->getSource()
);
$this->assertSame(
'google.cloud.pubsub.topic.v1.messagePublished',
$cloudevent->getType()
);
$this->assertNull($cloudevent->getSubject());
$this->assertEqualsWithDelta(
strtotime(gmdate('%Y-%m-%dT%H:%M:%S.%6NZ')),
strtotime($cloudevent->getTime()),
1
);
$this->assertSame(
'123',
$cloudevent->getData()['message']['attributes']['test']
);
$this->assertSame(
'eyJmb28iOiJiYXIifQ==',
$cloudevent->getData()['message']['data']
);
}

public function testResourceAsString(): void
{
$mapper = new LegacyEventMapper();
@@ -112,7 +192,7 @@ public function testResourceAsString(): void
'eventType' => 'providers/cloud.pubsub/eventTypes/topic.publish',
'resource' => 'projects/MY-PROJECT/topics/MY-TOPIC',
];
$cloudevent = $mapper->fromJsonData($jsonData);
$cloudevent = $mapper->fromJsonData($jsonData, '');

$this->assertSame('1413058901901494', $cloudevent->getId());
$this->assertSame(
@@ -151,7 +231,7 @@ public function testCloudStorage(): void
],
]
];
$cloudevent = $mapper->fromJsonData($jsonData);
$cloudevent = $mapper->fromJsonData($jsonData, '');

$this->assertSame('1413058901901494', $cloudevent->getId());
$this->assertSame(
@@ -198,7 +278,7 @@ public function testFirebaseAuth(): void
'resource' => 'projects/my-project-id',
'timestamp' => '2020-09-29T11:32:00.000Z',
];
$cloudevent = $mapper->fromJsonData($jsonData);
$cloudevent = $mapper->fromJsonData($jsonData, '');

$this->assertSame('aaaaaa-1111-bbbb-2222-cccccccccccc', $cloudevent->getId());
$this->assertSame(
@@ -243,7 +323,7 @@ public function testFirebaseAuthDbDelete(): void
'timestamp' => '2020-05-21T11:53:45.337Z',
'eventId' => 'oIcVXHEMZfhQMNs/yD4nwpuKE0s='
];
$cloudevent = $mapper->fromJsonData($jsonData);
$cloudevent = $mapper->fromJsonData($jsonData, '');

$this->assertSame('oIcVXHEMZfhQMNs/yD4nwpuKE0s=', $cloudevent->getId());
$this->assertSame(
@@ -286,7 +366,7 @@ public function testFirebaseAuthDbDeleteWithAlternateDomain(): void
'timestamp' => '2020-05-21T11:53:45.337Z',
'eventId' => 'oIcVXHEMZfhQMNs/yD4nwpuKE0s='
];
$cloudevent = $mapper->fromJsonData($jsonData);
$cloudevent = $mapper->fromJsonData($jsonData, '');

$this->assertSame('oIcVXHEMZfhQMNs/yD4nwpuKE0s=', $cloudevent->getId());
$this->assertSame(
@@ -329,7 +409,7 @@ public function testFirebaseAuthDbDeleteWithInvalidDomain(): void
'timestamp' => '2020-05-21T11:53:45.337Z',
'eventId' => 'oIcVXHEMZfhQMNs/yD4nwpuKE0s='
];
$cloudevent = $mapper->fromJsonData($jsonData);
$cloudevent = $mapper->fromJsonData($jsonData, '');

$this->assertSame('oIcVXHEMZfhQMNs/yD4nwpuKE0s=', $cloudevent->getId());
$this->assertSame(