diff --git a/docs/api-sdk/COMPOSITION_INHERITANCE.md b/docs/api-sdk/COMPOSITION_INHERITANCE.md new file mode 100644 index 0000000000..13e0156fdb --- /dev/null +++ b/docs/api-sdk/COMPOSITION_INHERITANCE.md @@ -0,0 +1,214 @@ +# Composition and Inheritance +This page refers to the [OpenApi Inheritance feature](https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/) +and the [allOf, oneOf schemes](https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/). + +AllOf schema are supported by the generator and will be treated as inheritance or composition depending on the presence of a discriminator property. + +## Composition +An "allOf" schema without a discriminator will be considered as a composition of other models without any hierarchy link. +This means all the properties of each model will be copied into the model without any reference to the models it is +composed of. + +For example, the following model +```yaml +components: + schemas: + BasicErrorModel: + type: object + required: + - message + - code + properties: + message: + type: string + code: + type: integer + ExtendedErrorModel: + allOf: # Combines the BasicErrorModel and the inline model + - $ref: '#/components/schemas/BasicErrorModel' + - type: object + required: + - rootCause + properties: + rootCause: + type: string +``` +will generate two models: +- BasicErrorModel +```typescript +export interface BasicErrorModel { + message: string; + code: number; +} +``` +- ExtendedErrorModel +```typescript +export interface ExtendedErrorModel { + message: string; + code: number; + rootCause: string; +} +``` + +## Inheritance: + +The addition of a discriminator allows a hierarchy between the models and the possibility to identify which child class +a model can be casted into. +For example, let's consider a Pet object and its two class child Cat and Dog: +```yaml +components: + schemas: + Pet: + type: object + required: + - pet_type + properties: + pet_type: + type: string + discriminator: + propertyName: petType + Dog: # "Dog" is a value for the pet_type property (the discriminator value) + allOf: # Combines the main `Pet` schema with `Dog`-specific properties + - $ref: '#/components/schemas/Pet' + - type: object + # all other properties specific to a `Dog` + properties: + bark: + type: boolean + breed: + type: string + enum: [Dingo, Husky, Retriever, Shepherd] + Cat: # "Cat" is a value for the pet_type property (the discriminator value) + allOf: # Combines the main `Pet` schema with `Cat`-specific properties + - $ref: '#/components/schemas/Pet' + - type: object + # all other properties specific to a `Cat` + properties: + hunts: + type: boolean + age: + type: integer +``` + +A Pet can be cast into a Cat or a Dog depending the petType value. In this case, there will be a hierarchy between Pet +and Cat/Dog: + +```typescript +export interface Pet { + petType: PetTypeEnum; +} + +export interface Cat extends Pet { + hunts: boolean; + age: number; +} + +export interface Dog extends Pet { + bark: boolean; + bread: BreedEnum; +} + +export type BreedEnum = 'Dingo' | 'Husky' | 'Retriever' | 'Sheperd'; +export type PetTypeEnum = 'Cat' | 'Dog'; +``` + +This will mainly impact the revival of the Pet model. +For the Cat and Dog models, this is pretty straightforward as we know each of these models. +It is a bit more tricky to revive the parent model. + +In this case, the SDK will try to rely on the discriminator's value and try to map one of the child models. If it fails, +it will try to revive its own properties. +```typescript +export function revivePet(data: any): undefined | T | Cat | Dog { + if (!data) {return;} + if (data.petType) { + if (data.petType[0].toUpperCase() + data.petType.slice(1) === 'Cat') { + return reviveCat(data); + } + if (data.petType[0].toUpperCase() + data.petType.slice(1) === 'Dog') { + return reviveDog(data) + } + } + return data as T; +} +``` + +> **Note**: The discriminator needs to be of enum type as the string format would be too generic to map the accepted +> value to the supported models. + +## Union Type +The oneOf schema is also supported and is handled as an union type. + +Instead of considering a generic Pet model and its two children Cat and Dog, let's consider instead that a Pet can be +either a Cat or a Dog. + +This time, the specification will be as follows: + +```yaml +components: + schemas: + Pet: + oneOf: + - $ref: '#/components/schemas/Cat' + - $ref: '#/components/schemas/Dog' + discriminator: + propertyName: petType + + Dog: # "Dog" is a value for the pet_type property (the discriminator value) + - type: object + required: + - petType + properties: + petType: + type: string + enum: [Cat, Dog] + # all other properties specific to a `Dog` + bark: + type: boolean + breed: + type: string + enum: [Dingo, Husky, Retriever, Shepherd] + Cat: # "Cat" is a value for the pet_type property (the discriminator value) + - type: object + required: + - petType + properties: + petType: + type: string + enum: [Cat, Dog] + # all other properties specific to a `Cat` + properties: + hunts: + type: boolean + age: + type: integer +``` + +For this use case, the relation between Pet, Cat and Dog will be a union. + +Hence the generated code will be as follows: +```typescript +export interface Cat { + petType: PetTypeEnum; + hunts: boolean; + age: number; +} + +export interface Dog { + petType: PetTypeEnum; + bark: boolean; + bread: BreedEnum; +} + +export type BreedEnum = 'Dingo' | 'Husky' | 'Retriever' | 'Sheperd'; + +export type PetTypeEnum = 'Cat' | 'Dog'; +export type Pet = Cat | Dog; +``` +The revival of the Pet is handled the same way as the problem is the same: a discriminator is needed to ensure which +model to revive. + +>**Note** Just as the inheritance, for a proper revival of the object, the discriminator shall be an enum. + +### Non supported schema type +As of today, the Typescript generator does not support the anyOf and the not schema. diff --git a/docs/sdk-tools/SDK_MODELS_HIERARCHY.md b/docs/api-sdk/SDK_MODELS_HIERARCHY.md similarity index 100% rename from docs/sdk-tools/SDK_MODELS_HIERARCHY.md rename to docs/api-sdk/SDK_MODELS_HIERARCHY.md diff --git a/packages/@ama-sdk/schematics/README.md b/packages/@ama-sdk/schematics/README.md index 3cc50c1d7e..929627a61d 100644 --- a/packages/@ama-sdk/schematics/README.md +++ b/packages/@ama-sdk/schematics/README.md @@ -1,26 +1,43 @@ # SDK Generator -This package provides `schematics` generators to create an SDK based on an API swagger spec. +This package provides `schematics` generators to create an SDK based on an OpenAPI specifications. +There are two SDK generators in the Otter Framework: Typescript and Java. The Java generator is currently in maintenance +mode and only the Typescript generator is actively supported and will see future evolutions. +- [Typescript SDK](#typescript-sdk) +- [Java SDK](#java-client-core-sdk) -## Setup +## Typescript SDK -### Create a new repository +The Typescript SDK generator is a custom template for the OpenAPITools generator with a full integration of the +[@ama-sdk/core](https://www.npmjs.com/package/@ama-sdk/core) client capabilities. -Generate a new single SDK repository +It supports both Swagger 2+ and Open API 3 specifications. +- [Setup](#setup) +- [How to use](#how-to-use) +- [Debug](#debug) +- [Going further](#going-further) + +### Setup + +#### Create a new repository + +Generate a new single SDK repository ```shell npm create @ama-sdk typescript -- [--spec-path=./path/to/spec.yaml] ``` -or - +or ```shell yarn create @ama-sdk typescript [--spec-path=./path/to/spec.yaml] ``` +> **Warning** : Please notice that the command `yarn create` is **not** available for versions *>= 2.0.0* +> (see [Yarn cli commands](https://yarnpkg.com/cli)). + > **Note**: Get more information on the [@ama-sdk/create package](https://www.npmjs.com/package/@ama-sdk/create). -### Create a new Otter workspace package +#### Create a new Otter workspace package The Angular schematics package is required to use these generators: @@ -38,9 +55,7 @@ npx -p @angular/cli ng add @ama-sdk/schematics npx -p @angular/cli ng add @ama-sdk/core ``` -## How to use? - -### Typescript SDK +### How to use? The typescript generator provides 2 generators: @@ -64,19 +79,7 @@ If you use `Yarn2+`, you can use the following `scripts` in `package.json`: Use `generate` to (re)generate your SDK based on the content of `./swagger-spec.yaml` (make sure you have this file at the root of your project) and `upgrade:repository` to regenerate the structure of your project. -### Java Client Core SDK - -Generate a Java Client Core SDK: - -Make sure to have a `./swagger-spec.yaml` file at the root of your project and run: - -```shell -yarn schematics @ama-sdk/schematics:java-client-core --spec-path ./swagger-spec.yaml --swagger-config-path ./swagger-codegen-config.json -``` - -[Default swagger config](schematics/java/client-core/swagger-codegen-java-client/config/swagger-codegen-config.json) will be used if `--swagger-config-path` is not provided. - -### Debug the typescript generator +### Debug The OpenApi generator extracts an enhanced JSON data model from the specification YAML and uses this data model to feed the templates to generate the code. If there is an issue with the files generated with the spec provided, the generator provides debugging features that log this data model. @@ -91,3 +94,23 @@ yarn schematics @ama-sdk/schematics:typescript-core --spec-path ./swagger-spec.y You can also use npx instead of yarn in the command. You can correlate this data model with the [templates](https://github.com/AmadeusITGroup/otter/tree/main/packages/%40ama-sdk/schematics/schematics/typescript/core/openapi-codegen-typescript/src/main/resources/typescriptFetch) used by the generator. + +### Going further +For more information on the sdk generated and how the framework supports different feature such as the Composition, you +can refer to the sdk dedicated documentation: +- [Generated SDK hierarchy and extension](https://github.com/AmadeusITGroup/otter/blob/main/docs/sdk-tools/SDK_MODELS_HIERARCHY.md) +- [Composition and Inheritance support](https://github.com/AmadeusITGroup/otter/blob/main/docs/sdk-tools/COMPOSITION_INHERITANCE.md) + +## Java Client Core SDK +> **Warning** +> This feature is on maintenance mode and will see no future evolution + +Generate a Java Client Core SDK: + +Make sure to have a `./swagger-spec.yaml` file at the root of your project and run: + +```shell +yarn schematics @ama-sdk/schematics:java-client-core --spec-path ./swagger-spec.yaml --swagger-config-path ./swagger-codegen-config.json +``` + +[Default swagger config](schematics/java/client-core/swagger-codegen-java-client/config/swagger-codegen-config.json) will be used if `--swagger-config-path` is not provided.