Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/Validator/InputValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public function __construct(array $resolverArgs, ?ValidatorInterface $validator,
{
if (null === $validator) {
throw new ServiceNotFoundException(
"The 'validator' service is not found. To use the 'InputValidator' you need to install the
"The 'validator' service is not found. To use the 'InputValidator' you need to install the
Symfony Validator Component first. See: 'https://symfony.com/doc/current/validation.html'"
);
}
Expand Down Expand Up @@ -128,6 +128,20 @@ public function validate($groups = null, bool $throw = true): ?ConstraintViolati
}
}

private function getMetadata(ValidationNode $rootObject): ObjectMetadata
{
// Return existing metadata if present
if ($this->metadataFactory->hasMetadataFor($rootObject)) {
return $this->metadataFactory->getMetadataFor($rootObject);
}

// Create new metadata and add it to the factory
$metadata = new ObjectMetadata($rootObject);
$this->metadataFactory->addMetadata($metadata);

return $metadata;
}

/**
* Creates a composition of ValidationNode objects from args
* and simultaneously applies to them validation constraints.
Expand All @@ -140,7 +154,7 @@ public function validate($groups = null, bool $throw = true): ?ConstraintViolati
*/
protected function buildValidationTree(ValidationNode $rootObject, array $constraintMapping, array $args): ValidationNode
{
$metadata = new ObjectMetadata($rootObject);
$metadata = $this->getMetadata($rootObject);

$this->applyClassConstraints($metadata, $constraintMapping['class']);

Expand All @@ -162,10 +176,16 @@ protected function buildValidationTree(ValidationNode $rootObject, array $constr
}

$metadata->addPropertyConstraint($property, $valid);

continue;
} else {
$rootObject->$property = $args[$property] ?? null;
}

if ($metadata->hasPropertyMetadata($property)) {
continue;
}

foreach ($params ?? [] as $key => $value) {
if (null === $value) {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ Address:
- Choice:
groups: ['group1']
choices: ['New York', 'Berlin', 'Tokyo']
country:
type: Country
validation: cascade
zipCode:
type: Int!
validation:
- Expression: "service('service_validator').isZipCodeValid(value)"
period:
type: Period!
type: Period
validation: cascade
13 changes: 13 additions & 0 deletions tests/Functional/App/config/validator/mapping/Country.types.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Country:
type: input-object
config:
fields:
name:
type: String
validation:
- NotBlank:
allowNull: true
officialLanguage:
type: String
validation:
- Choice: ['en', 'de', 'fr']
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,11 @@ Mutation:
validation:
cascade:
groups: ['group2']

partialInputObjectsCollectionValidation:
type: Boolean
resolve: "@=mut('mutation_mock', [args])"
args:
addresses:
type: '[Address]'
validation: cascade
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ Period:
config:
fields:
startDate:
type: String!
type: String
validation:
- Date: ~
endDate:
type: String!
type: String
validation:
- Expression: "this.getParent().getName() === 'Address'"
- Date: ~
Expand Down
28 changes: 28 additions & 0 deletions tests/Functional/Validator/ExpectedErrors.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,34 @@ class ExpectedErrors
'path' => ['collectionValidation'],
];

public const PARTIAL_INPUT_OBJECTS_COLLECTION = [
'message' => 'validation',
'locations' => [['line' => 3, 'column' => 17]],
'path' => ['partialInputObjectsCollectionValidation'],
'extensions' => [
'validation' => [
'addresses[0].country.officialLanguage' => [
0 => [
'message' => 'The value you selected is not a valid choice.',
'code' => '8e179f1b-97aa-4560-a02f-2a8b42e49df7',
],
],
'addresses[1].country.name' => [
0 => [
'message' => 'This value should not be blank.',
'code' => 'c1051bb4-d103-4f74-8988-acbcafc7fdc3',
],
],
'addresses[1].period.endDate' => [
0 => [
'message' => 'This value should be greater than "2000-01-01".',
'code' => '778b7ae0-84d3-481a-9dec-35fdb64b1d78',
],
],
],
],
];

public static function simpleValidation($fieldName)
{
return [
Expand Down
64 changes: 56 additions & 8 deletions tests/Functional/Validator/InputValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ public function testCollectionValidationPasses(): void
mutation {
collectionValidation(
addresses: [{
city: "Berlin",
street: "Brettnacher-Str. 14a",
zipCode: 10546,
city: "Berlin",
street: "Brettnacher-Str. 14a",
zipCode: 10546,
period: {
startDate: "2016-01-01",
startDate: "2016-01-01",
endDate: "2019-07-14"
}
}]
Expand All @@ -134,11 +134,11 @@ public function testCollectionValidationFails(): void
mutation {
collectionValidation(
addresses: [{
city: "Moscow",
street: "ul. Lazo",
zipCode: -15,
city: "Moscow",
street: "ul. Lazo",
zipCode: -15,
period: {
startDate: "2020-01-01",
startDate: "2020-01-01",
endDate: "2019-07-14"
}
}]
Expand Down Expand Up @@ -368,4 +368,52 @@ public function testAutoValidationAutoThrowWithGroupsFails(): void
$this->assertSame(ExpectedErrors::cascadeWithGroups('autoValidationAutoThrowWithGroups'), $result['errors'][0]);
$this->assertNull($result['data']['autoValidationAutoThrowWithGroups']);
}

public function testPartialInputObjectsCollectionValidation(): void
{
$query = '
mutation {
partialInputObjectsCollectionValidation(
addresses: [
{
street: "Washington Street"
city: "Berlin"
zipCode: 10000
# Country is present, but the language is invalid
country: {
name: "Germany"
officialLanguage: "ru"
}
# Period is completely missing, skip validation
},
{
street: "Washington Street"
city: "New York"
zipCode: 10000
# Country is partially present
country: {
name: "" # Name should not be blank
# language is missing
}
period: {
startDate: "2000-01-01"
endDate: "1990-01-01"
}
},
{
street: "Washington Street"
city: "New York"
zipCode: 10000
country: {} # Empty input object, skip validation
period: {} # Empty input object, skip validation
}
]
)
}
';

$result = $this->executeGraphQLRequest($query);
$this->assertSame(ExpectedErrors::PARTIAL_INPUT_OBJECTS_COLLECTION, $result['errors'][0]);
$this->assertNull($result['data']['partialInputObjectsCollectionValidation']);
}
}
Loading