core
core copied to clipboard
[OpenAPI] non-nullable properties is possibly undefined in typescript (missing required field?)
API Platform version(s) affected: 2.6.8
Description
OpenAPI schema definition is missing the required
field, causing all types generated to be possible undefined:
This is an one of the objects generated, with a few nullable types:
"Indicator": {
"type": "object",
"description": "",
"properties": {
"id": {
"readOnly": true,
"type": "integer"
},
"order": {
"type": "integer"
},
"group": {
"type": "string",
"format": "iri-reference",
"nullable": true
},
"title": {
"type": "string"
},
"remoteId": {
"readOnly": true,
"type": "string",
"nullable": true
},
"createdAt": {
"readOnly": true,
"type": "string",
"format": "date-time"
},
"disabled": {
"readOnly": true,
"type": "boolean"
},
"version": {
"readOnly": true,
"type": "integer"
},
"optional": {
"type": "boolean"
},
"description": {
"type": "string"
},
"answers": {
"writeOnly": true,
"type": "array",
"items": {
"$ref": "#/components/schemas/AbstractAnswer"
}
},
"dependencies": {
"type": "array",
"items": {
"type": "string",
"format": "iri-reference"
}
},
"relianceOn": {
"readOnly": true,
"type": "array",
"items": {
"type": "string",
"format": "iri-reference"
}
},
"benchmarks": {
"writeOnly": true,
"type": "array",
"items": {
"$ref": "#/components/schemas/Benchmark"
}
},
"form": {
"readOnly": true,
"type": "string",
"format": "iri-reference",
"nullable": true
},
"public": {
"type": "boolean"
},
"copyFunction": {
"type": "boolean"
},
"dependencyMethods": {
"readOnly": true,
"type": "array",
"items": {
"type": "string"
}
},
"dependency": {
"writeOnly": true,
"$ref": "#/components/schemas/Indicator"
},
"subType": {
"readOnly": true,
"type": "string"
}
}
},
Notice that only group
, form
and remoteId
is nullable.
openapi-typescript
sets all fields as possible undefined, with the proper optional null types:
interface Indicator {
id?: number;
order?: number;
/** Format: iri-reference */
group?: string | null;
title?: string;
remoteId?: string | null;
/** Format: date-time */
createdAt?: string;
disabled?: boolean;
version?: number;
optional?: boolean;
description?: string;
answers?: components["schemas"]["AbstractAnswer"][];
dependencies?: string[];
relianceOn?: string[];
benchmarks?: components["schemas"]["Benchmark"][];
/** Format: iri-reference */
form?: string | null;
public?: boolean;
copyFunction?: boolean;
dependencyMethods?: string[];
dependency?: components["schemas"]["Indicator"];
subType?: string;
};
How to reproduce
Inspect the output from this command: npx openapi-typescript http://localhost:8000/api/docs.json?spec_version=3 --output assets/Generated/api.ts
Additional Context
OpenAPI's specification includes the required
field in the examples:
(ref. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#model-with-example)
in: query
name: coordinates
content:
application/json:
schema:
type: object
required:
- lat
- long
properties:
lat:
type: number
long:
type: number
Also a different related discussion about the problem: https://github.com/OpenAPITools/openapi-generator/issues/7565
I wrote a OpenApiFactory to modify the type, that "works", but I dont know enough about OpenAPI / API-Platform to verify it, or if it will have any unforseen side-effects...
class OpenApiFactory implements OpenApiFactoryInterface
{
public function __construct(
private OpenApiFactoryInterface $decorated
) {
}
public function __invoke(array $context = []): OpenApi
{
$api = $this->decorated->__invoke($context);
/** @var \ArrayObject[] $schemas */
$schemas = $api->getComponents()->getSchemas();
foreach ($schemas as $schema) {
$properties = $schema['properties'] ?? [];
$required = $schema['required'] ?? [];
foreach ($properties as $field => $property) {
$type = $property['type'] ?? false;
$nullable = $property['nullable'] ?? false;
if ($type && !$nullable && !in_array($field, $required)) {
$required[] = $field;
}
}
$schema['required'] = $required;
}
return $api;
}
}
This is the end result (generated typescript types):
interface Indicator: {
id: number;
order: number;
/** Format: iri-reference */
group?: string | null;
title: string;
remoteId?: string | null;
/** Format: date-time */
createdAt: string;
disabled: boolean;
version: number;
optional: boolean;
description: string;
answers: components["schemas"]["AbstractAnswer"][];
dependencies: string[];
relianceOn: string[];
benchmarks: components["schemas"]["Benchmark"][];
/** Format: iri-reference */
form?: string | null;
public: boolean;
copyFunction: boolean;
dependencyMethods: string[];
dependency?: components["schemas"]["Indicator"];
subType: string;
};
Thank you for the report, according to the JSON schema specification required must always be an array
The value of this keyword MUST be an array. This array MUST have at least one element. Elements of this array MUST be strings, and MUST be unique. An object instance is valid against this keyword if its property set contains all elements in this keyword's array value.
This needs to be fixed in the SchemaFactory.
Any progress on this? I have to add exclamation mark to access any properties :(
@Pearseak checked your profile, do you also have back-end in ASP.NET? If that is true, you can consider using https://github.com/reinforced/Reinforced.Typings. I used it in my last project, and in worked well, though I still hope someone fix this issue, becuase its crutial for my https://github.com/MadL1me/aspnet-awesome-templates repo
So, what's the deal here? Everything can be undefined, why is it so?
This bug is targeting 2.7, as API Platform 3.2 is out, version 2.7 has reached end of life. Therefore we'll close this issue.
We recommend to upgrade to API Platform 3.0, Les-Tilleuls.coop can offer paid support to help or even migrate your projects if they have tests.
We want to fund a Long Term Stable version of API Platform, if you or your organization would like to contribute to LTS support, please visit our Open Collective crowdfunding.
So we investigated and null
and required
are two different things, also detecting this would be a mistake from our perspective. You can use #[ApiProperty(required: true)]
if you want.
I am encountering the same issue where all read operations from the API define all fields as possibly undefined, e.g., name?: string
. When using a TypeScript generator, in my case, the official one from https://openapistack.co/docs/openapicmd/typegen/, I always have to check whether the property exists. While the issue can be addressed by using the #[ApiProperty(required: true)]
attribute, it becomes very tedious to check each of the thousands of possible fields individually.
I believe a good compromise would be to declare properties as required by default if the field is not nullable in the database. For example:
#[ORM\Table]
class Address
{
#[ORM\Column]
private string $name;
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(referencedColumnName: 'uuid', nullable: false)]
private User $user;
}
In this case, we can be quite certain that both properties will be present in the response, as long as the normalization group is marked. Whereas if the fields are optional for the database, they could continue to appear as possibly undefined.
#[ORM\Table]
class Address
{
#[ORM\Column(nullable: true)]
private null|string $name = null;
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(referencedColumnName: 'uuid', nullable: true)]
private null|User $user = null;
}
This approach would significantly reduce the manual checking required.