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;
}