diff --git a/CHANGELOG.md b/CHANGELOG.md index f0e52e5..d1e14c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v0.3.3 (2022-09-12) +* Added Spectrum Colour Processor and Constraints +* Added Exception management to validator + ## v0.3.2 (2022-09-12) * Moved Interval constraints back into place diff --git a/composer.json b/composer.json index 3598119..6754ee0 100644 --- a/composer.json +++ b/composer.json @@ -28,10 +28,12 @@ "symplify/easy-coding-standard": "^11", "decodelabs/phpstan-decodelabs": "^0.6", - "decodelabs/compass": "^0.1.1" + "decodelabs/compass": "^0.1.1", + "decodelabs/spectrum": "^0.2.1" }, "suggest": { - "decodelabs/compass": "IP address validation" + "decodelabs/compass": "IP address validation support", + "decodelabs/spectrum": "Color validation support" }, "autoload": { "psr-4": { diff --git a/src/Lucid/Constraint/Color/MaxLightness.php b/src/Lucid/Constraint/Color/MaxLightness.php new file mode 100644 index 0000000..cc2f7ec --- /dev/null +++ b/src/Lucid/Constraint/Color/MaxLightness.php @@ -0,0 +1,66 @@ + + */ +class MaxLightness implements Constraint +{ + /** + * @phpstan-use ConstraintTrait + */ + use ConstraintTrait; + + public const OUTPUT_TYPES = [ + 'Spectrum:Color' + ]; + + protected ?float $max = null; + + public function getWeight(): int + { + return 20; + } + + public function setParameter(mixed $param): static + { + $this->max = (float)$param; + return $this; + } + + public function getParameter(): mixed + { + return $this->max; + } + + public function validate(mixed $value): Generator + { + if ($value === null) { + return false; + } + + if ($value->getHslLightness() > $this->max) { + yield new Error( + $this, + $value, + '%type% value must not have lightness greater than %maxLightness%' + ); + } + + return true; + } +} diff --git a/src/Lucid/Constraint/Color/MaxSaturation.php b/src/Lucid/Constraint/Color/MaxSaturation.php new file mode 100644 index 0000000..a3fff86 --- /dev/null +++ b/src/Lucid/Constraint/Color/MaxSaturation.php @@ -0,0 +1,66 @@ + + */ +class MaxSaturation implements Constraint +{ + /** + * @phpstan-use ConstraintTrait + */ + use ConstraintTrait; + + public const OUTPUT_TYPES = [ + 'Spectrum:Color' + ]; + + protected ?float $max = null; + + public function getWeight(): int + { + return 20; + } + + public function setParameter(mixed $param): static + { + $this->max = (float)$param; + return $this; + } + + public function getParameter(): mixed + { + return $this->max; + } + + public function validate(mixed $value): Generator + { + if ($value === null) { + return false; + } + + if ($value->getHslSaturation() > $this->max) { + yield new Error( + $this, + $value, + '%type% value must not have saturation greater than %maxSaturation%' + ); + } + + return true; + } +} diff --git a/src/Lucid/Constraint/Color/MinLightness.php b/src/Lucid/Constraint/Color/MinLightness.php new file mode 100644 index 0000000..23535b8 --- /dev/null +++ b/src/Lucid/Constraint/Color/MinLightness.php @@ -0,0 +1,66 @@ + + */ +class MinLightness implements Constraint +{ + /** + * @phpstan-use ConstraintTrait + */ + use ConstraintTrait; + + public const OUTPUT_TYPES = [ + 'Spectrum:Color' + ]; + + protected ?float $min = null; + + public function getWeight(): int + { + return 20; + } + + public function setParameter(mixed $param): static + { + $this->min = (float)$param; + return $this; + } + + public function getParameter(): mixed + { + return $this->min; + } + + public function validate(mixed $value): Generator + { + if ($value === null) { + return false; + } + + if ($value->getHslLightness() < $this->min) { + yield new Error( + $this, + $value, + '%type% value must have lightness of at least %minLightness%' + ); + } + + return true; + } +} diff --git a/src/Lucid/Constraint/Color/MinSaturation.php b/src/Lucid/Constraint/Color/MinSaturation.php new file mode 100644 index 0000000..16be8ce --- /dev/null +++ b/src/Lucid/Constraint/Color/MinSaturation.php @@ -0,0 +1,66 @@ + + */ +class MinSaturation implements Constraint +{ + /** + * @phpstan-use ConstraintTrait + */ + use ConstraintTrait; + + public const OUTPUT_TYPES = [ + 'Spectrum:Color' + ]; + + protected ?float $min = null; + + public function getWeight(): int + { + return 20; + } + + public function setParameter(mixed $param): static + { + $this->min = (float)$param; + return $this; + } + + public function getParameter(): mixed + { + return $this->min; + } + + public function validate(mixed $value): Generator + { + if ($value === null) { + return false; + } + + if ($value->getHslSaturation() < $this->min) { + yield new Error( + $this, + $value, + '%type% value must have saturation of at least %minSaturation%' + ); + } + + return true; + } +} diff --git a/src/Lucid/Processor/Color.php b/src/Lucid/Processor/Color.php new file mode 100644 index 0000000..e7f34c2 --- /dev/null +++ b/src/Lucid/Processor/Color.php @@ -0,0 +1,62 @@ + + */ +class Color implements Processor +{ + /** + * @phpstan-use ProcessorTrait + */ + use ProcessorTrait; + + public function getOutputTypes(): array + { + return ['Spectrum:Color', 'DecodeLabs\\Spectrum\\Color']; + } + + /** + * Convert prepared value to string or null + */ + public function coerce(mixed $value): ?Spectrum + { + if (!class_exists(Spectrum::class)) { + throw Exceptional::ComponentUnavailable( + 'Color validation requires decodelabs-spectrum package' + ); + } + + if ($value === null) { + return null; + } + + if ( + is_string($value) || + is_float($value) || + is_array($value) || + $value instanceof Spectrum + ) { + return Spectrum::create($value); + } + + throw Exceptional::UnexpectedValue( + 'Could not coerce value to Spectrum Color', + null, + $value + ); + } +} diff --git a/src/Lucid/Sanitizer/ValueContainer.php b/src/Lucid/Sanitizer/ValueContainer.php index d474db8..4b12636 100644 --- a/src/Lucid/Sanitizer/ValueContainer.php +++ b/src/Lucid/Sanitizer/ValueContainer.php @@ -12,9 +12,12 @@ use Closure; use DecodeLabs\Archetype; use DecodeLabs\Exceptional; +use DecodeLabs\Lucid\Constraint\Processor as ProcessorConstraint; use DecodeLabs\Lucid\Processor; use DecodeLabs\Lucid\Sanitizer; +use DecodeLabs\Lucid\Validate\Error; use DecodeLabs\Lucid\Validate\Result; +use Exception; class ValueContainer implements Sanitizer { @@ -42,7 +45,16 @@ public function as( ): mixed { $processor = $this->loadProcessor($type, $setup); $value = $processor->prepareValue($this->value); - $value = $processor->coerce($value); + + try { + $value = $processor->coerce($value); + } catch (Exception $e) { + throw Exceptional::UnexpectedValue([ + 'message' => 'Unable to coerce value to ' . $processor->getName(), + 'original' => $e, + 'data' => $value + ]); + } if ($value !== null) { $value = $processor->alterValue($value); @@ -74,14 +86,33 @@ public function validate( array|Closure|null $setup = null ): Result { $processor = $this->loadProcessor($type, $setup); - $value = $processor->prepareValue($this->value); - $value = $processor->coerce($value); + $result = new Result($processor); + $value = $this->value; - if ($value !== null) { - $value = $processor->alterValue($value); + try { + $value = $processor->prepareValue($value); + $value = $processor->coerce($value); + + if ($value !== null) { + $value = $processor->alterValue($value); + } + } catch (Exception $e) { + $result->setValue($value); + + $result->addError(new Error( + new ProcessorConstraint($processor), + $value, + 'Unable to coerce value to ' . $processor->getName() . ': %message%', + [ + 'exception' => $e, + 'message' => $e->getMessage() + ] + )); + + return $result; } - $result = new Result($processor); + foreach ($gen = $processor->validate($value) as $error) { if ($error === null) {