Skip to content

Commit 9d4748c

Browse files
committed
Fixed hydration for value type arrays and added support for yaml input in the tooling
Signed-off-by: Jean-Baptiste Bianchi <[email protected]>
1 parent 14e768a commit 9d4748c

6 files changed

+56
-16
lines changed

tools/1_download-schemas.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import $RefParser from '@apidevtools/json-schema-ref-parser';
1717
import { promises as fsPromises } from 'fs';
1818
import * as path from 'path';
1919
import { URL } from 'url';
20-
import { schemaDir, reset, schemaUrl } from './utils';
20+
import { schemaDir, reset, jsonSchemaUrl, yamlSchemaUrl } from './utils';
21+
import * as yaml from 'js-yaml';
2122

2223
const { writeFile, mkdir } = fsPromises;
2324

@@ -29,21 +30,27 @@ const { writeFile, mkdir } = fsPromises;
2930
*/
3031
const download = async (schemaUrl: URL, destDir: string): Promise<void> => {
3132
try {
32-
await reset(destDir);
3333
const fileName = path.basename(schemaUrl.pathname);
34+
const isJson = fileName.endsWith('.json');
3435
const urlBase = schemaUrl.href.replace(fileName, '');
3536
const $refParser = new $RefParser();
3637
await $refParser.resolve(schemaUrl.href);
3738
const externalSchemas = $refParser.$refs
3839
.paths()
3940
.filter((p, index, arr) => arr.indexOf(p) === index && p !== schemaUrl.href);
40-
await writeFile(path.resolve(destDir, fileName), JSON.stringify($refParser.schema, null, 2));
41+
await writeFile(
42+
path.resolve(destDir, fileName),
43+
isJson ? JSON.stringify($refParser.schema, null, 2) : yaml.dump($refParser.schema),
44+
);
4145
externalSchemas.forEach(async (externalSchemaUrl: string) => {
4246
const externalSchema = $refParser.$refs.get(externalSchemaUrl);
4347
if (externalSchema) {
4448
const externalSchemaFileName = externalSchemaUrl.replace(urlBase, '');
4549
await mkdir(path.resolve(destDir, path.dirname(externalSchemaFileName)), { recursive: true });
46-
await writeFile(path.resolve(destDir, externalSchemaFileName), JSON.stringify(externalSchema, null, 2));
50+
await writeFile(
51+
path.resolve(destDir, externalSchemaFileName),
52+
isJson ? JSON.stringify(externalSchema, null, 2) : yaml.dump(externalSchema),
53+
);
4754
}
4855
});
4956
return Promise.resolve();
@@ -52,4 +59,8 @@ const download = async (schemaUrl: URL, destDir: string): Promise<void> => {
5259
}
5360
};
5461

55-
download(schemaUrl, schemaDir).then(console.log.bind(console)).catch(console.error.bind(console));
62+
reset(schemaDir)
63+
.then(() => download(yamlSchemaUrl, schemaDir))
64+
.then(() => download(jsonSchemaUrl, schemaDir))
65+
.then(console.log.bind(console))
66+
.catch(console.error.bind(console));

tools/2_generate-definitions.ts

+29-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { promises as fsPromises } from 'fs';
1919
import * as path from 'path';
2020
import { fileHeader, inFileDisclaimer } from './consts';
2121
import { definitionsDir, isObject, reset, schemaDir, toPascalCase } from './utils';
22+
import * as yaml from 'js-yaml';
2223

2324
const { writeFile, readFile } = fsPromises;
2425

@@ -60,6 +61,7 @@ const metadataProperties = ['title', 'description', 'default', 'type'];
6061
* Embellishes the provided schema to increase its compatibility with json-schema-to-typescript, the resulting schema should keep the validation properties as the input one (phase 1)
6162
* - adds missing type:object properties // not necessary ?
6263
* - adds missing titles to objects
64+
* - removes extra titles on value types
6365
* @param schema The schema to embellish
6466
* @param path The current path of the schema relative to the original schema
6567
* @param parentTitle The title of the parent object, if any
@@ -74,7 +76,7 @@ function prepareSchema(schema: any, path: string[] = ['#'], parentTitle: string
7476
}
7577
const newSchema = JSON.parse(JSON.stringify(schema));
7678
const parent = path.slice(-1)[0];
77-
const schemaKeys = Object.keys(newSchema);
79+
let schemaKeys = Object.keys(newSchema);
7880
const isItemWithAdditionalProperties =
7981
parent === 'additionalProperties' && path.slice(-2)[0] === 'items' && newSchema.properties; // only "useful" for SwitchTask.Switch.Cases
8082
if (!structuralObjectProperties.includes(parent) || isItemWithAdditionalProperties) {
@@ -86,6 +88,16 @@ function prepareSchema(schema: any, path: string[] = ['#'], parentTitle: string
8688
if (newSchema.title) {
8789
parentTitle = newSchema.title;
8890
}
91+
if (
92+
newSchema.title &&
93+
newSchema.type &&
94+
newSchema.type !== 'object' &&
95+
newSchema.type !== 'array' &&
96+
newSchema.title != 'RuntimeExpression' // RuntimeExpression is a string but used as its own type, we want to keep its title to build a JSON pointer later
97+
) {
98+
delete newSchema.title;
99+
schemaKeys = schemaKeys.filter((key) => key === 'title');
100+
}
89101
if (
90102
!newSchema.title &&
91103
(!newSchema.type || newSchema.type === 'object' || newSchema.type === 'array') && // only naming object or array types
@@ -169,8 +181,16 @@ function mutateSchema(schema: any, path: string[] = ['#']): any {
169181
*/
170182
async function generate(srcFile: string, destFile: string): Promise<void> {
171183
const options: Partial<Options> = {
172-
customName: (schema: JSONSchema, keyNameFromDefinition: string | undefined) =>
173-
schema.$id?.includes('serverlessworkflow.io') ? 'Workflow' : keyNameFromDefinition,
184+
customName: (schema: JSONSchema, keyNameFromDefinition: string | undefined) => {
185+
if (schema.$id?.includes('serverlessworkflow.io')) {
186+
return 'Workflow';
187+
}
188+
if (keyNameFromDefinition === 'oauth2Token') {
189+
// seems to ignore the title from this object, so forcing it...
190+
return schema.title;
191+
}
192+
return keyNameFromDefinition;
193+
},
174194
bannerComment: `${fileHeader}
175195
${inFileDisclaimer}
176196
@@ -181,8 +201,11 @@ ${inFileDisclaimer}
181201
//unreachableDefinitions: true,
182202
};
183203
const schemaText = await readFile(srcFile, { encoding: 'utf-8' });
184-
let schema = prepareSchema(JSON.parse(schemaText));
185-
await writeFile(srcFile.replace('workflow', '__internal_workflow'), JSON.stringify(schema, null, 2));
204+
let schema = prepareSchema(yaml.load(schemaText));
205+
await writeFile(
206+
srcFile.replace('workflow', '__internal_workflow').replace('.yaml', '.json'),
207+
JSON.stringify(schema, null, 2).replace('workflow.yaml', 'workflow.json'),
208+
);
186209
schema = mutateSchema(schema);
187210
//await writeFile(srcFile.replace('workflow', '__mutated_workflow'), JSON.stringify(schema, null, 2));
188211
const declarations = await compile(schema, 'Workflow', options);
@@ -192,7 +215,7 @@ ${inFileDisclaimer}
192215
await writeFile(path.resolve(destDir, 'index.ts'), `${fileHeader}export * as Specification from './specification';`);
193216
}
194217

195-
const srcFile = path.resolve(schemaDir, 'workflow.json');
218+
const srcFile = path.resolve(schemaDir, 'workflow.yaml');
196219
const destFile = path.resolve(definitionsDir, 'specification.ts');
197220

198221
generate(srcFile, destFile).then(console.log.bind(console)).catch(console.error.bind(console));

tools/3_generate-validation-pointers.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { fileHeader, inFileDisclaimer } from './consts';
2020
import { definitionsDir, isObject, reset, schemaDir, vallidationDir } from './utils';
2121
import { JSONSchema } from 'json-schema-to-typescript';
2222
import { getExportedDeclarations } from './reflection';
23+
import * as yaml from 'js-yaml';
2324

2425
const { writeFile, readFile } = fsPromises;
2526

@@ -66,8 +67,8 @@ async function generate(schemaFile: string, definitionFile: string, destFile: st
6667
const declarations = Array.from(getExportedDeclarations(definitions).keys())
6768
.filter((name) => name !== 'Workflow')
6869
.sort((a, b) => a.localeCompare(b));
69-
const schema = JSON.parse(schemaTxt) as JSONSchema;
70-
const baseUri = schema.$id + '#';
70+
const schema = yaml.load(schemaTxt) as JSONSchema;
71+
const baseUri = schema.$id.replace('.yaml', '.json') + '#';
7172
const jsonPointers = [
7273
['Workflow', baseUri],
7374
...declarations.map((name) => [name, getJsonPointer(schema, name, baseUri)]),

tools/4_generate-classes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ function getArrayLikeClassDeclaration(name: string, arrayTypeName: string, type:
171171
${inFileDisclaimer}
172172
173173
${hydrationResult.imports.map((type) => `import { _${type} } from './${toKebabCase(normalizeKnownAllCaps(type))}';`)}
174-
import { Specification } from '../definitions';
174+
${hydrationResult.imports.length ? `import { Specification } from '../definitions';` : ''}
175175
import { ArrayHydrator } from '../../hydrator';
176176
import { getLifecycleHooks } from '../../lifecycle-hooks';
177177
import { validate } from '../../validation';

tools/reflection.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,9 @@ export function getArrayHydration(type: Type): HydrationResult {
282282
const arrayType = type.getArrayElementType() || getUnderlyingTypes(type)[0];
283283
const lines: string[] = ['if (model?.length) {', 'this.splice(0, this.length);'];
284284
const imports: string[] = [];
285-
if (!arrayType.isAnonymous()) {
285+
if (isValueType(arrayType)) {
286+
lines.push(`model.forEach(item => this.push(item));`);
287+
} else if (!arrayType.isAnonymous()) {
286288
const typeName = getTypeName(arrayType);
287289
imports.push(typeName);
288290
lines.push(`model.forEach(item => this.push(new _${typeName}(item)));`);

tools/utils.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ const argv = yargs(process.argv.slice(2))
9595
})
9696
.parseSync();
9797
/** The URL to download the schema from */
98-
export const schemaUrl: URL = new URL(
98+
export const yamlSchemaUrl: URL = new URL(
99+
argv.url || `https://serverlessworkflow.io/schemas/${schemaVersion}/workflow.yaml`,
100+
);
101+
export const jsonSchemaUrl: URL = new URL(
99102
argv.url || `https://serverlessworkflow.io/schemas/${schemaVersion}/workflow.json`,
100103
);

0 commit comments

Comments
 (0)