diff --git a/apps/showcase/src/assets/trainings/sdk/program.json b/apps/showcase/src/assets/trainings/sdk/program.json index c62cd15479..ec06d4fdbf 100644 --- a/apps/showcase/src/assets/trainings/sdk/program.json +++ b/apps/showcase/src/assets/trainings/sdk/program.json @@ -97,7 +97,7 @@ ], "mode": "interactive", "commands": [ - "npm install --legacy-peer-deps --ignore-scripts --force", + "npm install --legacy-peer-deps --ignore-scripts --no-audit --prefer-dedupe", "npm run ng run sdk:build", "npm run ng run tutorial-app:serve" ] @@ -135,7 +135,7 @@ ], "mode": "interactive", "commands": [ - "npm install --legacy-peer-deps --ignore-scripts --force", + "npm install --legacy-peer-deps --ignore-scripts --no-audit --prefer-dedupe", "npm run ng run app:serve" ] } @@ -182,7 +182,7 @@ "contentUrl": "@o3r-training/training-sdk/structure/src.json" }, { - "path": "./libs/sdk/src/models", + "path": ".", "contentUrl": "./steps/model-extension/exercise.json" } ], @@ -194,7 +194,7 @@ ], "mode": "interactive", "commands": [ - "npm install --legacy-peer-deps --ignore-scripts --force", + "npm install --legacy-peer-deps --ignore-scripts --no-audit --prefer-dedupe", "npm run ng run tutorial-app:serve" ] } diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/apps/tutorial-app/src/app/app.component.html b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/apps/tutorial-app/src/app/app.component.html new file mode 100644 index 0000000000..ce64f2c5af --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/apps/tutorial-app/src/app/app.component.html @@ -0,0 +1,2 @@ +Revived flight: +
{{flight() | json }}
diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/apps/tutorial-app/src/app/app.component.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/apps/tutorial-app/src/app/app.component.ts new file mode 100644 index 0000000000..b889583a0d --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/apps/tutorial-app/src/app/app.component.ts @@ -0,0 +1,28 @@ +import { Component, inject, signal } from '@angular/core'; +import { JsonPipe } from '@angular/common'; +import { DummyApi, Flight } from 'sdk'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [JsonPipe], + templateUrl: './app.component.html', + styleUrl: './app.component.scss' +}) +export class AppComponent { + /** Title of the application */ + public title = 'tutorial-app'; + + public readonly dummyApi = inject(DummyApi); + + public readonly flight = signal(undefined); + + constructor() { + void this.loadDummyData(); + } + + async loadDummyData() { + const dummyData = await this.dummyApi.dummyGet({}); + this.flight.set(dummyData); + } +} diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/apps/tutorial-app/src/app/app.config.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/apps/tutorial-app/src/app/app.config.ts new file mode 100644 index 0000000000..2c118f01af --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/apps/tutorial-app/src/app/app.config.ts @@ -0,0 +1,45 @@ +import { ApiFetchClient } from '@ama-sdk/client-fetch'; +import { MockInterceptRequest, SequentialMockAdapter } from '@ama-sdk/core'; +import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { ConsoleLogger, Logger, LOGGER_CLIENT_TOKEN, LoggerService } from '@o3r/logger'; +import { DummyApi } from 'sdk'; +import { OPERATION_ADAPTER } from 'sdk/spec'; +import { routes } from './app.routes'; +import { additionalModules } from '../environments/environment'; + +function dummyApiFactory(logger: Logger) { + const apiConfig = new ApiFetchClient( + { + basePath: 'http://localhost:3000', + requestPlugins: [ + new MockInterceptRequest({ + adapter: new SequentialMockAdapter( + OPERATION_ADAPTER, + { + '/dummy_get': [{ + mockData: { + originLocationCode: 'PAR', + destinationLocationCode: 'NYC' + } + }] + } + ) + }) + ], + fetchPlugins: [], + logger + } + ); + return new DummyApi(apiConfig); +} + +export const appConfig: ApplicationConfig = { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + importProvidersFrom(additionalModules), + {provide: LOGGER_CLIENT_TOKEN, useValue: new ConsoleLogger()}, + {provide: DummyApi, useFactory: dummyApiFactory, deps: [LoggerService]} + ] +}; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/index.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/index.ts new file mode 100644 index 0000000000..4b8d9646e5 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/index.ts @@ -0,0 +1,2 @@ +export * from './base'; +export * from './core'; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/base/flight/index.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/base/flight/index.ts new file mode 100644 index 0000000000..a2a630b8a4 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/base/flight/index.ts @@ -0,0 +1,3 @@ +/* TODO Export your extended model and reviver instead of the original ones */ +export type { Flight } from './flight'; +export { reviveFlight } from './flight.reviver'; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/flight/flight.reviver.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/flight/flight.reviver.ts new file mode 100644 index 0000000000..2ce19a27e4 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/flight/flight.reviver.ts @@ -0,0 +1,11 @@ +/* TODO Modify the implementation of reviveFlightFactory to call `baseRevive` and add an extra id */ +import type { reviveFlight } from '../../base/flight/flight.reviver'; + +/** + * Extended reviver for Flight + * + * @param baseRevive + */ +export function reviveFlightFactory(baseRevive: R) { + return baseRevive; +} diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/flight/flight.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/flight/flight.ts new file mode 100644 index 0000000000..dea0e51a6f --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/flight/flight.ts @@ -0,0 +1,9 @@ +/* TODO create the type FlightCoreIfy which extends Flight, imported from the ../base folder */ +/* Add an extra field `id: string` */ + +/** + * Extended type for Flight + */ +export type FlightCoreIfy = { + +}; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/flight/index.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/flight/index.ts new file mode 100644 index 0000000000..e3d7e78403 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/flight/index.ts @@ -0,0 +1,2 @@ +export * from './flight'; +export * from './flight.reviver'; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/index.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/index.ts new file mode 100644 index 0000000000..026280ae72 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/exercise/libs/sdk/src/models/core/index.ts @@ -0,0 +1,2 @@ +// Export your core models here +export * from './flight'; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/instructions.md b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/instructions.md new file mode 100644 index 0000000000..8bfade9808 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/instructions.md @@ -0,0 +1,57 @@ +### Objective +Let's continue with the use case of the previous exercise.\ +In order to keep track of the user's current booking, it would be useful to generate an ID.\ +To do this, we are going to create a new model which extends the previously generated `Flight` type. + +### Exercise + +#### Check out the base model +Before proceeding with the extension of the model, let's take a moment to review what is in the base model. +In the folder `libs/sdk/src/models/base/flight`, there are 3 files: +- `flight.ts` is the base model definition +- `flight.reviver.ts` is the reviver of the base model +- `index.ts` is the exposed entry point + +By default, the revivers are only generated when needed: +- If `Date` fields are present and not stringified +- If `dictionaries` are present +- If `modelExtension` is enabled + +If you open the file `libs/sdk/openapitools.json`, you can see that we have set the value of `allowModelExtension` to `true`. +This way, we make sure that the revivers will always be generated. + +Now that we've seen the base model, let's start with the extension. + +#### Creating the extended model +The extended model will follow a similar structure to the base model. +In the folder `libs/sdk/src/models/core/flight`, you will see the same 3 files mentioned before. + +First, let's create the type `FlightCoreIfy` in `libs/sdk/src/models/core/flight.ts`. +This type should extend the type `Flight`, imported from the `base` folder and add a new field `id` of type `string`. + +> [!WARNING] +> The naming convention requires the core model to contain the suffix `CoreIfy`.\ +> You can find more information on core models in the +> SDK models hierarchy documentation. + +#### Creating the extended reviver +Now that you have your extended model, let's create the associated reviver in `libs/sdk/src/models/core/flight.reviver.ts`.\ +This extended reviver will call the reviver of the base `Flight` model and add the `id` to the returned object. + +#### Updating the exports +Once the core model and its reviver are created, we can go back to the base model to update the exported models and revivers.\ +Update the file `libs/sdk/src/models/base/flight/index.ts` to export your extended model and reviver instead of the original. + +#### Seeing the result +Your extension should now be working!\ +Check out the preview to see if the `id` has been added to the model. + +#### Persistence of the change +You may have realized that we have modified a portion of code that was originally generated. +We don't want to lose the change that we made on `libs/sdk/src/models/base/flight/index.ts` next time it's regenerated. +To avoid that, we can add `src/models/base/flight/index.ts` to the `libs/sdk/.openapi-generator-ignore` file. +We cannot regenerate the SDK in the code editor due to a Java dependency but feel free to try it in a local project. + +> [!TIP] +> Don't forget to check out the solution of this exercise! + diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/.openapi-generator-ignore b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/.openapi-generator-ignore new file mode 100644 index 0000000000..de7f0771e6 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/.openapi-generator-ignore @@ -0,0 +1,2 @@ +# Indicate the index.ts file you have override to redirect to custom interface definition +src/models/base/flight/index.ts diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/base/flight/index.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/base/flight/index.ts new file mode 100644 index 0000000000..0fc3dd5419 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/base/flight/index.ts @@ -0,0 +1,7 @@ +import { FlightCoreIfy, reviveFlightFactory } from '../../core/flight'; +import type { Flight as BaseModel } from './flight'; +import { reviveFlight as baseReviver } from './flight.reviver'; + +export type Flight = FlightCoreIfy; +export const reviveFlight = reviveFlightFactory(baseReviver); +export type { BaseModel as BaseFlight }; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/flight/flight.reviver.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/flight/flight.reviver.ts new file mode 100644 index 0000000000..a81da1d4ff --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/flight/flight.reviver.ts @@ -0,0 +1,20 @@ +import type { Flight } from '../../base/flight/flight'; +import type { reviveFlight } from '../../base/flight/flight.reviver'; +import type { FlightCoreIfy } from './flight'; + +/** + * Extended reviver for Flight + * + * @param baseRevive + */ +export function reviveFlightFactory(baseRevive: R) { + const reviver = (data: any, dictionaries?: any) => { + const revivedData = baseRevive>(data, dictionaries); + if (!revivedData) { return; } + /* Set the value of your new fields here */ + revivedData.id = 'sampleIdValue'; + return revivedData; + }; + + return reviver; +} diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/flight/flight.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/flight/flight.ts new file mode 100644 index 0000000000..f47f52e836 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/flight/flight.ts @@ -0,0 +1,9 @@ +import type { Flight } from '../../base/flight/flight'; +import type { IgnoreEnum } from '@ama-sdk/core'; + +/** + * Extended type for Flight + */ +export type FlightCoreIfy> = T & { + id: string; +}; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/flight/index.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/flight/index.ts new file mode 100644 index 0000000000..e3d7e78403 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/flight/index.ts @@ -0,0 +1,2 @@ +export * from './flight'; +export * from './flight.reviver'; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/index.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/index.ts new file mode 100644 index 0000000000..026280ae72 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/core/index.ts @@ -0,0 +1,2 @@ +// Export your core models here +export * from './flight'; diff --git a/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/index.ts b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/index.ts new file mode 100644 index 0000000000..4b8d9646e5 --- /dev/null +++ b/apps/showcase/src/assets/trainings/sdk/steps/model-extension/solution/src/models/index.ts @@ -0,0 +1,2 @@ +export * from './base'; +export * from './core'; diff --git a/packages/@ama-sdk/core/src/plugins/mock-intercept/README.md b/packages/@ama-sdk/core/src/plugins/mock-intercept/README.md index e84d939cdb..60f81589f3 100644 --- a/packages/@ama-sdk/core/src/plugins/mock-intercept/README.md +++ b/packages/@ama-sdk/core/src/plugins/mock-intercept/README.md @@ -1,6 +1,6 @@ # Mock intercept plugin -The mock interception statregy works based on two interceptions: request and fetch. For each interception, a plugin has been made. +The mock interception strategy works based on two interceptions: request and fetch. For each interception, a plugin has been made. ## Mock intercept request plugin @@ -66,7 +66,7 @@ Example of usage: */ import {OPERATION_ADAPTER} from '@ama-sdk/sdk/spec/operation-adapter'; -const myRandomAdapter: new RandomMockAdapter( +const myRandomAdapter = new RandomMockAdapter( OPERATION_ADAPTER, { // Mock data for createCart operation @@ -76,7 +76,7 @@ const myRandomAdapter: new RandomMockAdapter( } ); -const myRandomAdapter: new SequentialMockAdapter( +const myRandomAdapter = new SequentialMockAdapter( OPERATION_ADAPTER, { // Mock data for createCart operation @@ -110,7 +110,7 @@ Example of usage: */ import {OPERATION_ADAPTER} from '@ama-sdk/sdk/spec/operation-adapter'; -const myAdapter: new RandomMockAdapter( +const myAdapter = new RandomMockAdapter( OPERATION_ADAPTER, () => { return fetch('http://my-test-server/getMocks'); diff --git a/packages/@o3r-training/training-sdk/open-api.yaml b/packages/@o3r-training/training-sdk/open-api.yaml index 2899ad180a..f0c5739cad 100644 --- a/packages/@o3r-training/training-sdk/open-api.yaml +++ b/packages/@o3r-training/training-sdk/open-api.yaml @@ -18,6 +18,10 @@ paths: responses: 200: description: "Successful operation" + content: + application/json: + schema: + $ref: '#/components/schemas/Flight' components: schemas: Flight: diff --git a/packages/@o3r-training/training-sdk/src/api/dummy/dummy-api.jest.fixture.ts b/packages/@o3r-training/training-sdk/src/api/dummy/dummy-api.jest.fixture.ts index 9df11a0e7c..ad27a85610 100644 --- a/packages/@o3r-training/training-sdk/src/api/dummy/dummy-api.jest.fixture.ts +++ b/packages/@o3r-training/training-sdk/src/api/dummy/dummy-api.jest.fixture.ts @@ -1,3 +1,4 @@ +import { Flight } from '../../models/base/flight/index'; import { DummyApi, DummyApiDummyGetRequestData } from './dummy-api'; @@ -6,9 +7,9 @@ export class DummyApiFixture implements Partial> { /** @inheritDoc */ public readonly apiName = 'DummyApi'; - /** + /** * Fixture associated to function dummyGet */ - public dummyGet: jest.Mock, [DummyApiDummyGetRequestData]> = jest.fn(); + public dummyGet: jest.Mock, [DummyApiDummyGetRequestData]> = jest.fn(); } diff --git a/packages/@o3r-training/training-sdk/src/api/dummy/dummy-api.ts b/packages/@o3r-training/training-sdk/src/api/dummy/dummy-api.ts index fd0d09c920..da8977d5d8 100644 --- a/packages/@o3r-training/training-sdk/src/api/dummy/dummy-api.ts +++ b/packages/@o3r-training/training-sdk/src/api/dummy/dummy-api.ts @@ -1,4 +1,5 @@ -import { Api, ApiClient, ApiTypes, computePiiParameterTokens, RequestBody, RequestMetadata, } from '@ama-sdk/core'; +import { Flight, reviveFlight } from '../../models/base/flight/index'; +import { Api, ApiClient, ApiTypes, computePiiParameterTokens, RequestBody, RequestMetadata } from '@ama-sdk/core'; /** Parameters object to DummyApi's dummyGet function */ export interface DummyApiDummyGetRequestData { @@ -27,12 +28,12 @@ export class DummyApi implements Api { } /** - * - * + * + * * @param data Data to provide to the API call * @param metadata Metadata to pass to the API call */ - public async dummyGet(data: DummyApiDummyGetRequestData, metadata?: RequestMetadata): Promise { + public async dummyGet(data: DummyApiDummyGetRequestData, metadata?: RequestMetadata): Promise { const queryParams = this.client.extractQueryParams(data, [] as never[]); const metadataHeaderAccept = metadata?.headerAccept || 'application/json'; const headers: { [key: string]: string | undefined } = { @@ -40,7 +41,7 @@ export class DummyApi implements Api { ...(metadataHeaderAccept ? {'Accept': metadataHeaderAccept} : {}) }; - let body: RequestBody = ''; + const body: RequestBody = ''; const basePath = `${this.client.options.basePath}/dummy`; const tokenizedUrl = `${this.client.options.basePath}/dummy`; const tokenizedOptions = this.client.tokenizeRequestOptions(tokenizedUrl, queryParams, this.piiParamTokens, data); @@ -59,7 +60,7 @@ export class DummyApi implements Api { const options = await this.client.getRequestOptions(requestOptions); const url = this.client.prepareUrl(options.basePath, options.queryParams); - const ret = this.client.processCall(url, options, ApiTypes.DEFAULT, DummyApi.apiName, { 200: undefined } , 'dummyGet'); + const ret = this.client.processCall(url, options, ApiTypes.DEFAULT, DummyApi.apiName, { 200: reviveFlight } , 'dummyGet'); return ret; }