Browse Source

default types validation (#648)

default types validation + updated readme with instructions
lcuis 4 years ago
parent
commit
e6c6225850
No account linked to committer's email address

+ 45
- 3
README.md View File

@@ -597,7 +597,7 @@ You can enable the following middleware using the "middlewares" config parameter
597 597
 - "basicAuth": Support for "Basic Authentication"
598 598
 - "reconnect": Reconnect to the database with different parameters
599 599
 - "authorization": Restrict access to certain tables or columns
600
-- "validation": Return input validation errors for custom rules
600
+- "validation": Return input validation errors for custom rules and default type rules
601 601
 - "ipAddress": Fill a protected field with the IP address on create
602 602
 - "sanitation": Apply input sanitation on create and update
603 603
 - "multiTenancy": Restricts tenants access in a multi-tenant scenario
@@ -652,6 +652,7 @@ You can tune the middleware behavior using middleware specific configuration par
652 652
 - "authorization.columnHandler": Handler to implement column authorization rules ("")
653 653
 - "authorization.recordHandler": Handler to implement record authorization filter rules ("")
654 654
 - "validation.handler": Handler to implement validation rules for input values ("")
655
+- "validation.types": List of types for which the default validation must take place ("all")
655 656
 - "ipAddress.tables": Tables to search for columns to override with IP address ("")
656 657
 - "ipAddress.columns": Columns to protect and override with the IP address on create ("")
657 658
 - "sanitation.handler": Handler to implement sanitation rules for input values ("")
@@ -888,8 +889,12 @@ The above example will strip all HTML tags from strings in the input.
888 889
 
889 890
 ### Validating input
890 891
 
891
-By default all input is accepted. If you want to validate the input, you may add the 'validation' middleware and define a 
892
-'validation.handler' function that returns a boolean indicating whether or not the value is valid.
892
+By default all input is accepted unless the validation middleware is specified. The default types validations are then applied.
893
+
894
+#### Validation handler
895
+
896
+If you want to validate the input in a custom way, you may add the 'validation' middleware and define a 'validation.handler' 
897
+function that returns a boolean indicating whether or not the value is valid.
893 898
 
894 899
     'validation.handler' => function ($operation, $tableName, $column, $value, $context) {
895 900
         return ($column['name'] == 'post_id' && !is_numeric($value)) ? 'must be numeric' : true;
@@ -915,6 +920,43 @@ Then the server will return a '422' HTTP status code and nice error message:
915 920
 
916 921
 You can parse this output to make form fields show up with a red border and their appropriate error message.
917 922
 
923
+#### Validation types
924
+
925
+The default types validations return the following error messages:
926
+| error message | applies to types |
927
+| ---- | ---- |
928
+| cannot be null | any non-nullable column |
929
+| must be numeric | integer bigint |
930
+| exceeds range | integer bigint |
931
+| too long | varchar varbinary |
932
+| not a float | decimal float double |
933
+| not a valid boolean | boolean |
934
+| invalid date format use yyyy-mm-dd | date timestamp |
935
+| not a valid date | date timestamp |
936
+| invalid time format use hh:mm:ss | time timestamp |
937
+| non-numeric time value | time timestamp |
938
+| not a valid time | time timestamp |
939
+| invalid timestamp format use yyyy-mm-dd hh:mm:ss | timestamp |
940
+
941
+If you want the types validation to apply to all the types, you must activate the `validation` middleware.
942
+By default, all types are enabled. Which is equivalent to the two configuration possibilities:
943
+
944
+    'validation.types' => 'all',
945
+    
946
+or
947
+    
948
+    'validation.types'=> 'integer,bigint,varchar,decimal,float,double,boolean,date,time,timestamp,clob,blob,varbinary,geometry',
949
+
950
+Types with no declared error message can be checked whether null when the column is non-nullable.
951
+
952
+In case you want to use a validation handler but don't want any types validation, use either:
953
+
954
+    'validation.types' => '',
955
+    
956
+or
957
+    
958
+    'validation.types'=> 'none',
959
+
918 960
 ### Multi-tenancy support
919 961
 
920 962
 Two forms of multi-tenancy are supported:

+ 210
- 61
api.php View File

@@ -8218,74 +8218,223 @@ namespace Tqdev\PhpCrudApi\Middleware {
8218 8218
     use Psr\Http\Message\ResponseInterface;
8219 8219
     use Psr\Http\Message\ServerRequestInterface;
8220 8220
     use Psr\Http\Server\RequestHandlerInterface;
8221
-    use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
8222 8221
     use Tqdev\PhpCrudApi\Column\ReflectionService;
8222
+    use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
8223 8223
     use Tqdev\PhpCrudApi\Controller\Responder;
8224 8224
     use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
8225 8225
     use Tqdev\PhpCrudApi\Middleware\Router\Router;
8226 8226
     use Tqdev\PhpCrudApi\Record\ErrorCode;
8227 8227
     use Tqdev\PhpCrudApi\RequestUtils;
8228 8228
 
8229
-    class ValidationMiddleware extends Middleware
8230
-    {
8231
-        private $reflection;
8232
-
8233
-        public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
8234
-        {
8235
-            parent::__construct($router, $responder, $properties);
8236
-            $this->reflection = $reflection;
8237
-        }
8238
-
8239
-        private function callHandler($handler, $record, string $operation, ReflectedTable $table) /*: ResponseInterface?*/
8240
-        {
8241
-            $context = (array) $record;
8242
-            $details = array();
8243
-            $tableName = $table->getName();
8244
-            foreach ($context as $columnName => $value) {
8245
-                if ($table->hasColumn($columnName)) {
8246
-                    $column = $table->getColumn($columnName);
8247
-                    $valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
8248
-                    if ($valid !== true && $valid !== '') {
8249
-                        $details[$columnName] = $valid;
8250
-                    }
8251
-                }
8252
-            }
8253
-            if (count($details) > 0) {
8254
-                return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $tableName, $details);
8255
-            }
8256
-            return null;
8257
-        }
8258
-
8259
-        public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
8260
-        {
8261
-            $operation = RequestUtils::getOperation($request);
8262
-            if (in_array($operation, ['create', 'update', 'increment'])) {
8263
-                $tableName = RequestUtils::getPathSegment($request, 2);
8264
-                if ($this->reflection->hasTable($tableName)) {
8265
-                    $record = $request->getParsedBody();
8266
-                    if ($record !== null) {
8267
-                        $handler = $this->getProperty('handler', '');
8268
-                        if ($handler !== '') {
8269
-                            $table = $this->reflection->getTable($tableName);
8270
-                            if (is_array($record)) {
8271
-                                foreach ($record as $r) {
8272
-                                    $response = $this->callHandler($handler, $r, $operation, $table);
8273
-                                    if ($response !== null) {
8274
-                                        return $response;
8275
-                                    }
8276
-                                }
8277
-                            } else {
8278
-                                $response = $this->callHandler($handler, $record, $operation, $table);
8279
-                                if ($response !== null) {
8280
-                                    return $response;
8281
-                                }
8282
-                            }
8283
-                        }
8284
-                    }
8285
-                }
8286
-            }
8287
-            return $next->handle($request);
8288
-        }
8229
+    class ValidationMiddleware extends Middleware {
8230
+    	private $reflection;
8231
+    	private $typesToValidate;
8232
+
8233
+    	public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection) {
8234
+    		parent::__construct($router, $responder, $properties);
8235
+    		$this->reflection = $reflection;
8236
+    		$typesStr = $this->getProperty('types', 'all');
8237
+    		if (is_null($typesStr)) {
8238
+    			$typesStr = 'all';
8239
+    		}
8240
+    		if (strlen($typesStr) == 0) {
8241
+    			$typesStr = 'none';
8242
+    		}
8243
+    		$this->typesToValidate = explode(',', $typesStr);
8244
+    		if (is_null($this->typesToValidate) || count($this->typesToValidate) == 0) {
8245
+    			$this->typesToValidate = ['all'];
8246
+    		}
8247
+    	}
8248
+
8249
+    	private function callHandler($handler, $record, string $operation, ReflectedTable $table) /*: ResponseInterface?*/ {
8250
+    		$context = (array) $record;
8251
+    		$details = array();
8252
+    		$tableName = $table->getName();
8253
+    		foreach ($context as $columnName => $value) {
8254
+    			if ($table->hasColumn($columnName)) {
8255
+    				$column = $table->getColumn($columnName);
8256
+    				$valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
8257
+    				if ($valid || $valid == '') {
8258
+    					$valid = $this->validateType($column->serialize(), $value);
8259
+    				}
8260
+    				if ($valid !== true && $valid !== '') {
8261
+    					$details[$columnName] = $valid;
8262
+    				}
8263
+    			}
8264
+    		}
8265
+    		if (count($details) > 0) {
8266
+    			return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $tableName, $details);
8267
+    		}
8268
+    		return null;
8269
+    	}
8270
+
8271
+    	private function validateType($column, $value) {
8272
+    		if ($this->typesToValidate[0] == 'none') {
8273
+    			return (true);
8274
+    		}
8275
+    		if ($this->typesToValidate[0] != 'all') {
8276
+    			if (!in_array($column['type'], $this->typesToValidate)) {
8277
+    				return (true);
8278
+    			}
8279
+    		}
8280
+    		if (is_null($value)) {
8281
+    			return ($column["nullable"] ? true : "cannot be null");
8282
+    		}
8283
+    		switch ($column['type']) {
8284
+    		case 'integer':
8285
+    			if (!is_numeric($value)) {
8286
+    				return ('must be numeric');
8287
+    			}
8288
+
8289
+    			if (strlen($value) > 20) {
8290
+    				return ('exceeds range');
8291
+    			}
8292
+
8293
+    			break;
8294
+    		case 'bigint':
8295
+    			if (!is_numeric($value)) {
8296
+    				return ('must be numeric');
8297
+    			}
8298
+
8299
+    			if (strlen($value) > 20) {
8300
+    				return ('exceeds range');
8301
+    			}
8302
+
8303
+    			break;
8304
+    		case 'varchar':
8305
+    			if (strlen($value) > $column['length']) {
8306
+    				return ('too long');
8307
+    			}
8308
+
8309
+    			break;
8310
+    		case 'decimal':
8311
+    			if (!is_float($value) && !is_numeric($value)) {
8312
+    				return ('not a float');
8313
+    			}
8314
+
8315
+    			break;
8316
+    		case 'float':
8317
+    			if (!is_float($value) && !is_numeric($value)) {
8318
+    				return ('not a float');
8319
+    			}
8320
+
8321
+    			break;
8322
+    		case 'double':
8323
+    			if (!is_float($value) && !is_numeric($value)) {
8324
+    				return ('not a float');
8325
+    			}
8326
+
8327
+    			break;
8328
+    		case 'boolean':
8329
+    			if ($value != 0 && $value != 1) {
8330
+    				return ('not a valid boolean');
8331
+    			}
8332
+
8333
+    			break;
8334
+    		case 'date':
8335
+    			$date_array = explode('-', $value);
8336
+    			if (count($date_array) != 3) {
8337
+    				return ('invalid date format use yyyy-mm-dd');
8338
+    			}
8339
+
8340
+    			if (!@checkdate($date_array[1], $date_array[2], $date_array[0])) {
8341
+    				return ('not a valid date');
8342
+    			}
8343
+
8344
+    			break;
8345
+    		case 'time':
8346
+    			$time_array = explode(':', $value);
8347
+    			if (count($time_array) != 3) {
8348
+    				return ('invalid time format use hh:mm:ss');
8349
+    			}
8350
+
8351
+    			foreach ($time_array as $t) {
8352
+    				if (!is_numeric($t)) {
8353
+    					return ('non-numeric time value');
8354
+    				}
8355
+    			}
8356
+
8357
+    			if ($time_array[1] < 0 || $time_array[2] < 0 || $time_array[0] < -838 || $time_array[1] > 59 || $time_array[2] > 59 || $time_array[0] > 838) {
8358
+    				return ('not a valid time');
8359
+    			}
8360
+
8361
+    			break;
8362
+    		case 'timestamp':
8363
+    			$split_timestamp = explode(' ', $value);
8364
+    			if (count($split_timestamp) != 2) {
8365
+    				return ('invalid timestamp format use yyyy-mm-dd hh:mm:ss');
8366
+    			}
8367
+
8368
+    			$date_array = explode('-', $split_timestamp[0]);
8369
+    			if (count($date_array) != 3) {
8370
+    				return ('invalid date format use yyyy-mm-dd');
8371
+    			}
8372
+
8373
+    			if (!@checkdate($date_array[1], $date_array[2], $date_array[0])) {
8374
+    				return ('not a valid date');
8375
+    			}
8376
+
8377
+    			$time_array = explode(':', $split_timestamp[1]);
8378
+    			if (count($time_array) != 3) {
8379
+    				return ('invalid time format use hh:mm:ss');
8380
+    			}
8381
+
8382
+    			foreach ($time_array as $t) {
8383
+    				if (!is_numeric($t)) {
8384
+    					return ('non-numeric time value');
8385
+    				}
8386
+    			}
8387
+
8388
+    			if ($time_array[1] < 0 || $time_array[2] < 0 || $time_array[0] < 0 || $time_array[1] > 59 || $time_array[2] > 59 || $time_array[0] > 23) {
8389
+    				return ('not a valid time');
8390
+    			}
8391
+
8392
+    			break;
8393
+    		case 'clob':
8394
+    			break;
8395
+    		case 'blob':
8396
+    			break;
8397
+    		case 'varbinary':
8398
+    			if (((strlen($value) * 3 / 4) - substr_count(substr($value, -2), '=')) > $column['length']) {
8399
+    				return ('too long');
8400
+    			}
8401
+
8402
+    			break;
8403
+    		case 'geometry':
8404
+    			break;
8405
+    		}
8406
+    		return (true);
8407
+    	}
8408
+
8409
+    	public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface{
8410
+    		$operation = RequestUtils::getOperation($request);
8411
+    		if (in_array($operation, ['create', 'update', 'increment'])) {
8412
+    			$tableName = RequestUtils::getPathSegment($request, 2);
8413
+    			if ($this->reflection->hasTable($tableName)) {
8414
+    				$record = $request->getParsedBody();
8415
+    				if ($record !== null) {
8416
+    					$handler = $this->getProperty('handler', '');
8417
+    					if ($handler !== '') {
8418
+    						$table = $this->reflection->getTable($tableName);
8419
+    						if (is_array($record)) {
8420
+    							foreach ($record as $r) {
8421
+    								$response = $this->callHandler($handler, $r, $operation, $table);
8422
+    								if ($response !== null) {
8423
+    									return $response;
8424
+    								}
8425
+    							}
8426
+    						} else {
8427
+    							$response = $this->callHandler($handler, $record, $operation, $table);
8428
+    							if ($response !== null) {
8429
+    								return $response;
8430
+    							}
8431
+    						}
8432
+    					}
8433
+    				}
8434
+    			}
8435
+    		}
8436
+    		return $next->handle($request);
8437
+    	}
8289 8438
     }
8290 8439
 }
8291 8440
 

+ 1
- 1
docker/ubuntu16/run.sh View File

@@ -41,7 +41,7 @@ echo "done"
41 41
 
42 42
 echo -n "[3/4] Starting SQLServer 2017 ... "
43 43
 # run sqlserver server
44
-nohup /opt/mssql/bin/sqlservr --accept-eula > /root/mysql.log 2>&1 &
44
+nohup /opt/mssql/bin/sqlservr --accept-eula > /root/mssql.log 2>&1 &
45 45
 # create database and user on postgres
46 46
 /opt/mssql-tools/bin/sqlcmd -l 30 -S localhost -U SA -P sapwd123! >/dev/null << 'EOF'
47 47
 CREATE DATABASE [php-crud-api]

+ 3
- 0
docker/ubuntu18/Dockerfile View File

@@ -2,6 +2,9 @@ FROM ubuntu:18.04
2 2
 
3 3
 ARG DEBIAN_FRONTEND=noninteractive
4 4
 
5
+ENV TZ=Etc/UTC
6
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
7
+
5 8
 # install: php / mysql / postgres / sqlite / tools
6 9
 RUN apt-get update && apt-get -y install \
7 10
 php-cli php-xml \

+ 3
- 0
docker/ubuntu20/Dockerfile View File

@@ -2,6 +2,9 @@ FROM ubuntu:20.04
2 2
 
3 3
 ARG DEBIAN_FRONTEND=noninteractive
4 4
 
5
+ENV TZ=Etc/UTC
6
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
7
+
5 8
 # install: php / mysql / postgres / sqlite / tools
6 9
 RUN apt-get update && apt-get -y install \
7 10
 php-cli php-xml \

+ 210
- 61
src/Tqdev/PhpCrudApi/Middleware/ValidationMiddleware.php View File

@@ -5,72 +5,221 @@ namespace Tqdev\PhpCrudApi\Middleware;
5 5
 use Psr\Http\Message\ResponseInterface;
6 6
 use Psr\Http\Message\ServerRequestInterface;
7 7
 use Psr\Http\Server\RequestHandlerInterface;
8
-use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
9 8
 use Tqdev\PhpCrudApi\Column\ReflectionService;
9
+use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
10 10
 use Tqdev\PhpCrudApi\Controller\Responder;
11 11
 use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
12 12
 use Tqdev\PhpCrudApi\Middleware\Router\Router;
13 13
 use Tqdev\PhpCrudApi\Record\ErrorCode;
14 14
 use Tqdev\PhpCrudApi\RequestUtils;
15 15
 
16
-class ValidationMiddleware extends Middleware
17
-{
18
-    private $reflection;
19
-
20
-    public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
21
-    {
22
-        parent::__construct($router, $responder, $properties);
23
-        $this->reflection = $reflection;
24
-    }
25
-
26
-    private function callHandler($handler, $record, string $operation, ReflectedTable $table) /*: ResponseInterface?*/
27
-    {
28
-        $context = (array) $record;
29
-        $details = array();
30
-        $tableName = $table->getName();
31
-        foreach ($context as $columnName => $value) {
32
-            if ($table->hasColumn($columnName)) {
33
-                $column = $table->getColumn($columnName);
34
-                $valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
35
-                if ($valid !== true && $valid !== '') {
36
-                    $details[$columnName] = $valid;
37
-                }
38
-            }
39
-        }
40
-        if (count($details) > 0) {
41
-            return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $tableName, $details);
42
-        }
43
-        return null;
44
-    }
45
-
46
-    public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
47
-    {
48
-        $operation = RequestUtils::getOperation($request);
49
-        if (in_array($operation, ['create', 'update', 'increment'])) {
50
-            $tableName = RequestUtils::getPathSegment($request, 2);
51
-            if ($this->reflection->hasTable($tableName)) {
52
-                $record = $request->getParsedBody();
53
-                if ($record !== null) {
54
-                    $handler = $this->getProperty('handler', '');
55
-                    if ($handler !== '') {
56
-                        $table = $this->reflection->getTable($tableName);
57
-                        if (is_array($record)) {
58
-                            foreach ($record as $r) {
59
-                                $response = $this->callHandler($handler, $r, $operation, $table);
60
-                                if ($response !== null) {
61
-                                    return $response;
62
-                                }
63
-                            }
64
-                        } else {
65
-                            $response = $this->callHandler($handler, $record, $operation, $table);
66
-                            if ($response !== null) {
67
-                                return $response;
68
-                            }
69
-                        }
70
-                    }
71
-                }
72
-            }
73
-        }
74
-        return $next->handle($request);
75
-    }
16
+class ValidationMiddleware extends Middleware {
17
+	private $reflection;
18
+	private $typesToValidate;
19
+
20
+	public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection) {
21
+		parent::__construct($router, $responder, $properties);
22
+		$this->reflection = $reflection;
23
+		$typesStr = $this->getProperty('types', 'all');
24
+		if (is_null($typesStr)) {
25
+			$typesStr = 'all';
26
+		}
27
+		if (strlen($typesStr) == 0) {
28
+			$typesStr = 'none';
29
+		}
30
+		$this->typesToValidate = explode(',', $typesStr);
31
+		if (is_null($this->typesToValidate) || count($this->typesToValidate) == 0) {
32
+			$this->typesToValidate = ['all'];
33
+		}
34
+	}
35
+
36
+	private function callHandler($handler, $record, string $operation, ReflectedTable $table) /*: ResponseInterface?*/ {
37
+		$context = (array) $record;
38
+		$details = array();
39
+		$tableName = $table->getName();
40
+		foreach ($context as $columnName => $value) {
41
+			if ($table->hasColumn($columnName)) {
42
+				$column = $table->getColumn($columnName);
43
+				$valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
44
+				if ($valid || $valid == '') {
45
+					$valid = $this->validateType($column->serialize(), $value);
46
+				}
47
+				if ($valid !== true && $valid !== '') {
48
+					$details[$columnName] = $valid;
49
+				}
50
+			}
51
+		}
52
+		if (count($details) > 0) {
53
+			return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $tableName, $details);
54
+		}
55
+		return null;
56
+	}
57
+
58
+	private function validateType($column, $value) {
59
+		if ($this->typesToValidate[0] == 'none') {
60
+			return (true);
61
+		}
62
+		if ($this->typesToValidate[0] != 'all') {
63
+			if (!in_array($column['type'], $this->typesToValidate)) {
64
+				return (true);
65
+			}
66
+		}
67
+		if (is_null($value)) {
68
+			return ($column["nullable"] ? true : "cannot be null");
69
+		}
70
+		switch ($column['type']) {
71
+		case 'integer':
72
+			if (!is_numeric($value)) {
73
+				return ('must be numeric');
74
+			}
75
+
76
+			if (strlen($value) > 20) {
77
+				return ('exceeds range');
78
+			}
79
+
80
+			break;
81
+		case 'bigint':
82
+			if (!is_numeric($value)) {
83
+				return ('must be numeric');
84
+			}
85
+
86
+			if (strlen($value) > 20) {
87
+				return ('exceeds range');
88
+			}
89
+
90
+			break;
91
+		case 'varchar':
92
+			if (strlen($value) > $column['length']) {
93
+				return ('too long');
94
+			}
95
+
96
+			break;
97
+		case 'decimal':
98
+			if (!is_float($value) && !is_numeric($value)) {
99
+				return ('not a float');
100
+			}
101
+
102
+			break;
103
+		case 'float':
104
+			if (!is_float($value) && !is_numeric($value)) {
105
+				return ('not a float');
106
+			}
107
+
108
+			break;
109
+		case 'double':
110
+			if (!is_float($value) && !is_numeric($value)) {
111
+				return ('not a float');
112
+			}
113
+
114
+			break;
115
+		case 'boolean':
116
+			if ($value != 0 && $value != 1) {
117
+				return ('not a valid boolean');
118
+			}
119
+
120
+			break;
121
+		case 'date':
122
+			$date_array = explode('-', $value);
123
+			if (count($date_array) != 3) {
124
+				return ('invalid date format use yyyy-mm-dd');
125
+			}
126
+
127
+			if (!@checkdate($date_array[1], $date_array[2], $date_array[0])) {
128
+				return ('not a valid date');
129
+			}
130
+
131
+			break;
132
+		case 'time':
133
+			$time_array = explode(':', $value);
134
+			if (count($time_array) != 3) {
135
+				return ('invalid time format use hh:mm:ss');
136
+			}
137
+
138
+			foreach ($time_array as $t) {
139
+				if (!is_numeric($t)) {
140
+					return ('non-numeric time value');
141
+				}
142
+			}
143
+
144
+			if ($time_array[1] < 0 || $time_array[2] < 0 || $time_array[0] < -838 || $time_array[1] > 59 || $time_array[2] > 59 || $time_array[0] > 838) {
145
+				return ('not a valid time');
146
+			}
147
+
148
+			break;
149
+		case 'timestamp':
150
+			$split_timestamp = explode(' ', $value);
151
+			if (count($split_timestamp) != 2) {
152
+				return ('invalid timestamp format use yyyy-mm-dd hh:mm:ss');
153
+			}
154
+
155
+			$date_array = explode('-', $split_timestamp[0]);
156
+			if (count($date_array) != 3) {
157
+				return ('invalid date format use yyyy-mm-dd');
158
+			}
159
+
160
+			if (!@checkdate($date_array[1], $date_array[2], $date_array[0])) {
161
+				return ('not a valid date');
162
+			}
163
+
164
+			$time_array = explode(':', $split_timestamp[1]);
165
+			if (count($time_array) != 3) {
166
+				return ('invalid time format use hh:mm:ss');
167
+			}
168
+
169
+			foreach ($time_array as $t) {
170
+				if (!is_numeric($t)) {
171
+					return ('non-numeric time value');
172
+				}
173
+			}
174
+
175
+			if ($time_array[1] < 0 || $time_array[2] < 0 || $time_array[0] < 0 || $time_array[1] > 59 || $time_array[2] > 59 || $time_array[0] > 23) {
176
+				return ('not a valid time');
177
+			}
178
+
179
+			break;
180
+		case 'clob':
181
+			break;
182
+		case 'blob':
183
+			break;
184
+		case 'varbinary':
185
+			if (((strlen($value) * 3 / 4) - substr_count(substr($value, -2), '=')) > $column['length']) {
186
+				return ('too long');
187
+			}
188
+
189
+			break;
190
+		case 'geometry':
191
+			break;
192
+		}
193
+		return (true);
194
+	}
195
+
196
+	public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface{
197
+		$operation = RequestUtils::getOperation($request);
198
+		if (in_array($operation, ['create', 'update', 'increment'])) {
199
+			$tableName = RequestUtils::getPathSegment($request, 2);
200
+			if ($this->reflection->hasTable($tableName)) {
201
+				$record = $request->getParsedBody();
202
+				if ($record !== null) {
203
+					$handler = $this->getProperty('handler', '');
204
+					if ($handler !== '') {
205
+						$table = $this->reflection->getTable($tableName);
206
+						if (is_array($record)) {
207
+							foreach ($record as $r) {
208
+								$response = $this->callHandler($handler, $r, $operation, $table);
209
+								if ($response !== null) {
210
+									return $response;
211
+								}
212
+							}
213
+						} else {
214
+							$response = $this->callHandler($handler, $record, $operation, $table);
215
+							if ($response !== null) {
216
+								return $response;
217
+							}
218
+						}
219
+					}
220
+				}
221
+			}
222
+		}
223
+		return $next->handle($request);
224
+	}
76 225
 }

+ 283
- 0
tests/functional/003_columns/015_update_types_table.log View File

@@ -0,0 +1,283 @@
1
+===
2
+POST /columns
3
+
4
+{"name":"types","type":"table","columns":[{"name":"id","type":"integer","pk":true},{"name":"integer","type":"integer"},{"name":"bigint","type":"bigint"},{"name":"varchar","type":"varchar","length":10},{"name":"decimal","type":"decimal","precision":10,"scale":4},{"name":"float","type":"float"},{"name":"double","type":"double"},{"name":"boolean","type":"boolean"},{"name":"date","type":"date"},{"name":"time","type":"time"},{"name":"timestamp","type":"timestamp"},{"name":"clob","type":"clob"},{"name":"blob","type":"blob"},{"name":"geometry","type":"geometry"}]}
5
+===
6
+200
7
+Content-Type: application/json
8
+Content-Length: 4
9
+
10
+true
11
+===
12
+POST /records/types
13
+Content-Type: application/json
14
+
15
+{"integer":2,"bigint":3,"varchar":"abc","decimal":"1.23","float":1,"double":23.45,"boolean":true,"date":"1970-01-01","time":"00:00:01","timestamp":"2001-02-03 04:05:06","clob":"a","blob":"YQ==","geometry":"POINT(1 2)"}
16
+===
17
+200
18
+Content-Type: application/json
19
+Content-Length: 1
20
+
21
+1
22
+===
23
+PUT /records/types/1
24
+
25
+{"integer":2,"bigint":2,"varchar":"b","decimal":2,"float":2,"double":2,"boolean":false,"date":"2000-01-02","time":"02:01:01","timestamp":"2000-01-02 02:01:01","clob":"b","blob":"Cg==","geometry":"POINT(2 2)"}
26
+===
27
+200
28
+Content-Type: application/json
29
+Content-Length: 1
30
+
31
+1
32
+===
33
+GET /records/types/1
34
+===
35
+200
36
+Content-Type: application/json
37
+Content-Length: 222
38
+
39
+{"id":1,"integer":2,"bigint":2,"varchar":"b","decimal":"2.0000","float":2,"double":2,"boolean":false,"date":"2000-01-02","time":"02:01:01","timestamp":"2000-01-02 02:01:01","clob":"b","blob":"Cg==","geometry":"POINT(2 2)"}
40
+===
41
+PUT /records/types/1
42
+
43
+{"integer":3,"bigint":3,"varchar":"c","decimal":3,"float":3,"double":3,"boolean":true,"date":"2000-01-03","time":"03:01:01","timestamp":"2000-01-03 03:01:01","clob":"c","blob":"Yw==","geometry":"POINT(3 3)"}
44
+===
45
+200
46
+Content-Type: application/json
47
+Content-Length: 1
48
+
49
+1
50
+===
51
+GET /records/types/1
52
+===
53
+200
54
+Content-Type: application/json
55
+Content-Length: 221
56
+
57
+{"id":1,"integer":3,"bigint":3,"varchar":"c","decimal":"3.0000","float":3,"double":3,"boolean":true,"date":"2000-01-03","time":"03:01:01","timestamp":"2000-01-03 03:01:01","clob":"c","blob":"Yw==","geometry":"POINT(3 3)"}
58
+===
59
+PUT /records/types/1
60
+
61
+{"integer":"string"}
62
+===
63
+422
64
+Content-Type: application/json
65
+Content-Length: 101
66
+
67
+{"code":1013,"message":"Input validation failed for 'types'","details":{"integer":"must be numeric"}}
68
+===
69
+PUT /records/types/1
70
+
71
+{"bigint":"string"}
72
+===
73
+422
74
+Content-Type: application/json
75
+Content-Length: 100
76
+
77
+{"code":1013,"message":"Input validation failed for 'types'","details":{"bigint":"must be numeric"}}
78
+===
79
+PUT /records/types/1
80
+
81
+{"varchar":"12345678901"}
82
+===
83
+422
84
+Content-Type: application/json
85
+Content-Length: 94
86
+
87
+{"code":1013,"message":"Input validation failed for 'types'","details":{"varchar":"too long"}}
88
+===
89
+PUT /records/types/1
90
+
91
+{"decimal":"string"}
92
+===
93
+422
94
+Content-Type: application/json
95
+Content-Length: 97
96
+
97
+{"code":1013,"message":"Input validation failed for 'types'","details":{"decimal":"not a float"}}
98
+===
99
+PUT /records/types/1
100
+
101
+{"float":"string"}
102
+===
103
+422
104
+Content-Type: application/json
105
+Content-Length: 95
106
+
107
+{"code":1013,"message":"Input validation failed for 'types'","details":{"float":"not a float"}}
108
+===
109
+PUT /records/types/1
110
+
111
+{"double":"string"}
112
+===
113
+422
114
+Content-Type: application/json
115
+Content-Length: 96
116
+
117
+{"code":1013,"message":"Input validation failed for 'types'","details":{"double":"not a float"}}
118
+===
119
+PUT /records/types/1
120
+
121
+{"boolean":-1}
122
+===
123
+422
124
+Content-Type: application/json
125
+Content-Length: 105
126
+
127
+{"code":1013,"message":"Input validation failed for 'types'","details":{"boolean":"not a valid boolean"}}
128
+===
129
+PUT /records/types/1
130
+
131
+{"boolean":null}
132
+===
133
+422
134
+Content-Type: application/json
135
+Content-Length: 100
136
+
137
+{"code":1013,"message":"Input validation failed for 'types'","details":{"boolean":"cannot be null"}}
138
+===
139
+PUT /records/types/1
140
+
141
+{"date":"string"}
142
+===
143
+422
144
+Content-Type: application/json
145
+Content-Length: 117
146
+
147
+{"code":1013,"message":"Input validation failed for 'types'","details":{"date":"invalid date format use yyyy-mm-dd"}}
148
+===
149
+PUT /records/types/1
150
+
151
+{"date":"still-no-date"}
152
+===
153
+422
154
+Content-Type: application/json
155
+Content-Length: 99
156
+
157
+{"code":1013,"message":"Input validation failed for 'types'","details":{"date":"not a valid date"}}
158
+===
159
+PUT /records/types/1
160
+
161
+{"time":"string"}
162
+===
163
+422
164
+Content-Type: application/json
165
+Content-Length: 115
166
+
167
+{"code":1013,"message":"Input validation failed for 'types'","details":{"time":"invalid time format use hh:mm:ss"}}
168
+===
169
+PUT /records/types/1
170
+
171
+{"time":"still:no:time"}
172
+===
173
+422
174
+Content-Type: application/json
175
+Content-Length: 105
176
+
177
+{"code":1013,"message":"Input validation failed for 'types'","details":{"time":"non-numeric time value"}}
178
+===
179
+PUT /records/types/1
180
+
181
+{"time":"999:999:999"}
182
+===
183
+422
184
+Content-Type: application/json
185
+Content-Length: 99
186
+
187
+{"code":1013,"message":"Input validation failed for 'types'","details":{"time":"not a valid time"}}
188
+===
189
+PUT /records/types/1
190
+
191
+{"timestamp":"string"}
192
+===
193
+422
194
+Content-Type: application/json
195
+Content-Length: 136
196
+
197
+{"code":1013,"message":"Input validation failed for 'types'","details":{"timestamp":"invalid timestamp format use yyyy-mm-dd hh:mm:ss"}}
198
+===
199
+PUT /records/types/1
200
+
201
+{"timestamp":"string 01:01:01"}
202
+===
203
+422
204
+Content-Type: application/json
205
+Content-Length: 122
206
+
207
+{"code":1013,"message":"Input validation failed for 'types'","details":{"timestamp":"invalid date format use yyyy-mm-dd"}}
208
+===
209
+PUT /records/types/1
210
+
211
+{"timestamp":"still-no-date 01:01:01"}
212
+===
213
+422
214
+Content-Type: application/json
215
+Content-Length: 104
216
+
217
+{"code":1013,"message":"Input validation failed for 'types'","details":{"timestamp":"not a valid date"}}
218
+===
219
+PUT /records/types/1
220
+
221
+{"timestamp":"2001-01-01 string"}
222
+===
223
+422
224
+Content-Type: application/json
225
+Content-Length: 120
226
+
227
+{"code":1013,"message":"Input validation failed for 'types'","details":{"timestamp":"invalid time format use hh:mm:ss"}}
228
+===
229
+PUT /records/types/1
230
+
231
+{"timestamp":"2001-01-01 still:no:time"}
232
+===
233
+422
234
+Content-Type: application/json
235
+Content-Length: 110
236
+
237
+{"code":1013,"message":"Input validation failed for 'types'","details":{"timestamp":"non-numeric time value"}}
238
+===
239
+PUT /records/types/1
240
+
241
+{"timestamp":"2001-01-01 999:999:999"}
242
+===
243
+422
244
+Content-Type: application/json
245
+Content-Length: 104
246
+
247
+{"code":1013,"message":"Input validation failed for 'types'","details":{"timestamp":"not a valid time"}}
248
+===
249
+PUT /records/types/1
250
+
251
+{"clob":"HLnVf8PrxX/vPMElDxrlFLIHoBd1fnLVSXVEDLjt0bk="}
252
+===
253
+200
254
+Content-Type: application/json
255
+Content-Length: 1
256
+
257
+1
258
+===
259
+PUT /records/types/1
260
+
261
+{"blob":"T8O5IGVzdCBsZSBjYWbDqSBsZSBwbHVzIHByb2NoZT8="}
262
+===
263
+200
264
+Content-Type: application/json
265
+Content-Length: 1
266
+
267
+1
268
+===
269
+GET /records/types/1
270
+===
271
+200
272
+Content-Type: application/json
273
+Content-Length: 305
274
+
275
+{"id":1,"integer":3,"bigint":3,"varchar":"c","decimal":"3.0000","float":3,"double":3,"boolean":true,"date":"2000-01-03","time":"03:01:01","timestamp":"2000-01-03 03:01:01","clob":"HLnVf8PrxX\/vPMElDxrlFLIHoBd1fnLVSXVEDLjt0bk=","blob":"T8O5IGVzdCBsZSBjYWbDqSBsZSBwbHVzIHByb2NoZT8=","geometry":"POINT(3 3)"}
276
+===
277
+DELETE /columns/types
278
+===
279
+200
280
+Content-Type: application/json
281
+Content-Length: 4
282
+
283
+true

Loading…
Cancel
Save