Skip to content

Commit

Permalink
Refactor to pipeline
Browse files Browse the repository at this point in the history
  • Loading branch information
Riley Aven committed Aug 28, 2024
1 parent cefdb9f commit 7aed516
Show file tree
Hide file tree
Showing 16 changed files with 476 additions and 445 deletions.
22 changes: 21 additions & 1 deletion config/rules-to-schema.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
<?php

// config for LaravelRulesToSchema/LaravelRulesToSchema
return [

/*
* The key to store validation rules under
* This should be unique and not match any real property names
* that will be submitted in requests.
*/
'validation_rule_token' => '##_VALIDATION_RULES_##',

/*
* The pipeline to run rules through
*/
'pipes' => [
\LaravelRulesToSchema\Parsers\TypeParser::class,
\LaravelRulesToSchema\Parsers\NestedObjectParser::class,
\LaravelRulesToSchema\Parsers\RequiredParser::class,
\LaravelRulesToSchema\Parsers\MiscPropertyParser::class,
\LaravelRulesToSchema\Parsers\FormatParser::class,
\LaravelRulesToSchema\Parsers\EnumParser::class,
\LaravelRulesToSchema\Parsers\ExcludedParser::class,
\LaravelRulesToSchema\Parsers\ConfirmedParser::class,
],

];
9 changes: 8 additions & 1 deletion src/Contracts/RuleParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@

interface RuleParser
{
/**
* @param string $property
* @param FluentSchema $schema
* @param array $validationRules
* @param array $nestedRuleset
* @return null|FluentSchema|FluentSchema[]
*/
public function __invoke(
string $property,
FluentSchema $schema,
array $validationRules,
array $nestedRuleset,
);
): array|FluentSchema|null;
}
23 changes: 20 additions & 3 deletions src/LaravelRulesToSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,28 @@

use FluentJsonSchema\FluentSchema;

class LaravelRulesToSchema {
class LaravelRulesToSchema
{
use ParsesNormalizedRuleset;

public function parse(array $rules): FluentSchema
{
return (new RuleParser($rules))->parse();
}
$normalizedRules = (new ValidationRuleNormalizer($rules))->getRules();

$schema = FluentSchema::make()
->type()->object()
->return();

foreach($normalizedRules as $property => $rawRules) {
$propertySchema = $this->parseRuleset($property, $rawRules);

if ($propertySchema instanceof FluentSchema) {
$schema->object()->property($property, $propertySchema);
} elseif (is_array($propertySchema)) {
$schema->object()->properties($propertySchema);
}
}

return $schema;
}
}
8 changes: 8 additions & 0 deletions src/NormalizesValidationRules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace LaravelRulesToSchema;

class NormalizesValidationRules
{

}
15 changes: 13 additions & 2 deletions src/Parsers/ConfirmedParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,19 @@
class ConfirmedParser implements RuleParser
{

public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,)
public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,): array|FluentSchema|null
{
// TODO: Implement __invoke() method.
foreach($validationRules as $ruleArgs) {
[$rule, $args] = $ruleArgs;

if ($rule === 'confirmed') {
return [
$property => $schema,
"{$property}_confirmed" => clone $schema,
];
}
}

return $schema;
}
}
26 changes: 23 additions & 3 deletions src/Parsers/EnumParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,33 @@
namespace LaravelRulesToSchema\Parsers;

use FluentJsonSchema\FluentSchema;
use Illuminate\Validation\Rules\Enum as EnumRule;
use LaravelRulesToSchema\Contracts\RuleParser;
use ReflectionClass;

enum EnumParser implements RuleParser
class EnumParser implements RuleParser
{

public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,)
public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,): array|FluentSchema|null
{
// TODO: Implement __invoke() method.
foreach($validationRules as $ruleArgs) {
[$rule, $args] = $ruleArgs;

if ($rule instanceof EnumRule) {
$enumType = invade($rule)->type;

$reflection = new ReflectionClass($enumType);

if (count($reflection->getConstants()) > 0) {
$values = array_values(array_map(function(\UnitEnum|\BackedEnum $c) {
return $c instanceof \BackedEnum ? $c->value : $c->name;
}, $reflection->getConstants()));

$schema->object()->enum($values);
}
}
}

return $schema;
}
}
13 changes: 11 additions & 2 deletions src/Parsers/ExcludedParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@

use FluentJsonSchema\FluentSchema;
use LaravelRulesToSchema\Contracts\RuleParser;
use LaravelRulesToSchema\RuleCategory;

class ExcludedParser implements RuleParser
{

public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,)
public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,): array|FluentSchema|null
{
// TODO: Implement __invoke() method.
foreach($validationRules as $ruleArgs) {
[$rule, $args] = $ruleArgs;

if (is_string($rule) && in_array($rule, RuleCategory::excluded())) {
return null;
}
}

return $schema;
}
}
43 changes: 41 additions & 2 deletions src/Parsers/FormatParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,47 @@
class FormatParser implements RuleParser
{

public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,)
public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,): array|FluentSchema|null
{
// TODO: Implement __invoke() method.
foreach($validationRules as $ruleArgs) {
[$rule, $args] = $ruleArgs;

// Only enabling formatting for supported rules
// see https://laravel.com/docs/11.x/validation#available-validation-rules

// Dates are not enabled because Laravel doesn't differentiate between dates and date-times

match ($rule) {
// 'regex', 'not_regex' => $schema->format()->regex(),
// 'json-pointer' => $schema->format()->jsonPointer(),
// 'relative-json-pointer' => $schema->format()->relativeJsonPointer(),
// 'uri-template' => $schema->format()->uriTemplate(),
'uuid' => $schema->format()->uuid(),
// 'iri-reference' => $schema->format()->iriReference(),
// 'iri' => $schema->format()->iri(),
// 'uri-reference' => $schema->format()->uriReference(),
// 'uri' => $schema->format()->uri(),
'url' => $schema->format()->uri(),
'ipv4' => $schema->format()->ipv4(),
'ipv6' => $schema->format()->ipv6(),
// 'hostname' => $schema->format()->hostname(),
// 'idn-hostname' => $schema->format()->idnHostname(),
'email' => $schema->format()->email(),
// 'idn-email' => $schema->format()->idnEmail(),
// 'date-time' => $schema->format()->dateTime(),
// 'date', 'date_format', 'date_equals' => $schema->format()->dateTime(),
// 'time' => $schema->format()->time(),
// 'duration' => $schema->format()->duration(),
default => null,
};

// TODO: Also validate mimes file extensions somehow?
if ($rule == 'mimetypes' && count($args) > 0) {
// TODO: What to do about the rest of the specified mime types
$schema->content()->mediaType($args[0]);
}
}

return $schema;
}
}
43 changes: 41 additions & 2 deletions src/Parsers/MiscPropertyParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,52 @@

namespace LaravelRulesToSchema\Parsers;

use FluentJsonSchema\Enums\JsonSchemaType;
use FluentJsonSchema\FluentSchema;

class MiscPropertyParser implements \LaravelRulesToSchema\Contracts\RuleParser
{

public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,)
public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,): array|FluentSchema|null
{
// TODO: Implement __invoke() method.
/** @var JsonSchemaType[] $schemaTypes */
$schemaTypes = $schema->getSchemaDTO()->type;

foreach($validationRules as $ruleArgs) {
[$rule, $args] = $ruleArgs;

foreach($schemaTypes ?? [] as $type) {
if ($type === JsonSchemaType::STRING) {
if ($rule === 'min' && count($args) > 0) {
$schema->string()->minLength($args[0]);
} elseif ($rule === 'max' && count($args) > 0) {
$schema->string()->maxLength($args[0]);
}
} elseif (in_array($type, [JsonSchemaType::INTEGER, JsonSchemaType::NUMBER])) {
if ($rule === 'min' && count($args) > 0) {
$schema->number()->minimum($args[0]);
} elseif ($rule === 'max' && count($args) > 0) {
$schema->number()->maximum($args[0]);
}
} elseif ($type === JsonSchemaType::ARRAY) {
if ($rule === 'min' && count($args) > 0) {
$schema->array()->minItems($args[0]);
} elseif ($rule === 'max' && count($args) > 0) {
$schema->array()->maxItems($args[0]);
}
}
}

if ($rule === 'regex' && count($args) > 0) {

$matched = preg_match('/^(.)(.*?)\1[a-zA-Z]*$/', $args[0], $matches);

if ($matched) {
$schema->string()->pattern($matches[2]);
}
}
}

return $schema;
}
}
24 changes: 22 additions & 2 deletions src/Parsers/NestedObjectParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,32 @@

use FluentJsonSchema\FluentSchema;
use LaravelRulesToSchema\Contracts\RuleParser;
use LaravelRulesToSchema\ParsesNormalizedRuleset;

class NestedObjectParser implements RuleParser
{
use ParsesNormalizedRuleset;

public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,)
public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,): array|FluentSchema|null
{
// TODO: Implement __invoke() method.
$nestedObjects = array_filter($nestedRuleset, fn($x) => $x != config('rules-to-schema.validation_rule_token'), ARRAY_FILTER_USE_KEY);

if (count($nestedObjects) > 0) {
$isArray = array_key_exists('*', $nestedObjects);

if ($isArray) {
$objSchema = $this->parseRuleset("$property.*", $nestedObjects['*']);

$schema->type()->array()
->items($objSchema);
} else {
foreach($nestedObjects as $propName => $objValidationRules) {
$schema->type()->object()
->property($propName, $this->parseRuleset($propName, $objValidationRules));
}
}
}

return $schema;
}
}
23 changes: 21 additions & 2 deletions src/Parsers/RequiredParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,27 @@
class RequiredParser implements RuleParser
{

public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,)
public function __invoke(string $property, FluentSchema $schema, array $validationRules, array $nestedRuleset,): array|FluentSchema|null
{
// TODO: Implement __invoke() method.
$foundRequired = false;
foreach($validationRules as $ruleArgs) {
[$rule, $args] = $ruleArgs;

if (!is_string($rule)) {
continue;
}

if ($rule === 'sometimes') {
return $schema;
} elseif ($rule == 'required') {
$foundRequired = true;
}
}

if ($foundRequired) {
$schema->object()->required();
}

return $schema;
}
}
Loading

0 comments on commit 7aed516

Please sign in to comment.