Repository: hotmeteor/spectator (php)

Issue:
URL Parameter typed as number will cause Error
I'm using [apib2swagger](https://github.com/kminami/apib2swagger). 
API definition in apib is like this.
```md
## Projects [GET /api/v1/user/projects{?page}{?per_page}]

+ Parameters
    + page: 1 (number, optional)
    + `per_page`: 10 (number, optional)

+ Response 200 (application/json)
    + Attributes (ProjectListResponse)
+ Response 401 (application/json)
```

generated openapi's schema is like this.
(pasted only parameter part)
```json
"parameters": [
    {
        "name": "page",
        "in": "query",
        "description": "",
        "example": "1",
        "schema": {
            "type": "number"
        }
    },
    {
        "name": "per_page",
        "in": "query",
        "description": "",
        "example": "10",
        "schema": {
            "type": "number"
        }
    }
]
```

and test API like

```php
$response = $this->getJson('/api/v1/user/projects?page=1&per_page=10')
    ->dump()
    ->assertStatus(200)
    ->assertValidRequest()
    ->assertValidResponse();
```

this will cause Error. Dumped result is

```
^ {#2009
  +"exception": "ValueError"
  +"message": "settype(): Argument #2 ($type) must be a valid type"
}
```

This is because of code
https://github.com/hotmeteor/spectator/blob/master/src/Validation/RequestValidator.php#L113-L136

```php
                // Get parameter, then validate it.
                if ($parameter->in === 'path' && $route->hasParameter($parameter->name)) {
                    $parameter_value = $route->parameters()[$parameter->name];
                } elseif ($parameter->in === 'query' && $this->request->query->has($parameter->name)) {
                    $parameter_value = $this->request->query->get($parameter->name);
                } elseif ($parameter->in === 'header' && $this->request->headers->has($parameter->name)) {
                    $parameter_value = $this->request->headers->get($parameter->name);
                } elseif ($parameter->in === 'cookie' && $this->request->cookies->has($parameter->name)) {
                    $parameter_value = $this->request->cookies->get($parameter->name);
                }

                if ($parameter_value) {
                    if ($expected_parameter_schema->type && gettype($parameter_value) !== $expected_parameter_schema->type) {
                        settype($parameter_value, $expected_parameter_schema->type);
                    }

                    $result = $validator->validate($parameter_value, $expected_parameter_schema);

                    // If the result is not valid, then display failure reason.
                    if ($result->isValid() === false) {
                        $message = RequestValidationException::validationErrorMessage($expected_parameter_schema, $result->error());
                        throw RequestValidationException::withError($message, $result->error());
                    }
                }
```

number is not valid argument for `settype`.

https://www.php.net/manual/ja/function.settype.php

> settype(mixed &$var, string $type): bool
> type
>     Possibles values of type are:
>         "boolean" or "bool"
>         "integer" or "int"
>         "float" or "double"
>         "string"
>         "array"
>         "object"
>         "null"


So, If I specify `number` in parameter, it always fail.

```php
                    if ($expected_parameter_schema->type && gettype($parameter_value) !== $expected_parameter_schema->type) {
                        $conversion_type = $expected_parameter_schema->type;
                        if($expected_parameter_schema->type == "number"){
                            if(preg_match('/[0-9]+)/', $parameter_value)){
                                $conversion_type = 'int';
                            }else if(preg_match('/[0-9]+\.[0-9]+)/', $parameter_value)){
                                $conversion_type = 'float';
                            }
                        }
                        settype($parameter_value, $conversion_type);
                    }
```

When I placed some conversion before calling `settype` will fix the problem.

Open API 3.0.3 supports `number` as floating point value (api-blueprint only supports `number`)
https://swagger.io/specification/#data-types
(Currently I just replaced `number` to `string` in definition)



Pull request description:
#120 Fixes handling 'number' type
Adds support for `number` query parameter type in request.

Resolves #120 

Patch:
diff --git a/src/Validation/RequestValidator.php b/src/Validation/RequestValidator.php
index 8d1d3ba..45e6272 100644
--- a/src/Validation/RequestValidator.php
+++ b/src/Validation/RequestValidator.php
@@ -127,7 +127,13 @@ class RequestValidator extends AbstractValidator
 
                 if ($parameter_value) {
                     if ($expected_parameter_schema->type && gettype($parameter_value) !== $expected_parameter_schema->type) {
-                        settype($parameter_value, $expected_parameter_schema->type);
+                        $expected_type = $expected_parameter_schema->type;
+
+                        if ($expected_type === 'number') {
+                            $expected_type = is_float($parameter_value) ? 'float' : 'int';
+                        }
+
+                        settype($parameter_value, $expected_type);
                     }
 
                     $result = $validator->validate($parameter_value, $expected_parameter_schema);


Test patch:
diff --git a/tests/Fixtures/Numbers.v1.json b/tests/Fixtures/Numbers.v1.json
new file mode 100644
index 0000000..aceefe2
--- /dev/null
+++ b/tests/Fixtures/Numbers.v1.json
@@ -0,0 +1,151 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Numbers.v1",
+    "version": "1.0"
+  },
+  "servers": [
+    {
+      "url": "http://localhost:3000"
+    }
+  ],
+  "paths": {
+    "/users": {
+      "get": {
+        "summary": "Get users",
+        "tags": [],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "array",
+                  "items": {
+                    "type": "object",
+                    "properties": {
+                      "id": {
+                        "type": "number",
+                        "description": "User ID",
+                        "example": 1
+                      },
+                      "name": {
+                        "type": "string",
+                        "description": "User name",
+                        "example": "Adam Campbell"
+                      },
+                      "email": {
+                        "type": "string",
+                        "description": "User email address",
+                        "format": "email",
+                        "example": "test@test.com"
+                      }
+                    }
+                  }
+                },
+                "examples": {
+                  "example-1": {
+                    "value": [
+                      {
+                        "id": 1,
+                        "name": "Adam Campbell",
+                        "email": "test@test.com"
+                      }
+                    ]
+                  }
+                }
+              }
+            }
+          },
+          "422": {
+            "description": "Unprocessable entity",
+            "content": {
+              "application/problem+json": {
+                "schema": {
+                  "type": "array",
+                  "items": {
+                    "type": "object",
+                    "properties": {
+                      "id": {
+                        "type": "number",
+                        "description": "User ID",
+                        "example": 1
+                      },
+                      "name": {
+                        "type": "string",
+                        "description": "User name",
+                        "example": "Adam Campbell"
+                      },
+                      "email": {
+                        "type": "string",
+                        "description": "User email address",
+                        "format": "email",
+                        "example": "test@test.com"
+                      }
+                    }
+                  }
+                },
+                "examples": {
+                  "example-1": {
+                    "value": [
+                      {
+                        "id": 1,
+                        "name": "Adam Campbell",
+                        "email": "test@test.com"
+                      }
+                    ]
+                  }
+                }
+              }
+            }
+          }
+        },
+        "operationId": "get-users",
+        "parameters": [
+          {
+            "name": "page",
+            "in": "query",
+            "description": "",
+            "example": 1,
+            "schema": {
+              "type": "number"
+            }
+          },
+          {
+            "name": "per_page",
+            "in": "query",
+            "description": "",
+            "example": 10,
+            "schema": {
+              "type": "integer"
+            }
+          },
+          {
+            "name": "float_param",
+            "in": "query",
+            "description": "",
+            "example": "4.54",
+            "schema": {
+              "type": "number"
+            }
+          },
+          {
+            "in": "query",
+            "name": "order",
+            "schema": {
+              "type": "string",
+              "enum": [
+                "name",
+                "email"
+              ]
+            },
+            "allowEmptyValue": true
+          }
+        ]
+      }
+    }
+  },
+  "components": {
+    "schemas": {}
+  }
+}
diff --git a/tests/RequestValidatorTest.php b/tests/RequestValidatorTest.php
index 7916c1e..ea87e56 100644
--- a/tests/RequestValidatorTest.php
+++ b/tests/RequestValidatorTest.php
@@ -616,6 +616,27 @@ class RequestValidatorTest extends TestCase
         }
     }
 
+    public function test_numeric_values()
+    {
+        Spectator::using('Numbers.v1.json');
+
+        Route::get('/users', function () {
+            return [
+                [
+                    'id' => 1,
+                    'name' => 'Jim',
+                    'email' => 'test@test.test',
+                ],
+            ];
+        })
+            ->middleware(Middleware::class);
+
+        $this->getJson('/users?page=1&per_page=10&float_param=3.14')
+            ->assertStatus(200)
+            ->assertValidRequest()
+            ->assertValidResponse();
+    }
+
     public function nullableObjectProvider(): array
     {
         return [