strategy = new class { use ParsesValidationRules; public function parse($validationRules, $customParameterData = []): array { $this->config = new DocumentationConfig([]); $bodyParametersFromValidationRules = $this->getParametersFromValidationRules($validationRules, $customParameterData); return $this->normaliseArrayAndObjectParameters($bodyParametersFromValidationRules); } }; } /** * @test * @dataProvider supportedRules */ public function can_parse_supported_rules(array $ruleset, array $customInfo, array $expected) { $results = $this->strategy->parse($ruleset, $customInfo); $parameterName = array_keys($ruleset)[0]; $this->assertEquals($expected['description'], $results[$parameterName]['description']); if (isset($expected['type'])) { $this->assertEquals($expected['type'], $results[$parameterName]['type']); } // Validate that the generated values actually pass validation $exampleData = [$parameterName => $results[$parameterName]['example']]; $validator = Validator::make($exampleData, $ruleset); try { $validator->validate(); } catch (ValidationException $e) { dump('Value: ', $exampleData[$parameterName]); dump($e->errors()); $this->fail("Generated example data from validation rule failed to match actual."); } } /** @test */ public function can_transform_arrays_and_objects() { $ruleset = [ 'array_param' => 'array|required', 'array_param.*' => 'string', ]; $results = $this->strategy->parse($ruleset); $this->assertCount(1, $results); $this->assertEquals('string[]', $results['array_param']['type']); $ruleset = [ 'object_param' => 'array|required', 'object_param.field1.*' => 'string', 'object_param.field2' => 'integer|required', ]; $results = $this->strategy->parse($ruleset); $this->assertCount(3, $results); $this->assertEquals('object', $results['object_param']['type']); $this->assertEquals('string[]', $results['object_param.field1']['type']); $this->assertEquals('integer', $results['object_param.field2']['type']); $ruleset = [ 'array_of_objects_with_array.*.another.*.one.field1.*' => 'string|required', 'array_of_objects_with_array.*.another.*.one.field2' => 'integer', 'array_of_objects_with_array.*.another.*.two.field2' => 'numeric', ]; $results = $this->strategy->parse($ruleset); $this->assertCount(7, $results); $this->assertEquals('object[]', $results['array_of_objects_with_array']['type']); $this->assertEquals('object[]', $results['array_of_objects_with_array[].another']['type']); $this->assertEquals('object', $results['array_of_objects_with_array[].another[].one']['type']); $this->assertEquals('object', $results['array_of_objects_with_array[].another[].two']['type']); $this->assertEquals('string[]', $results['array_of_objects_with_array[].another[].one.field1']['type']); $this->assertEquals('integer', $results['array_of_objects_with_array[].another[].one.field2']['type']); $this->assertEquals('number', $results['array_of_objects_with_array[].another[].two.field2']['type']); } public function supportedRules() { $description = 'A description'; // Key is just an identifier // First array in each key is the validation ruleset, // Second is custom information (from bodyParameters() or comments) // Third is expected result yield 'string' => [ ['string_param' => 'string'], ['string_param' => ['description' => $description]], [ 'type' => 'string', 'description' => $description . ".", ], ]; yield 'boolean' => [ ['boolean_param' => 'boolean'], [], [ 'type' => 'boolean', 'description' => "", ], ]; yield 'integer' => [ ['integer_param' => 'integer'], [], [ 'type' => 'integer', 'description' => "", ], ]; yield 'numeric' => [ ['numeric_param' => 'numeric'], ['numeric_param' => ['description' => $description]], [ 'type' => 'number', 'description' => $description . ".", ], ]; yield 'array' => [ ['array_param' => 'array'], [], [ 'type' => 'string[]', 'description' => '', ], ]; yield 'file' => [ ['file_param' => 'file|required'], ['file_param' => ['description' => $description]], [ 'description' => "$description. Must be a file.", 'type' => 'file', ], ]; yield 'timezone' => [ ['timezone_param' => 'timezone|required'], [], [ 'description' => 'Must be a valid time zone, such as Africa/Accra.', 'type' => 'string', ], ]; yield 'email' => [ ['email_param' => 'email|required'], [], [ 'description' => 'Must be a valid email address.', 'type' => 'string', ], ]; yield 'url' => [ ['url_param' => 'url|required'], ['url_param' => ['description' => $description]], [ 'description' => "$description. Must be a valid URL.", 'type' => 'string', ], ]; yield 'ip' => [ ['ip_param' => 'ip|required'], ['ip_param' => ['description' => $description]], [ 'description' => "$description. Must be a valid IP address.", 'type' => 'string', ], ]; yield 'json' => [ ['json_param' => 'json|required'], ['json_param' => []], [ 'description' => 'Must be a valid JSON string.', 'type' => 'string', ], ]; yield 'date' => [ ['date_param' => 'date|required'], [], [ 'description' => 'Must be a valid date.', 'type' => 'string', ], ]; yield 'date_format' => [ ['date_format_param' => 'date_format:Y-m-d|required'], ['date_format_param' => ['description' => $description]], [ 'description' => "$description. Must be a valid date in the format Y-m-d.", 'type' => 'string', ], ]; yield 'in' => [ ['in_param' => 'in:3,5,6'], ['in_param' => ['description' => $description]], [ 'description' => "$description. Must be one of 3, 5, or 6.", 'type' => 'string', ], ]; yield 'not_in' => [ ['not_param' => 'not_in:3,5,6'], [], [ 'description' => "Must not be one of 3, 5, or 6.", ], ]; yield 'digits' => [ ['digits_param' => 'digits:8'], [], [ 'description' => "Must be 8 digits.", 'type' => 'string', ], ]; yield 'digits_between' => [ ['digits_between_param' => 'digits_between:2,8'], [], [ 'description' => "Must be between 2 and 8 digits.", 'type' => 'string', ], ]; yield 'alpha' => [ ['alpha_param' => 'alpha'], [], [ 'description' => "Must contain only letters.", 'type' => 'string', ], ]; yield 'alpha_dash' => [ ['alpha_dash_param' => 'alpha_dash'], [], [ 'description' => "Must contain only letters, numbers, dashes and underscores.", 'type' => 'string', ], ]; yield 'alpha_num' => [ ['alpha_num_param' => 'alpha_num'], [], [ 'description' => "Must contain only letters and numbers.", 'type' => 'string', ], ]; yield 'ends_with' => [ ['ends_with_param' => 'ends_with:go,ha'], [], [ 'description' => "Must end with one of go or ha.", 'type' => 'string', ], ]; yield 'starts_with' => [ ['starts_with_param' => 'starts_with:go,ha'], [], [ 'description' => "Must start with one of go or ha.", 'type' => 'string', ], ]; yield 'uuid' => [ ['uuid_param' => 'uuid'], [], [ 'description' => "Must be a valid UUID.", 'type' => 'string', ], ]; yield 'required_if' => [ ['required_if_param' => 'required_if:another_field,a_value'], [], ['description' => "This field is required when another_field is a_value."], ]; yield 'required_unless' => [ ['required_unless_param' => 'string|required_unless:another_field,a_value'], [], ['description' => "This field is required unless another_field is in a_value."], ]; yield 'required_with' => [ ['required_with_param' => 'required_with:another_field,some_other_field'], [], ['description' => 'This field is required when another_field or some_other_field is present.'], ]; yield 'required_with_all' => [ ['required_with_all_param' => 'required_with_all:another_field,some_other_field'], [], ['description' => 'This field is required when another_field and some_other_field are present.'], ]; yield 'required_without' => [ ['required_without_param' => 'string|required_without:another_field,some_other_field'], [], ['description' => 'This field is required when another_field or some_other_field is not present.'], ]; yield 'required_without_all' => [ ['required_without_all_param' => 'string|required_without_all:another_field,some_other_field'], [], ['description' => 'This field is required when none of another_field and some_other_field are present.'], ]; yield 'same' => [ ['same_param' => 'same:other_field'], [], ['description' => "The value and other_field must match."], ]; if (version_compare(Application::VERSION, '7.0.0', '>=')) { yield 'different' => [ ['different_param' => 'string|different:other_field'], [], ['description' => "The value and other_field must be different."], ]; } yield 'after' => [ ['after_param' => 'after:2020-02-12'], [], ['description' => "Must be a date after 2020-02-12."], ]; yield 'before_or_equal' => [ ['before_or_equal_param' => 'before_or_equal:2020-02-12'], [], ['description' => "Must be a date before or equal to 2020-02-12."], ]; yield 'size (number)' => [ ['size_param' => 'numeric|size:6'], [], ['description' => "Must be 6."], ]; yield 'size (string)' => [ ['size_param' => 'string|size:6'], [], ['description' => "Must be 6 characters."], ]; yield 'size (file)' => [ ['size_param' => 'file|size:6'], [], ['description' => "Must be a file. Must be 6 kilobytes."], ]; yield 'max (number)' => [ ['max_param' => 'numeric|max:6'], [], ['description' => "Must not be greater than 6."], ]; yield 'max (string)' => [ ['max_param' => 'string|max:6'], [], ['description' => "Must not be greater than 6 characters."], ]; yield 'max (file)' => [ ['max_param' => 'file|max:6'], [], ['description' => "Must be a file. Must not be greater than 6 kilobytes."], ]; } }