| 1 |
<?php |
| 2 |
|
| 3 |
namespace Dudlewebs\WPMCS\s3\Aws\Api; |
| 4 |
|
| 5 |
use Dudlewebs\WPMCS\s3\Aws; |
| 6 |
/** |
| 7 |
* Validates a schema against a hash of input. |
| 8 |
*/ |
| 9 |
class Validator |
| 10 |
{ |
| 11 |
private $path = []; |
| 12 |
private $errors = []; |
| 13 |
private $constraints = []; |
| 14 |
private static $defaultConstraints = ['required' => \true, 'min' => \true, 'max' => \false, 'pattern' => \false]; |
| 15 |
/** |
| 16 |
* @param array $constraints Associative array of constraints to enforce. |
| 17 |
* Accepts the following keys: "required", "min", |
| 18 |
* "max", and "pattern". If a key is not |
| 19 |
* provided, the constraint will assume false. |
| 20 |
*/ |
| 21 |
public function __construct(array $constraints = null) |
| 22 |
{ |
| 23 |
static $assumedFalseValues = ['required' => \false, 'min' => \false, 'max' => \false, 'pattern' => \false]; |
| 24 |
$this->constraints = empty($constraints) ? self::$defaultConstraints : $constraints + $assumedFalseValues; |
| 25 |
} |
| 26 |
/** |
| 27 |
* Validates the given input against the schema. |
| 28 |
* |
| 29 |
* @param string $name Operation name |
| 30 |
* @param Shape $shape Shape to validate |
| 31 |
* @param array $input Input to validate |
| 32 |
* |
| 33 |
* @throws \InvalidArgumentException if the input is invalid. |
| 34 |
*/ |
| 35 |
public function validate($name, Shape $shape, array $input) |
| 36 |
{ |
| 37 |
$this->dispatch($shape, $input); |
| 38 |
if ($this->errors) { |
| 39 |
$message = \sprintf("Found %d error%s while validating the input provided for the " . "%s operation:\n%s", \count($this->errors), \count($this->errors) > 1 ? 's' : '', $name, \implode("\n", $this->errors)); |
| 40 |
$this->errors = []; |
| 41 |
throw new \InvalidArgumentException($message); |
| 42 |
} |
| 43 |
} |
| 44 |
private function dispatch(Shape $shape, $value) |
| 45 |
{ |
| 46 |
static $methods = ['structure' => 'check_structure', 'list' => 'check_list', 'map' => 'check_map', 'blob' => 'check_blob', 'boolean' => 'check_boolean', 'integer' => 'check_numeric', 'float' => 'check_numeric', 'long' => 'check_numeric', 'string' => 'check_string', 'byte' => 'check_string', 'char' => 'check_string']; |
| 47 |
$type = $shape->getType(); |
| 48 |
if (isset($methods[$type])) { |
| 49 |
$this->{$methods[$type]}($shape, $value); |
| 50 |
} |
| 51 |
} |
| 52 |
private function check_structure(StructureShape $shape, $value) |
| 53 |
{ |
| 54 |
$isDocument = isset($shape['document']) && $shape['document']; |
| 55 |
$isUnion = isset($shape['union']) && $shape['union']; |
| 56 |
if ($isDocument) { |
| 57 |
if (!$this->checkDocumentType($value)) { |
| 58 |
$this->addError("is not a valid document type"); |
| 59 |
return; |
| 60 |
} |
| 61 |
} elseif ($isUnion) { |
| 62 |
if (!$this->checkUnion($value)) { |
| 63 |
$this->addError("is a union type and must have exactly one non null value"); |
| 64 |
return; |
| 65 |
} |
| 66 |
} elseif (!$this->checkAssociativeArray($value)) { |
| 67 |
return; |
| 68 |
} |
| 69 |
if ($this->constraints['required'] && $shape['required']) { |
| 70 |
foreach ($shape['required'] as $req) { |
| 71 |
if (!isset($value[$req])) { |
| 72 |
$this->path[] = $req; |
| 73 |
$this->addError('is missing and is a required parameter'); |
| 74 |
\array_pop($this->path); |
| 75 |
} |
| 76 |
} |
| 77 |
} |
| 78 |
if (!$isDocument) { |
| 79 |
foreach ($value as $name => $v) { |
| 80 |
if ($shape->hasMember($name)) { |
| 81 |
$this->path[] = $name; |
| 82 |
$this->dispatch($shape->getMember($name), isset($value[$name]) ? $value[$name] : null); |
| 83 |
\array_pop($this->path); |
| 84 |
} |
| 85 |
} |
| 86 |
} |
| 87 |
} |
| 88 |
private function check_list(ListShape $shape, $value) |
| 89 |
{ |
| 90 |
if (!\is_array($value)) { |
| 91 |
$this->addError('must be an array. Found ' . Aws\describe_type($value)); |
| 92 |
return; |
| 93 |
} |
| 94 |
$this->validateRange($shape, \count($value), "list element count"); |
| 95 |
$items = $shape->getMember(); |
| 96 |
foreach ($value as $index => $v) { |
| 97 |
$this->path[] = $index; |
| 98 |
$this->dispatch($items, $v); |
| 99 |
\array_pop($this->path); |
| 100 |
} |
| 101 |
} |
| 102 |
private function check_map(MapShape $shape, $value) |
| 103 |
{ |
| 104 |
if (!$this->checkAssociativeArray($value)) { |
| 105 |
return; |
| 106 |
} |
| 107 |
$values = $shape->getValue(); |
| 108 |
foreach ($value as $key => $v) { |
| 109 |
$this->path[] = $key; |
| 110 |
$this->dispatch($values, $v); |
| 111 |
\array_pop($this->path); |
| 112 |
} |
| 113 |
} |
| 114 |
private function check_blob(Shape $shape, $value) |
| 115 |
{ |
| 116 |
static $valid = ['string' => \true, 'integer' => \true, 'double' => \true, 'resource' => \true]; |
| 117 |
$type = \gettype($value); |
| 118 |
if (!isset($valid[$type])) { |
| 119 |
if ($type != 'object' || !\method_exists($value, '__toString')) { |
| 120 |
$this->addError('must be an fopen resource, a ' . 'Dudlewebs\\WPMCS\\s3\\GuzzleHttp\\Stream\\StreamInterface object, or something ' . 'that can be cast to a string. Found ' . Aws\describe_type($value)); |
| 121 |
} |
| 122 |
} |
| 123 |
} |
| 124 |
private function check_numeric(Shape $shape, $value) |
| 125 |
{ |
| 126 |
if (!\is_numeric($value)) { |
| 127 |
$this->addError('must be numeric. Found ' . Aws\describe_type($value)); |
| 128 |
return; |
| 129 |
} |
| 130 |
$this->validateRange($shape, $value, "numeric value"); |
| 131 |
} |
| 132 |
private function check_boolean(Shape $shape, $value) |
| 133 |
{ |
| 134 |
if (!\is_bool($value)) { |
| 135 |
$this->addError('must be a boolean. Found ' . Aws\describe_type($value)); |
| 136 |
} |
| 137 |
} |
| 138 |
private function check_string(Shape $shape, $value) |
| 139 |
{ |
| 140 |
if ($shape['jsonvalue']) { |
| 141 |
if (!self::canJsonEncode($value)) { |
| 142 |
$this->addError('must be a value encodable with \'json_encode\'.' . ' Found ' . Aws\describe_type($value)); |
| 143 |
} |
| 144 |
return; |
| 145 |
} |
| 146 |
if (!$this->checkCanString($value)) { |
| 147 |
$this->addError('must be a string or an object that implements ' . '__toString(). Found ' . Aws\describe_type($value)); |
| 148 |
return; |
| 149 |
} |
| 150 |
$value = isset($value) ? $value : ''; |
| 151 |
$this->validateRange($shape, \strlen($value), "string length"); |
| 152 |
if ($this->constraints['pattern']) { |
| 153 |
$pattern = $shape['pattern']; |
| 154 |
if ($pattern && !\preg_match("/{$pattern}/", $value)) { |
| 155 |
$this->addError("Pattern /{$pattern}/ failed to match '{$value}'"); |
| 156 |
} |
| 157 |
} |
| 158 |
} |
| 159 |
private function validateRange(Shape $shape, $length, $descriptor) |
| 160 |
{ |
| 161 |
if ($this->constraints['min']) { |
| 162 |
$min = $shape['min']; |
| 163 |
if ($min && $length < $min) { |
| 164 |
$this->addError("expected {$descriptor} to be >= {$min}, but " . "found {$descriptor} of {$length}"); |
| 165 |
} |
| 166 |
} |
| 167 |
if ($this->constraints['max']) { |
| 168 |
$max = $shape['max']; |
| 169 |
if ($max && $length > $max) { |
| 170 |
$this->addError("expected {$descriptor} to be <= {$max}, but " . "found {$descriptor} of {$length}"); |
| 171 |
} |
| 172 |
} |
| 173 |
} |
| 174 |
private function checkArray($arr) |
| 175 |
{ |
| 176 |
return $this->isIndexed($arr) || $this->isAssociative($arr); |
| 177 |
} |
| 178 |
private function isAssociative($arr) |
| 179 |
{ |
| 180 |
return \count(\array_filter(\array_keys($arr), "is_string")) == \count($arr); |
| 181 |
} |
| 182 |
private function isIndexed(array $arr) |
| 183 |
{ |
| 184 |
return $arr == \array_values($arr); |
| 185 |
} |
| 186 |
private function checkCanString($value) |
| 187 |
{ |
| 188 |
static $valid = ['string' => \true, 'integer' => \true, 'double' => \true, 'NULL' => \true]; |
| 189 |
$type = \gettype($value); |
| 190 |
return isset($valid[$type]) || $type == 'object' && \method_exists($value, '__toString'); |
| 191 |
} |
| 192 |
private function checkAssociativeArray($value) |
| 193 |
{ |
| 194 |
$isAssociative = \false; |
| 195 |
if (\is_array($value)) { |
| 196 |
$expectedIndex = 0; |
| 197 |
$key = \key($value); |
| 198 |
do { |
| 199 |
$isAssociative = $key !== $expectedIndex++; |
| 200 |
\next($value); |
| 201 |
$key = \key($value); |
| 202 |
} while (!$isAssociative && null !== $key); |
| 203 |
} |
| 204 |
if (!$isAssociative) { |
| 205 |
$this->addError('must be an associative array. Found ' . Aws\describe_type($value)); |
| 206 |
return \false; |
| 207 |
} |
| 208 |
return \true; |
| 209 |
} |
| 210 |
private function checkDocumentType($value) |
| 211 |
{ |
| 212 |
if (\is_array($value)) { |
| 213 |
$typeOfFirstKey = \gettype(\key($value)); |
| 214 |
foreach ($value as $key => $val) { |
| 215 |
if (!$this->checkDocumentType($val) || \gettype($key) != $typeOfFirstKey) { |
| 216 |
return \false; |
| 217 |
} |
| 218 |
} |
| 219 |
return $this->checkArray($value); |
| 220 |
} |
| 221 |
return \is_null($value) || \is_numeric($value) || \is_string($value) || \is_bool($value); |
| 222 |
} |
| 223 |
private function checkUnion($value) |
| 224 |
{ |
| 225 |
if (\is_array($value)) { |
| 226 |
$nonNullCount = 0; |
| 227 |
foreach ($value as $key => $val) { |
| 228 |
if (!\is_null($val) && !(\strpos($key, "@") === 0)) { |
| 229 |
$nonNullCount++; |
| 230 |
} |
| 231 |
} |
| 232 |
return $nonNullCount == 1; |
| 233 |
} |
| 234 |
return !\is_null($value); |
| 235 |
} |
| 236 |
private function addError($message) |
| 237 |
{ |
| 238 |
$this->errors[] = \implode('', \array_map(function ($s) { |
| 239 |
return "[{$s}]"; |
| 240 |
}, $this->path)) . ' ' . $message; |
| 241 |
} |
| 242 |
private function canJsonEncode($data) |
| 243 |
{ |
| 244 |
return !\is_resource($data); |
| 245 |
} |
| 246 |
} |
| 247 |
|