From 38152f24da2606d644a18ba18b88c6122f3f1ef6 Mon Sep 17 00:00:00 2001 From: pgautier404 Date: Fri, 21 Oct 2022 13:15:30 -0700 Subject: [PATCH 1/6] chore: convert deadline secs to expected gRPC value in constructor (#34) Co-authored-by: Pete Gautier --- src/Cache/_ScsDataClient.php | 48 ++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Cache/_ScsDataClient.php b/src/Cache/_ScsDataClient.php index cfcb3960..a8d7f146 100644 --- a/src/Cache/_ScsDataClient.php +++ b/src/Cache/_ScsDataClient.php @@ -111,13 +111,12 @@ class _ScsDataClient { private static int $DEFAULT_DEADLINE_SECONDS = 5; - // TODO: is looks like PHP gRPC wants microsecond timeout values, - // but python's wanted seconds. Need to take a closer look to make sure - // this is accurate. - private static int $TIMEOUT_MULTIPLIER = 1000000; private int $deadline_seconds; + // Used to convert deadline_seconds into microseconds for gRPC + private static int $TIMEOUT_MULTIPLIER = 1000000; private int $defaultTtlSeconds; private _DataGrpcManager $grpcManager; + private int $timeout; public function __construct(string $authToken, string $endpoint, int $defaultTtlSeconds, ?int $operationTimeoutMs) { @@ -125,6 +124,7 @@ public function __construct(string $authToken, string $endpoint, int $defaultTtl validateOperationTimeout($operationTimeoutMs); $this->defaultTtlSeconds = $defaultTtlSeconds; $this->deadline_seconds = $operationTimeoutMs ? $operationTimeoutMs / 1000.0 : self::$DEFAULT_DEADLINE_SECONDS; + $this->timeout = $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER; $this->grpcManager = new _DataGrpcManager($authToken, $endpoint); } @@ -156,7 +156,7 @@ public function set(string $cacheName, string $key, string $value, int $ttlSecon $setRequest->setCacheBody($value); $setRequest->setTtlMilliseconds($ttlMillis); $call = $this->grpcManager->client->Set( - $setRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $setRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $response = $this->processCall($call); } catch (SdkError $e) { @@ -174,7 +174,7 @@ public function get(string $cacheName, string $key): CacheGetResponse $getRequest = new _GetRequest(); $getRequest->setCacheKey($key); $call = $this->grpcManager->client->Get( - $getRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $getRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $response = $this->processCall($call); $ecacheResult = $response->getResult(); @@ -199,7 +199,7 @@ public function delete(string $cacheName, string $key): CacheDeleteResponse $deleteRequest = new _DeleteRequest(); $deleteRequest->setCacheKey($key); $call = $this->grpcManager->client->Delete( - $deleteRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $deleteRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $this->processCall($call); } catch (SdkError $e) { @@ -218,7 +218,7 @@ public function listFetch(string $cacheName, string $listName): CacheListFetchRe $listFetchRequest = new _ListFetchRequest(); $listFetchRequest->setListName($listName); $call = $this->grpcManager->client->ListFetch( - $listFetchRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $listFetchRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $response = $this->processCall($call); } catch (SdkError $e) { @@ -250,7 +250,7 @@ public function listPushFront( $listPushFrontRequest->setTruncateBackToSize($truncateBackToSize); } $call = $this->grpcManager->client->ListPushFront( - $listPushFrontRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $listPushFrontRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $response = $this->processCall($call); } catch (SdkError $e) { @@ -279,7 +279,7 @@ public function listPushBack( $listPushBackRequest->setTruncateFrontToSize($truncateFrontToSize); } $call = $this->grpcManager->client->ListPushBack( - $listPushBackRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $listPushBackRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $response = $this->processCall($call); } catch (SdkError $e) { @@ -298,7 +298,7 @@ public function listPopFront(string $cacheName, string $listName): CacheListPopF $listPopFrontRequest = new _ListPopFrontRequest(); $listPopFrontRequest->setListName($listName); $call = $this->grpcManager->client->ListPopFront( - $listPopFrontRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $listPopFrontRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $response = $this->processCall($call); } catch (SdkError $e) { @@ -320,7 +320,7 @@ public function listPopBack(string $cacheName, string $listName): CacheListPopBa $listPopBackRequest = new _ListPopBackRequest(); $listPopBackRequest->setListName($listName); $call = $this->grpcManager->client->ListPopBack( - $listPopBackRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $listPopBackRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $response = $this->processCall($call); } catch (SdkError $e) { @@ -343,7 +343,7 @@ public function listRemoveValue(string $cacheName, string $listName, string $val $listRemoveValueRequest->setListName($listName); $listRemoveValueRequest->setAllElementsWithValue($value); $call = $this->grpcManager->client->ListRemove( - $listRemoveValueRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $listRemoveValueRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $this->processCall($call); } catch (SdkError $e) { @@ -362,7 +362,7 @@ public function listLength(string $cacheName, string $listName): CacheListLength $listLengthRequest = new _ListLengthRequest(); $listLengthRequest->setListName($listName); $call = $this->grpcManager->client->ListLength( - $listLengthRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $listLengthRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $response = $this->processCall($call); } catch (SdkError $e) { @@ -393,7 +393,7 @@ public function listErase(string $cacheName, string $listName, ?int $beginIndex $listEraseRequest->setAll($all); } $call = $this->grpcManager->client->ListErase( - $listEraseRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $listEraseRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $this->processCall($call); } catch (SdkError $e) { @@ -418,7 +418,7 @@ public function dictionarySet(string $cacheName, string $dictionaryName, string $dictionarySetRequest->setItems([$this->toSingletonFieldValuePair($field, $value)]); $dictionarySetRequest->setRefreshTtl($refreshTtl); $dictionarySetRequest->setTtlMilliseconds($ttlMillis); - $call = $this->grpcManager->client->DictionarySet($dictionarySetRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER]); + $call = $this->grpcManager->client->DictionarySet($dictionarySetRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout]); $this->processCall($call); } catch (SdkError $e) { return new CacheDictionarySetResponseError($e); @@ -445,7 +445,7 @@ public function dictionaryGet(string $cacheName, string $dictionaryName, string $dictionaryGetRequest = new _DictionaryGetRequest(); $dictionaryGetRequest->setDictionaryName($dictionaryName); $dictionaryGetRequest->setFields([$field]); - $call = $this->grpcManager->client->DictionaryGet($dictionaryGetRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER]); + $call = $this->grpcManager->client->DictionaryGet($dictionaryGetRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout]); $dictionaryGetResponse = $this->processCall($call); } catch (SdkError $e) { return new CacheDictionaryGetResponseError($e); @@ -473,7 +473,7 @@ public function dictionaryDelete(string $cacheName, string $dictionaryName): Cac $dictionaryDeleteRequest->setDictionaryName($dictionaryName); $all = new All(); $dictionaryDeleteRequest->setAll($all); - $call = $this->grpcManager->client->DictionaryDelete($dictionaryDeleteRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER]); + $call = $this->grpcManager->client->DictionaryDelete($dictionaryDeleteRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout]); $this->processCall($call); } catch (SdkError $e) { return new CacheDictionaryDeleteResponseError($e); @@ -490,7 +490,7 @@ public function dictionaryFetch(string $cacheName, string $dictionaryName): Cach validateDictionaryName($dictionaryName); $dictionaryFetchRequest = new _DictionaryFetchRequest(); $dictionaryFetchRequest->setDictionaryName($dictionaryName); - $call = $this->grpcManager->client->DictionaryFetch($dictionaryFetchRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER]); + $call = $this->grpcManager->client->DictionaryFetch($dictionaryFetchRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout]); $dictionaryFetchResponse = $this->processCall($call); } catch (SdkError $e) { return new CacheDictionaryFetchResponseError($e); @@ -523,7 +523,7 @@ public function dictionarySetBatch(string $cacheName, string $dictionaryName, ar $dictionarySetBatchRequest->setRefreshTtl($refreshTtl); $dictionarySetBatchRequest->setItems($protoItems); $dictionarySetBatchRequest->setTtlMilliseconds($ttlMillis); - $call = $this->grpcManager->client->DictionarySet($dictionarySetBatchRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER]); + $call = $this->grpcManager->client->DictionarySet($dictionarySetBatchRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout]); $this->processCall($call); } catch (SdkError $e) { return new CacheDictionarySetBatchResponseError($e); @@ -542,7 +542,7 @@ public function dictionaryGetBatch(string $cacheName, string $dictionaryName, ar $dictionaryGetBatchRequest = new _DictionaryGetRequest(); $dictionaryGetBatchRequest->setDictionaryName($dictionaryName); $dictionaryGetBatchRequest->setFields($fields); - $call = $this->grpcManager->client->DictionaryGet($dictionaryGetBatchRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER]); + $call = $this->grpcManager->client->DictionaryGet($dictionaryGetBatchRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout]); $dictionaryGetBatchResponse = $this->processCall($call); } catch (SdkError $e) { return new CacheDictionaryGetBatchResponseError($e); @@ -573,7 +573,7 @@ public function dictionaryIncrement( ->setRefreshTtl($refreshTtl) ->setTtlMilliseconds($ttlMillis); $call = $this->grpcManager->client->DictionaryIncrement( - $dictionaryIncrementRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $dictionaryIncrementRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $response = $this->processCall($call); } catch (SdkError $e) { @@ -596,7 +596,7 @@ public function dictionaryRemoveField(string $cacheName, string $dictionaryName, $dictionaryRemoveFieldRequest->setDictionaryName($dictionaryName); $dictionaryRemoveFieldRequest->setSome($some); $call = $this->grpcManager->client->DictionaryDelete( - $dictionaryRemoveFieldRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $dictionaryRemoveFieldRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $this->processCall($call); } catch (SdkError $e) { @@ -619,7 +619,7 @@ public function dictionaryRemoveFields(string $cacheName, string $dictionaryName $dictionaryRemoveFieldsRequest->setDictionaryName($dictionaryName); $dictionaryRemoveFieldsRequest->setSome($some); $call = $this->grpcManager->client->DictionaryDelete( - $dictionaryRemoveFieldsRequest, ["cache" => [$cacheName]], ["timeout" => $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER] + $dictionaryRemoveFieldsRequest, ["cache" => [$cacheName]], ["timeout" => $this->timeout] ); $this->processCall($call); } catch (SdkError $e) { From fc57a6bb39a12229bc8eccdac542a48d77e6b870 Mon Sep 17 00:00:00 2001 From: pgautier404 Date: Mon, 24 Oct 2022 08:18:21 -0700 Subject: [PATCH 2/6] fix: toString additions and fixes (#36) * chore: adding and fixing toString overrides * fix: add values to response object __toString where appropriate Also truncates printed representations of object data where needed. Co-authored-by: Pete Gautier --- .../CacheOperationTypes.php | 47 +++- tests/Cache/CacheClientTest.php | 219 ++++++++++++++++++ 2 files changed, 260 insertions(+), 6 deletions(-) diff --git a/src/Cache/CacheOperationTypes/CacheOperationTypes.php b/src/Cache/CacheOperationTypes/CacheOperationTypes.php index 9192f269..03251083 100644 --- a/src/Cache/CacheOperationTypes/CacheOperationTypes.php +++ b/src/Cache/CacheOperationTypes/CacheOperationTypes.php @@ -73,6 +73,7 @@ public function name(): string abstract class ResponseBase { protected string $baseType; + protected int $valueSubstringLength = 32; public function __construct() { @@ -108,6 +109,14 @@ protected function isMiss(): bool { return get_class($this) == "{$this->baseType}Miss"; } + + protected function shortValue(string $value): string + { + if (strlen($value) <= $this->valueSubstringLength) { + return $value; + } + return mb_substr($value, 0, $this->valueSubstringLength) . "..."; + } } abstract class CreateCacheResponse extends ResponseBase @@ -280,7 +289,7 @@ public function value(): string public function __toString() { - return get_class($this) . ": key {$this->key} = {$this->value}"; + return get_class($this) . ": key {$this->shortValue($this->key)} = {$this->shortValue($this->value)}"; } } @@ -334,7 +343,7 @@ public function value(): string public function __toString() { - return parent::__toString() . ": {$this->value}"; + return parent::__toString() . ": {$this->shortValue($this->value)}"; } } @@ -472,6 +481,11 @@ public function listLength(): int { return $this->listLength; } + + public function __toString() + { + return parent::__toString() . ": " . $this->listLength . " items"; + } } class CacheListPushFrontResponseError extends CacheListPushFrontResponse @@ -512,6 +526,11 @@ public function listLength(): int { return $this->listLength; } + + public function __toString() + { + return parent::__toString() . ": " . $this->listLength . " items"; + } } class CacheListPushBackResponseError extends CacheListPushBackResponse @@ -563,7 +582,7 @@ public function value(): string public function __toString() { - return parent::__toString() . ": {$this->value}"; + return parent::__toString() . ": {$this->shortValue($this->value)}"; } } @@ -620,7 +639,7 @@ public function value(): string public function __toString() { - return parent::__toString() . ": {$this->value}"; + return parent::__toString() . ": {$this->shortValue($this->value)}"; } } @@ -811,9 +830,8 @@ public function value(): string public function __toString() { - return parent::__toString() . ": " . $this->value; + return parent::__toString() . ": " . $this->shortValue($this->value); } - } class CacheDictionaryGetResponseMiss extends CacheDictionaryGetResponse @@ -897,6 +915,12 @@ public function dictionary(): array { return $this->dictionary; } + + public function __toString() + { + $numItems = count($this->dictionary); + return parent::__toString() . ": $numItems items"; + } } class CacheDictionaryFetchResponseMiss extends CacheDictionaryFetchResponse @@ -994,6 +1018,12 @@ public function values(): array } return $ret; } + + public function __toString() + { + $numResponses = count($this->responsesList); + return parent::__toString() . ": $numResponses responses"; + } } class CacheDictionaryGetBatchResponseError extends CacheDictionaryGetBatchResponse @@ -1040,6 +1070,11 @@ public function string(): string { return "{$this->value}"; } + + public function __toString() + { + return parent::__toString() . ": " . $this->value; + } } class CacheDictionaryIncrementResponseError extends CacheDictionaryIncrementResponse diff --git a/tests/Cache/CacheClientTest.php b/tests/Cache/CacheClientTest.php index 60887c70..81844519 100644 --- a/tests/Cache/CacheClientTest.php +++ b/tests/Cache/CacheClientTest.php @@ -1568,4 +1568,223 @@ public function testDictionaryGetBatchDictionaryMissing_HappyPath() $values = [null, null, null]; $this->assertEquals($values, $response->asSuccess()->values()); } + + // __toString() tests + + public function testCacheSetToString_HappyPath() + { + $key = uniqid(); + $value = "a short value"; + $response = $this->client->set($this->TEST_CACHE_NAME, $key, $value); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": key $key = $value", "$response"); + } + + public function testCacheSetToString_LongValues() + { + $key = str_repeat("k", 256); + $value = str_repeat("v", 256); + $response = $this->client->set($this->TEST_CACHE_NAME, $key, $value); + $this->assertNull($response->asError()); + $this->assertStringStartsWith(get_class($response) . ": key ", "$response"); + $this->assertMatchesRegularExpression("/: key k+\.\.\. = v+\.\.\.$/", "$response"); + } + + public function testCacheGetToString_HappyPath() + { + $key = uniqid(); + $value = "a short value"; + $response = $this->client->set($this->TEST_CACHE_NAME, $key, $value); + $this->assertNull($response->asError()); + + $response = $this->client->get($this->TEST_CACHE_NAME, $key); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": $value", "$response"); + } + + public function testCacheGetToString_LongValue() + { + $key = uniqid(); + $value = str_repeat("a", 256); + $response = $this->client->set($this->TEST_CACHE_NAME, $key, $value); + $this->assertNull($response->asError()); + + $response = $this->client->get($this->TEST_CACHE_NAME, $key); + $this->assertNull($response->asError()); + $this->assertEquals($value, $response->asHit()->value()); + $this->assertStringEndsWith("...", "$response"); + } + + public function testCacheListPopFrontToString_HappyPath() + { + $listName = uniqid(); + $value = "a short value"; + $response = $this->client->listPushFront($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + + $response = $this->client->listPopFront($this->TEST_CACHE_NAME, $listName); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": $value", "$response"); + } + + public function testCacheListPopFrontToString_LongValue() + { + $listName = uniqid(); + $value = str_repeat("a", 256); + $response = $this->client->listPushFront($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + + $response = $this->client->listPopFront($this->TEST_CACHE_NAME, $listName); + $this->assertNull($response->asError()); + $this->assertEquals($value, $response->asHit()->value()); + $this->assertStringEndsWith("...", "$response"); + } + + public function testCacheListPopBackToString_HappyPath() + { + $listName = uniqid(); + $value = "a short value"; + $response = $this->client->listPushBack($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + + $response = $this->client->listPopBack($this->TEST_CACHE_NAME, $listName); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": $value", "$response"); + } + + public function testCacheListPopBackToString_LongValue() + { + $listName = uniqid(); + $value = str_repeat("a", 256); + $response = $this->client->listPushBack($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + + $response = $this->client->listPopBack($this->TEST_CACHE_NAME, $listName); + $this->assertNull($response->asError()); + $this->assertEquals($value, $response->asHit()->value()); + $this->assertStringEndsWith("...", "$response"); + } + + public function testCacheListFetchToString_HappyPath() + { + $listName = uniqid(); + $value = uniqid(); + $response = $this->client->listPushBack($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + $response = $this->client->listPushBack($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + $response = $this->client->listPushBack($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + + $response = $this->client->listFetch($this->TEST_CACHE_NAME, $listName); + $this->assertNull($response->asError()); + $this->assertCount(3, $response->asHit()->values()); + $this->assertEquals(get_class($response) . ": 3 items", "$response"); + } + + public function testCacheListPushFrontToString_HappyPath() + { + $listName = uniqid(); + $value = uniqid(); + $response = $this->client->listPushFront($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": 1 items", "$response"); + $response = $this->client->listPushFront($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": 2 items", "$response"); + $response = $this->client->listPushFront($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": 3 items", "$response"); + } + + public function testCacheListPushBackToString_HappyPath() + { + $listName = uniqid(); + $value = uniqid(); + $response = $this->client->listPushBack($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": 1 items", "$response"); + $response = $this->client->listPushBack($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": 2 items", "$response"); + $response = $this->client->listPushBack($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": 3 items", "$response"); + } + + public function testCacheListLengthToString_HappyPath() + { + $listName = uniqid(); + $value = uniqid(); + $response = $this->client->listPushBack($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + $response = $this->client->listPushBack($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + $response = $this->client->listPushBack($this->TEST_CACHE_NAME, $listName, $value, false); + $this->assertNull($response->asError()); + + $response = $this->client->listLength($this->TEST_CACHE_NAME, $listName); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": 3", "$response"); + } + + public function testDictionaryGetToString_HappyPath() + { + $dictionaryName = uniqid(); + $field = uniqid(); + $value = uniqid(); + $response = $this->client->dictionarySet($this->TEST_CACHE_NAME, $dictionaryName, $field, $value, false); + $this->assertNull($response->asError()); + + $response = $this->client->dictionaryGet($this->TEST_CACHE_NAME, $dictionaryName, $field); + $this->assertNull($response->asError()); + $this->assertNotNull($response->asHit(), "Expected a hit but got: $response"); + $this->assertEquals(get_class($response) . ": $value", "$response"); + } + + public function testDictionaryGetToString_LongValue() + { + $dictionaryName = uniqid(); + $field = uniqid(); + $value = str_repeat("a", 256); + $response = $this->client->dictionarySet($this->TEST_CACHE_NAME, $dictionaryName, $field, $value, false); + $this->assertNull($response->asError()); + + $response = $this->client->dictionaryGet($this->TEST_CACHE_NAME, $dictionaryName, $field); + $this->assertNull($response->asError()); + $this->assertNotNull($response->asHit(), "Expected a hit but got: $response"); + $this->assertEquals($value, $response->asHit()->value()); + $this->assertStringEndsWith("...", "$response"); + } + + public function testDictionaryFetchToString_HappyPath() + { + $dictionaryName = uniqid(); + $field = uniqid(); + $value = uniqid(); + for ($i = 0; $i < 5; $i++) { + $response = $this->client->dictionarySet($this->TEST_CACHE_NAME, $dictionaryName, "$field-$i", $value, false); + $this->assertNull($response->asError()); + } + $response = $this->client->dictionaryFetch($this->TEST_CACHE_NAME, $dictionaryName); + $this->assertNull($response->asError()); + $this->assertNotNull($response->asHit(), "Expected a hit but got: $response"); + $this->assertEquals(get_class($response) . ": 5 items", "$response"); + } + + public function testDictionaryIncrementToString_HappyPath() + { + $dictionaryName = uniqid(); + $field = uniqid(); + $response = $this->client->dictionarySet($this->TEST_CACHE_NAME, $dictionaryName, $field, "1", false); + $this->assertNull($response->asError()); + + $response = $this->client->dictionaryIncrement($this->TEST_CACHE_NAME, $dictionaryName, $field, false); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": 2", "$response"); + + $response = $this->client->dictionaryIncrement($this->TEST_CACHE_NAME, $dictionaryName, $field, false, 10); + $this->assertNull($response->asError()); + $this->assertEquals(get_class($response) . ": 12", "$response"); + } } From baa460c561af26bb8ffacd757fa579559cfd94ab Mon Sep 17 00:00:00 2001 From: pgautier404 Date: Mon, 24 Oct 2022 08:18:45 -0700 Subject: [PATCH 3/6] chore: add github release workflow (#35) Co-authored-by: Pete Gautier --- .github/workflows/manual-release.yaml | 20 +++++++ .github/workflows/on-push-to-release.yaml | 67 +++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 .github/workflows/manual-release.yaml create mode 100644 .github/workflows/on-push-to-release.yaml diff --git a/.github/workflows/manual-release.yaml b/.github/workflows/manual-release.yaml new file mode 100644 index 00000000..d0501f05 --- /dev/null +++ b/.github/workflows/manual-release.yaml @@ -0,0 +1,20 @@ +name: Manual Release +# Triggers a merge from main->release, which will then trigger a release +# from the release branch. +on: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + merge-to-release-branch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + + - name: Merge main -> release + uses: devmasx/merge-branch@master + with: + type: now + from_branch: main + target_branch: release + github_token: ${{ secrets.MOMENTO_MACHINE_USER_GITHUB_TOKEN }} diff --git a/.github/workflows/on-push-to-release.yaml b/.github/workflows/on-push-to-release.yaml new file mode 100644 index 00000000..39225c3f --- /dev/null +++ b/.github/workflows/on-push-to-release.yaml @@ -0,0 +1,67 @@ +name: On push to release + +on: + push: + branches: [ release ] + +jobs: + release: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.release.outputs.release }} + steps: + - uses: actions/checkout@v3 + - name: Set release + id: semrel + uses: go-semantic-release/action@v1 + with: + github-token: ${{ secrets.MOMENTO_MACHINE_USER_GITHUB_TOKEN }} + allow-initial-development-versions: true + force-bump-patch-version: true + # For whatever reason, this silly tool won't let you do releases from branches + # other than the default branch unless you pass this flag, which doesn't seem + # to actually have anything to do with CI: + # https://github.com/go-semantic-release/semantic-release/blob/master/cmd/semantic-release/main.go#L173-L194 + # https://github.com/go-semantic-release/condition-github/blob/4c8af3fc516151423fff2f77eb08bf7082570676/pkg/condition/github.go#L42-L44 + custom-arguments: "--no-ci" + + - name: Output release + id: release + run: echo "::set-output name=release::${{ steps.semrel.outputs.version }}" + + test: + runs-on: ubuntu-latest + env: + TEST_AUTH_TOKEN: ${{ secrets.ALPHA_TEST_AUTH_TOKEN }} + TEST_CACHE_NAME: php-integration-test-cache + + steps: + - uses: actions/checkout@v3 + + - name: Verify README generation + uses: momentohq/standards-and-practices/github-actions/oss-readme-template@gh-actions-v1 + with: + project_status: official + project_stability: alpha + project_type: sdk + sdk_language: PHP + usage_example_path: ./examples/example.php + + - name: Commitlint and Other Shared Build Steps + uses: momentohq/standards-and-practices/github-actions/shared-build@gh-actions-v1 + env: + GITHUB_TOKEN: ${{ secrets.MOMENTO_MACHINE_USER_GITHUB_TOKEN }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + extensions: grpc + tools: composer + + - name: Install dependencies + run: composer install + + - name: Run tests + run: php vendor/phpunit/phpunit/phpunit --configuration phpunit.xml + From 574419caa689885c657bb5d2d44577f64c6d9a1c Mon Sep 17 00:00:00 2001 From: pgautier404 Date: Wed, 26 Oct 2022 17:22:31 -0700 Subject: [PATCH 4/6] chore: add Firebase JWT (#38) --- composer.json | 5 ++- composer.lock | 64 +++++++++++++++++++++++++++- src/Auth/AuthUtils.php | 24 ++++++++--- src/Auth/EnvMomentoTokenProvider.php | 11 ++--- 4 files changed, 89 insertions(+), 15 deletions(-) diff --git a/composer.json b/composer.json index 728b8d2d..a1f26297 100644 --- a/composer.json +++ b/composer.json @@ -22,9 +22,10 @@ }, "require": { "php": ">=7.4", + "ext-grpc": "*", + "firebase/php-jwt": "^6.3", "google/protobuf": "3.21.5", - "grpc/grpc": "1.42.0", - "ext-grpc": "*" + "grpc/grpc": "1.42.0" }, "require-dev": { "composer/composer" : "^2.4.1", diff --git a/composer.lock b/composer.lock index 5945f891..41f5742f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,70 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9fb1e2162d704cb87a9350d50e09390f", + "content-hash": "f2cb31622d5214790efe279b607c00c3", "packages": [ + { + "name": "firebase/php-jwt", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "018dfc4e1da92ad8a1b90adc4893f476a3b41cb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/018dfc4e1da92ad8a1b90adc4893f476a3b41cb8", + "reference": "018dfc4e1da92ad8a1b90adc4893f476a3b41cb8", + "shasum": "" + }, + "require": { + "php": "^7.1||^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^1.1", + "phpunit/phpunit": "^7.5||^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.3.0" + }, + "time": "2022-07-15T16:48:45+00:00" + }, { "name": "google/protobuf", "version": "v3.21.5", diff --git a/src/Auth/AuthUtils.php b/src/Auth/AuthUtils.php index b6ddc919..4aaf5349 100644 --- a/src/Auth/AuthUtils.php +++ b/src/Auth/AuthUtils.php @@ -2,26 +2,36 @@ namespace Momento\Auth; +use Firebase\JWT\JWT; use Momento\Cache\Errors\InvalidArgumentError; + class AuthUtils { - private static function throwBadAuthToken() { + private static function throwBadAuthToken() + { throw new InvalidArgumentError('Invalid Momento auth token.'); } - public static function parseAuthToken(string $authToken) : array { - $exploded = explode (".", $authToken); + public static function parseAuthToken(string $authToken): object + { + $exploded = explode(".", $authToken); if (count($exploded) != 3) { self::throwBadAuthToken(); } - list($header, $payload, $signature) = $exploded; - $token = json_decode(base64_decode($payload), true); - if ($token === null) { + + try { + list($header, $payload, $signature) = $exploded; + $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($payload)); + } catch (\Exception) { + self::throwBadAuthToken(); + } + + if ($payload === null) { self::throwBadAuthToken(); } - return $token; + return $payload; } } diff --git a/src/Auth/EnvMomentoTokenProvider.php b/src/Auth/EnvMomentoTokenProvider.php index 24605d08..f95ecd11 100644 --- a/src/Auth/EnvMomentoTokenProvider.php +++ b/src/Auth/EnvMomentoTokenProvider.php @@ -1,4 +1,5 @@ authToken = $authToken; - $this->controlEndpoint = $payload["cp"]; - $this->cacheEndpoint = $payload["c"]; + $this->controlEndpoint = $payload->cp; + $this->cacheEndpoint = $payload->c; } - public function getAuthToken() : string + public function getAuthToken(): string { return $this->authToken; } - public function getCacheEndpoint() : string + public function getCacheEndpoint(): string { return $this->cacheEndpoint; } - public function getControlEndpoint() : string + public function getControlEndpoint(): string { return $this->controlEndpoint; } From 391f6ec6a47e67a8911b6be2b6d30d9cc8a99f3e Mon Sep 17 00:00:00 2001 From: pgautier404 Date: Thu, 27 Oct 2022 14:12:06 -0700 Subject: [PATCH 5/6] feat: using strict types (#43) --- README.ja.md | 2 +- README.template.md | 2 +- composer.json | 72 ++++++++++--------- src/Auth/AuthUtils.php | 1 + src/Auth/EnvMomentoTokenProvider.php | 3 +- src/Auth/ICredentialProvider.php | 8 ++- .../CacheOperationTypes.php | 1 + src/Cache/Errors/Errors.php | 1 + src/Cache/SimpleCacheClient.php | 1 + src/Cache/_ControlGrpcManager.php | 8 ++- src/Cache/_DataGrpcManager.php | 6 +- src/Cache/_ScsControlClient.php | 1 + src/Cache/_ScsDataClient.php | 13 ++-- src/Utilities/_DataValidation.php | 1 + src/Utilities/_ErrorConverter.php | 7 +- tests/Cache/CacheClientTest.php | 6 +- 16 files changed, 77 insertions(+), 56 deletions(-) diff --git a/README.ja.md b/README.ja.md index 400537b7..31bc69bd 100644 --- a/README.ja.md +++ b/README.ja.md @@ -20,7 +20,7 @@ Momento Serverless Cache の PHP クライアント SDK:従来のキャッシ ### 必要条件 - Momento Auth Token が必要です。[Momento CLI](https://github.com/momentohq/momento-cli)を使って生成できます。 -- 少なくとも PHP 7 +- 少なくとも PHP 8.0 - grpc PHP エクステンション。 インストール方法はこちらの[gRPC docs](https://github.com/grpc/grpc/blob/v1.46.3/src/php/README.md)を参考にしてください。 **IDE に関する注意事項**: [PhpStorm](https://www.jetbrains.com/phpstorm/)や[Microsoft Visual Studio Code](https://code.visualstudio.com/)の様な PHP 開発をサポートできる IDE が必要となります。 diff --git a/README.template.md b/README.template.md index bb0756cb..7ef17a78 100644 --- a/README.template.md +++ b/README.template.md @@ -7,7 +7,7 @@ Japanese: [日本語](README.ja.md) ### Requirements - A Momento Auth Token is required, you can generate one using the [Momento CLI](https://github.com/momentohq/momento-cli) -- At least PHP 7 +- At least PHP 8.0 - The grpc PHP extension. See the [gRPC docs](https://github.com/grpc/grpc/blob/v1.46.3/src/php/README.md) section on installing the extension. **IDE Notes**: You'll most likely want to use an IDE that supports PHP development, such as [PhpStorm](https://www.jetbrains.com/phpstorm/) or [Microsoft Visual Studio Code](https://code.visualstudio.com/). diff --git a/composer.json b/composer.json index a1f26297..7ea3327a 100644 --- a/composer.json +++ b/composer.json @@ -1,39 +1,41 @@ { - "name": "momentohq/client-sdk-php", - "type": "library", - "autoload": { - "psr-4": { - "Momento\\": "src/", - "Cache_client\\": "types/Cache_client/", - "Control_client\\": "types/Control_client/", - "Auth\\": "types/Auth/", - "GPBMetadata\\": "types/GPBMetadata" - }, - "files": [ - "src/Utilities/_DataValidation.php" - ], - "classmap": [ - "src/Cache/CacheOperationTypes/CacheOperationTypes.php", - "src/Cache/Errors/Errors.php" - ] + "name": "momentohq/client-sdk-php", + "type": "library", + "autoload": { + "psr-4": { + "Momento\\": "src/", + "Cache_client\\": "types/Cache_client/", + "Control_client\\": "types/Control_client/", + "Auth\\": "types/Auth/", + "GPBMetadata\\": "types/GPBMetadata" }, - "autoload-dev": { - "psr-4": { "Momento\\Tests\\": "tests/" } - }, - "require": { - "php": ">=7.4", - "ext-grpc": "*", - "firebase/php-jwt": "^6.3", - "google/protobuf": "3.21.5", - "grpc/grpc": "1.42.0" - }, - "require-dev": { - "composer/composer" : "^2.4.1", - "phpunit/phpunit": "^9.5.23" - }, - "config": { - "optimize-autoloader": true, - "preferred-install": "dist", - "sort-packages": true + "files": [ + "src/Utilities/_DataValidation.php" + ], + "classmap": [ + "src/Cache/CacheOperationTypes/CacheOperationTypes.php", + "src/Cache/Errors/Errors.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Momento\\Tests\\": "tests/" } + }, + "require": { + "php": ">=8.0", + "ext-grpc": "*", + "firebase/php-jwt": "^6.3", + "google/protobuf": "3.21.5", + "grpc/grpc": "1.42.0" + }, + "require-dev": { + "composer/composer": "^2.4.1", + "phpunit/phpunit": "^9.5.23" + }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + } } diff --git a/src/Auth/AuthUtils.php b/src/Auth/AuthUtils.php index 4aaf5349..e3395754 100644 --- a/src/Auth/AuthUtils.php +++ b/src/Auth/AuthUtils.php @@ -1,4 +1,5 @@ function($metadata) use ($authToken) { + "update_metadata" => function ($metadata) use ($authToken) { $metadata["authorization"] = [$authToken]; $metadata["agent"] = ["php:0.1"]; return $metadata; } ]; - $channel = new Channel($endpoint, ["credentials"=>ChannelCredentials::createSsl()]); + $channel = new Channel($endpoint, ["credentials" => ChannelCredentials::createSsl()]); $this->client = new ScsControlClient($endpoint, $options, $channel); } diff --git a/src/Cache/_DataGrpcManager.php b/src/Cache/_DataGrpcManager.php index 1ca266cc..cdac9140 100644 --- a/src/Cache/_DataGrpcManager.php +++ b/src/Cache/_DataGrpcManager.php @@ -1,4 +1,6 @@ function($metadata) use ($authToken) { + "update_metadata" => function ($metadata) use ($authToken) { $metadata["authorization"] = [$authToken]; $metadata["agent"] = ["php:0.1"]; return $metadata; } ]; - $channel = new Channel($endpoint, ["credentials"=>ChannelCredentials::createSsl()]); + $channel = new Channel($endpoint, ["credentials" => ChannelCredentials::createSsl()]); $this->client = new ScsClient($endpoint, $options, $channel); } } diff --git a/src/Cache/_ScsControlClient.php b/src/Cache/_ScsControlClient.php index 488ac1c7..9f092454 100644 --- a/src/Cache/_ScsControlClient.php +++ b/src/Cache/_ScsControlClient.php @@ -1,4 +1,5 @@ defaultTtlSeconds = $defaultTtlSeconds; - $this->deadline_seconds = $operationTimeoutMs ? $operationTimeoutMs / 1000.0 : self::$DEFAULT_DEADLINE_SECONDS; - $this->timeout = $this->deadline_seconds * self::$TIMEOUT_MULTIPLIER; + $this->deadline_milliseconds = $operationTimeoutMs ? $operationTimeoutMs : self::$DEFAULT_DEADLINE_MILLISECONDS; + $this->timeout = $this->deadline_milliseconds * self::$TIMEOUT_MULTIPLIER; $this->grpcManager = new _DataGrpcManager($authToken, $endpoint); } diff --git a/src/Utilities/_DataValidation.php b/src/Utilities/_DataValidation.php index 1646fc30..8d6ca10a 100644 --- a/src/Utilities/_DataValidation.php +++ b/src/Utilities/_DataValidation.php @@ -1,4 +1,5 @@ InvalidArgumentError::class, @@ -39,7 +42,7 @@ class _ErrorConverter { Grpc\STATUS_DATA_LOSS => InternalServerError::class ]; - public static function convert(int $status, string $details, ?array $metadata=null) : SdkError + public static function convert(int $status, string $details, ?array $metadata = null): SdkError { if (array_key_exists($status, self::$rpcToError)) { $class = self::$rpcToError[$status]; diff --git a/tests/Cache/CacheClientTest.php b/tests/Cache/CacheClientTest.php index 81844519..d2b08e7f 100644 --- a/tests/Cache/CacheClientTest.php +++ b/tests/Cache/CacheClientTest.php @@ -1,4 +1,5 @@ client->createCache(1); - $this->assertNotNull($response->asError(), "Expected error but got: $response"); - $this->assertEquals(MomentoErrorCode::INVALID_ARGUMENT_ERROR, $response->asError()->errorCode()); + $this->expectException(TypeError::class); + $this->client->createCache(1); } public function testCreateCacheBadAuth() From 431ab487f55c01db810a06e5b583d13573eb1f75 Mon Sep 17 00:00:00 2001 From: pgautier404 Date: Thu, 27 Oct 2022 21:13:50 +0000 Subject: [PATCH 6/6] Update templated README.md file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc0e2d1f..8a5c5cad 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Japanese: [日本語](README.ja.md) ### Requirements - A Momento Auth Token is required, you can generate one using the [Momento CLI](https://github.com/momentohq/momento-cli) -- At least PHP 7 +- At least PHP 8.0 - The grpc PHP extension. See the [gRPC docs](https://github.com/grpc/grpc/blob/v1.46.3/src/php/README.md) section on installing the extension. **IDE Notes**: You'll most likely want to use an IDE that supports PHP development, such as [PhpStorm](https://www.jetbrains.com/phpstorm/) or [Microsoft Visual Studio Code](https://code.visualstudio.com/).