true, 'min' => true, 'max' => false, 'pattern' => false ]; /** * @param array $constraints Associative array of constraints to enforce. * Accepts the following keys: "required", "min", * "max", and "pattern". If a key is not * provided, the constraint will assume false. */ public function __construct(array $constraints = null) { static $assumedFalseValues = [ 'required' => false, 'min' => false, 'max' => false, 'pattern' => false ]; $this->constraints = empty($constraints) ? self::$defaultConstraints : $constraints + $assumedFalseValues; } /** * Validates the given input against the schema. * * @param string $name Operation name * @param Shape $shape Shape to validate * @param array $input Input to validate * * @throws \InvalidArgumentException if the input is invalid. */ public function validate($name, Shape $shape, array $input) { $this->dispatch($shape, $input); if ($this->errors) { $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) ); $this->errors = []; throw new \InvalidArgumentException($message); } } private function dispatch(Shape $shape, $value) { 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' ]; $type = $shape->getType(); if (isset($methods[$type])) { $this->{$methods[$type]}($shape, $value); } } private function check_structure(StructureShape $shape, $value) { $isDocument = (isset($shape['document']) && $shape['document']); $isUnion = (isset($shape['union']) && $shape['union']); if ($isDocument) { if (!$this->checkDocumentType($value)) { $this->addError("is not a valid document type"); return; } } elseif ($isUnion) { if (!$this->checkUnion($value)) { $this->addError("is a union type and must have exactly one non null value"); return; } } elseif (!$this->checkAssociativeArray($value)) { return; } if ($this->constraints['required'] && $shape['required']) { foreach ($shape['required'] as $req) { if (!isset($value[$req])) { $this->path[] = $req; $this->addError('is missing and is a required parameter'); array_pop($this->path); } } } if (!$isDocument) { foreach ($value as $name => $v) { if ($shape->hasMember($name)) { $this->path[] = $name; $this->dispatch( $shape->getMember($name), isset($value[$name]) ? $value[$name] : null ); array_pop($this->path); } } } } private function check_list(ListShape $shape, $value) { if (!is_array($value)) { $this->addError('must be an array. Found ' . Aws\describe_type($value)); return; } $this->validateRange($shape, count($value), "list element count"); $items = $shape->getMember(); foreach ($value as $index => $v) { $this->path[] = $index; $this->dispatch($items, $v); array_pop($this->path); } } private function check_map(MapShape $shape, $value) { if (!$this->checkAssociativeArray($value)) { return; } $values = $shape->getValue(); foreach ($value as $key => $v) { $this->path[] = $key; $this->dispatch($values, $v); array_pop($this->path); } } private function check_blob(Shape $shape, $value) { static $valid = [ 'string' => true, 'integer' => true, 'double' => true, 'resource' => true ]; $type = gettype($value); if (!isset($valid[$type])) { if ($type != 'object' || !method_exists($value, '__toString')) { $this->addError('must be an fopen resource, a ' . 'GuzzleHttp\Stream\StreamInterface object, or something ' . 'that can be cast to a string. Found ' . Aws\describe_type($value)); } } } private function check_numeric(Shape $shape, $value) { if (!is_numeric($value)) { $this->addError('must be numeric. Found ' . Aws\describe_type($value)); return; } $this->validateRange($shape, $value, "numeric value"); } private function check_boolean(Shape $shape, $value) { if (!is_bool($value)) { $this->addError('must be a boolean. Found ' . Aws\describe_type($value)); } } private function check_string(Shape $shape, $value) { if ($shape['jsonvalue']) { if (!self::canJsonEncode($value)) { $this->addError('must be a value encodable with \'json_encode\'.' . ' Found ' . Aws\describe_type($value)); } return; } if (!$this->checkCanString($value)) { $this->addError('must be a string or an object that implements ' . '__toString(). Found ' . Aws\describe_type($value)); return; } $value = isset($value) ? $value : ''; $this->validateRange($shape, strlen($value), "string length"); if ($this->constraints['pattern']) { $pattern = $shape['pattern']; if ($pattern && !preg_match("/$pattern/", $value)) { $this->addError("Pattern /$pattern/ failed to match '$value'"); } } } private function validateRange(Shape $shape, $length, $descriptor) { if ($this->constraints['min']) { $min = $shape['min']; if ($min && $length < $min) { $this->addError("expected $descriptor to be >= $min, but " . "found $descriptor of $length"); } } if ($this->constraints['max']) { $max = $shape['max']; if ($max && $length > $max) { $this->addError("expected $descriptor to be <= $max, but " . "found $descriptor of $length"); } } } private function checkArray($arr) { return $this->isIndexed($arr) || $this->isAssociative($arr); } private function isAssociative($arr) { return count(array_filter(array_keys($arr), "is_string")) == count($arr); } private function isIndexed(array $arr) { return $arr == array_values($arr); } private function checkCanString($value) { static $valid = [ 'string' => true, 'integer' => true, 'double' => true, 'NULL' => true, ]; $type = gettype($value); return isset($valid[$type]) || ($type == 'object' && method_exists($value, '__toString')); } private function checkAssociativeArray($value) { $isAssociative = false; if (is_array($value)) { $expectedIndex = 0; $key = key($value); do { $isAssociative = $key !== $expectedIndex++; next($value); $key = key($value); } while (!$isAssociative && null !== $key); } if (!$isAssociative) { $this->addError('must be an associative array. Found ' . Aws\describe_type($value)); return false; } return true; } private function checkDocumentType($value) { if (is_array($value)) { $typeOfFirstKey = gettype(key($value)); foreach ($value as $key => $val) { if (!$this->checkDocumentType($val) || gettype($key) != $typeOfFirstKey) { return false; } } return $this->checkArray($value); } return is_null($value) || is_numeric($value) || is_string($value) || is_bool($value); } private function checkUnion($value) { if (is_array($value)) { $nonNullCount = 0; foreach ($value as $key => $val) { if (!is_null($val) && !(strpos($key, "@") === 0)) { $nonNullCount++; } } return $nonNullCount == 1; } return !is_null($value); } private function addError($message) { $this->errors[] = implode('', array_map(function ($s) { return "[{$s}]"; }, $this->path)) . ' ' . $message; } private function canJsonEncode($data) { return !is_resource($data); } }