Skip to content

Commit

Permalink
Release/1.1.0 (#4)
Browse files Browse the repository at this point in the history
* feat: Adds inspection of Ksuid components.
  • Loading branch information
gustavofreze authored Jun 9, 2023
1 parent 64162da commit 10d6a5c
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 14 deletions.
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,46 @@ composer require tiny-blocks/ksuid

The library exposes a concrete implementation through the `Ksuid` class.

### Creating a Ksuid

With the `random` method, a new instance of type `Ksuid` is created from a timestamp (_current unix timestamp - EPOCH_)
and a payload (_cryptographically secure pseudo-random bytes_).

```php
$ksuid = Ksuid::random();

echo $ksuid->getValue(); # 2QvY47aUlV3cSyYcpo53FQxgSFg
echo $ksuid->getPayload(); # bdf0a2329620aa70cebe4026ca9ff49c
echo $ksuid->getTimestamp(); # 286235327
echo $ksuid->getValue(); # 2QzPUGEaAKHhVcQYrqQodbiZat1
echo $ksuid->getPayload(); # 464932c1194da98e752145d72b8f0aab
echo $ksuid->getUnixTime(); # 1686353450
echo $ksuid->getTimestamp(); # 286353450
```

You can also choose from other factory models.

```php

Ksuid::from(payload: hex2bin("9850EEEC191BF4FF26F99315CE43B0C8"), timestamp: 286235327);
Ksuid::from(payload: hex2bin('9850EEEC191BF4FF26F99315CE43B0C8'), timestamp: 286235327);

Ksuid::fromPayload(value: '0o5Fs0EELR0fUjHjbCnEtdUwQe3');

Ksuid::fromTimestamp(value: 286235327);
```

### Inspecting a Ksuid

You can inspect the components used to create a `Ksuid`, using the `inspectFrom` method.

```php
$ksuid = Ksuid::inspectFrom(ksuid: '2QzPUGEaAKHhVcQYrqQodbiZat1');

print_r($ksuid); # Array
# (
# [time] => 2023-06-09 23:30:50 +0000 GMT+0000
# [payload] => 464932c1194da98e752145d72b8f0aab
# [timestamp] => 286353450
# )
```

## License

Math is licensed under [MIT](/LICENSE).
Expand Down
9 changes: 6 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"type": "library",
"license": "MIT",
"homepage": "https://github.com/tiny-blocks/ksuid",
"description": "K-Sortable Unique Identifier for PHP.",
"description": "K-Sortable Unique Identifier.",
"prefer-stable": true,
"minimum-stability": "stable",
"keywords": [
Expand Down Expand Up @@ -39,15 +39,18 @@
}
},
"require": {
"php": "^8.2",
"tiny-blocks/encoder": "^1"
"php": "^8.1||^8.2",
"tiny-blocks/encoder": "^1.2"
},
"require-dev": {
"infection/infection": "^0.26",
"phpmd/phpmd": "^2.12",
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "^3.7"
},
"suggest": {
"ext-gmp": "Enables faster math with arbitrary-precision integers using GMP."
},
"scripts": {
"phpcs": "phpcs --standard=PSR12 --extensions=php ./src",
"phpmd": "phpmd ./src text phpmd.xml --suffixes php --ignore-violations-on-exit",
Expand Down
3 changes: 2 additions & 1 deletion infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"UnwrapSubstr": false,
"UnwrapStrRepeat": false,
"IncrementInteger": false,
"DecrementInteger": false
"DecrementInteger": false,
"PublicVisibility": false
},
"phpUnit": {
"configDir": "",
Expand Down
14 changes: 14 additions & 0 deletions src/Internal/Exceptions/InvalidKsuidForInspection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace TinyBlocks\Ksuid\Internal\Exceptions;

use RuntimeException;

final class InvalidKsuidForInspection extends RuntimeException
{
public function __construct(private readonly string $ksuid)
{
$template = 'The KSUID <%s> is not valid for inspection.';
parent::__construct(message: sprintf($template, $this->ksuid));
}
}
2 changes: 1 addition & 1 deletion src/Internal/Exceptions/InvalidPayloadSize.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ final class InvalidPayloadSize extends RuntimeException
public function __construct(private readonly int $currentSize, private readonly int $payloadBytes)
{
$template = 'Current length is <%s> bytes. Payload size must be exactly <%s> bytes.';
parent::__construct(sprintf($template, $this->currentSize, $this->payloadBytes));
parent::__construct(message: sprintf($template, $this->currentSize, $this->payloadBytes));
}
}
24 changes: 19 additions & 5 deletions src/Internal/Timestamp.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@

namespace TinyBlocks\Ksuid\Internal;

use DateTime;
use TinyBlocks\Encoder\Base62;

final class Timestamp
{
public const EPOCH = 1400000000;

private function __construct(private readonly int $value)
private readonly int $time;

private function __construct(private readonly int $value, private readonly int $epoch)
{
$this->time = $this->value - $this->epoch;
}

public static function from(int $value): Timestamp
{
return new Timestamp(value: $value);
return new Timestamp(value: $value, epoch: 0);
}

public static function fromBytes(string $value): Timestamp
Expand All @@ -24,16 +28,26 @@ public static function fromBytes(string $value): Timestamp
$timestamp = substr($timestamp, -4);
$timestamp = (array)unpack("Nuint", $timestamp);

return new Timestamp(value: $timestamp["uint"]);
return new Timestamp(value: $timestamp["uint"], epoch: 0);
}

public static function fromAdjustedCurrentTime(): Timestamp
{
return new Timestamp(value: time() - self::EPOCH);
return new Timestamp(value: time(), epoch: self::EPOCH);
}

public static function format(int $timestamp): string
{
return (new DateTime("@$timestamp"))->format('Y-m-d H:i:s O T');
}

public function getValue(): int
{
return $this->value;
return $this->time;
}

public function getUnixTime(): int
{
return $this->time + self::EPOCH;
}
}
21 changes: 21 additions & 0 deletions src/Ksuid.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace TinyBlocks\Ksuid;

use TinyBlocks\Encoder\Base62;
use TinyBlocks\Ksuid\Internal\Exceptions\InvalidKsuidForInspection;
use TinyBlocks\Ksuid\Internal\Payload;
use TinyBlocks\Ksuid\Internal\Timestamp;

Expand Down Expand Up @@ -34,6 +35,21 @@ public static function fromTimestamp(int $value): Ksuid
return new Ksuid(payload: Payload::random(), timestamp: Timestamp::from(value: $value));
}

public static function inspectFrom(string $ksuid): array
{
if (strlen($ksuid) !== self::ENCODED_SIZE) {
throw new InvalidKsuidForInspection(ksuid: $ksuid);
}

$ksuid = self::fromPayload(value: $ksuid);

return [
'time' => Timestamp::format(timestamp: $ksuid->getUnixTime()),
'payload' => $ksuid->getPayload(),
'timestamp' => $ksuid->getTimestamp()
];
}

public function getValue(): string
{
$encoded = Base62::encode(value: $this->getBytes());
Expand All @@ -56,6 +72,11 @@ public function getPayload(): string
return bin2hex($this->payload->getValue());
}

public function getUnixTime(): int
{
return $this->timestamp->getUnixTime();
}

public function getTimestamp(): int
{
return $this->timestamp->getValue();
Expand Down
50 changes: 50 additions & 0 deletions tests/KsuidTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace TinyBlocks\Ksuid;

use Exception;
use PHPUnit\Framework\TestCase;
use TinyBlocks\Ksuid\Internal\Exceptions\InvalidKsuidForInspection;

class KsuidTest extends TestCase
{
Expand Down Expand Up @@ -59,4 +61,52 @@ public function testFromPayloadAndTimestamp(): void
self::assertEquals(20, strlen($ksuid->getBytes()));
self::assertEquals(Ksuid::ENCODED_SIZE, strlen($ksuid->getValue()));
}

/**
* @dataProvider providerForTestInspectFrom
*/
public function testInspectFrom(string $ksuid, array $expected): void
{
$actual = Ksuid::inspectFrom(ksuid: $ksuid);

self::assertEquals($expected, $actual);
}

/**
* @throws Exception
*/
public function testExceptionWhenInvalidKsuidForInspection(): void
{
/** @Given a invalid KSUID */
$ksuid = random_bytes(5);
$template = 'The KSUID <%s> is not valid for inspection.';

/** @Then an exception indicating that KSUID is invalid for inspection should occur */
$this->expectException(InvalidKsuidForInspection::class);
$this->expectExceptionMessage(sprintf($template, $ksuid));

Ksuid::inspectFrom(ksuid: $ksuid);
}

public function providerForTestInspectFrom(): array
{
return [
[
'ksuid' => '2QzPUGEaAKHhVcQYrqQodbiZat1',
'expected' => [
'time' => '2023-06-09 23:30:50 +0000 GMT+0000',
'payload' => '464932c1194da98e752145d72b8f0aab',
'timestamp' => 286353450
]
],
[
'ksuid' => '0ujzPyRiIAffKhBux4PvQdDqMHY',
'expected' => [
'time' => '2017-10-10 04:46:20 +0000 GMT+0000',
'payload' => '73fc1aa3b2446246d6e89fcd909e8fe8',
'timestamp' => 107610780
]
]
];
}
}

0 comments on commit 10d6a5c

Please sign in to comment.