ErrorParser
11 months ago
Parser
11 months ago
Serializer
11 months ago
AbstractModel.php
11 months ago
ApiProvider.php
11 months ago
DateTimeResult.php
11 months ago
DocModel.php
11 months ago
ListShape.php
11 months ago
MapShape.php
11 months ago
Operation.php
11 months ago
Service.php
11 months ago
Shape.php
11 months ago
ShapeMap.php
11 months ago
StructureShape.php
11 months ago
TimestampShape.php
11 months ago
Validator.php
11 months ago
Validator.php
347 lines
| 1 | <?php |
| 2 | namespace Aws\Api; |
| 3 | |
| 4 | use Aws; |
| 5 | |
| 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 | |
| 15 | private static $defaultConstraints = [ |
| 16 | 'required' => true, |
| 17 | 'min' => true, |
| 18 | 'max' => false, |
| 19 | 'pattern' => false |
| 20 | ]; |
| 21 | |
| 22 | /** |
| 23 | * @param array $constraints Associative array of constraints to enforce. |
| 24 | * Accepts the following keys: "required", "min", |
| 25 | * "max", and "pattern". If a key is not |
| 26 | * provided, the constraint will assume false. |
| 27 | */ |
| 28 | public function __construct(array $constraints = null) |
| 29 | { |
| 30 | static $assumedFalseValues = [ |
| 31 | 'required' => false, |
| 32 | 'min' => false, |
| 33 | 'max' => false, |
| 34 | 'pattern' => false |
| 35 | ]; |
| 36 | $this->constraints = empty($constraints) |
| 37 | ? self::$defaultConstraints |
| 38 | : $constraints + $assumedFalseValues; |
| 39 | } |
| 40 | |
| 41 | /** |
| 42 | * Validates the given input against the schema. |
| 43 | * |
| 44 | * @param string $name Operation name |
| 45 | * @param Shape $shape Shape to validate |
| 46 | * @param array $input Input to validate |
| 47 | * |
| 48 | * @throws \InvalidArgumentException if the input is invalid. |
| 49 | */ |
| 50 | public function validate($name, Shape $shape, array $input) |
| 51 | { |
| 52 | $this->dispatch($shape, $input); |
| 53 | |
| 54 | if ($this->errors) { |
| 55 | $message = sprintf( |
| 56 | "Found %d error%s while validating the input provided for the " |
| 57 | . "%s operation:\n%s", |
| 58 | count($this->errors), |
| 59 | count($this->errors) > 1 ? 's' : '', |
| 60 | $name, |
| 61 | implode("\n", $this->errors) |
| 62 | ); |
| 63 | $this->errors = []; |
| 64 | |
| 65 | throw new \InvalidArgumentException($message); |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | private function dispatch(Shape $shape, $value) |
| 70 | { |
| 71 | static $methods = [ |
| 72 | 'structure' => 'check_structure', |
| 73 | 'list' => 'check_list', |
| 74 | 'map' => 'check_map', |
| 75 | 'blob' => 'check_blob', |
| 76 | 'boolean' => 'check_boolean', |
| 77 | 'integer' => 'check_numeric', |
| 78 | 'float' => 'check_numeric', |
| 79 | 'long' => 'check_numeric', |
| 80 | 'string' => 'check_string', |
| 81 | 'byte' => 'check_string', |
| 82 | 'char' => 'check_string' |
| 83 | ]; |
| 84 | |
| 85 | $type = $shape->getType(); |
| 86 | if (isset($methods[$type])) { |
| 87 | $this->{$methods[$type]}($shape, $value); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | private function check_structure(StructureShape $shape, $value) |
| 92 | { |
| 93 | $isDocument = (isset($shape['document']) && $shape['document']); |
| 94 | $isUnion = (isset($shape['union']) && $shape['union']); |
| 95 | if ($isDocument) { |
| 96 | if (!$this->checkDocumentType($value)) { |
| 97 | $this->addError("is not a valid document type"); |
| 98 | return; |
| 99 | } |
| 100 | } elseif ($isUnion) { |
| 101 | if (!$this->checkUnion($value)) { |
| 102 | $this->addError("is a union type and must have exactly one non null value"); |
| 103 | return; |
| 104 | } |
| 105 | } elseif (!$this->checkAssociativeArray($value)) { |
| 106 | return; |
| 107 | } |
| 108 | |
| 109 | if ($this->constraints['required'] && $shape['required']) { |
| 110 | foreach ($shape['required'] as $req) { |
| 111 | if (!isset($value[$req])) { |
| 112 | $this->path[] = $req; |
| 113 | $this->addError('is missing and is a required parameter'); |
| 114 | array_pop($this->path); |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | if (!$isDocument) { |
| 119 | foreach ($value as $name => $v) { |
| 120 | if ($shape->hasMember($name)) { |
| 121 | $this->path[] = $name; |
| 122 | $this->dispatch( |
| 123 | $shape->getMember($name), |
| 124 | isset($value[$name]) ? $value[$name] : null |
| 125 | ); |
| 126 | array_pop($this->path); |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | private function check_list(ListShape $shape, $value) |
| 133 | { |
| 134 | if (!is_array($value)) { |
| 135 | $this->addError('must be an array. Found ' |
| 136 | . Aws\describe_type($value)); |
| 137 | return; |
| 138 | } |
| 139 | |
| 140 | $this->validateRange($shape, count($value), "list element count"); |
| 141 | |
| 142 | $items = $shape->getMember(); |
| 143 | foreach ($value as $index => $v) { |
| 144 | $this->path[] = $index; |
| 145 | $this->dispatch($items, $v); |
| 146 | array_pop($this->path); |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | private function check_map(MapShape $shape, $value) |
| 151 | { |
| 152 | if (!$this->checkAssociativeArray($value)) { |
| 153 | return; |
| 154 | } |
| 155 | |
| 156 | $values = $shape->getValue(); |
| 157 | foreach ($value as $key => $v) { |
| 158 | $this->path[] = $key; |
| 159 | $this->dispatch($values, $v); |
| 160 | array_pop($this->path); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | private function check_blob(Shape $shape, $value) |
| 165 | { |
| 166 | static $valid = [ |
| 167 | 'string' => true, |
| 168 | 'integer' => true, |
| 169 | 'double' => true, |
| 170 | 'resource' => true |
| 171 | ]; |
| 172 | |
| 173 | $type = gettype($value); |
| 174 | if (!isset($valid[$type])) { |
| 175 | if ($type != 'object' || !method_exists($value, '__toString')) { |
| 176 | $this->addError('must be an fopen resource, a ' |
| 177 | . 'GuzzleHttp\Stream\StreamInterface object, or something ' |
| 178 | . 'that can be cast to a string. Found ' |
| 179 | . Aws\describe_type($value)); |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | private function check_numeric(Shape $shape, $value) |
| 185 | { |
| 186 | if (!is_numeric($value)) { |
| 187 | $this->addError('must be numeric. Found ' |
| 188 | . Aws\describe_type($value)); |
| 189 | return; |
| 190 | } |
| 191 | |
| 192 | $this->validateRange($shape, $value, "numeric value"); |
| 193 | } |
| 194 | |
| 195 | private function check_boolean(Shape $shape, $value) |
| 196 | { |
| 197 | if (!is_bool($value)) { |
| 198 | $this->addError('must be a boolean. Found ' |
| 199 | . Aws\describe_type($value)); |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | private function check_string(Shape $shape, $value) |
| 204 | { |
| 205 | if ($shape['jsonvalue']) { |
| 206 | if (!self::canJsonEncode($value)) { |
| 207 | $this->addError('must be a value encodable with \'json_encode\'.' |
| 208 | . ' Found ' . Aws\describe_type($value)); |
| 209 | } |
| 210 | return; |
| 211 | } |
| 212 | |
| 213 | if (!$this->checkCanString($value)) { |
| 214 | $this->addError('must be a string or an object that implements ' |
| 215 | . '__toString(). Found ' . Aws\describe_type($value)); |
| 216 | return; |
| 217 | } |
| 218 | |
| 219 | $value = isset($value) ? $value : ''; |
| 220 | $this->validateRange($shape, strlen($value), "string length"); |
| 221 | |
| 222 | if ($this->constraints['pattern']) { |
| 223 | $pattern = $shape['pattern']; |
| 224 | if ($pattern && !preg_match("/$pattern/", $value)) { |
| 225 | $this->addError("Pattern /$pattern/ failed to match '$value'"); |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | private function validateRange(Shape $shape, $length, $descriptor) |
| 231 | { |
| 232 | if ($this->constraints['min']) { |
| 233 | $min = $shape['min']; |
| 234 | if ($min && $length < $min) { |
| 235 | $this->addError("expected $descriptor to be >= $min, but " |
| 236 | . "found $descriptor of $length"); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | if ($this->constraints['max']) { |
| 241 | $max = $shape['max']; |
| 242 | if ($max && $length > $max) { |
| 243 | $this->addError("expected $descriptor to be <= $max, but " |
| 244 | . "found $descriptor of $length"); |
| 245 | } |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | private function checkArray($arr) |
| 250 | { |
| 251 | return $this->isIndexed($arr) || $this->isAssociative($arr); |
| 252 | } |
| 253 | |
| 254 | private function isAssociative($arr) |
| 255 | { |
| 256 | return count(array_filter(array_keys($arr), "is_string")) == count($arr); |
| 257 | } |
| 258 | |
| 259 | private function isIndexed(array $arr) |
| 260 | { |
| 261 | return $arr == array_values($arr); |
| 262 | } |
| 263 | |
| 264 | private function checkCanString($value) |
| 265 | { |
| 266 | static $valid = [ |
| 267 | 'string' => true, |
| 268 | 'integer' => true, |
| 269 | 'double' => true, |
| 270 | 'NULL' => true, |
| 271 | ]; |
| 272 | |
| 273 | $type = gettype($value); |
| 274 | |
| 275 | return isset($valid[$type]) || |
| 276 | ($type == 'object' && method_exists($value, '__toString')); |
| 277 | } |
| 278 | |
| 279 | private function checkAssociativeArray($value) |
| 280 | { |
| 281 | $isAssociative = false; |
| 282 | |
| 283 | if (is_array($value)) { |
| 284 | $expectedIndex = 0; |
| 285 | $key = key($value); |
| 286 | |
| 287 | do { |
| 288 | $isAssociative = $key !== $expectedIndex++; |
| 289 | next($value); |
| 290 | $key = key($value); |
| 291 | } while (!$isAssociative && null !== $key); |
| 292 | } |
| 293 | |
| 294 | if (!$isAssociative) { |
| 295 | $this->addError('must be an associative array. Found ' |
| 296 | . Aws\describe_type($value)); |
| 297 | return false; |
| 298 | } |
| 299 | |
| 300 | return true; |
| 301 | } |
| 302 | |
| 303 | private function checkDocumentType($value) |
| 304 | { |
| 305 | if (is_array($value)) { |
| 306 | $typeOfFirstKey = gettype(key($value)); |
| 307 | foreach ($value as $key => $val) { |
| 308 | if (!$this->checkDocumentType($val) || gettype($key) != $typeOfFirstKey) { |
| 309 | return false; |
| 310 | } |
| 311 | } |
| 312 | return $this->checkArray($value); |
| 313 | } |
| 314 | return is_null($value) |
| 315 | || is_numeric($value) |
| 316 | || is_string($value) |
| 317 | || is_bool($value); |
| 318 | } |
| 319 | |
| 320 | private function checkUnion($value) |
| 321 | { |
| 322 | if (is_array($value)) { |
| 323 | $nonNullCount = 0; |
| 324 | foreach ($value as $key => $val) { |
| 325 | if (!is_null($val) && !(strpos($key, "@") === 0)) { |
| 326 | $nonNullCount++; |
| 327 | } |
| 328 | } |
| 329 | return $nonNullCount == 1; |
| 330 | } |
| 331 | return !is_null($value); |
| 332 | } |
| 333 | |
| 334 | private function addError($message) |
| 335 | { |
| 336 | $this->errors[] = |
| 337 | implode('', array_map(function ($s) { return "[{$s}]"; }, $this->path)) |
| 338 | . ' ' |
| 339 | . $message; |
| 340 | } |
| 341 | |
| 342 | private function canJsonEncode($data) |
| 343 | { |
| 344 | return !is_resource($data); |
| 345 | } |
| 346 | } |
| 347 |