Browse Source

Add type sanitation

Maurits van der Schee 4 years ago
parent
commit
78e1314864

+ 54
- 52
README.md View File

652
 - "authorization.columnHandler": Handler to implement column authorization rules ("")
652
 - "authorization.columnHandler": Handler to implement column authorization rules ("")
653
 - "authorization.recordHandler": Handler to implement record authorization filter rules ("")
653
 - "authorization.recordHandler": Handler to implement record authorization filter rules ("")
654
 - "validation.handler": Handler to implement validation rules for input values ("")
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
+- "validation.types": Types to enable type validation for, empty means 'none' ("all")
656
+- "validation.tables": Tables to enable type validation for, empty means 'none' ("all")
656
 - "ipAddress.tables": Tables to search for columns to override with IP address ("")
657
 - "ipAddress.tables": Tables to search for columns to override with IP address ("")
657
 - "ipAddress.columns": Columns to protect and override with the IP address on create ("")
658
 - "ipAddress.columns": Columns to protect and override with the IP address on create ("")
658
 - "sanitation.handler": Handler to implement sanitation rules for input values ("")
659
 - "sanitation.handler": Handler to implement sanitation rules for input values ("")
660
+- "sanitation.types": Types to enable type sanitation for, empty means 'none' ("all")
661
+- "sanitation.tables": Tables to enable type sanitation for, empty means 'none' ("all")
659
 - "multiTenancy.handler": Handler to implement simple multi-tenancy rules ("")
662
 - "multiTenancy.handler": Handler to implement simple multi-tenancy rules ("")
660
 - "pageLimits.pages": The maximum page number that a list operation allows ("100")
663
 - "pageLimits.pages": The maximum page number that a list operation allows ("100")
661
 - "pageLimits.records": The maximum number of records returned by a list operation ("1000")
664
 - "pageLimits.records": The maximum number of records returned by a list operation ("1000")
887
 
890
 
888
 The above example will strip all HTML tags from strings in the input.
891
 The above example will strip all HTML tags from strings in the input.
889
 
892
 
890
-### Validating input
893
+### Type sanitation
894
+
895
+If you enable the 'sanitation' middleware, then you (automtically) also enable type sanitation. When this is enabled you may:
896
+
897
+- send leading and trailing whitespace (it will be ignored).
898
+- send a float to an integer field (it will be rounded).
899
+- send a base64url encoded (it will be converted to regular base64 encoding).
900
+- send a time/date/timestamp in any strtotime accepted format (it will be converted).
891
 
901
 
892
-By default all input is accepted unless the validation middleware is specified. The default types validations are then applied.
902
+You may use the config settings "`sanitation.types`" and "`sanitation.tables`"' to define for which types and
903
+in which tables you want to apply type sanitation (defaults to 'all'). Example:
893
 
904
 
894
-#### Validation handler
905
+    'sanitation.types' => 'date,timestamp',
906
+    'sanitation.tables' => 'posts,comments',
895
 
907
 
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.
908
+Here we enable the type sanitation for date and timestamp fields in the posts and comments tables.
909
+
910
+### Validating input
911
+
912
+By default all input is accepted and sent to the database. If you want to validate the input in a custom way, 
913
+you may add the 'validation' middleware and define a 'validation.handler' function that returns a boolean 
914
+indicating whether or not the value is valid.
898
 
915
 
899
     'validation.handler' => function ($operation, $tableName, $column, $value, $context) {
916
     'validation.handler' => function ($operation, $tableName, $column, $value, $context) {
900
         return ($column['name'] == 'post_id' && !is_numeric($value)) ? 'must be numeric' : true;
917
         return ($column['name'] == 'post_id' && !is_numeric($value)) ? 'must be numeric' : true;
920
 
937
 
921
 You can parse this output to make form fields show up with a red border and their appropriate error message.
938
 You can parse this output to make form fields show up with a red border and their appropriate error message.
922
 
939
 
923
-#### Validation types
940
+### Type validations
924
 
941
 
925
-The default types validations return the following error messages:
942
+If you enable the 'validation' middleware, then you (automtically) also enable type validation. 
943
+This includes the following error messages:
926
 
944
 
927
 | error message       | reason                      | applies to types                            |
945
 | error message       | reason                      | applies to types                            |
928
 | ------------------- | --------------------------- | ------------------------------------------- |
946
 | ------------------- | --------------------------- | ------------------------------------------- |
940
 | invalid timestamp   | use yyyy-mm-dd hh:mm:ss     | timestamp                                   |
958
 | invalid timestamp   | use yyyy-mm-dd hh:mm:ss     | timestamp                                   |
941
 | invalid base64      | illegal characters          | varbinary, blob                             |
959
 | invalid base64      | illegal characters          | varbinary, blob                             |
942
 
960
 
943
-If you want the types validation to apply to all the types, you must activate the "`validation`" middleware.
944
-By default, all types are enabled. Which is equivalent to the two configuration possibilities:
945
-
946
-    'validation.types' => 'all',
947
-    
948
-or
949
-
950
-    'validation.types'=> 'integer,bigint,varchar,decimal,float,double,boolean,date,time,timestamp,clob,blob,varbinary,geometry',
961
+You may use the config settings "`validation.types`" and "`validation.tables`"' to define for which types and
962
+in which tables you want to apply type validation (defaults to 'all'). Example:
951
 
963
 
952
-In case you want to use a validation handler but don't want any types validation, use:
964
+    'validation.types' => 'date,timestamp',
965
+    'validation.tables' => 'posts,comments',
953
 
966
 
954
-    'validation.types' => '',
967
+Here we enable the type validation for date and timestamp fields in the posts and comments tables.
955
 
968
 
956
 NB: Types that are enabled will be checked for null values when the column is non-nullable.
969
 NB: Types that are enabled will be checked for null values when the column is non-nullable.
957
 
970
 
1058
 
1071
 
1059
 ## Types
1072
 ## Types
1060
 
1073
 
1061
-These are the supported types with their default length/precision/scale:
1062
-
1063
-character types
1064
-- varchar(255)
1065
-- clob
1066
-
1067
-boolean types:
1068
-- boolean
1069
-
1070
-integer types:
1071
-- integer
1072
-- bigint
1073
-
1074
-floating point types:
1075
-- float
1076
-- double
1077
-
1078
-decimal types:
1079
-- decimal(19,4)
1080
-
1081
-date/time types:
1082
-- date
1083
-- time
1084
-- timestamp
1085
-
1086
-binary types:
1087
-- varbinary(255)
1088
-- blob
1089
-
1090
-other types:
1091
-- geometry /* non-jdbc type, extension with limited support */
1074
+These are the supported types with their length, category, JSON type and format:
1075
+
1076
+| type       | length | category  | JSON type | format              |
1077
+| ---------- | ------ | --------- | --------- | ------------------- |
1078
+| varchar    | 255    | character | string    |                     |
1079
+| clob       |        | character | string    |                     |
1080
+| boolean    |        | boolean   | boolean   |                     |
1081
+| integer    |        | integer   | number    |                     |
1082
+| bigint     |        | integer   | number    |                     |
1083
+| float      |        | float     | number    |                     |
1084
+| double     |        | float     | number    |                     |
1085
+| decimal    | 19,4   | decimal   | string    |                     |
1086
+| date       |        | date/time | string    | yyyy-mm-dd          | 
1087
+| time       |        | date/time | srting    | hh:mm:ss            |
1088
+| timestamp  |        | date/time | string    | yyyy-mm-dd hh:mm:ss |
1089
+| varbinary  | 255    | binary    | string    | base64 encoded      |
1090
+| blob       |        | binary    | string    | base64 encoded      |
1091
+| geometry   |        | other     | string    | well-known text     |
1092
+
1093
+Note that geometry is a non-jdbc type and thus has limited support.
1092
 
1094
 
1093
 ## Data types in JavaScript
1095
 ## Data types in JavaScript
1094
 
1096
 
1095
-Javascript and Javascript object notation are not very well suited for reading database records. Decimal, date/time, binary and geometry types are represented as strings in JSON (binary is base64 encoded, geometries are in WKT format). Below are two more serious issues described.
1097
+Javascript and Javascript object notation (JSON) are not very well suited for reading database records. Decimal, date/time, binary and geometry types must be represented as strings in JSON (binary is base64 encoded, geometries are in WKT format). Below are two more serious issues described.
1096
 
1098
 
1097
 ### 64 bit integers
1099
 ### 64 bit integers
1098
 
1100
 
1147
   - (Docker) Debian 9 with PHP 7.0, MariaDB 10.1, PostgreSQL 9.6 (PostGIS 2.3) and SQLite 3.16
1149
   - (Docker) Debian 9 with PHP 7.0, MariaDB 10.1, PostgreSQL 9.6 (PostGIS 2.3) and SQLite 3.16
1148
   - (Docker) Ubuntu 18.04 with PHP 7.2, MySQL 5.7, PostgreSQL 10.4 (PostGIS 2.4) and SQLite 3.22
1150
   - (Docker) Ubuntu 18.04 with PHP 7.2, MySQL 5.7, PostgreSQL 10.4 (PostGIS 2.4) and SQLite 3.22
1149
   - (Docker) Debian 10 with PHP 7.3, MariaDB 10.3, PostgreSQL 11.4 (PostGIS 2.5) and SQLite 3.27
1151
   - (Docker) Debian 10 with PHP 7.3, MariaDB 10.3, PostgreSQL 11.4 (PostGIS 2.5) and SQLite 3.27
1150
-  - (Docker) Ubuntu 20.04 with PHP 7.3, MySQL 8.0, PostgreSQL 12.2 (PostGIS 3.0) and SQLite 3.31
1152
+  - (Docker) Ubuntu 20.04 with PHP 7.4, MySQL 8.0, PostgreSQL 12.2 (PostGIS 3.0) and SQLite 3.31
1151
   - (Docker) CentOS 8 with PHP 7.4, MariaDB 10.4, PostgreSQL 12.2 (PostGIS 3.0) and SQLite 3.26
1153
   - (Docker) CentOS 8 with PHP 7.4, MariaDB 10.4, PostgreSQL 12.2 (PostGIS 3.0) and SQLite 3.26
1152
 
1154
 
1153
 This covers not all environments (yet), so please notify me of failing tests and report your environment. 
1155
 This covers not all environments (yet), so please notify me of failing tests and report your environment. 
1262
     sqlsrv: skipped, driver not loaded
1264
     sqlsrv: skipped, driver not loaded
1263
     sqlite: 105 tests ran in 1063 ms, 12 skipped, 0 failed
1265
     sqlite: 105 tests ran in 1063 ms, 12 skipped, 0 failed
1264
     ================================================
1266
     ================================================
1265
-    Ubuntu 20.04 (PHP 7.3)
1267
+    Ubuntu 20.04 (PHP 7.4)
1266
     ================================================
1268
     ================================================
1267
     [1/4] Starting MySQL 8.0 ........ done
1269
     [1/4] Starting MySQL 8.0 ........ done
1268
     [2/4] Starting PostgreSQL 12.2 .. done
1270
     [2/4] Starting PostgreSQL 12.2 .. done

+ 118
- 40
api.php View File

8155
     use Psr\Http\Message\ServerRequestInterface;
8155
     use Psr\Http\Message\ServerRequestInterface;
8156
     use Psr\Http\Server\RequestHandlerInterface;
8156
     use Psr\Http\Server\RequestHandlerInterface;
8157
     use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
8157
     use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
8158
+    use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
8158
     use Tqdev\PhpCrudApi\Column\ReflectionService;
8159
     use Tqdev\PhpCrudApi\Column\ReflectionService;
8159
     use Tqdev\PhpCrudApi\Controller\Responder;
8160
     use Tqdev\PhpCrudApi\Controller\Responder;
8160
     use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
8161
     use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
8179
                 if ($table->hasColumn($columnName)) {
8180
                 if ($table->hasColumn($columnName)) {
8180
                     $column = $table->getColumn($columnName);
8181
                     $column = $table->getColumn($columnName);
8181
                     $value = call_user_func($handler, $operation, $tableName, $column->serialize(), $value);
8182
                     $value = call_user_func($handler, $operation, $tableName, $column->serialize(), $value);
8183
+                    $value = $this->sanitizeType($table, $column, $value);
8182
                 }
8184
                 }
8183
             }
8185
             }
8184
             return (object) $context;
8186
             return (object) $context;
8185
         }
8187
         }
8186
 
8188
 
8189
+        private function sanitizeType(ReflectedTable $table, ReflectedColumn $column, $value)
8190
+        {
8191
+            $tables = $this->getArrayProperty('tables', 'all');
8192
+            $types = $this->getArrayProperty('types', 'all');
8193
+            if (
8194
+                (in_array('all', $tables) || in_array($table->getName(), $tables)) &&
8195
+                (in_array('all', $types) || in_array($column->getType(), $types))
8196
+            ) {
8197
+                if (is_null($value)) {
8198
+                    return $value;
8199
+                }
8200
+                if (is_string($value)) {
8201
+                    $newValue = null;
8202
+                    switch ($column->getType()) {
8203
+                        case 'integer':
8204
+                        case 'bigint':
8205
+                            $newValue = filter_var(trim($value), FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
8206
+                            break;
8207
+                        case 'decimal':
8208
+                            $newValue = filter_var(trim($value), FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
8209
+                            if (is_float($newValue)) {
8210
+                                $newValue = number_format($newValue, $column->getScale(), '.', '');
8211
+                            }
8212
+                            break;
8213
+                        case 'float':
8214
+                        case 'double':
8215
+                            $newValue = filter_var(trim($value), FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
8216
+                            break;
8217
+                        case 'boolean':
8218
+                            $newValue = filter_var(trim($value), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
8219
+                            break;
8220
+                        case 'date':
8221
+                            $time = strtotime(trim($value));
8222
+                            if ($time !== false) {
8223
+                                $newValue = date('Y-m-d', $time);
8224
+                            }
8225
+                            break;
8226
+                        case 'time':
8227
+                            $time = strtotime(trim($value));
8228
+                            if ($time !== false) {
8229
+                                $newValue = date('H:i:s', $time);
8230
+                            }
8231
+                            break;
8232
+                        case 'timestamp':
8233
+                            $time = strtotime(trim($value));
8234
+                            if ($time !== false) {
8235
+                                $newValue = date('Y-m-d H:i:s', $time);
8236
+                            }
8237
+                            break;
8238
+                        case 'blob':
8239
+                        case 'varbinary':
8240
+                            // allow base64url format
8241
+                            $newValue = strtr(trim($value), '-_', '+/');
8242
+                            break;
8243
+                        case 'clob':
8244
+                        case 'varchar':
8245
+                            $newValue = $value;
8246
+                            break;
8247
+                        case 'geometry':
8248
+                            $newValue = trim($value);
8249
+                            break;
8250
+                    }
8251
+                    if (!is_null($newValue)) {
8252
+                        $value = $newValue;
8253
+                    }
8254
+                } else {
8255
+                    switch ($column->getType()) {
8256
+                        case 'integer':
8257
+                        case 'bigint':
8258
+                            if (is_float($value)) {
8259
+                                $value = (int) round($value);
8260
+                            }
8261
+                            break;
8262
+                        case 'decimal':
8263
+                            if (is_float($value) || is_int($value)) {
8264
+                                $value = number_format((float) $value, $column->getScale(), '.', '');
8265
+                            }
8266
+                            break;
8267
+                    }
8268
+                }
8269
+                // post process
8270
+            }
8271
+            return $value;
8272
+        }
8273
+
8187
         public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
8274
         public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
8188
         {
8275
         {
8189
             $operation = RequestUtils::getOperation($request);
8276
             $operation = RequestUtils::getOperation($request);
8247
     				$column = $table->getColumn($columnName);
8334
     				$column = $table->getColumn($columnName);
8248
     				$valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
8335
     				$valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
8249
     				if ($valid === true || $valid === '') {
8336
     				if ($valid === true || $valid === '') {
8250
-    					$valid = $this->validateType($column, $value);
8337
+    					$valid = $this->validateType($table, $column, $value);
8251
     				}
8338
     				}
8252
     				if ($valid !== true && $valid !== '') {
8339
     				if ($valid !== true && $valid !== '') {
8253
     					$details[$columnName] = $valid;
8340
     					$details[$columnName] = $valid;
8260
     		return null;
8347
     		return null;
8261
     	}
8348
     	}
8262
 
8349
 
8263
-    	private function validateType(ReflectedColumn $column, $value)
8350
+    	private function validateType(ReflectedTable $table, ReflectedColumn $column, $value)
8264
     	{
8351
     	{
8352
+    		$tables = $this->getArrayProperty('tables', 'all');
8265
     		$types = $this->getArrayProperty('types', 'all');
8353
     		$types = $this->getArrayProperty('types', 'all');
8266
-    		if (in_array('all', $types) || in_array($column->getType(), $types)) {
8354
+    		if (
8355
+    			(in_array('all', $tables) || in_array($table->getName(), $tables)) &&
8356
+    			(in_array('all', $types) || in_array($column->getType(), $types))
8357
+    		) {
8267
     			if (is_null($value)) {
8358
     			if (is_null($value)) {
8268
     				return ($column->getNullable() ? true : "cannot be null");
8359
     				return ($column->getNullable() ? true : "cannot be null");
8269
     			}
8360
     			}
8290
     							return 'invalid integer';
8381
     							return 'invalid integer';
8291
     						}
8382
     						}
8292
     						break;
8383
     						break;
8293
-    					case 'varchar':
8294
-    						if (mb_strlen($value, 'UTF-8') > $column->getLength()) {
8295
-    							return 'string too long';
8296
-    						}
8297
-    						break;
8298
     					case 'decimal':
8384
     					case 'decimal':
8299
-    						if (!is_numeric($value)) {
8385
+    						if (strpos($value, '.') !== false) {
8386
+    							list($whole, $decimals) = explode('.', $value, 2);
8387
+    						} else {
8388
+    							list($whole, $decimals) = array($value, '');
8389
+    						}
8390
+    						if (strlen($whole) > 0 && !ctype_digit($whole)) {
8391
+    							return 'invalid decimal';
8392
+    						}
8393
+    						if (strlen($decimals) > 0 && !ctype_digit($decimals)) {
8300
     							return 'invalid decimal';
8394
     							return 'invalid decimal';
8301
     						}
8395
     						}
8396
+    						if (strlen($whole) > $column->getPrecision() - $column->getScale()) {
8397
+    							return 'decimal too large';
8398
+    						}
8399
+    						if (strlen($decimals) > $column->getScale()) {
8400
+    							return 'decimal too precise';
8401
+    						}
8302
     						break;
8402
     						break;
8303
     					case 'float':
8403
     					case 'float':
8304
     					case 'double':
8404
     					case 'double':
8310
     						}
8410
     						}
8311
     						break;
8411
     						break;
8312
     					case 'boolean':
8412
     					case 'boolean':
8313
-    						if (
8314
-    							filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === null
8315
-    						) {
8413
+    						if (!in_array(strtolower($value), array('true', 'false'))) {
8316
     							return 'invalid boolean';
8414
     							return 'invalid boolean';
8317
     						}
8415
     						}
8318
     						break;
8416
     						break;
8332
     						}
8430
     						}
8333
     						break;
8431
     						break;
8334
     					case 'clob':
8432
     					case 'clob':
8335
-    						// no checks needed
8433
+    					case 'varchar':
8434
+    						if ($column->hasLength() && mb_strlen($value, 'UTF-8') > $column->getLength()) {
8435
+    							return 'string too long';
8436
+    						}
8336
     						break;
8437
     						break;
8337
     					case 'blob':
8438
     					case 'blob':
8338
     					case 'varbinary':
8439
     					case 'varbinary':
8339
     						if (base64_decode($value, true) === false) {
8440
     						if (base64_decode($value, true) === false) {
8340
     							return 'invalid base64';
8441
     							return 'invalid base64';
8341
     						}
8442
     						}
8443
+    						if ($column->hasLength() && strlen(base64_decode($value)) > $column->getLength()) {
8444
+    							return 'string too long';
8445
+    						}
8342
     						break;
8446
     						break;
8343
     					case 'geometry':
8447
     					case 'geometry':
8344
     						// no checks yet
8448
     						// no checks yet
8352
     							return 'invalid integer';
8456
     							return 'invalid integer';
8353
     						}
8457
     						}
8354
     						break;
8458
     						break;
8355
-    					case 'decimal':
8356
     					case 'float':
8459
     					case 'float':
8357
     					case 'double':
8460
     					case 'double':
8358
     						if (!is_float($value) && !is_int($value)) {
8461
     						if (!is_float($value) && !is_int($value)) {
8360
     						}
8463
     						}
8361
     						break;
8464
     						break;
8362
     					case 'boolean':
8465
     					case 'boolean':
8363
-    						if (!(is_int($value) && ($value === 1 || $value === 0)) && !is_bool($value)) {
8466
+    						if (!is_bool($value) && ($value !== 0) && ($value !== 1)) {
8364
     							return 'invalid boolean';
8467
     							return 'invalid boolean';
8365
     						}
8468
     						}
8366
     						break;
8469
     						break;
8376
     						return 'invalid integer';
8479
     						return 'invalid integer';
8377
     					}
8480
     					}
8378
     					break;
8481
     					break;
8379
-    				case 'decimal':
8380
-    					$value = "$value";
8381
-    					if (strpos($value, '.') !== false) {
8382
-    						list($whole, $decimals) = explode('.', $value, 2);
8383
-    					} else {
8384
-    						list($whole, $decimals) = array($value, '');
8385
-    					}
8386
-    					if (strlen($whole) > 0 && !ctype_digit($whole)) {
8387
-    						return 'invalid decimal';
8388
-    					}
8389
-    					if (strlen($decimals) > 0 && !ctype_digit($decimals)) {
8390
-    						return 'invalid decimal';
8391
-    					}
8392
-    					if (strlen($whole) > $column->getPrecision() - $column->getScale()) {
8393
-    						return 'decimal too large';
8394
-    					}
8395
-    					if (strlen($decimals) > $column->getScale()) {
8396
-    						return 'decimal too precise';
8397
-    					}
8398
-    					break;
8399
-    				case 'varbinary':
8400
-    					if (strlen(base64_decode($value)) > $column->getLength()) {
8401
-    						return 'string too long';
8402
-    					}
8403
-    					break;
8404
     			}
8482
     			}
8405
     		}
8483
     		}
8406
     		return (true);
8484
     		return (true);

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

1
 #!/bin/bash
1
 #!/bin/bash
2
 echo "================================================"
2
 echo "================================================"
3
-echo " Ubuntu 20.04 (PHP 7.3)"
3
+echo " Ubuntu 20.04 (PHP 7.4)"
4
 echo "================================================"
4
 echo "================================================"
5
 
5
 
6
 echo -n "[1/4] Starting MySQL 8.0 ........ "
6
 echo -n "[1/4] Starting MySQL 8.0 ........ "

+ 87
- 0
src/Tqdev/PhpCrudApi/Middleware/SanitationMiddleware.php View File

6
 use Psr\Http\Message\ServerRequestInterface;
6
 use Psr\Http\Message\ServerRequestInterface;
7
 use Psr\Http\Server\RequestHandlerInterface;
7
 use Psr\Http\Server\RequestHandlerInterface;
8
 use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
8
 use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
9
+use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
9
 use Tqdev\PhpCrudApi\Column\ReflectionService;
10
 use Tqdev\PhpCrudApi\Column\ReflectionService;
10
 use Tqdev\PhpCrudApi\Controller\Responder;
11
 use Tqdev\PhpCrudApi\Controller\Responder;
11
 use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
12
 use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
30
             if ($table->hasColumn($columnName)) {
31
             if ($table->hasColumn($columnName)) {
31
                 $column = $table->getColumn($columnName);
32
                 $column = $table->getColumn($columnName);
32
                 $value = call_user_func($handler, $operation, $tableName, $column->serialize(), $value);
33
                 $value = call_user_func($handler, $operation, $tableName, $column->serialize(), $value);
34
+                $value = $this->sanitizeType($table, $column, $value);
33
             }
35
             }
34
         }
36
         }
35
         return (object) $context;
37
         return (object) $context;
36
     }
38
     }
37
 
39
 
40
+    private function sanitizeType(ReflectedTable $table, ReflectedColumn $column, $value)
41
+    {
42
+        $tables = $this->getArrayProperty('tables', 'all');
43
+        $types = $this->getArrayProperty('types', 'all');
44
+        if (
45
+            (in_array('all', $tables) || in_array($table->getName(), $tables)) &&
46
+            (in_array('all', $types) || in_array($column->getType(), $types))
47
+        ) {
48
+            if (is_null($value)) {
49
+                return $value;
50
+            }
51
+            if (is_string($value)) {
52
+                $newValue = null;
53
+                switch ($column->getType()) {
54
+                    case 'integer':
55
+                    case 'bigint':
56
+                        $newValue = filter_var(trim($value), FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
57
+                        break;
58
+                    case 'decimal':
59
+                        $newValue = filter_var(trim($value), FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
60
+                        if (is_float($newValue)) {
61
+                            $newValue = number_format($newValue, $column->getScale(), '.', '');
62
+                        }
63
+                        break;
64
+                    case 'float':
65
+                    case 'double':
66
+                        $newValue = filter_var(trim($value), FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
67
+                        break;
68
+                    case 'boolean':
69
+                        $newValue = filter_var(trim($value), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
70
+                        break;
71
+                    case 'date':
72
+                        $time = strtotime(trim($value));
73
+                        if ($time !== false) {
74
+                            $newValue = date('Y-m-d', $time);
75
+                        }
76
+                        break;
77
+                    case 'time':
78
+                        $time = strtotime(trim($value));
79
+                        if ($time !== false) {
80
+                            $newValue = date('H:i:s', $time);
81
+                        }
82
+                        break;
83
+                    case 'timestamp':
84
+                        $time = strtotime(trim($value));
85
+                        if ($time !== false) {
86
+                            $newValue = date('Y-m-d H:i:s', $time);
87
+                        }
88
+                        break;
89
+                    case 'blob':
90
+                    case 'varbinary':
91
+                        // allow base64url format
92
+                        $newValue = strtr(trim($value), '-_', '+/');
93
+                        break;
94
+                    case 'clob':
95
+                    case 'varchar':
96
+                        $newValue = $value;
97
+                        break;
98
+                    case 'geometry':
99
+                        $newValue = trim($value);
100
+                        break;
101
+                }
102
+                if (!is_null($newValue)) {
103
+                    $value = $newValue;
104
+                }
105
+            } else {
106
+                switch ($column->getType()) {
107
+                    case 'integer':
108
+                    case 'bigint':
109
+                        if (is_float($value)) {
110
+                            $value = (int) round($value);
111
+                        }
112
+                        break;
113
+                    case 'decimal':
114
+                        if (is_float($value) || is_int($value)) {
115
+                            $value = number_format((float) $value, $column->getScale(), '.', '');
116
+                        }
117
+                        break;
118
+                }
119
+            }
120
+            // post process
121
+        }
122
+        return $value;
123
+    }
124
+
38
     public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
125
     public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
39
     {
126
     {
40
         $operation = RequestUtils::getOperation($request);
127
         $operation = RequestUtils::getOperation($request);

+ 31
- 40
src/Tqdev/PhpCrudApi/Middleware/ValidationMiddleware.php View File

34
 				$column = $table->getColumn($columnName);
34
 				$column = $table->getColumn($columnName);
35
 				$valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
35
 				$valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
36
 				if ($valid === true || $valid === '') {
36
 				if ($valid === true || $valid === '') {
37
-					$valid = $this->validateType($column, $value);
37
+					$valid = $this->validateType($table, $column, $value);
38
 				}
38
 				}
39
 				if ($valid !== true && $valid !== '') {
39
 				if ($valid !== true && $valid !== '') {
40
 					$details[$columnName] = $valid;
40
 					$details[$columnName] = $valid;
47
 		return null;
47
 		return null;
48
 	}
48
 	}
49
 
49
 
50
-	private function validateType(ReflectedColumn $column, $value)
50
+	private function validateType(ReflectedTable $table, ReflectedColumn $column, $value)
51
 	{
51
 	{
52
+		$tables = $this->getArrayProperty('tables', 'all');
52
 		$types = $this->getArrayProperty('types', 'all');
53
 		$types = $this->getArrayProperty('types', 'all');
53
-		if (in_array('all', $types) || in_array($column->getType(), $types)) {
54
+		if (
55
+			(in_array('all', $tables) || in_array($table->getName(), $tables)) &&
56
+			(in_array('all', $types) || in_array($column->getType(), $types))
57
+		) {
54
 			if (is_null($value)) {
58
 			if (is_null($value)) {
55
 				return ($column->getNullable() ? true : "cannot be null");
59
 				return ($column->getNullable() ? true : "cannot be null");
56
 			}
60
 			}
77
 							return 'invalid integer';
81
 							return 'invalid integer';
78
 						}
82
 						}
79
 						break;
83
 						break;
80
-					case 'varchar':
81
-						if (mb_strlen($value, 'UTF-8') > $column->getLength()) {
82
-							return 'string too long';
83
-						}
84
-						break;
85
 					case 'decimal':
84
 					case 'decimal':
86
-						if (!is_numeric($value)) {
85
+						if (strpos($value, '.') !== false) {
86
+							list($whole, $decimals) = explode('.', $value, 2);
87
+						} else {
88
+							list($whole, $decimals) = array($value, '');
89
+						}
90
+						if (strlen($whole) > 0 && !ctype_digit($whole)) {
87
 							return 'invalid decimal';
91
 							return 'invalid decimal';
88
 						}
92
 						}
93
+						if (strlen($decimals) > 0 && !ctype_digit($decimals)) {
94
+							return 'invalid decimal';
95
+						}
96
+						if (strlen($whole) > $column->getPrecision() - $column->getScale()) {
97
+							return 'decimal too large';
98
+						}
99
+						if (strlen($decimals) > $column->getScale()) {
100
+							return 'decimal too precise';
101
+						}
89
 						break;
102
 						break;
90
 					case 'float':
103
 					case 'float':
91
 					case 'double':
104
 					case 'double':
97
 						}
110
 						}
98
 						break;
111
 						break;
99
 					case 'boolean':
112
 					case 'boolean':
100
-						if (
101
-							filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === null
102
-						) {
113
+						if (!in_array(strtolower($value), array('true', 'false'))) {
103
 							return 'invalid boolean';
114
 							return 'invalid boolean';
104
 						}
115
 						}
105
 						break;
116
 						break;
119
 						}
130
 						}
120
 						break;
131
 						break;
121
 					case 'clob':
132
 					case 'clob':
122
-						// no checks needed
133
+					case 'varchar':
134
+						if ($column->hasLength() && mb_strlen($value, 'UTF-8') > $column->getLength()) {
135
+							return 'string too long';
136
+						}
123
 						break;
137
 						break;
124
 					case 'blob':
138
 					case 'blob':
125
 					case 'varbinary':
139
 					case 'varbinary':
126
 						if (base64_decode($value, true) === false) {
140
 						if (base64_decode($value, true) === false) {
127
 							return 'invalid base64';
141
 							return 'invalid base64';
128
 						}
142
 						}
143
+						if ($column->hasLength() && strlen(base64_decode($value)) > $column->getLength()) {
144
+							return 'string too long';
145
+						}
129
 						break;
146
 						break;
130
 					case 'geometry':
147
 					case 'geometry':
131
 						// no checks yet
148
 						// no checks yet
139
 							return 'invalid integer';
156
 							return 'invalid integer';
140
 						}
157
 						}
141
 						break;
158
 						break;
142
-					case 'decimal':
143
 					case 'float':
159
 					case 'float':
144
 					case 'double':
160
 					case 'double':
145
 						if (!is_float($value) && !is_int($value)) {
161
 						if (!is_float($value) && !is_int($value)) {
147
 						}
163
 						}
148
 						break;
164
 						break;
149
 					case 'boolean':
165
 					case 'boolean':
150
-						if (!(is_int($value) && ($value === 1 || $value === 0)) && !is_bool($value)) {
166
+						if (!is_bool($value) && ($value !== 0) && ($value !== 1)) {
151
 							return 'invalid boolean';
167
 							return 'invalid boolean';
152
 						}
168
 						}
153
 						break;
169
 						break;
163
 						return 'invalid integer';
179
 						return 'invalid integer';
164
 					}
180
 					}
165
 					break;
181
 					break;
166
-				case 'decimal':
167
-					$value = "$value";
168
-					if (strpos($value, '.') !== false) {
169
-						list($whole, $decimals) = explode('.', $value, 2);
170
-					} else {
171
-						list($whole, $decimals) = array($value, '');
172
-					}
173
-					if (strlen($whole) > 0 && !ctype_digit($whole)) {
174
-						return 'invalid decimal';
175
-					}
176
-					if (strlen($decimals) > 0 && !ctype_digit($decimals)) {
177
-						return 'invalid decimal';
178
-					}
179
-					if (strlen($whole) > $column->getPrecision() - $column->getScale()) {
180
-						return 'decimal too large';
181
-					}
182
-					if (strlen($decimals) > $column->getScale()) {
183
-						return 'decimal too precise';
184
-					}
185
-					break;
186
-				case 'varbinary':
187
-					if (strlen(base64_decode($value)) > $column->getLength()) {
188
-						return 'string too long';
189
-					}
190
-					break;
191
 			}
182
 			}
192
 		}
183
 		}
193
 		return (true);
184
 		return (true);

+ 2
- 1
tests/config/base.php View File

4
     'username' => 'incorrect_username',
4
     'username' => 'incorrect_username',
5
     'password' => 'incorrect_password',
5
     'password' => 'incorrect_password',
6
     'controllers' => 'records,columns,cache,openapi,geojson',
6
     'controllers' => 'records,columns,cache,openapi,geojson',
7
-    'middlewares' => 'cors,reconnect,dbAuth,jwtAuth,basicAuth,authorization,validation,ipAddress,sanitation,multiTenancy,pageLimits,joinLimits,customization',
7
+    'middlewares' => 'cors,reconnect,dbAuth,jwtAuth,basicAuth,authorization,sanitation,validation,ipAddress,multiTenancy,pageLimits,joinLimits,customization',
8
     'dbAuth.mode' => 'optional',
8
     'dbAuth.mode' => 'optional',
9
     'dbAuth.returnedColumns' => 'id,username,password',
9
     'dbAuth.returnedColumns' => 'id,username,password',
10
     'jwtAuth.mode' => 'optional',
10
     'jwtAuth.mode' => 'optional',
35
     'sanitation.handler' => function ($operation, $tableName, $column, $value) {
35
     'sanitation.handler' => function ($operation, $tableName, $column, $value) {
36
         return is_string($value) ? strip_tags($value) : $value;
36
         return is_string($value) ? strip_tags($value) : $value;
37
     },
37
     },
38
+    'sanitation.tables' => 'forgiving',
38
     'validation.handler' => function ($operation, $tableName, $column, $value, $context) {
39
     'validation.handler' => function ($operation, $tableName, $column, $value, $context) {
39
         return ($column['name'] == 'post_id' && !is_numeric($value)) ? 'must be numeric' : true;
40
         return ($column['name'] == 'post_id' && !is_numeric($value)) ? 'must be numeric' : true;
40
     },
41
     },

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

12
 POST /records/types
12
 POST /records/types
13
 Content-Type: application/json
13
 Content-Type: application/json
14
 
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)"}
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
 ===
16
 ===
17
 200
17
 200
18
 Content-Type: application/json
18
 Content-Type: application/json
62
 ===
62
 ===
63
 PUT /records/types/1
63
 PUT /records/types/1
64
 
64
 
65
-{"integer":"12345678901"}
65
+{"integer":"2.3"}
66
 ===
66
 ===
67
 422
67
 422
68
 Content-Type: application/json
68
 Content-Type: application/json
72
 ===
72
 ===
73
 PUT /records/types/1
73
 PUT /records/types/1
74
 
74
 
75
-{"bigint":"12345678901234567890"}
75
+{"integer":2.3}
76
 ===
76
 ===
77
 422
77
 422
78
 Content-Type: application/json
78
 Content-Type: application/json
79
-Content-Length: 100
79
+Content-Length: 101
80
 
80
 
81
-{"code":1013,"message":"Input validation failed for 'types'","details":{"bigint":"invalid integer"}}
81
+{"code":1013,"message":"Input validation failed for 'types'","details":{"integer":"invalid integer"}}
82
 ===
82
 ===
83
 PUT /records/types/1
83
 PUT /records/types/1
84
 
84
 
85
-{"varchar":"12345678901"}
85
+{"integer":"12345678901"}
86
 ===
86
 ===
87
 422
87
 422
88
 Content-Type: application/json
88
 Content-Type: application/json
89
 Content-Length: 101
89
 Content-Length: 101
90
 
90
 
91
-{"code":1013,"message":"Input validation failed for 'types'","details":{"varchar":"string too long"}}
91
+{"code":1013,"message":"Input validation failed for 'types'","details":{"integer":"invalid integer"}}
92
 ===
92
 ===
93
 PUT /records/types/1
93
 PUT /records/types/1
94
 
94
 
95
-{"decimal":"12.23.34"}
95
+{"bigint":"12345678901234567890"}
96
 ===
96
 ===
97
 422
97
 422
98
 Content-Type: application/json
98
 Content-Type: application/json
99
-Content-Length: 101
99
+Content-Length: 100
100
 
100
 
101
-{"code":1013,"message":"Input validation failed for 'types'","details":{"decimal":"invalid decimal"}}
101
+{"code":1013,"message":"Input validation failed for 'types'","details":{"bigint":"invalid integer"}}
102
 ===
102
 ===
103
 PUT /records/types/1
103
 PUT /records/types/1
104
 
104
 
105
-{"decimal":1131313145345}
105
+{"varchar":"12345678901"}
106
 ===
106
 ===
107
 422
107
 422
108
 Content-Type: application/json
108
 Content-Type: application/json
109
-Content-Length: 103
109
+Content-Length: 101
110
 
110
 
111
-{"code":1013,"message":"Input validation failed for 'types'","details":{"decimal":"decimal too large"}}
111
+{"code":1013,"message":"Input validation failed for 'types'","details":{"varchar":"string too long"}}
112
 ===
112
 ===
113
 PUT /records/types/1
113
 PUT /records/types/1
114
 
114
 
115
-{"decimal":"1234567.123"}
115
+{"decimal":"12.23.34"}
116
 ===
116
 ===
117
 422
117
 422
118
 Content-Type: application/json
118
 Content-Type: application/json
119
-Content-Length: 103
119
+Content-Length: 101
120
 
120
 
121
-{"code":1013,"message":"Input validation failed for 'types'","details":{"decimal":"decimal too large"}}
121
+{"code":1013,"message":"Input validation failed for 'types'","details":{"decimal":"invalid decimal"}}
122
 ===
122
 ===
123
 PUT /records/types/1
123
 PUT /records/types/1
124
 
124
 
125
-{"decimal":11313131e+5}
125
+{"decimal":"1131313145345"}
126
 ===
126
 ===
127
 422
127
 422
128
 Content-Type: application/json
128
 Content-Type: application/json
132
 ===
132
 ===
133
 PUT /records/types/1
133
 PUT /records/types/1
134
 
134
 
135
-{"decimal":"123456.12345"}
135
+{"decimal":"1234567.123"}
136
 ===
136
 ===
137
 422
137
 422
138
 Content-Type: application/json
138
 Content-Type: application/json
139
-Content-Length: 105
139
+Content-Length: 103
140
 
140
 
141
-{"code":1013,"message":"Input validation failed for 'types'","details":{"decimal":"decimal too precise"}}
141
+{"code":1013,"message":"Input validation failed for 'types'","details":{"decimal":"decimal too large"}}
142
 ===
142
 ===
143
 PUT /records/types/1
143
 PUT /records/types/1
144
 
144
 
145
-{"decimal":113131.3145345}
145
+{"decimal":"123456.12345"}
146
 ===
146
 ===
147
 422
147
 422
148
 Content-Type: application/json
148
 Content-Type: application/json
152
 ===
152
 ===
153
 PUT /records/types/1
153
 PUT /records/types/1
154
 
154
 
155
-{"decimal":11313131E-5}
155
+{"decimal":"113131.3145345"}
156
 ===
156
 ===
157
 422
157
 422
158
 Content-Type: application/json
158
 Content-Type: application/json

+ 383
- 0
tests/functional/003_columns/016_update_forgiving_table.log View File

1
+===
2
+POST /columns
3
+
4
+{"name":"forgiving","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/forgiving
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/forgiving/1
24
+
25
+{"boolean":"true"}
26
+===
27
+200
28
+Content-Type: application/json
29
+Content-Length: 1
30
+
31
+1
32
+===
33
+GET /records/forgiving/1?include=boolean
34
+===
35
+200
36
+Content-Type: application/json
37
+Content-Length: 16
38
+
39
+{"boolean":true}
40
+===
41
+PUT /records/forgiving/1
42
+
43
+{"boolean":"yes"}
44
+===
45
+200
46
+Content-Type: application/json
47
+Content-Length: 1
48
+
49
+1
50
+===
51
+GET /records/forgiving/1?include=boolean
52
+===
53
+200
54
+Content-Type: application/json
55
+Content-Length: 16
56
+
57
+{"boolean":true}
58
+===
59
+PUT /records/forgiving/1
60
+
61
+{"boolean":"1"}
62
+===
63
+200
64
+Content-Type: application/json
65
+Content-Length: 1
66
+
67
+1
68
+===
69
+GET /records/forgiving/1?include=boolean
70
+===
71
+200
72
+Content-Type: application/json
73
+Content-Length: 16
74
+
75
+{"boolean":true}
76
+===
77
+PUT /records/forgiving/1
78
+
79
+{"boolean":1}
80
+===
81
+200
82
+Content-Type: application/json
83
+Content-Length: 1
84
+
85
+1
86
+===
87
+GET /records/forgiving/1?include=boolean
88
+===
89
+200
90
+Content-Type: application/json
91
+Content-Length: 16
92
+
93
+{"boolean":true}
94
+===
95
+PUT /records/forgiving/1
96
+
97
+{"integer":" 2\n"}
98
+===
99
+200
100
+Content-Type: application/json
101
+Content-Length: 1
102
+
103
+1
104
+===
105
+GET /records/forgiving/1?include=integer
106
+===
107
+200
108
+Content-Type: application/json
109
+Content-Length: 13
110
+
111
+{"integer":2}
112
+===
113
+PUT /records/forgiving/1
114
+
115
+integer=2%20
116
+===
117
+200
118
+Content-Type: application/json
119
+Content-Length: 1
120
+
121
+1
122
+===
123
+GET /records/forgiving/1?include=integer
124
+===
125
+200
126
+Content-Type: application/json
127
+Content-Length: 13
128
+
129
+{"integer":2}
130
+===
131
+PUT /records/forgiving/1
132
+
133
+{"integer":1.99999}
134
+===
135
+200
136
+Content-Type: application/json
137
+Content-Length: 1
138
+
139
+1
140
+===
141
+GET /records/forgiving/1?include=integer
142
+===
143
+200
144
+Content-Type: application/json
145
+Content-Length: 13
146
+
147
+{"integer":2}
148
+===
149
+PUT /records/forgiving/1
150
+
151
+{"bigint":" 3\n"}
152
+===
153
+200
154
+Content-Type: application/json
155
+Content-Length: 1
156
+
157
+1
158
+===
159
+GET /records/forgiving/1?include=bigint
160
+===
161
+200
162
+Content-Type: application/json
163
+Content-Length: 12
164
+
165
+{"bigint":3}
166
+===
167
+PUT /records/forgiving/1
168
+
169
+{"bigint":2.99999}
170
+===
171
+200
172
+Content-Type: application/json
173
+Content-Length: 1
174
+
175
+1
176
+===
177
+GET /records/forgiving/1?include=bigint
178
+===
179
+200
180
+Content-Type: application/json
181
+Content-Length: 12
182
+
183
+{"bigint":3}
184
+===
185
+PUT /records/forgiving/1
186
+
187
+{"decimal":"1.23"}
188
+===
189
+200
190
+Content-Type: application/json
191
+Content-Length: 1
192
+
193
+1
194
+===
195
+GET /records/forgiving/1?include=decimal
196
+===
197
+200
198
+Content-Type: application/json
199
+Content-Length: 20
200
+
201
+{"decimal":"1.2300"}
202
+===
203
+PUT /records/forgiving/1
204
+
205
+{"decimal":"1.23004"}
206
+===
207
+200
208
+Content-Type: application/json
209
+Content-Length: 1
210
+
211
+1
212
+===
213
+GET /records/forgiving/1?include=decimal
214
+===
215
+200
216
+Content-Type: application/json
217
+Content-Length: 20
218
+
219
+{"decimal":"1.2300"}
220
+===
221
+PUT /records/forgiving/1
222
+
223
+{"decimal":"1.23006"}
224
+===
225
+200
226
+Content-Type: application/json
227
+Content-Length: 1
228
+
229
+1
230
+===
231
+GET /records/forgiving/1?include=decimal
232
+===
233
+200
234
+Content-Type: application/json
235
+Content-Length: 20
236
+
237
+{"decimal":"1.2301"}
238
+===
239
+PUT /records/forgiving/1
240
+
241
+float=1%20
242
+===
243
+200
244
+Content-Type: application/json
245
+Content-Length: 1
246
+
247
+1
248
+===
249
+GET /records/forgiving/1?include=float
250
+===
251
+200
252
+Content-Type: application/json
253
+Content-Length: 11
254
+
255
+{"float":1}
256
+===
257
+PUT /records/forgiving/1
258
+
259
+{"double":" 23.45e-1 "}
260
+===
261
+200
262
+Content-Type: application/json
263
+Content-Length: 1
264
+
265
+1
266
+===
267
+GET /records/forgiving/1?include=double
268
+===
269
+200
270
+Content-Type: application/json
271
+Content-Length: 16
272
+
273
+{"double":2.345}
274
+===
275
+PUT /records/forgiving/1
276
+
277
+{"date":"2020-12-05"}
278
+===
279
+200
280
+Content-Type: application/json
281
+Content-Length: 1
282
+
283
+1
284
+===
285
+GET /records/forgiving/1?include=date
286
+===
287
+200
288
+Content-Type: application/json
289
+Content-Length: 21
290
+
291
+{"date":"2020-12-05"}
292
+===
293
+PUT /records/forgiving/1
294
+
295
+{"date":"December 20th, 2020"}
296
+===
297
+200
298
+Content-Type: application/json
299
+Content-Length: 1
300
+
301
+1
302
+===
303
+GET /records/forgiving/1?include=date
304
+===
305
+200
306
+Content-Type: application/json
307
+Content-Length: 21
308
+
309
+{"date":"2020-12-20"}
310
+===
311
+PUT /records/forgiving/1
312
+
313
+{"time":"13:15"}
314
+===
315
+200
316
+Content-Type: application/json
317
+Content-Length: 1
318
+
319
+1
320
+===
321
+GET /records/forgiving/1?include=time
322
+===
323
+200
324
+Content-Type: application/json
325
+Content-Length: 19
326
+
327
+{"time":"13:15:00"}
328
+===
329
+PUT /records/forgiving/1
330
+
331
+{"timestamp":"2012-1-1 23:46"}
332
+===
333
+200
334
+Content-Type: application/json
335
+Content-Length: 1
336
+
337
+1
338
+===
339
+GET /records/forgiving/1?include=timestamp
340
+===
341
+200
342
+Content-Type: application/json
343
+Content-Length: 35
344
+
345
+{"timestamp":"2012-01-01 23:46:00"}
346
+===
347
+PUT /records/forgiving/1
348
+
349
+{"clob":"𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢𠻗"}
350
+===
351
+200
352
+Content-Type: application/json
353
+Content-Length: 1
354
+
355
+1
356
+===
357
+PUT /records/forgiving/1
358
+
359
+{"blob":"!"}
360
+===
361
+422
362
+Content-Type: application/json
363
+Content-Length: 101
364
+
365
+{"code":1013,"message":"Input validation failed for 'forgiving'","details":{"blob":"invalid base64"}}
366
+===
367
+PUT /records/forgiving/1
368
+
369
+{"blob":"T8O5IGVzdCBsZSBjYWbDqSBsZSBwbHVzIHByb2NoZT8"}
370
+===
371
+200
372
+Content-Type: application/json
373
+Content-Length: 1
374
+
375
+1
376
+===
377
+DELETE /columns/forgiving
378
+===
379
+200
380
+Content-Type: application/json
381
+Content-Length: 4
382
+
383
+true

Loading…
Cancel
Save