Skip to content

Commit 33f51be

Browse files
authored
HCK-9560: add object type FE (#43)
* add object types FE * add names validation * add explicit config for required property to allow it on array item * add implements of interfaces mapper * rename arguments var * add arguments for fields * add tests * clear not used validation rule * remove duplicated tests * update mock reset * rename arguments parameter * fix error on non-string statement * refactor statements formatter * extract object definitions type
1 parent 1fc941f commit 33f51be

File tree

16 files changed

+529
-83
lines changed

16 files changed

+529
-83
lines changed

forward_engineering/api.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ module.exports = {
5757
generateModelScript(data, logger, cb) {
5858
try {
5959
const modelDefinitions = JSON.parse(data.modelDefinitions);
60-
const idToNameMap = generateIdToNameMap(modelDefinitions.properties);
61-
const typeDefinitions = getTypeDefinitionStatements({ modelDefinitions });
60+
const definitionsIdToNameMap = generateIdToNameMap(modelDefinitions.properties);
61+
const typeDefinitions = getTypeDefinitionStatements({ modelDefinitions, definitionsIdToNameMap });
6262

6363
const schemaScript = mockedRootQuery + '\n\n' + typeDefinitions;
6464
cb(null, schemaScript);

forward_engineering/helpers/feStatementFormatHelper.js

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,19 @@ function formatFEStatement({ feStatement }) {
2525
endNestedStatementsSign = '}',
2626
nestedStatementsSeparator = '\n',
2727
} = feStatement;
28-
let result = '';
2928

30-
if (description?.trim()) {
31-
const formattedDescription = getStatementDescription({ description });
32-
result += `${formattedDescription}\n`;
33-
}
29+
let result = '';
3430

31+
result += formatDescription(description);
3532
result += statement;
36-
37-
if (nestedStatements?.length > 0) {
38-
const formattedNestedStatements = nestedStatements
39-
.map(nestedStatement =>
40-
addIndentToStatement({ statement: formatFEStatement({ feStatement: nestedStatement }) }),
41-
)
42-
.join(nestedStatementsSeparator);
43-
44-
if (useNestedStatementSigns) {
45-
result += ` ${startNestedStatementsSign}\n${formattedNestedStatements}\n${endNestedStatementsSign}`;
46-
} else {
47-
result += `\n${formattedNestedStatements}`;
48-
}
49-
}
33+
result += formatNestedStatements({
34+
nestedStatements,
35+
isParentActivated: isActivated,
36+
useNestedStatementSigns,
37+
startNestedStatementsSign,
38+
endNestedStatementsSign,
39+
nestedStatementsSeparator,
40+
});
5041

5142
if (!isActivated) {
5243
result = commentOutDeactivatedRootFEStatement({ statement: result, isActivated });
@@ -55,6 +46,45 @@ function formatFEStatement({ feStatement }) {
5546
return result;
5647
}
5748

49+
function formatDescription(description) {
50+
if (description?.trim()) {
51+
const formattedDescription = getStatementDescription({ description });
52+
return `${formattedDescription}\n`;
53+
}
54+
return '';
55+
}
56+
57+
function formatNestedStatements({
58+
nestedStatements,
59+
isParentActivated,
60+
useNestedStatementSigns,
61+
startNestedStatementsSign,
62+
endNestedStatementsSign,
63+
nestedStatementsSeparator,
64+
}) {
65+
if (!nestedStatements?.length) {
66+
return '';
67+
}
68+
69+
const formattedNestedStatements = nestedStatements
70+
.map(nestedStatement => {
71+
const formattedStatement = formatFEStatement({
72+
feStatement: {
73+
...nestedStatement,
74+
isActivated: !isParentActivated ? true : nestedStatement.isActivated,
75+
},
76+
});
77+
return addIndentToStatement({ statement: formattedStatement });
78+
})
79+
.join(nestedStatementsSeparator);
80+
81+
if (useNestedStatementSigns) {
82+
return ` ${startNestedStatementsSign}\n${formattedNestedStatements}\n${endNestedStatementsSign}`;
83+
} else {
84+
return `\n${formattedNestedStatements}`;
85+
}
86+
}
87+
5888
module.exports = {
5989
formatFEStatement,
6090
};

forward_engineering/helpers/feStatementJoinHelper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99
function joinInlineStatements({ statements }) {
1010
return statements
11-
.map(statement => statement.trim())
11+
.map(statement => statement?.trim())
1212
.filter(statement => typeof statement === 'string' && statement.length > 0)
1313
.join(' ');
1414
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Get the definition name from the reference path
3+
*
4+
* @param {Object} param0
5+
* @param {string} param0.referencePath - The reference path, separated by '/', where the definition name is the last element.
6+
* @returns {string} - The definition name.
7+
*/
8+
function getDefinitionNameFromReferencePath({ referencePath = '' }) {
9+
return referencePath.split('/').pop();
10+
}
11+
12+
module.exports = {
13+
getDefinitionNameFromReferencePath,
14+
};

forward_engineering/mappers/arguments.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,18 @@ const mapArgument = ({ argument, idToNameMap = {} }) => {
4545
};
4646

4747
/**
48-
* Maps an array of arguments to a formated string with all configured properties.
48+
* Maps an array of arguments to a formatted string with all configured properties.
4949
* @param {Object} args - arguments object.
50-
* @param {Argument[]} args.arguments - The arguments to map.
50+
* @param {Argument[]} args.graphqlArguments - The arguments to map.
5151
* @param {IdToNameMap} [args.idToNameMap] - The ID to name map of all available types in model.
52-
* @returns {string} - returns the arguments list as a formated string
52+
* @returns {string} - returns the arguments list as a formatted string
5353
*/
54-
const getArguments = ({ arguments, idToNameMap = {} }) => {
55-
const hasDescription = arguments.some(argument => argument.description);
56-
const argumentStatements = arguments.map(argument => mapArgument({ argument, idToNameMap }));
54+
const getArguments = ({ graphqlArguments, idToNameMap = {} }) => {
55+
if (!Array.isArray(graphqlArguments) || graphqlArguments.length === 0) {
56+
return '';
57+
}
58+
const hasDescription = graphqlArguments.some(argument => argument.description);
59+
const argumentStatements = graphqlArguments.map(argument => mapArgument({ argument, idToNameMap }));
5760

5861
if (!hasDescription) {
5962
// For current state of code if arguments don't have any description we return them as a single line

forward_engineering/mappers/fields.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* @import { FEStatement, DirectivePropertyData, FieldData, ArrayItem, IdToNameMap } from "../types/types"
3+
*/
4+
5+
const { joinInlineStatements } = require('../helpers/feStatementJoinHelper');
6+
const { getDefinitionNameFromReferencePath } = require('../helpers/referencesHelper');
7+
const { getArguments } = require('./arguments');
8+
const { getDirectivesUsageStatement } = require('./directives');
9+
10+
/**
11+
* @typedef {Object.<string, FieldData>} FieldsData
12+
*/
13+
14+
/**
15+
* Gets the object types from the model definitions.
16+
*
17+
* @param {Object} param0
18+
* @param {FieldsData} param0.fields - The object types to get.
19+
* @param {string[]} param0.requiredFields - The required fields list.
20+
* @param {IdToNameMap} param0.definitionsIdToNameMap - The definitions id to name map.
21+
* @returns {FEStatement[]} - The object types.
22+
*/
23+
function getFields({ fields, requiredFields = [], definitionsIdToNameMap }) {
24+
return Object.entries(fields).map(([name, fieldData]) =>
25+
mapField({ name, fieldData, required: requiredFields.includes(name), definitionsIdToNameMap }),
26+
);
27+
}
28+
29+
/**
30+
* Maps a field to an FEStatement.
31+
*
32+
* @param {Object} param0
33+
* @param {string} param0.name - The name of the field.
34+
* @param {FieldData} param0.fieldData - The field data object.
35+
* @param {boolean} param0.required - Indicates if the field is required.
36+
* @param {IdToNameMap} param0.definitionsIdToNameMap - The definitions id to name map.
37+
* @returns {FEStatement}
38+
*/
39+
function mapField({ name, fieldData, required, definitionsIdToNameMap }) {
40+
const fieldArguments = getArguments({ graphqlArguments: fieldData.arguments, idToNameMap: definitionsIdToNameMap });
41+
const fieldNameStatement = joinInlineStatements({ statements: [name, fieldArguments] });
42+
const fieldTypeStatement = `${fieldNameStatement}: ${getFieldType({ field: fieldData, required })}`;
43+
const directivesStatement = getDirectivesUsageStatement({ directives: fieldData.typeDirectives });
44+
45+
return {
46+
statement: joinInlineStatements({ statements: [fieldTypeStatement, directivesStatement] }),
47+
description: fieldData.refDescription || fieldData.description,
48+
isActivated: fieldData.isActivated,
49+
};
50+
}
51+
52+
/**
53+
* Gets the field type.
54+
*
55+
* @param {Object} param0
56+
* @param {FieldData} param0.field - The field data object.
57+
* @param {boolean} param0.required - Indicates if the field is required.
58+
* @returns {string} - The field type.
59+
*/
60+
function getFieldType({ field, required }) {
61+
if (field.$ref) {
62+
const definitionName = getDefinitionNameFromReferencePath({ referencePath: field.$ref });
63+
return addRequiredField({ field: definitionName, required });
64+
}
65+
66+
if (field.type === 'List') {
67+
const arrayItem = getFieldFromArrayItems({ items: field.items });
68+
return addRequiredField({
69+
field: `[${getFieldType({ field: arrayItem, required: arrayItem.required })}]`,
70+
required,
71+
});
72+
}
73+
74+
return addRequiredField({ field: field.type, required });
75+
}
76+
77+
/**
78+
* Gets the field from array items.
79+
*
80+
* @param {Object} param0
81+
* @param {FieldData['items']} param0.items - The array items.
82+
* @returns {ArrayItem} - The field.
83+
*/
84+
function getFieldFromArrayItems({ items }) {
85+
if (Array.isArray(items)) {
86+
return items[0];
87+
}
88+
return items;
89+
}
90+
91+
/**
92+
* Adds required field indicator.
93+
*
94+
* @param {Object} param0
95+
* @param {string} param0.field - The field type statement.
96+
* @param {boolean} param0.required - Indicates if the field is required.
97+
* @returns {string} - The field with required indicator.
98+
*/
99+
function addRequiredField({ field, required }) {
100+
if (required) {
101+
return `${field}!`;
102+
}
103+
return field;
104+
}
105+
106+
module.exports = {
107+
getFields,
108+
// exported only for tests:
109+
mapField,
110+
getFieldType,
111+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @import { IdToNameMap, ImplementsInterface } from "../types/types"
3+
*/
4+
5+
/**
6+
* Get implements interfaces statement
7+
*
8+
* @param {Object} param0
9+
* @param {ImplementsInterface[]} param0.interfaces - The interfaces to implement.
10+
* @param {IdToNameMap} param0.definitionsIdToNameMap - The definitions id to name map.
11+
* @returns {string} - The implements interfaces statement.
12+
*/
13+
function getImplementsInterfacesStatement({ interfaces = [], definitionsIdToNameMap }) {
14+
const implementedInterfacesList = getImplementedInterfacesList({ interfaces, definitionsIdToNameMap });
15+
if (!implementedInterfacesList.length) {
16+
return '';
17+
}
18+
const implementedInterfacesStatement = `implements ${implementedInterfacesList.join(' & ')}`;
19+
return implementedInterfacesStatement;
20+
}
21+
22+
/**
23+
* Get implemented interfaces list
24+
*
25+
* @param {Object} param0
26+
* @param {ImplementsInterface[]} param0.interfaces - The interfaces to implement.
27+
* @param {IdToNameMap} param0.definitionsIdToNameMap - The definitions id to name map.
28+
* @returns {string[]} - The implemented interfaces list.
29+
*/
30+
function getImplementedInterfacesList({ interfaces, definitionsIdToNameMap }) {
31+
return interfaces.map(interfaceData => definitionsIdToNameMap[interfaceData.interface]).filter(Boolean);
32+
}
33+
34+
module.exports = {
35+
getImplementsInterfacesStatement,
36+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @import { FEStatement, DirectivePropertyData, ObjectTypeDefinition, ObjectTypeDefinitions, IdToNameMap, ImplementsInterface } from "../types/types"
3+
*/
4+
5+
const { joinInlineStatements } = require('../helpers/feStatementJoinHelper');
6+
const { getDirectivesUsageStatement } = require('./directives');
7+
const { getFields } = require('./fields');
8+
const { getImplementsInterfacesStatement } = require('./implementsInterfaces');
9+
10+
/**
11+
* Gets the object types from the model definitions.
12+
*
13+
* @param {Object} param0
14+
* @param {ObjectTypeDefinitions} param0.objectTypes - The object types to get.
15+
* @param {IdToNameMap} param0.definitionsIdToNameMap - The definitions id to name map.
16+
* @returns {FEStatement[]} - The object types.
17+
*/
18+
function getObjectTypes({ objectTypes, definitionsIdToNameMap }) {
19+
return Object.entries(objectTypes).map(([name, objectType]) =>
20+
mapObjectType({ name, objectType, definitionsIdToNameMap }),
21+
);
22+
}
23+
24+
/**
25+
* Maps an object type to an FEStatement.
26+
*
27+
* @param {Object} param0
28+
* @param {string} param0.name - The name of the object.
29+
* @param {ObjectTypeDefinition} param0.objectType - The object type definition object.
30+
* @param {IdToNameMap} param0.definitionsIdToNameMap - The definitions id to name map.
31+
* @returns {FEStatement}
32+
*/
33+
function mapObjectType({ name, objectType, definitionsIdToNameMap }) {
34+
const nameStatement = `type ${name}`;
35+
const implementsInterfacesStatement = getImplementsInterfacesStatement({
36+
interfaces: objectType.implementsInterfaces,
37+
definitionsIdToNameMap,
38+
});
39+
const directivesStatement = getDirectivesUsageStatement({ directives: objectType.typeDirectives });
40+
41+
return {
42+
statement: joinInlineStatements({
43+
statements: [nameStatement, implementsInterfacesStatement, directivesStatement],
44+
}),
45+
description: objectType.description,
46+
isActivated: objectType.isActivated,
47+
nestedStatements: getFields({
48+
fields: objectType.properties,
49+
requiredFields: objectType.required,
50+
definitionsIdToNameMap,
51+
}),
52+
};
53+
}
54+
55+
module.exports = {
56+
getObjectTypes,
57+
};

forward_engineering/mappers/typeDefinitions.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
1+
/**
2+
* @import { IdToNameMap } from "../types/types"
3+
*/
4+
15
const { formatFEStatement } = require('../helpers/feStatementFormatHelper');
26
const { getCustomScalars } = require('./customScalars');
37
const { getEnums } = require('./enums');
8+
const { getObjectTypes } = require('./objectType');
49
const { getUnions } = require('./unions');
510

611
/**
712
* Gets the type definition statements from model definitions.
813
*
914
* @param {Object} param0
1015
* @param {Object} param0.modelDefinitions - The model definitions object.
16+
* @param {IdToNameMap} param0.definitionsIdToNameMap - The definitions id to name map.
1117
* @returns {string} - The formatted type definition statements.
1218
*/
13-
function getTypeDefinitionStatements({ modelDefinitions }) {
19+
function getTypeDefinitionStatements({ modelDefinitions, definitionsIdToNameMap }) {
1420
const customScalars = getCustomScalars({
1521
customScalars: getModelDefinitionsBySubtype({ modelDefinitions, subtype: 'scalar' }),
1622
});
1723
const enums = getEnums({ enumsDefinitions: getModelDefinitionsBySubtype({ modelDefinitions, subtype: 'enum' }) });
24+
const objectTypes = getObjectTypes({
25+
objectTypes: getModelDefinitionsBySubtype({ modelDefinitions, subtype: 'object' }),
26+
definitionsIdToNameMap,
27+
});
1828
const unions = getUnions({ unions: getModelDefinitionsBySubtype({ modelDefinitions, subtype: 'union' }) });
1929

20-
const typeDefinitions = [...customScalars, ...enums, ...unions];
30+
const typeDefinitions = [...customScalars, ...enums, ...objectTypes, ...unions];
2131
const formattedTypeDefinitions = typeDefinitions
2232
.map(typeDefinition => formatFEStatement({ feStatement: typeDefinition }))
2333
.join('\n\n');

0 commit comments

Comments
 (0)