Validator.php 8.2 KB

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