Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Constraint.php 2.47 KiB
<?php

declare(strict_types=1);

namespace Distantmagic\Resonance;

use LogicException;

/**
 * @psalm-import-type PJsonSchema from JsonSchemableInterface
 */
abstract readonly class Constraint implements JsonSchemableInterface
{
    abstract public function default(mixed $defaultValue): self;

    abstract public function nullable(): self;

    abstract public function optional(): self;

    /**
     * @return PJsonSchema
     */
    abstract protected function doConvertToJsonSchema(): array|object;

    /**
     * @param mixed $notValidatedData explicitly mixed for typechecks
     */
    abstract protected function doValidate(mixed $notValidatedData, ConstraintPath $path): ConstraintResult;

    public function __construct(
        public ?ConstraintDefaultValue $defaultValue = null,
        public bool $isNullable = false,
        public bool $isRequired = true,
    ) {}

    public function jsonSerialize(): array|object
    {
        return $this->toJsonSchema();
    }

    /**
     * @return PJsonSchema
     */
    public function toJsonSchema(): array|object
    {
        $jsonSchema = $this->doConvertToJsonSchema();

        if (is_object($jsonSchema)) {
            return $jsonSchema;
        }

        if ($this->defaultValue) {
            /**
             * @var mixed explicitly mixed for typechecks
             */
            $jsonSchema['default'] = $this->defaultValue->defaultValue;
        }

        if ($this->isNullable) {
            if (!isset($jsonSchema['type'])) {
                throw new LogicException('Schema cannot be nullable without a type');
            }

            if (is_array($jsonSchema['type'])) {
                $jsonSchema['type'] = ['null', ...$jsonSchema['type']];
            } else {
                $jsonSchema['type'] = ['null', $jsonSchema['type']];
            }
        }
        /**
         * @var PJsonSchema
         */
        return $jsonSchema;
    }

    /**
     * @param mixed $notValidatedData explicitly mixed for typechecks
     */
    public function validate(
        mixed $notValidatedData,
        ConstraintPath $path = new ConstraintPath(),
    ): ConstraintResult {
        if ($this->isNullable && is_null($notValidatedData)) {
            return new ConstraintResult(
                castedData: null,
                path: $path,
                reason: ConstraintReason::Ok,
                status: ConstraintResultStatus::Valid,
            );
        }

        return $this->doValidate($notValidatedData, $path);
    }
}