1<?php 2namespace Aws\Api; 3 4use Aws; 5 6/** 7 * Validates a schema against a hash of input. 8 */ 9class 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 if (!$this->checkAssociativeArray($value)) { 94 return; 95 } 96 97 if ($this->constraints['required'] && $shape['required']) { 98 foreach ($shape['required'] as $req) { 99 if (!isset($value[$req])) { 100 $this->path[] = $req; 101 $this->addError('is missing and is a required parameter'); 102 array_pop($this->path); 103 } 104 } 105 } 106 107 foreach ($value as $name => $v) { 108 if ($shape->hasMember($name)) { 109 $this->path[] = $name; 110 $this->dispatch( 111 $shape->getMember($name), 112 isset($value[$name]) ? $value[$name] : null 113 ); 114 array_pop($this->path); 115 } 116 } 117 } 118 119 private function check_list(ListShape $shape, $value) 120 { 121 if (!is_array($value)) { 122 $this->addError('must be an array. Found ' 123 . Aws\describe_type($value)); 124 return; 125 } 126 127 $this->validateRange($shape, count($value), "list element count"); 128 129 $items = $shape->getMember(); 130 foreach ($value as $index => $v) { 131 $this->path[] = $index; 132 $this->dispatch($items, $v); 133 array_pop($this->path); 134 } 135 } 136 137 private function check_map(MapShape $shape, $value) 138 { 139 if (!$this->checkAssociativeArray($value)) { 140 return; 141 } 142 143 $values = $shape->getValue(); 144 foreach ($value as $key => $v) { 145 $this->path[] = $key; 146 $this->dispatch($values, $v); 147 array_pop($this->path); 148 } 149 } 150 151 private function check_blob(Shape $shape, $value) 152 { 153 static $valid = [ 154 'string' => true, 155 'integer' => true, 156 'double' => true, 157 'resource' => true 158 ]; 159 160 $type = gettype($value); 161 if (!isset($valid[$type])) { 162 if ($type != 'object' || !method_exists($value, '__toString')) { 163 $this->addError('must be an fopen resource, a ' 164 . 'GuzzleHttp\Stream\StreamInterface object, or something ' 165 . 'that can be cast to a string. Found ' 166 . Aws\describe_type($value)); 167 } 168 } 169 } 170 171 private function check_numeric(Shape $shape, $value) 172 { 173 if (!is_numeric($value)) { 174 $this->addError('must be numeric. Found ' 175 . Aws\describe_type($value)); 176 return; 177 } 178 179 $this->validateRange($shape, $value, "numeric value"); 180 } 181 182 private function check_boolean(Shape $shape, $value) 183 { 184 if (!is_bool($value)) { 185 $this->addError('must be a boolean. Found ' 186 . Aws\describe_type($value)); 187 } 188 } 189 190 private function check_string(Shape $shape, $value) 191 { 192 if ($shape['jsonvalue']) { 193 if (!self::canJsonEncode($value)) { 194 $this->addError('must be a value encodable with \'json_encode\'.' 195 . ' Found ' . Aws\describe_type($value)); 196 } 197 return; 198 } 199 200 if (!$this->checkCanString($value)) { 201 $this->addError('must be a string or an object that implements ' 202 . '__toString(). Found ' . Aws\describe_type($value)); 203 return; 204 } 205 206 $this->validateRange($shape, strlen($value), "string length"); 207 208 if ($this->constraints['pattern']) { 209 $pattern = $shape['pattern']; 210 if ($pattern && !preg_match("/$pattern/", $value)) { 211 $this->addError("Pattern /$pattern/ failed to match '$value'"); 212 } 213 } 214 } 215 216 private function validateRange(Shape $shape, $length, $descriptor) 217 { 218 if ($this->constraints['min']) { 219 $min = $shape['min']; 220 if ($min && $length < $min) { 221 $this->addError("expected $descriptor to be >= $min, but " 222 . "found $descriptor of $length"); 223 } 224 } 225 226 if ($this->constraints['max']) { 227 $max = $shape['max']; 228 if ($max && $length > $max) { 229 $this->addError("expected $descriptor to be <= $max, but " 230 . "found $descriptor of $length"); 231 } 232 } 233 } 234 235 private function checkCanString($value) 236 { 237 static $valid = [ 238 'string' => true, 239 'integer' => true, 240 'double' => true, 241 'NULL' => true, 242 ]; 243 244 $type = gettype($value); 245 246 return isset($valid[$type]) || 247 ($type == 'object' && method_exists($value, '__toString')); 248 } 249 250 private function checkAssociativeArray($value) 251 { 252 $isAssociative = false; 253 254 if (is_array($value)) { 255 $expectedIndex = 0; 256 $key = key($value); 257 258 do { 259 $isAssociative = $key !== $expectedIndex++; 260 next($value); 261 $key = key($value); 262 } while (!$isAssociative && null !== $key); 263 } 264 265 if (!$isAssociative) { 266 $this->addError('must be an associative array. Found ' 267 . Aws\describe_type($value)); 268 return false; 269 } 270 271 return true; 272 } 273 274 private function addError($message) 275 { 276 $this->errors[] = 277 implode('', array_map(function ($s) { return "[{$s}]"; }, $this->path)) 278 . ' ' 279 . $message; 280 } 281 282 private function canJsonEncode($data) 283 { 284 return !is_resource($data); 285 } 286} 287