core icon indicating copy to clipboard operation
core copied to clipboard

PropertySchemaCollectionRestriction passes incorrect property metadata when merging constraint restrictions

Open lstrojny opened this issue 3 years ago • 1 comments

API Platform version(s) affected: main

Description
When PropertySchemaCollectionRestriction attempts to generate the restrictions for the sub-graph, it passes incorrect property metadata.

How to reproduce
Given a validation rule like this:

<property name="someArray">
  <constraint name="Collection">
    <option name="fields">
      <value key="someField">
        <constraint name="Optional">
          <constraint name="Length">
             <option name="min">5</option>
          </constraint>
        </constraint>
      </value>
    </option>
  </constraint>
</property>

This would be the expected OpenAPI schema:

"Schema": {
  "type": "object",
  "properties": {
    "someArray": {
      "type": "object",
      "properties": {
        "someField": {
          "minLength": 5
        },
      },
      "additionalProperties": false,
      "items": {
        "type": "string"
      }
    }
  }
}

Instead this is the schema generated:

"Schema": {
  "type": "object",
  "properties": {
    "someArray": {
      "type": "object",
      "properties": {
        "someField": [],
      },
      "additionalProperties": false,
      "items": {
        "type": "string"
      }
    }
  }
}

This is a) invalid because of the empty array but that one is filed with https://github.com/api-platform/core/issues/5413 but b) it misses the length restriction because of this check in PropertySchemaLengthRestriction::supports():

    public function supports(Constraint $constraint, ApiProperty $propertyMetadata): bool
    {
        return $constraint instanceof Length && null !== ($type = $propertyMetadata->getBuiltinTypes()[0] ?? null) && Type::BUILTIN_TYPE_STRING === $type->getBuiltinType();
    }

… in combination with PropertySchemaCollectionRestriction::mergeConstraintRestrictions():

    private function mergeConstraintRestrictions(Required|Optional $constraint, ApiProperty $propertyMetadata): array
    {
        $propertyRestrictions = [];
        $nestedConstraints = $constraint->constraints;

        foreach ($nestedConstraints as $nestedConstraint) {
            foreach ($this->restrictionsMetadata as $restrictionMetadata) {
                if ($restrictionMetadata->supports($nestedConstraint, $propertyMetadata) && !empty($nestedConstraintRestriction = $restrictionMetadata->create($nestedConstraint, $propertyMetadata))) {
                    $propertyRestrictions[] = $nestedConstraintRestriction;
                }
            }
        }

        return array_merge([], ...$propertyRestrictions);
    }

In mergeConstraintRestrictions $propertyMetadata is the property metadata of the container element (the array) and not the sub-element.

Since it’s an array, the sub elements are fundamentally unknowable. My hunch is, that the restriction implementations should not check at all for matching types and just assume that the validation rules are operating on the right type.

lstrojny avatar Feb 23 '23 12:02 lstrojny

:+1: good catch, feel free to propose a patch

soyuka avatar Feb 28 '23 14:02 soyuka