Skip to content

Commit

Permalink
chore: improve typescript sdk generator documentation (#1308)
Browse files Browse the repository at this point in the history
## Proposed change

<!-- Please include a summary of the changes and the related issue.
Please also include relevant motivation and context. List any
dependencies that is required for this change. -->

## Related issues

- 🐛 Fixes #(issue)
- 🚀 Feature #(issue)

<!-- Please make sure to follow the contributing guidelines on
https://github.com/amadeus-digital/Otter/blob/main/CONTRIBUTING.md -->
  • Loading branch information
cpaulve-1A authored Feb 1, 2024
2 parents 0e046b2 + 6652418 commit dcd9c66
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 23 deletions.
214 changes: 214 additions & 0 deletions docs/api-sdk/COMPOSITION_INHERITANCE.md
Original file line number Diff line number Diff line change
@@ -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<T extends Pet = Pet>(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.
File renamed without changes.
69 changes: 46 additions & 23 deletions packages/@ama-sdk/schematics/README.md
Original file line number Diff line number Diff line change
@@ -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 <project-name> -- [--spec-path=./path/to/spec.yaml]
```

or

or
```shell
yarn create @ama-sdk typescript <project-name> [--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:

Expand All @@ -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:

Expand All @@ -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.

Expand All @@ -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 generated SDK and how the framework supports different feature such as the Composition, you
can refer to the dedicated SDK 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.

0 comments on commit dcd9c66

Please sign in to comment.