pátek 21. července 2023

Použítí oneOf v YAML + openapi generator

 Definice problému

Mám Spring Boot aplikaci REST JSON služby. Služba má definované API pomocí YAML. Z YAML souboru jsou generovány Java třídy pomocí openapi-generator-maven-plugin verze 6.6.0 nebo novější.

Pokud je v YAML

Addressee:
oneOf:
- $ref: '#/components/schemas/AddresseeBranch'
- $ref: '#/components/schemas/AddresseeHome'

Je při generování vytvořeno rozhraní Addressee a třídy AddresseeBranch a AddresseeHome implementující rozhraní Addressee.
Při volání služby a následném pokusu o deserializaci nastane chyba

Cannot construct instance of `...Akddressee` (no Creators, like default constructor, exist).

Ta je způsobena tím, že deserializer neví na kterou instanci mapovat. Použije proto (nevhodně) samotné rozhraní, které však nelze instanciovat.
Tento problém je na githubu ve stavu otevřený.

Řešení

V některých případech (kdy je jedna z implementací pouhým rozšířením druhé) je možné pomocí SimpleAbstractTypeResolver napevno definovat na kterou z uvedených instancí se mají příchozí data deserializovat. Takové řešení je popsáno zde.

Obecné řešení však spočívá v použití diskriminátoru v YAML definici:

Addressee:
oneOf:
- $ref: '#/components/schemas/AddresseeBranch'
- $ref: '#/components/schemas/AddresseeHome'
discriminator:
propertyName: objectType

Následně je třeba v každém z referencovaných objektů definovat property objectType typu String. Ta by měla mít hodnotu odpovídající názvu reference (tj. názvu implementace rozhraní). Klient tak v requestu vlastně sám uvede, který z možných objektů posílá. objectType zároveň musí být povinná. Příklad pro první z uvedených instancí:

AddresseeBranch:
description: Office
type: object
required:
- objectType
properties:
objectType:
type: string
pattern: '^AddresseeBranch$'

Uvádět pattern není povinné. Je však vhodné ho definovat, aby v případě chybné hodnoty zadané klientem nevznikla chyba během zpracování. Takto bude případná chyba odchycena již při validaci requestu.

Pokud chceme aby hodnota property objectType neodpovídala názvu objektu, je třeba definovat mapování mezi hodnotou a názvem objektu. Podrobněji v sekci Mapping Type Names.