Core: Example values not rendered for non-resource objects related to Resource in Swagger UI

Created on 11 Feb 2020  路  10Comments  路  Source: api-platform/core

API Platform version(s) affected: 2.5.4/2.5.x-dev

Description
When Using Objects As Relations Inside Resources which means the object is not a resource, we have indeed the doc generated for those objects but the examples values set in the properties of the Resource are not rendered. We can see string as the value for all the fields of the non-resource object. When configuring the object as a resource then this does not happen.

How to reproduce

# my_resource.yaml

App\Entity\MyResource:
    properties:
        id:
            identifier: true
            attributes:
                openapi_context:
                    type: string
                    format: uuid
                    example: b5b2843dbd9f396d9307197e20999dba
        myNonResource:
            readableLink: true # whatever value I put here, it does not matter
            attributes:
                openapi_context:
                    type: object
                    properties:
                        foo:
                            type: string
                            example: example string
                        bar:
                            type: string
                            format: date-time
                            example: 2020-02-11T12:39:25+00:00
                        baz:
                            type: string
                            format: email
                            example: [email protected]

The resource entity class:

<?php

namespace App\Entity;

/**
 * @ORM\Entity
 */
class MyResource
{
    private $id;

    private $myNonResource;

// getters and setters

}

The non-resource class:

<?php

namespace App\Entity;

/**
 * @ORM\Entity
 */
class MyNonResource
{
    private $foo;

    private $bar;

    private $baz;

// getters and setters

}

then the generated schemas in the Swagger UI documentation below:

id | string($uuid)
      example: b5b2843dbd9f396d9307197e20999dba
      readOnly: true
myNonResource | MyNonResource 
        {
            foo* string
            bar* string
            baz* string
        }

Possible Solution

  • Support the creation of Swagger custom components. It could eventually override the generated schemas for the * non-resource object* in the Swagger UI. Declaring Swagger components is not possible currently, a question about this is already discussed https://stackoverflow.com/questions/52877461/how-to-use-ref-reference-and-declare-components-from-swagger-in-api-platform
  • Fill the example values in the generated schemas for the * non-resource object* with the examples from the properties of the Resource. If I have a resource MyResource related to a non-resource MyNonResource it should be possible to describe the properties of MyResource with examples, including the myNonResource property and then see all the example values displayed in both MyResource and MyNonResource schemas. Also, the schemas generated for the serialization groups

Additional Context

enhancement

Most helpful comment

After discussion on Slack, we've discovered that it's because "$ref": "#/components/schemas/MyNonResource" is used for the myNonResource property, thus overriding the inline definition.

Not a bug, but the user ought to be able to do this in a declarative way, without having to resort to service decoration.

All 10 comments

So, the actual problem is that openapi_context is not taken into account, right?

@teohhanhui well it is, but when it comes to a property which is a related object (here $myNonResource), the description for this property (with the examples) is not taken into account. The same happens when using swagger_context. It uses the default schema generated by the document normalizer for this non-resource object.This does not happen if the property is a resource itself.

@teohhanhui

<?php

namespace ApiPlatform\Core\JsonSchema;

final class TypeFactory implements TypeFactoryInterface
{
    /* ..... */
    /**
     * Gets the JSON Schema document which specifies the data type corresponding to the given PHP class, and recursively adds needed new schema to the current schema if provided.
     */
    private function getClassType(?string $className, string $format = 'json', ?bool $readableLink = null, ?array $serializerContext = null, ?Schema $schema = null): array
    {
        if (null === $className) {
            return ['type' => 'string'];
        }

        if (is_a($className, \DateTimeInterface::class, true)) {
            return [
                'type' => 'string',
                'format' => 'date-time',
            ];
        }
        if (is_a($className, UuidInterface::class, true)) {
            return [
                'type' => 'string',
                'format' => 'uuid',
            ];
        }

        // Skip if $schema is null (filters only support basic types)
        if (null === $schema) {
            return ['type' => 'string'];
        }

        if ($this->isResourceClass($className) && true !== $readableLink) {
            return [
                'type' => 'string',
                'format' => 'iri-reference',
            ];
        }

        $version = $schema->getVersion();

        $subSchema = new Schema($version);
        $subSchema->setDefinitions($schema->getDefinitions()); // Populate definitions of the main schema

        if (null === $this->schemaFactory) {
            throw new \LogicException('The schema factory must be injected by calling the "setSchemaFactory" method.');
        }

        $subSchema = $this->schemaFactory->buildSchema($className, $format, Schema::TYPE_OUTPUT, null, null, $subSchema, $serializerContext);

        return ['$ref' => $subSchema['$ref']];
    }
}

basically, for $myNonResource property, all the if conditions above return false.

Then this code is executed to generate the documentation for the non-resource property:

        $version = $schema->getVersion();

        $subSchema = new Schema($version);
        $subSchema->setDefinitions($schema->getDefinitions()); // Populate definitions of the main schema

        if (null === $this->schemaFactory) {
            throw new \LogicException('The schema factory must be injected by calling the "setSchemaFactory" method.');
        }

        $subSchema = $this->schemaFactory->buildSchema($className, $format, Schema::TYPE_OUTPUT, null, null, $subSchema, $serializerContext);

        return ['$ref' => $subSchema['$ref']];

since the property is not a resource, readableLink will never impact the last if condition

since the property is not a resource, readableLink will never impact the last if condition

And that's correct, because there's no IRI for the embedded object.

And that's correct, because there's no IRI for the embedded object.

Are you suggesting that it should have one? Normally a non-resource does not need to be identified right? Or am I missing something. For sure the documentation references the schemas this way though.

So, it seems to me like the issue is that the openapi_context / swagger_context attribute of the myNonResource property is not being used?

But why? It should have been merged here:

https://github.com/api-platform/core/blob/21145c7669bf2383fb370f7a05a67149ccd21fc3/src/JsonSchema/SchemaFactory.php#L202

Are you suggesting that it should have one? Normally a non-resource does not need to be identified right?

No, it should not. And that's why that part of the code is correct.

@teohhanhui I need to go deeper in the code because I do not have a full understanding of the whole picture, I mean how things are done from A to Z

After discussion on Slack, we've discovered that it's because "$ref": "#/components/schemas/MyNonResource" is used for the myNonResource property, thus overriding the inline definition.

Not a bug, but the user ought to be able to do this in a declarative way, without having to resort to service decoration.

I have exacrlt the same issue. Overriding OpenApi context for simple types (like string, etc.) works as expected, but when I try to override context for a property with a class type declaration, it doesn't work because this code adds $ref to the result definition which has more priority

https://github.com/api-platform/core/blob/21145c7669bf2383fb370f7a05a67149ccd21fc3/src/JsonSchema/SchemaFactory.php#L202

Would excluding $ref be a proper solution when the OpenApi property is overridden by the user?

Was this page helpful?
0 / 5 - 0 ratings