Skip to content

Commit e19138a

Browse files
danielgsimsjdpedrie
authored andcommitted
feat: Allow for topics to updated via a new method (#2256)
* feat: Allow for topics to updated via a new method Creates the Google\Cloud\PubSub\Topic::update method, docs and tests. * Update PubSub/src/Topic.php Co-Authored-By: John Pedrie <jdpedrie@users.noreply.github.com> * Update PubSub/src/Topic.php Co-Authored-By: John Pedrie <jdpedrie@users.noreply.github.com> * Update PubSub/src/Topic.php Co-Authored-By: John Pedrie <jdpedrie@users.noreply.github.com> * Update PubSub/src/Topic.php Co-Authored-By: John Pedrie <jdpedrie@users.noreply.github.com> * Update PubSub/src/Topic.php Co-Authored-By: David Supplee <dwsupplee@gmail.com> * Addresses feedback in PR * Addresses PR feedback * Fixing missing dataProvider from merge conflict * Removed test for filtering immutable fields * Update PubSub/src/Topic.php Co-Authored-By: John Pedrie <jdpedrie@users.noreply.github.com>
1 parent 223560a commit e19138a

9 files changed

Lines changed: 246 additions & 2 deletions

File tree

PubSub/src/Connection/ConnectionInterface.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public function getTopic(array $args);
3838
*/
3939
public function deleteTopic(array $args);
4040

41+
/**
42+
* @param array $args
43+
*/
44+
public function updateTopic(array $args);
45+
4146
/**
4247
* @param array $args
4348
*/

PubSub/src/Connection/Grpc.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Google\Cloud\PubSub\V1\PushConfig;
3232
use Google\Cloud\PubSub\V1\SubscriberClient;
3333
use Google\Cloud\PubSub\V1\Subscription;
34+
use Google\Cloud\PubSub\V1\Topic;
3435
use Google\Protobuf\Duration;
3536
use Google\Protobuf\FieldMask;
3637
use Google\Protobuf\Timestamp;
@@ -142,6 +143,33 @@ public function deleteTopic(array $args)
142143
]);
143144
}
144145

146+
/**
147+
* @param array $args
148+
*/
149+
public function updateTopic(array $args)
150+
{
151+
$updateMaskPaths = [];
152+
foreach (explode(',', $this->pluck('updateMask', $args)) as $path) {
153+
$updateMaskPaths[] = Serializer::toSnakeCase($path);
154+
}
155+
156+
$fieldMask = new FieldMask([
157+
'paths' => $updateMaskPaths
158+
]);
159+
160+
$topic = $this->serializer->decodeMessage(
161+
new Topic,
162+
$this->pluck('topic', $args)
163+
);
164+
165+
unset($args['name']);
166+
return $this->send([$this->publisherClient, 'updateTopic'], [
167+
$topic,
168+
$fieldMask,
169+
$args
170+
]);
171+
}
172+
145173
/**
146174
* @param array $args
147175
*/

PubSub/src/Connection/Rest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ public function createTopic(array $args)
7373
return $this->send('topics', 'create', $args);
7474
}
7575

76+
/**
77+
* @param array $args
78+
*/
79+
public function updateTopic(array $args)
80+
{
81+
return $this->send('topics', 'patch', $args);
82+
}
83+
7684
/**
7785
* @param array $args
7886
*/

PubSub/src/Topic.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,97 @@ public function create(array $options = [])
190190
return $this->info;
191191
}
192192

193+
/**
194+
* Update the topic.
195+
*
196+
* Note that the topic's name and kms key name are immutable properties and may not be modified.
197+
*
198+
* Example:
199+
* ```
200+
* $topic->update([
201+
* 'messageStoragePolicy' => [
202+
* 'allowedPersistenceRegions' => ['us-central1']
203+
* ]
204+
* ]);
205+
* ```
206+
*
207+
* ```
208+
* // Updating labels with an explicit update mask
209+
* $topic->update([
210+
* 'labels' => [
211+
* 'foo' => 'bar'
212+
* ]
213+
* ], [
214+
* 'updateMask' => [
215+
* 'labels'
216+
* ]
217+
* ]);
218+
* ```
219+
*
220+
* @see https://cloud.google.com/pubsub/docs/reference/rest/v1/UpdateTopicRequest Update Topic
221+
*
222+
* @param array $topic {
223+
* The Topic data.
224+
*
225+
* @type array $labels Key value pairs used to organize your resources.
226+
* @type array $messageStoragePolicy Policy constraining the set of
227+
* Google Cloud Platform regions where messages published to the
228+
* topic may be stored. If not present, then no constraints are in
229+
* effect.
230+
* @type string[] $messageStoragePolicy.allowedPersistenceRegions A list
231+
* of IDs of GCP regions where messages that are published to the
232+
* topic may be persisted in storage. Messages published by
233+
* publishers running in non-allowed GCP regions (or running
234+
* outside of GCP altogether) will be routed for storage in one of
235+
* the allowed regions. An empty list means that no regions are
236+
* allowed, and is not a valid configuration.
237+
* }
238+
* @param array $options [optional] {
239+
* Configuration options.
240+
*
241+
* @type array $updateMask A list of field paths to be modified. Nested
242+
* key names should be dot-separated, e.g.
243+
* `messageStoragePolicy.allowedPersistenceRegions`. Google Cloud
244+
* PHP will attempt to infer this value on your behalf, however
245+
* modification of map fields with arbitrary keys (such as labels
246+
* or message storage policy) requires an explicit update mask.
247+
* }
248+
*
249+
* @return array The topic info.
250+
*/
251+
public function update(array $topic, array $options = [])
252+
{
253+
$updateMaskPaths = $this->pluck('updateMask', $options, false) ?: [];
254+
255+
if (!$updateMaskPaths) {
256+
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($topic));
257+
foreach ($iterator as $leafValue) {
258+
$excludes = ['name'];
259+
$keys = [];
260+
foreach (range(0, $iterator->getDepth()) as $depth) {
261+
$key = $iterator->getSubIterator($depth)->key();
262+
if (!is_string($key)) {
263+
break;
264+
}
265+
$keys[] = $key;
266+
}
267+
268+
$path = implode('.', $keys);
269+
if (!in_array($path, $excludes)) {
270+
$updateMaskPaths[] = $path;
271+
}
272+
}
273+
}
274+
275+
return $this->info = $this->connection->updateTopic([
276+
'name' => $this->name,
277+
'topic' => [
278+
'name' => $this->name,
279+
] + $topic,
280+
'updateMask' => implode(',', $updateMaskPaths)
281+
] + $options);
282+
}
283+
193284
/**
194285
* Delete a topic.
195286
*

PubSub/tests/Snippet/TopicTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,34 @@ public function testCreate()
9898
$this->assertEquals([], $res->returnVal());
9999
}
100100

101+
public function testUpdate()
102+
{
103+
$snippet = $this->snippetFromMethod(Topic::class, 'update');
104+
$snippet->addLocal('topic', $this->topic);
105+
106+
$this->connection->updateTopic(Argument::any())
107+
->shouldBeCalled();
108+
109+
$this->topic->___setProperty('connection', $this->connection->reveal());
110+
111+
$snippet->invoke();
112+
}
113+
114+
public function testUpdateWithMask()
115+
{
116+
$snippet = $this->snippetFromMethod(Topic::class, 'update', 1);
117+
$snippet->addLocal('topic', $this->topic);
118+
119+
$this->connection->updateTopic(Argument::allOf(
120+
Argument::withKey('topic'),
121+
Argument::withKey('updateMask')
122+
))->shouldBeCalled();
123+
124+
$this->topic->___setProperty('connection', $this->connection->reveal());
125+
126+
$snippet->invoke();
127+
}
128+
101129
public function testDelete()
102130
{
103131
$snippet = $this->snippetFromMethod(Topic::class, 'delete');

PubSub/tests/System/ManageTopicsTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,50 @@ public function testReloadTopic($client)
7878
$this->assertEquals($topic->name(), $topic->reload()['name']);
7979
}
8080

81+
/**
82+
* @dataProvider clientProvider
83+
*/
84+
public function testUpdateTopic($client)
85+
{
86+
$shortName = uniqid(self::TESTING_PREFIX);
87+
$this->assertFalse($client->topic($shortName)->exists());
88+
$topic = $client->createTopic($shortName);
89+
self::$deletionQueue->add($topic);
90+
91+
$policy = [
92+
'allowedPersistenceRegions' => ['us-central1', 'us-east1']
93+
];
94+
95+
$topic->update([
96+
'messageStoragePolicy' => $policy
97+
]);
98+
99+
$this->assertEquals($policy, $topic->info()['messageStoragePolicy']);
100+
}
101+
102+
/**
103+
* @dataProvider clientProvider
104+
*/
105+
public function testUpdateTopicWithUpdateMask($client)
106+
{
107+
$shortName = uniqid(self::TESTING_PREFIX);
108+
$this->assertFalse($client->topic($shortName)->exists());
109+
$topic = $client->createTopic($shortName);
110+
self::$deletionQueue->add($topic);
111+
112+
$labels = [
113+
'foo' => 'bar'
114+
];
115+
116+
$topic->update([
117+
'labels' => $labels
118+
], [
119+
'updateMask' => [ 'labels' ]
120+
]);
121+
122+
$this->assertEquals($labels, $topic->info()['labels']);
123+
}
124+
81125
/**
82126
* @dataProvider clientProvider
83127
*/

PubSub/tests/Unit/Connection/GrpcTest.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Google\Cloud\PubSub\Connection\Grpc;
2323
use Google\Cloud\Core\Testing\GrpcTestTrait;
2424
use Google\ApiCore\Serializer;
25+
use Google\Cloud\PubSub\V1\Topic;
2526
use Google\Protobuf\FieldMask;
2627
use Google\Protobuf\Timestamp;
2728
use Prophecy\Argument;
@@ -118,12 +119,30 @@ public function methodProvider()
118119
$subscription->setRetainAckedMessages(true);
119120

120121
$serializer = new Serializer();
121-
$fieldMask = $serializer->decodeMessage(new FieldMask(), ['paths' => ['retain_acked_messages']]);
122+
$subscriptionFieldMask = $serializer->decodeMessage(new FieldMask(), ['paths' => ['retain_acked_messages']]);
122123

123124
$time = (new \DateTime)->format('Y-m-d\TH:i:s.u\Z');
124125
$timestamp = $serializer->decodeMessage(new Timestamp(), $this->formatTimestampForApi($time));
125126

127+
$topic = new Topic;
128+
$topic->setLabels(['foo' => 'bar']);
129+
$topic->setName('projects/foo/topics/bar');
130+
$topicFieldMask = $serializer->decodeMessage(new FieldMask(), ['paths' => ['labels']]);
131+
126132
return [
133+
[
134+
'updateTopic',
135+
[
136+
'topic' => [
137+
'name' => 'projects/foo/topics/bar',
138+
'labels' => [
139+
'foo' => 'bar'
140+
]
141+
],
142+
'updateMask' => 'labels'
143+
],
144+
[$topic, $topicFieldMask, []]
145+
],
127146
[
128147
'updateSubscription',
129148
[
@@ -133,7 +152,7 @@ public function methodProvider()
133152
],
134153
'updateMask' => 'retainAckedMessages'
135154
],
136-
[$subscription, $fieldMask, []]
155+
[$subscription, $subscriptionFieldMask, []]
137156
],
138157
[
139158
'listSnapshots',

PubSub/tests/Unit/Connection/RestTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public function methodProvider()
9494
['createTopic'],
9595
['getTopic'],
9696
['deleteTopic'],
97+
['updateTopic'],
9798
['listTopics'],
9899
['publishMessage'],
99100
['listSubscriptionsByTopic'],

PubSub/tests/Unit/TopicTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,26 @@ public function testCreate()
7171
$this->assertEquals('projects/project-name/topics/topic-name', $res['name']);
7272
}
7373

74+
public function testUpdate()
75+
{
76+
$this->connection->updateTopic(Argument::allOf(
77+
Argument::withEntry('topic', [
78+
'name' => $this->topic->name(),
79+
'foo' => 'bar'
80+
]),
81+
Argument::withEntry('updateMask', 'foo')
82+
))->shouldBeCalled()->willReturn([
83+
'foo' => 'bar'
84+
]);
85+
86+
$this->topic->___setProperty('connection', $this->connection->reveal());
87+
88+
$res = $this->topic->update(['foo' => 'bar']);
89+
90+
$this->assertEquals(['foo' => 'bar'], $res);
91+
$this->assertEquals('bar', $this->topic->info()['foo']);
92+
}
93+
7494
public function testDelete()
7595
{
7696
$this->connection->deleteTopic(Argument::withEntry('foo', 'bar'))

0 commit comments

Comments
 (0)