Repository navigation
Add a Decides validation rule backed by laya decisions #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => ':attribute ist nicht zulässig.', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => 'The :attribute is not acceptable.', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => 'El campo :attribute no es aceptable.', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => 'Le champ :attribute n’est pas acceptable.', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => 'Il campo :attribute non è accettabile.', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => ':attribute は許可されていません。', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => 'Het veld :attribute is niet toegestaan.', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => 'Pole :attribute jest niedozwolone.', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => 'O campo :attribute não é aceitável.', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => 'Поле :attribute недопустимо.', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => ':attribute alanı kabul edilemez.', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| <?php | ||
|
|
||
| return [ | ||
| 'laya' => ':attribute 不可接受。', | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace MarcReichel\Laya\Laravel\Rules; | ||
|
|
||
| use Closure; | ||
| use Illuminate\Container\Container; | ||
| use Illuminate\Contracts\Validation\ValidationRule; | ||
| use Illuminate\Translation\Translator; | ||
| use MarcReichel\Laya\Answers\YesNoAnswer; | ||
| use MarcReichel\Laya\Exceptions\ServerBusyException; | ||
| use MarcReichel\Laya\Exceptions\TransportException; | ||
| use MarcReichel\Laya\Laya; | ||
| use MarcReichel\Laya\Question; | ||
|
|
||
| /** | ||
| * Validation rule backed by laya. Resolves Laya from the container at validation time, so Laya::fake() works. | ||
| * | ||
| * 'body' => ['required', 'string', Decides::that(Moderation::class, fn (Moderation $m) => ! $m->abusive)], | ||
| * 'body' => [Decides::not('Does the text contain insults, hate speech or threats?')], | ||
| */ | ||
| final class Decides implements ValidationRule | ||
| { | ||
| private ?string $message = null; | ||
|
|
||
| /** @param Closure(Laya, string): bool $passes */ | ||
| private function __construct(private readonly Closure $passes, private bool $passWhenUnavailable = false) {} | ||
|
|
||
| /** | ||
| * Passes when $passes returns true for the decision laya makes about the value. | ||
| * | ||
| * @template T of object | ||
| * | ||
| * @param class-string<T> $class | ||
| * @param Closure(T): bool $passes | ||
| */ | ||
| public static function that(string $class, Closure $passes): self | ||
| { | ||
| return new self(fn (Laya $laya, string $value): bool => $passes($laya->decide($value, $class))); | ||
| } | ||
|
|
||
| /** Passes when laya answers "yes" to the question. */ | ||
| public static function yes(string $question): self | ||
| { | ||
| return new self(fn (Laya $laya, string $value): bool => self::answer($laya, $value, $question)->yes()); | ||
| } | ||
|
|
||
| /** Passes when laya answers "no" to the question. */ | ||
| public static function not(string $question): self | ||
| { | ||
| return new self(fn (Laya $laya, string $value): bool => self::answer($laya, $value, $question)->no()); | ||
| } | ||
|
|
||
| public function message(string $message): self | ||
| { | ||
| $this->message = $message; | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| /** Let the value pass when laya-serve is unreachable or busy, instead of failing the validation. */ | ||
| public function passWhenUnavailable(): self | ||
| { | ||
| $this->passWhenUnavailable = true; | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| public function validate(string $attribute, mixed $value, Closure $fail): void | ||
| { | ||
| if (is_string($value) && $this->passes($value)) { | ||
| return; | ||
| } | ||
|
|
||
| /** @var Translator $translator */ | ||
| $translator = Container::getInstance()->make('translator'); | ||
|
|
||
| $fail($this->message ?? ($translator->has('validation.laya') ? 'validation.laya' : 'laya::validation.laya'))->translate(); | ||
| } | ||
|
|
||
| private function passes(string $value): bool | ||
| { | ||
| try { | ||
| return ($this->passes)(Container::getInstance()->make(Laya::class), $value); | ||
| } catch (TransportException|ServerBusyException) { | ||
| return $this->passWhenUnavailable; | ||
| } | ||
| } | ||
|
|
||
| private static function answer(Laya $laya, string $value, string $question): YesNoAnswer | ||
| { | ||
| return $laya->predict($value, ['answer' => Question::yesNo($question)])->yesNo('answer'); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| use App\Decisions\Triage; | ||
| use Illuminate\Support\Facades\Validator; | ||
| use MarcReichel\Laya\Exceptions\ServerException; | ||
| use MarcReichel\Laya\Laravel\LayaServiceProvider; | ||
| use MarcReichel\Laya\Laravel\Rules\Decides; | ||
| use MarcReichel\Laya\Laya; | ||
| use Orchestra\Testbench\TestCase; | ||
| use Psr\Http\Client\ClientExceptionInterface; | ||
| use Psr\Http\Client\ClientInterface; | ||
| use Psr\Http\Message\RequestInterface; | ||
| use Psr\Http\Message\ResponseInterface; | ||
|
|
||
| uses(TestCase::class); | ||
|
|
||
| require_once __DIR__.'/Fixtures/Decisions.php'; | ||
|
|
||
| beforeEach(function () { | ||
| $this->app->register(LayaServiceProvider::class); | ||
| }); | ||
|
|
||
| function validates(mixed $value, Decides $rule, array $rules = []): Illuminate\Validation\Validator | ||
| { | ||
| return Validator::make(['body' => $value], ['body' => [...$rules, $rule]]); | ||
| } | ||
|
|
||
| it('passes or fails on the decision object', function (bool $churn, bool $passes) { | ||
| $laya = Laya::fake(['department' => 'billing', 'urgency' => 1, 'churn' => $churn]); | ||
|
|
||
| $rule = Decides::that(Triage::class, fn (Triage $t) => ! $t->churn); | ||
|
|
||
| expect(validates('Billed twice', $rule)->passes())->toBe($passes); | ||
| $laya->assertDecided(Triage::class); | ||
| })->with([[false, true], [true, false]]); | ||
|
|
||
| it('answers yes/no shorthands', function () { | ||
| Laya::fake(['answer' => true]); | ||
| expect(validates('You idiot', Decides::not('Insulting?'))->passes())->toBeFalse() | ||
| ->and(validates('You idiot', Decides::yes('Insulting?'))->passes())->toBeTrue(); | ||
|
|
||
| Laya::fake(['answer' => false]); | ||
| expect(validates('Nice', Decides::not('Insulting?'))->passes())->toBeTrue() | ||
| ->and(validates('Nice', Decides::yes('Insulting?'))->passes())->toBeFalse(); | ||
| }); | ||
|
|
||
| it('fails with the default message, translated and with the attribute', function () { | ||
| Laya::fake(['answer' => true]); | ||
|
|
||
| expect(validates('x', Decides::not('Insulting?'))->errors()->first('body'))->toBe('The body is not acceptable.'); | ||
|
|
||
| app()->setLocale('de'); | ||
| expect(validates('x', Decides::not('Insulting?'))->errors()->first('body'))->toBe('body ist nicht zulässig.'); | ||
| }); | ||
|
|
||
| it('prefers the app\'s validation.laya', function () { | ||
| Laya::fake(['answer' => true]); | ||
| app('translator')->addLines(['validation.laya' => 'Custom :attribute'], 'en'); | ||
|
|
||
| expect(validates('x', Decides::not('Insulting?'))->errors()->first('body'))->toBe('Custom body'); | ||
| }); | ||
|
|
||
| it('takes a custom message', function () { | ||
| Laya::fake(['answer' => true]); | ||
|
|
||
| expect(validates('x', Decides::not('Insulting?')->message('Keep it friendly.'))->errors()->first('body'))->toBe('Keep it friendly.'); | ||
| }); | ||
|
|
||
| it('fails non-strings without a request', function () { | ||
| $laya = Laya::fake(['answer' => false]); | ||
|
|
||
| expect(validates(['a'], Decides::not('Insulting?'))->passes())->toBeFalse(); | ||
| $laya->assertNothingPredicted(); | ||
| }); | ||
|
|
||
| it('fails or passes when laya is unavailable', function (int $status, bool $passWhenUnavailable) { | ||
| $rule = Decides::not('Insulting?'); | ||
| app()->instance(Laya::class, layaRespondingWith($status, 'busy')); | ||
|
|
||
| expect(validates('x', $passWhenUnavailable ? $rule->passWhenUnavailable() : $rule)->passes())->toBe($passWhenUnavailable); | ||
| })->with([[503, false], [503, true]]); | ||
|
|
||
| it('fails or passes when laya is unreachable', function (bool $passWhenUnavailable) { | ||
| app()->instance(Laya::class, new Laya('http://laya.local', httpClient: new class implements ClientInterface | ||
| { | ||
| public function sendRequest(RequestInterface $request): ResponseInterface | ||
| { | ||
| throw new class('down') extends RuntimeException implements ClientExceptionInterface {}; | ||
| } | ||
| })); | ||
| $rule = Decides::not('Insulting?'); | ||
|
|
||
| expect(validates('x', $passWhenUnavailable ? $rule->passWhenUnavailable() : $rule)->passes())->toBe($passWhenUnavailable); | ||
| })->with([false, true]); | ||
|
|
||
| it('lets other laya errors through', function () { | ||
| app()->instance(Laya::class, layaRespondingWith(500, 'boom')); | ||
|
|
||
| validates('x', Decides::not('Insulting?')->passWhenUnavailable())->passes(); | ||
| })->throws(ServerException::class); | ||
|
|
||
| it('ships a message with :attribute for every language', function () { | ||
| foreach (glob(__DIR__.'/../lang/*/validation.php') as $file) { | ||
| expect((require $file)['laya'])->toContain(':attribute'); | ||
| } | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.