Skip to content

Commit ff5d358

Browse files
committed
feat(ama-sdk-generator): add guard function for extended definitions
feat(ama-sdk-generator): enhance extended model definition with discriminator literal type feat(ama-sdk-generator): add discriminator extended list
1 parent 6908040 commit ff5d358

41 files changed

Lines changed: 508 additions & 28 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

nx.json

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,14 @@
223223
"source",
224224
"specs"
225225
],
226-
"dependsOn": [{
227-
"projects": ["test-helpers"],
228-
"target": "build"
229-
}],
226+
"dependsOn": [
227+
{
228+
"projects": [
229+
"test-helpers"
230+
],
231+
"target": "build"
232+
}
233+
],
230234
"outputs": [
231235
"{projectRoot}/dist-test/junit.xml",
232236
"{projectRoot}/coverage/cobertura-coverage.xml"
@@ -247,10 +251,14 @@
247251
},
248252
"test-int": {
249253
"executor": "@nx/jest:jest",
250-
"dependsOn": [{
251-
"projects": ["test-helpers"],
252-
"target": "build"
253-
}],
254+
"dependsOn": [
255+
{
256+
"projects": [
257+
"test-helpers"
258+
],
259+
"target": "build"
260+
}
261+
],
254262
"inputs": [
255263
{
256264
"env": "RUNNER_OS"
@@ -732,5 +740,6 @@
732740
"appsDir": "apps"
733741
},
734742
"cacheDirectory": ".cache/nx",
735-
"nxCloudId": "63e6951e043da20dd8321aec"
736-
}
743+
"nxCloudId": "63e6951e043da20dd8321aec",
744+
"analytics": true
745+
}

packages/@ama-sdk/schematics/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,26 @@ The Body request parameter generated in the SDK can also be defined in the speci
200200
> [!WARNING]
201201
> This vendor extension will take precedence over the global property `requestBodyTransform`.
202202
203+
##### Dynamic Enums Type
204+
205+
By default, enums are generated as static types. You can enable dynamic enum type generation by using the global property option `useDynamicEnumType` by adding `--global-property useDynamicEnumType=true` to the generator command.
206+
207+
This option allows enums to be generated as dynamic types that can handle additional values not explicitly defined in the specification, making your SDK more resilient to API changes.
208+
209+
Example:
210+
211+
```shell
212+
yarn schematics @ama-sdk/schematics:typescript-core --spec-path ./swagger-spec.yaml --global-property useDynamicEnumType=true
213+
```
214+
215+
##### Override Discriminator Type with Value
216+
217+
By default, when a discriminator is used in the specification file, the children models are generated with a type that corresponds to the discriminator property and its value (for example, if the discriminator is `petType` and its value is `Dog`, the generated type for the children model will be `Dog`). You can enable the generation of the discriminator property as a type instead of its value by using the global property option `overrideDiscriminatorTypeWithValue` by adding `--global-property overrideDiscriminatorTypeWithValue=true` to the generator command.
218+
219+
##### Children Model List Type Suffix
220+
221+
The options `childrenModelListTypeSuffix` will add a suffix to the generated type of the list of children models when a discriminator is used in the specification file. If not specified the list of object children will not be generated.
222+
203223
##### Extensible models
204224

205225
You may be in a case in which you want to be able to extend your SDK models and therefore ensure that revivers are generated

packages/@ama-sdk/schematics/schematics/typescript/core/openapi-codegen-typescript/src/main/java/com/amadeus/codegen/ts/AbstractTypeScriptClientCodegen.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp
4747
private final boolean stringifyDate;
4848
private final boolean allowModelExtension;
4949
private final boolean useLegacyDateExtension;
50+
private final boolean useDynamicEnumType;
51+
private final boolean overrideDiscriminatorTypeWithValue;
5052
private final String requestBodyTransform;
53+
private final String childrenModelListTypeSuffix;
5154

5255
public AbstractTypeScriptClientCodegen() {
5356
super();
@@ -120,6 +123,12 @@ public AbstractTypeScriptClientCodegen() {
120123
stringifyDate = stringifyDateString != null ? !"false".equalsIgnoreCase(stringifyDateString) : true;
121124
String requestBodyTransformString = GlobalSettings.getProperty("requestBodyTransform");
122125
requestBodyTransform = requestBodyTransformString != null ? requestBodyTransformString : "";
126+
String useDynamicEnumTypeString = GlobalSettings.getProperty("useDynamicEnumType");
127+
useDynamicEnumType = useDynamicEnumTypeString != null ? !"false".equalsIgnoreCase(useDynamicEnumTypeString) : false;
128+
String overrideDiscriminatorTypeWithValueString = GlobalSettings.getProperty("overrideDiscriminatorTypeWithValue");
129+
overrideDiscriminatorTypeWithValue = overrideDiscriminatorTypeWithValueString != null ? !"false".equalsIgnoreCase(overrideDiscriminatorTypeWithValueString) : false;
130+
String childrenModelListTypeSuffixString = GlobalSettings.getProperty("childrenModelListTypeSuffix");
131+
childrenModelListTypeSuffix = childrenModelListTypeSuffixString;
123132
typeMapping.put("DateTime", useLegacyDateExtension ? "utils.DateTime" : getDateTimeStandardTime(stringifyDate));
124133
typeMapping.put("Date", useLegacyDateExtension ? "utils.Date" : getDateType(stringifyDate));
125134
//TODO binary should be mapped to byte array
@@ -657,6 +666,15 @@ public ModelsMap postProcessModels(ModelsMap objs) {
657666
// Set additional properties and check non model objects
658667
for (ModelMap modelMap : models) {
659668
CodegenModel model = modelMap.getModel();
669+
670+
if (model.discriminator != null) {
671+
List<String> discriminatorValues = new ArrayList<String>();
672+
for (CodegenDiscriminator.MappedModel mm : model.discriminator.getMappedModels()) {
673+
discriminatorValues.add(mm.getModelName());
674+
}
675+
model.vendorExtensions.put("x-discriminator-mapping-values", discriminatorValues);
676+
}
677+
660678
if (model.isEnum) {
661679
nonObjectModels.add(toModelName(model.name));
662680
}
@@ -744,6 +762,9 @@ private ModelsMap postProcessImports(ModelsMap objs) {
744762
@Override
745763
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
746764
System.out.println("Non object models: " + nonObjectModels.toString());
765+
766+
Map<String, String> discriminatorField = new HashMap<>();
767+
Map<String, String> discriminatorValue = new HashMap<>();
747768
for (Map.Entry<String, ModelsMap> entry : objs.entrySet()) {
748769
ModelsMap modelsMap = entry.getValue();
749770
List<ModelMap> modelMaps = modelsMap.getModels();
@@ -769,12 +790,43 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
769790
prop.vendorExtensions.put("nonObjectDefinition", nonObjectDefinition);
770791
}
771792
}
793+
794+
if (model.discriminator != null) {
795+
for (CodegenDiscriminator.MappedModel mm : model.discriminator.getMappedModels()) {
796+
discriminatorField.put(mm.getModelName(), model.discriminator.getPropertyName());
797+
discriminatorValue.put(mm.getModelName(), mm.getMappingName());
798+
}
799+
}
772800
}
773801
}
802+
774803
objs = super.postProcessAllModels(objs);
775804
for (Map.Entry<String, ModelsMap> entry : objs.entrySet()) {
776805
entry.setValue(this.postProcessImports(entry.getValue()));
806+
ModelsMap modelsMap = entry.getValue();
807+
List<ModelMap> modelMaps = modelsMap.getModels();
808+
for (ModelMap _modelMap : modelMaps) {
809+
CodegenModel model = _modelMap.getModel();
810+
// Set the useDynamicEnumType vendor extension for the model and its properties to be able to use it in the templates
811+
model.vendorExtensions.put("x-use-dynamic-enum-type", useDynamicEnumType);
812+
// Set the overrideDiscriminatorTypeWithValue vendor extension for the model to be able to use it in the templates
813+
model.vendorExtensions.put("x-override-discriminator-type-with-value", overrideDiscriminatorTypeWithValue);
814+
// Set the childrenModelListTypeSuffix vendor extension for the model to be able to use it in the templates
815+
model.vendorExtensions.put("x-children-model-list-type-suffix", childrenModelListTypeSuffix);
816+
for (CodegenProperty prop : model.getAllVars()) {
817+
if (prop.isEnum) {
818+
prop.vendorExtensions.put("x-use-dynamic-enum-type", useDynamicEnumType);
819+
}
820+
}
821+
822+
// Set discriminator field and value as vendor extension for the model if any, to be able to use it in the templates
823+
if (discriminatorField.containsKey(model.classname) && discriminatorValue.containsKey(model.classname)) {
824+
model.vendorExtensions.put("x-discriminator-field", discriminatorField.get(model.classname));
825+
model.vendorExtensions.put("x-discriminator-value", discriminatorValue.get(model.classname));
826+
}
827+
}
777828
}
829+
778830
// if allowModelExtension is true, we don't need to analyze the conditions since we want to ensure the generation of the revivers
779831
if (!allowModelExtension) {
780832
Predicate<CodegenProperty> varRequiredReviverPredicate = var -> !(var.isPrimitiveType || var.complexType != null || var.isEnum || var.isEnumRef);

packages/@ama-sdk/schematics/schematics/typescript/core/openapi-codegen-typescript/src/main/resources/typescriptFetch/api/api.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{{#noUnusedImport}}
22
{{#imports}}
3-
import { {{import}} } from '../../models/base/{{#kebabCase}}{{import}}{{/kebabCase}}/index';
3+
import type { {{import}} } from '../../models/base/{{#kebabCase}}{{import}}{{/kebabCase}}/index';
44
{{#keepRevivers}}
5-
import { revive{{import}} } from '../../models/base/{{#kebabCase}}{{import}}{{/kebabCase}}/{{#kebabCase}}{{import}}{{/kebabCase}}.reviver';
5+
import type { revive{{import}} } from '../../models/base/{{#kebabCase}}{{import}}{{/kebabCase}}/{{#kebabCase}}{{import}}{{/kebabCase}}.reviver';
66
{{/keepRevivers}}
77
{{/imports}}
88
import { Api, ApiClient, ApiTypes, computePiiParameterTokens, isJsonMimeType, ParamSerializationOptions, RequestBody, RequestMetadata, Server, selectServerBasePath, utils } from '@ama-sdk/core';

packages/@ama-sdk/schematics/schematics/typescript/core/openapi-codegen-typescript/src/main/resources/typescriptFetch/model/model.mustache

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {utils} from '@ama-sdk/core';
1515
{{/models}}
1616

1717
{{#imports}}
18-
import { {{import}} } from '../{{#kebabCase}}{{import}}{{/kebabCase}}';
18+
import type { {{import}} } from '../{{#kebabCase}}{{import}}{{/kebabCase}}';
1919
{{/imports}}
2020

2121
{{#models}}
@@ -69,6 +69,10 @@ export interface {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{
6969
{{/x-map-name}}
7070
{{/vendorExtensions}}
7171
{{/vars}}
72+
{{#parent}}{{#vendorExtensions}}{{#x-discriminator-field}}{{#x-override-discriminator-type-with-value}}
73+
/** @see {{parent}}.{{x-discriminator-field}} */
74+
{{x-discriminator-field}}: '{{x-discriminator-value}}';
75+
{{/x-override-discriminator-type-with-value}}{{/x-discriminator-field}}{{/vendorExtensions}}{{/parent}}
7276
}
7377
{{/oneOf}}
7478
{{#oneOf}}
@@ -84,7 +88,11 @@ export type {{classname}} = {{#oneOf}}{{.}}{{^-last}} | {{/-last}}{{/oneOf}};
8488
export const {{#upperSnakeCase}}list{{#removeBrackets}}{{{datatypeWithEnum}}}{{/removeBrackets}}{{/upperSnakeCase}} = [{{#trimComma}}{{#vendorExtensions}}{{#x-sanitized-allowable-values}}'{{.}}'{{^-last}}, {{/-last}}{{/x-sanitized-allowable-values}}{{/vendorExtensions}}{{/trimComma}}] as const;
8589

8690
/** List of available values for {{#removeBrackets}}{{{datatypeWithEnum}}}{{/removeBrackets}} */
91+
{{#vendorExtensions}}{{#x-use-dynamic-enum-type}}
92+
export type {{#removeBrackets}}{{{datatypeWithEnum}}}{{/removeBrackets}} = typeof {{#upperSnakeCase}}list{{#removeBrackets}}{{{datatypeWithEnum}}}{{/removeBrackets}}{{/upperSnakeCase}}[number];
93+
{{/x-use-dynamic-enum-type}}{{^x-use-dynamic-enum-type}}
8794
export type {{#removeBrackets}}{{{datatypeWithEnum}}}{{/removeBrackets}} = {{#trimPipe}}{{#allowableValues}}{{#values}}'{{.}}' | {{/values}}{{/allowableValues}}{{/trimPipe}};
95+
{{/x-use-dynamic-enum-type}}{{/vendorExtensions}}
8896
{{/isEnum}}
8997
{{/allVars}}
9098
{{/hasEnums}}
@@ -97,12 +105,33 @@ export const {{baseName}}Pattern = {{#parseRegexp}}{{{pattern}}}{{/parseRegexp}}
97105
{{/vars}}
98106
{{/isEnum}}
99107
{{#isEnum}}
100-
/** Array of {{classname}} items */
101-
export const {{#upperSnakeCase}}list{{classname}}{{/upperSnakeCase}} = [{{#trimComma}}{{#allowableValues}}{{#values}}'{{.}}'{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}{{/trimComma}}] as const;
108+
/** @inheritDoc */
109+
export const {{#upperSnakeCase}}list{{classname}}{{/upperSnakeCase}} = [{{#trimComma}}{{#allowableValues}}{{#values}}'{{.}}'{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}{{/trimComma}}] as const satisfies {{classname}}[];
102110

103111
/** List of available values for {{classname}} */
104-
export type {{classname}} = {{#trimPipe}}{{#allowableValues}}{{#values}}'{{.}}' | {{/values}}{{/allowableValues}}{{/trimPipe}};
112+
{{#vendorExtensions}}{{#x-use-dynamic-enum-type}}
113+
export type {{classname}} = typeof {{#upperSnakeCase}}list{{classname}}{{/upperSnakeCase}}[number];
114+
{{/x-use-dynamic-enum-type}}{{^x-use-dynamic-enum-type}}
115+
export type {{classname}} = {{#trimPipe}}{{#allowableValues}}{{#values}}'{{.}}'{{^-last}} | {{/-last}}{{/values}}{{/allowableValues}}{{/trimPipe}};
116+
{{/x-use-dynamic-enum-type}}{{/vendorExtensions}}
105117
{{/isEnum}}
118+
119+
{{#vendorExtensions}}{{#discriminator}}{{#x-children-model-list-type-suffix}}
120+
/** Mapping between discriminator values and their corresponding types for {{classname}} */
121+
export type {{classname}}{{x-children-model-list-type-suffix}} = {{#x-discriminator-mapping-values}}{{.}}{{^-last}} | {{/-last}}{{/x-discriminator-mapping-values}};
122+
{{/x-children-model-list-type-suffix}}{{/discriminator}}{{/vendorExtensions}}
123+
124+
{{#parent}}{{#vendorExtensions}}{{#x-discriminator-field}}
125+
/**
126+
* Type guard for {{classname}} implementing the discriminator logic
127+
* @param data The data to check
128+
* @returns True if the data is of type {{classname}}, false otherwise
129+
*/
130+
export const is{{classname}} = (data: {{parent}}): data is {{classname}} => {
131+
return (data as any)['{{x-discriminator-field}}'] === '{{x-discriminator-value}}';
132+
};
133+
{{/x-discriminator-field}}{{/vendorExtensions}}{{/parent}}
134+
106135
{{/model}}
107136
{{/models}}
108137
{{/noUnusedImport}}

packages/@ama-sdk/schematics/schematics/typescript/shell/templates/base/openapitools.json.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"storageDir": ".openapi-generator",
77
"generators": {
88
"<%=projectName%>-<%=projectPackageName%>": {
9+
"useDynamicEnumType": true,
910
"generatorName": "typescriptFetch",
1011
"output": ".",
1112
"inputSpec": ""

packages/@ama-styling/figma-extractor/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"bin": "./cli/figma-extract.js",
2323
"generatorDependencies": {
2424
"@ama-styling/style-dictionary": "workspace:~",
25-
"style-dictionary": "~5.3.0"
25+
"style-dictionary": "~5.4.0"
2626
},
2727
"peerDependencies": {
2828
"@ama-sdk/client-fetch": "workspace:~",

packages/@o3r-training/showcase-sdk/open-api.yaml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ tags:
4141
- name: user
4242
description: Operations about user
4343
paths:
44+
/animal:
45+
get:
46+
tags:
47+
- "dummy"
48+
summary: Animal get
49+
description: Get an animal
50+
operationId: animalGet
51+
responses:
52+
200:
53+
description: "Successful operation"
54+
content:
55+
application/json:
56+
schema:
57+
$ref: '#/components/schemas/Animal'
4458
/pet:
4559
post:
4660
servers:
@@ -630,6 +644,38 @@ externalDocs:
630644
url: 'http://swagger.io'
631645
components:
632646
schemas:
647+
Animal:
648+
type: object
649+
required:
650+
- petType
651+
properties:
652+
petType:
653+
type: string
654+
enum: [cat, dog] # <- optional enum, can be forgotten in the declaration without changing generated code
655+
discriminator:
656+
propertyName: petType
657+
mapping: # <- mandatory
658+
dog: "#/components/schemas/Dog"
659+
cat: "#/components/schemas/Cat"
660+
Dog:
661+
allOf:
662+
- $ref: '#/components/schemas/Animal'
663+
- type: object
664+
properties:
665+
bark:
666+
type: boolean
667+
breed:
668+
type: string
669+
enum: [Dingo, Husky, Retriever, Shepherd]
670+
Cat:
671+
allOf:
672+
- $ref: '#/components/schemas/Animal'
673+
- type: object
674+
properties:
675+
hunts:
676+
type: boolean
677+
age:
678+
type: integer
633679
Order:
634680
x-swagger-router-model: io.swagger.petstore.model.Order
635681
properties:

packages/@o3r-training/showcase-sdk/openapitools.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
"output": ".",
1111
"inputSpec": "./open-api.yaml",
1212
"globalProperty": {
13+
"overrideDiscriminatorTypeWithValue": true,
14+
"useDynamicEnumType": true,
1315
"stringifyDate": true,
16+
"childrenModelListTypeSuffix": "Children",
1417
"requestBodyTransform": "content{{bodyRequest}}"
1518
}
1619
}

packages/@o3r-training/showcase-sdk/src/api/dummy/dummy-api.fixture.ts

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)