Skip to content

Commit

Permalink
feat: add validator for LexString values
Browse files Browse the repository at this point in the history
  • Loading branch information
ramsey committed May 30, 2023
1 parent 4118aed commit 9e73279
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 1 deletion.
3 changes: 2 additions & 1 deletion composer.json
Expand Up @@ -16,7 +16,8 @@
"security": "https://github.com/socialweb-php/atproto-lexicon/security/policy"
},
"require": {
"php": "^8.2"
"php": "^8.2",
"ext-intl": "*"
},
"require-dev": {
"ramsey/devtools": "^2.0",
Expand Down
77 changes: 77 additions & 0 deletions src/Validators/LexStringValidator.php
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace SocialWeb\Atproto\Lexicon\Validators;

use SocialWeb\Atproto\Lexicon\Nsid\NsidValidator;
use SocialWeb\Atproto\Lexicon\Types\LexString;
use SocialWeb\Atproto\Lexicon\Types\LexStringFormat;
use SocialWeb\Atproto\Lexicon\Validators\Formats\AtIdentifierValidator;
use SocialWeb\Atproto\Lexicon\Validators\Formats\AtUriValidator;
use SocialWeb\Atproto\Lexicon\Validators\Formats\DatetimeValidator;
use SocialWeb\Atproto\Lexicon\Validators\Formats\DidValidator;
use SocialWeb\Atproto\Lexicon\Validators\Formats\HandleValidator;
use SocialWeb\Atproto\Lexicon\Validators\Formats\UriValidator;

use function grapheme_strlen;
use function implode;
use function in_array;
use function is_string;
use function strlen;

class LexStringValidator implements Validator
{
public function __construct(private readonly LexString $type)
{
}

public function validate(mixed $value, ?string $path = null): string
{
if ($value === null) {
$value = $this->type->default;
}

$path = $path ?? 'Value';

if (!is_string($value)) {
throw new InvalidValue("$path must be a string");
}

if ($this->type->const !== null && $value !== $this->type->const) {
throw new InvalidValue("$path must be {$this->type->const}");
}

if ($this->type->enum !== null && !in_array($value, $this->type->enum)) {
throw new InvalidValue("$path must be one of (" . implode('|', $this->type->enum) . ')');
}

if ($this->type->maxLength !== null && strlen($value) > $this->type->maxLength) {
throw new InvalidValue("$path must not be longer than {$this->type->maxLength} characters");
}

if ($this->type->minLength !== null && strlen($value) < $this->type->minLength) {
throw new InvalidValue("$path must not be shorter than {$this->type->minLength} characters");
}

if ($this->type->maxGraphemes !== null && grapheme_strlen($value) > $this->type->maxGraphemes) {
throw new InvalidValue("$path must not be longer than {$this->type->maxGraphemes} graphemes");
}

if ($this->type->minGraphemes !== null && grapheme_strlen($value) < $this->type->minGraphemes) {
throw new InvalidValue("$path must not be shorter than {$this->type->minGraphemes} graphemes");
}

return match ($this->type->format) {
LexStringFormat::AtIdentifier => (new AtIdentifierValidator())->validate($value, $path),
LexStringFormat::AtUri => (new AtUriValidator())->validate($value, $path),
//LexStringFormat::Cid => ???,
LexStringFormat::DateTime => (new DatetimeValidator())->validate($value, $path),
LexStringFormat::Did => (new DidValidator())->validate($value, $path),
LexStringFormat::Handle => (new HandleValidator())->validate($value, $path),
LexStringFormat::Nsid => (new NsidValidator())->validate($value, $path),
LexStringFormat::Uri => (new UriValidator())->validate($value, $path),
default => $value,
};
}
}
85 changes: 85 additions & 0 deletions tests/Validators/LexStringValidatorTest.php
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

namespace SocialWeb\Test\Atproto\Lexicon\Validators;

use SocialWeb\Atproto\Lexicon\Types\LexEntity;
use SocialWeb\Atproto\Lexicon\Types\LexString;
use SocialWeb\Atproto\Lexicon\Types\LexStringFormat;
use SocialWeb\Atproto\Lexicon\Validators\LexStringValidator;
use SocialWeb\Atproto\Lexicon\Validators\Validator;

use function assert;

class LexStringValidatorTest extends ValidatorTestCase
{
protected function getValidator(LexEntity $type): Validator
{
assert($type instanceof LexString);

return new LexStringValidator($type);
}

/**
* @return array<array{0: LexString, 1: mixed}>
*/
public static function validTestProvider(): array
{
return [
[new LexString(), 'foo'],
[new LexString(default: 'bar'), null],
[new LexString(default: 'baz'), 'qux'],
[new LexString(const: 'qux'), 'qux'],
[new LexString(enum: ['a', 'b', 'c']), 'b'],
[new LexString(maxLength: 6), 'foobar'],
[new LexString(minLength: 3), 'baz'],
[new LexString(maxGraphemes: 5), '1234👨‍👩‍👧‍👦'],
[new LexString(minGraphemes: 2), '👨‍👩‍👧‍👦👩‍👩‍👧‍👦'],
[new LexString(format: LexStringFormat::AtIdentifier), 'did:plc:12345678abcdefghijklmnop'],
[new LexString(format: LexStringFormat::AtUri), 'at://user.bsky.social'],
[new LexString(format: LexStringFormat::DateTime), '2019-07-09T15:03:36.000+00:00'],
[new LexString(format: LexStringFormat::Did), 'did:plc:7iza6de2dwap2sbkpav7c6c6'],
[new LexString(format: LexStringFormat::Handle), 'john.test.bsky.app'],
[new LexString(format: LexStringFormat::Nsid), 'com.example.foo'],
[new LexString(format: LexStringFormat::Uri), 'foo://bar'],
];
}

/**
* @return array<array{0: LexString, 1: mixed, 2: string}>
*/
public static function invalidTestProvider(): array
{
return [
[new LexString(), 1234, 'Value must be a string'],
[new LexString(), 12.34, 'Value must be a string'],
[new LexString(), true, 'Value must be a string'],
[new LexString(), null, 'Value must be a string'],
[new LexString(), [], 'Value must be a string'],
[new LexString(), (object) [], 'Value must be a string'],
[new LexString(default: 'bar'), 1234, 'Value must be a string'],
[new LexString(default: 'bar'), 12.34, 'Value must be a string'],
[new LexString(default: 'bar'), true, 'Value must be a string'],
[new LexString(default: 'bar'), [], 'Value must be a string'],
[new LexString(default: 'bar'), (object) [], 'Value must be a string'],
[new LexString(const: 'foo'), 'qux', 'Value must be foo'],
[new LexString(enum: ['a', 'b', 'c']), 'd', 'Value must be one of (a|b|c)'],
[new LexString(maxLength: 8), 'foobarbaz', 'Value must not be longer than 8 characters'],
[new LexString(minLength: 4), 'qux', 'Value must not be shorter than 4 characters'],
[new LexString(maxGraphemes: 4), '1234👨‍👩‍👧‍👦', 'Value must not be longer than 4 graphemes'],
[new LexString(minGraphemes: 2), '👨‍👩‍👧‍👦', 'Value must not be shorter than 2 graphemes'],
[new LexString(format: LexStringFormat::AtIdentifier), 'bad id', 'Value must be a valid handle or DID'],
[new LexString(format: LexStringFormat::AtUri), 'http://did:plc:asdf123', 'AT URI must use "at://" scheme'],
[
new LexString(format: LexStringFormat::DateTime),
'2020-12-04',
'Value must be an ISO 8601 formatted datetime string',
],
[new LexString(format: LexStringFormat::Did), 'method:did:val', 'DID requires "did:" prefix'],
[new LexString(format: LexStringFormat::Handle), 'did:thing.test', 'Invalid characters found in handle'],
[new LexString(format: LexStringFormat::Nsid), 'example.com', 'NSID needs at least three parts'],
[new LexString(format: LexStringFormat::Uri), 'foobar', 'Value must be a URI'],
];
}
}

0 comments on commit 9e73279

Please sign in to comment.