Skip to content

Commit ce2caaa

Browse files
authored
Run conformance tests, support JSON serialization of CloudEvents (GoogleCloudPlatform#72)
1 parent 30d8102 commit ce2caaa

File tree

4 files changed

+216
-1
lines changed

4 files changed

+216
-1
lines changed

.github/workflows/conformance.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: PHP Conformance CI
2+
on:
3+
pull_request:
4+
branches:
5+
- master
6+
push:
7+
branches:
8+
- master
9+
workflow_dispatch:
10+
jobs:
11+
build:
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
php-version: [7.2, 7.3, 7.4]
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v2
19+
20+
- name: Setup Go
21+
uses: actions/setup-go@v2
22+
with:
23+
go-version: '1.15'
24+
25+
- name: Setup PHP ${{ matrix.php-version }}
26+
uses: shivammathur/setup-php@v2
27+
with:
28+
php-version: ${{ matrix.php-version }}
29+
30+
- name: Install Dependencies
31+
uses: nick-invision/retry@v1
32+
with:
33+
timeout_minutes: 10
34+
max_attempts: 3
35+
command: composer install
36+
37+
- name: Run HTTP conformance tests
38+
uses: GoogleCloudPlatform/functions-framework-conformance/action@v0.3.6
39+
env:
40+
FUNCTION_TARGET: 'httpFunc'
41+
FUNCTION_SIGNATURE_TYPE: 'http'
42+
FUNCTION_SOURCE: ${{ github.workspace }}/tests/conformance/index.php
43+
with:
44+
functionType: 'http'
45+
useBuildpacks: false
46+
cmd: "'php -S localhost:8080 router.php'"
47+
48+
- name: Run CloudEvent conformance tests
49+
uses: GoogleCloudPlatform/functions-framework-conformance/action@v0.3.6
50+
env:
51+
FUNCTION_TARGET: 'cloudEventFunc'
52+
FUNCTION_SIGNATURE_TYPE: 'cloudevent'
53+
FUNCTION_SOURCE: ${{ github.workspace }}/tests/conformance/index.php
54+
with:
55+
functionType: 'cloudevent'
56+
useBuildpacks: false
57+
validateMapping: false
58+
cmd: "'php -S localhost:8080 router.php'"

src/CloudEvent.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
namespace Google\CloudFunctions;
2020

21-
class CloudEvent
21+
class CloudEvent implements \JsonSerializable
2222
{
2323
// Required Fields
2424
private $id;
@@ -120,6 +120,21 @@ public static function fromArray(array $arr)
120120
return new static(...$args);
121121
}
122122

123+
public function jsonSerialize()
124+
{
125+
return [
126+
'id' => $this->id,
127+
'source' => $this->source,
128+
'specversion' => $this->specversion,
129+
'type' => $this->type,
130+
'datacontenttype' => $this->datacontenttype,
131+
'dataschema' => $this->dataschema,
132+
'subject' => $this->subject,
133+
'time' => $this->time,
134+
'data' => $this->data,
135+
];
136+
}
137+
123138
public function __toString()
124139
{
125140
$output = implode("\n", [

tests/CloudEventTest.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
/**
4+
* Copyright 2021 Google LLC.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
namespace Google\CloudFunctions\Tests;
20+
21+
use Google\CloudFunctions\CloudEvent;
22+
use PHPUnit\Framework\TestCase;
23+
24+
/**
25+
* @group gcf-framework
26+
*/
27+
class CloudEventTest extends TestCase
28+
{
29+
public function testJsonSerialize()
30+
{
31+
$event = new CloudEvent(
32+
'1413058901901494',
33+
'//pubsub.googleapis.com/projects/MY-PROJECT/topics/MY-TOPIC',
34+
'1.0',
35+
'com.google.cloud.pubsub.topic.publish',
36+
'application/json',
37+
'type.googleapis.com/google.logging.v2.LogEntry',
38+
'My Subject',
39+
'2020-12-08T20:03:19.162Z',
40+
[
41+
"message" => [
42+
"data" => "SGVsbG8gdGhlcmU=",
43+
"messageId" => "1408577928008405",
44+
"publishTime" => "2020-08-06T22:31:14.536Z"
45+
],
46+
"subscription" => "projects/MY-PROJECT/subscriptions/MY-SUB"
47+
]
48+
);
49+
50+
$want = '{
51+
"id": "1413058901901494",
52+
"source": "\/\/pubsub.googleapis.com\/projects\/MY-PROJECT\/topics\/MY-TOPIC",
53+
"specversion": "1.0",
54+
"type": "com.google.cloud.pubsub.topic.publish",
55+
"datacontenttype": "application\/json",
56+
"dataschema": "type.googleapis.com\/google.logging.v2.LogEntry",
57+
"subject": "My Subject",
58+
"time": "2020-12-08T20:03:19.162Z",
59+
"data": {
60+
"message": {
61+
"data": "SGVsbG8gdGhlcmU=",
62+
"messageId": "1408577928008405",
63+
"publishTime": "2020-08-06T22:31:14.536Z"
64+
},
65+
"subscription": "projects\\/MY-PROJECT\\/subscriptions\\/MY-SUB"
66+
}
67+
}';
68+
69+
$this->assertEquals(json_encode($event, JSON_PRETTY_PRINT), $want);
70+
}
71+
}

tests/conformance/index.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
use Google\CloudFunctions\CloudEvent;
4+
use Psr\Http\Message\ServerRequestInterface;
5+
6+
define('OUTPUT_FILE', 'function_output.json');
7+
8+
function httpFunc(ServerRequestInterface $request)
9+
{
10+
file_put_contents(OUTPUT_FILE, $request->getBody());
11+
return "ok" . PHP_EOL;
12+
}
13+
14+
// PHP cannot distinguish between an empty array and an empty map because they
15+
// are represented by the same type. This means that when a JSON object
16+
// containing either an empty array or an empty map is decoded the type
17+
// information is lost -- both will be an empty array. Furthermore, when an
18+
// empty PHP array is encoded to JSON it will always be represented as '[]'.
19+
// This means that a JSON -> PHP -> JSON round trip would look like this:
20+
//
21+
// '{"foo": {}}' -> array('foo' => array()) -> '{"foo": []}'
22+
//
23+
// There is a way to get PHP to output '{}' when encoding JSON, though: the
24+
// empty object. Unfortunately the built-in JSON decoder cannot use the empty
25+
// object only for empty maps. It insists on using *only* arrays or *only*
26+
// objects for *everything*, resulting in further problems when JSON arrays
27+
// are forced into PHP objects. So this is not a solution to the round trip
28+
// problem.
29+
//
30+
// The conformance tests depend on JSON -> [language representation] -> JSON
31+
// transformations so the fact that PHP throws away type information is
32+
// problematic. The function below addresses specific known cases of this
33+
// problem in the conformance test suite by changing the PHP representation
34+
// of the CloudEvent's data to use types that will encode correctly.
35+
function fixCloudEventData(CloudEvent $cloudevent): CloudEvent
36+
{
37+
$data = $cloudevent->getData();
38+
$dataModified = false;
39+
40+
// These fields are known to be maps, so if they're present and empty
41+
// change them to the empty object so they encode as '{}'.
42+
$fields = ['oldValue', 'updateMask'];
43+
foreach ($fields as $f) {
44+
if (array_key_exists($f, $data) && empty($data[$f])) {
45+
$data[$f] = new stdClass;
46+
$dataModified = true;
47+
}
48+
}
49+
50+
if ($dataModified) {
51+
// Clone the event but swap in the modified data.
52+
return new CloudEvent(
53+
$cloudevent->getId(),
54+
$cloudevent->getSource(),
55+
$cloudevent->getSpecversion(),
56+
$cloudevent->getType(),
57+
$cloudevent->getDatacontenttype(),
58+
$cloudevent->getDataschema(),
59+
$cloudevent->getSubject(),
60+
$cloudevent->getTime(),
61+
$data
62+
);
63+
}
64+
65+
return $cloudevent;
66+
}
67+
68+
function cloudEventFunc(CloudEvent $cloudevent)
69+
{
70+
file_put_contents(OUTPUT_FILE, json_encode(fixCloudEventData($cloudevent)));
71+
}

0 commit comments

Comments
 (0)