belgif / rest-guide

REST Guidelines of Belgian government institutions
https://www.belgif.be/specification/rest/api-guide/
Apache License 2.0
24 stars 4 forks source link

Extension of schemas and additionalProperties #153

Open pvdbosch opened 9 months ago

pvdbosch commented 9 months ago

OpenAPI (JSON Schema) allows adding undefined additional properties in objects.

For code generation, openapi-generator's default configuration will only deserialize unknown properties if additionalProperties is explicitly specified as true (i.e. generate @JsonAnyGetter/@JsonAnySetter methods). This behavior can be changed by configuring disallowAdditionalPropertiesIfNotPresent: false. Deserializing unknown/unexpected input by default could pose some security risk however (e.g. passing a bunch of undetected JSON properties along to backend services, store them in database, ...).

For requests, we now require to explicitly document when additional properties can being treated by the API (https://github.com/belgif/rest-guide/issues/110), though not explicitly how this should be documented. We could require to explicitly additionalProperties: true where they are expected.

This leaves a gray area however for schemas that are expected to be extended ("subclassed"), like Problem or LocalizedString. cfr https://github.com/belgif/openapi-common/issues/7 : how should they be documented, and how can polymorphism be implemented?

How to best document extensions of types

When the occurrence and types of the properties are known at time the API is being developed, I believe it would be best to always document this in OpenAPI, rather than requiring an out-of-band documentation that risks being out of date and erroneous because its not integrated in any tooling. It also avoid the problem of property redefinition. This is when an API defines a field to be of a different type that was already being emitted, or, is changing the type of an undefined field.

When the properties are of dynamic nature, unknown at development time (e.g. dynamically retrieved from a DB), additionalProperties seems a good fit. Dynamic metadata may be provided by the API in such a case e.g. GET /refData/supportedLanguages in case of localization. Restrictions on the additional properties may be useful in such a case (e.g. schema for the values, patternProperties or OAS 3.1's propertyNames).

I think most types in openapi-common (like HttpLink) fall under the "design time" cases, except for LocalizedString where it depends on the API if the supported languages are dynamic or determined design time.

How to implement open-world polymorphism

OpenAPI code generators currently only have good support for closed-world polymorphism through the use of discriminator: the list of known subtypes has to be exhaustively listed on the parent type in mapping . Without mapping, the discriminator property has to specify the exact name of the schema which would be very limiting e.g. for problem type URNs.

For serialization, a subtype (in the code) can still be used without a discriminator to add all properties in a typed way, but for deserialization it's not possible to cast a value down to a subtype by default.

Also, subtypes that aren't explicitly referenced in OpenAPI, are sometimes not generated by the openapi-generator (I believe when they're referenced in an external file).

Possible workarounds:

In addition for error responses for Java clients, I believe they're not serialized to their types specified in OpenAPI out-of-the-box, even if it specifies a more specific type (TBC).