Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions forward_engineering/mappers/fieldDefaultValue.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
* @import {FieldData} from "../../shared/types/types"
* @import {DirectivePropertyData, FieldData} from "../../shared/types/types"
*/

/**
* Generates the default value statement for a field.
*
* @param {object} param0
* @param {FieldData} param0.field - The field object.
* @param {FieldData<DirectivePropertyData>} param0.field - The field object.
* @returns {string} - The default value statement.
*/
function getFieldDefaultValueStatement({ field }) {
Expand Down
12 changes: 6 additions & 6 deletions forward_engineering/mappers/fields.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @import {FEStatement, BaseGetFieldParams, GetFieldsParams, ArrayItems, FieldData, ArrayItem, IdToNameMap} from "../../shared/types/types"
* @import {FEStatement, BaseGetFieldParams, GetFieldsParams, ArrayItems, FieldData, ArrayItem, IdToNameMap, DirectivePropertyData} from "../../shared/types/types"
*/

const { joinInlineStatements } = require('../helpers/feStatementJoinHelper');
Expand Down Expand Up @@ -30,7 +30,7 @@ function getFields({ fields = {}, requiredFields = [], definitionsIdToNameMap, a

/**
* @param {object} params
* @param {FieldData} params.fieldData
* @param {FieldData<DirectivePropertyData>} params.fieldData
* @returns {string}
*/
function getFieldDescription({ fieldData }) {
Expand All @@ -48,7 +48,7 @@ function getFieldDescription({ fieldData }) {
*
* @param {object} param0
* @param {string} param0.name - The name of the field.
* @param {FieldData} param0.fieldData - The field data object.
* @param {FieldData<DirectivePropertyData>} param0.fieldData - The field data object.
* @param {boolean} param0.required - Indicates if the field is required.
* @param {IdToNameMap} param0.definitionsIdToNameMap - The definitions id to name map.
* @param {boolean} param0.addArguments - Indicates if arguments should be added.
Expand Down Expand Up @@ -79,7 +79,7 @@ function mapField({ name, fieldData, required, definitionsIdToNameMap, addArgume
* Gets the field type.
*
* @param {object} param0
* @param {FieldData} param0.field - The field data object.
* @param {FieldData<DirectivePropertyData>} param0.field - The field data object.
* @param {boolean} [param0.required] - Indicates if the field is required.
* @returns {string} - The field type.
*/
Expand Down Expand Up @@ -116,8 +116,8 @@ function getFieldType({ field, required }) {
* Gets the field from array items.
*
* @param {object} param0
* @param {ArrayItems} [param0.items] - The array items.
* @returns {ArrayItem | undefined} - The field.
* @param {ArrayItems<DirectivePropertyData>} [param0.items] - The array items.
* @returns {ArrayItem<DirectivePropertyData> | undefined} - The field.
*/
function getFieldFromArrayItems({ items }) {
if (Array.isArray(items)) {
Expand Down
14 changes: 14 additions & 0 deletions reverse_engineering/constants/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// graphql types
const BUILT_IN_SCALAR = {
String: 'String',
Int: 'Int',
Float: 'Float',
Boolean: 'Boolean',
ID: 'ID',
};

const BUILT_IN_SCALAR_LIST = Object.values(BUILT_IN_SCALAR);

module.exports = {
BUILT_IN_SCALAR_LIST,
};
38 changes: 38 additions & 0 deletions reverse_engineering/helpers/getDefinitionCategoryByNameMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @import {DefinitionNode} from "graphql"
* @import {DefinitionNameToTypeNameMap} from "../../shared/types/re"
*/

const { astNodeKind } = require('../constants/graphqlAST');

const kindToDefinitionTypeName = {
[astNodeKind.SCALAR_TYPE_DEFINITION]: 'Scalars',
[astNodeKind.ENUM_TYPE_DEFINITION]: 'Enums',
[astNodeKind.OBJECT_TYPE_DEFINITION]: 'Objects',
[astNodeKind.INTERFACE_TYPE_DEFINITION]: 'Interfaces',
[astNodeKind.UNION_TYPE_DEFINITION]: 'Unions',
[astNodeKind.INPUT_OBJECT_TYPE_DEFINITION]: 'Input objects',
[astNodeKind.DIRECTIVE_DEFINITION]: 'Directives',
};

/**
* Find nodes by kind with proper typing
*
* @param {object} options
* @param {DefinitionNode[]} options.nodes - The nodes to search
* @returns {DefinitionNameToTypeNameMap} The found nodes with proper type
*/
function getDefinitionCategoryByNameMap({ nodes }) {
return nodes
.filter(node => kindToDefinitionTypeName[node.kind])
.reduce((acc, node) => {
if ('name' in node && node.name !== undefined) {
acc[node.name.value] = kindToDefinitionTypeName[node.kind];
}
return acc;
}, {});
}

module.exports = {
getDefinitionCategoryByNameMap,
};
6 changes: 3 additions & 3 deletions reverse_engineering/helpers/sortByName.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/**
* @import {FieldsOrder, REDefinition} from "../../shared/types/types"
* @import {FieldsOrder, PreProcessedFieldData, REDefinition} from "../../shared/types/types"
*/

/**
* Sorts an array of objects by the name according to the fields order option
*
* @param {object} params
* @param {REDefinition[]} params.items - The items to sort
* @param {REDefinition[] | PreProcessedFieldData[]} params.items - The items to sort
* @param {FieldsOrder} params.fieldsOrder - The fields order
* @returns {REDefinition[]} The sorted items
* @returns {REDefinition[] | PreProcessedFieldData[]} The sorted items
*/
function sortByName({ items, fieldsOrder }) {
if (!Array.isArray(items)) {
Expand Down
108 changes: 108 additions & 0 deletions reverse_engineering/mappers/field.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* @import {FieldDefinitionNode, TypeNode} from "graphql"
* @import {DefinitionNameToTypeNameMap, FieldTypeProperties, PreProcessedFieldData} from "./../../shared/types/types"
*/

const { mapDirectivesUsage } = require('./directiveUsage');
const { astNodeKind } = require('../constants/graphqlAST');
const { BUILT_IN_SCALAR_LIST } = require('../constants/types');

/**
* Maps a field
*
* @param {object} params
* @param {FieldDefinitionNode} params.field - The field to map
* @param {DefinitionNameToTypeNameMap} params.definitionCategoryByNameMap - The definition category by name map
* @returns {PreProcessedFieldData} The mapped field
*/
function mapField({ field, definitionCategoryByNameMap }) {
const fieldTypeProperties = getTypeProperties({ type: field.type, definitionCategoryByNameMap });
const sharedProperties = {
name: field.name.value,
fieldDirectives: mapDirectivesUsage({ directives: [...(field.directives || [])] }),
...fieldTypeProperties,
};
const description = field.description?.value;

if ('$ref' in fieldTypeProperties) {
return {
...sharedProperties,
refDescription: description,
// TODO: add arguments
};
}
return {
...sharedProperties,
description,
// TODO: add arguments
};
}

/**
* Recursively maps the type properties unwrapping non-null and list types and resolving named types to references
*
* @param {object} params
* @param {TypeNode} params.type - The GraphQL type node to process
* @param {DefinitionNameToTypeNameMap} params.definitionCategoryByNameMap - The definition category by name map
* @returns {FieldTypeProperties} JSON schema representation of the type
*/
function getTypeProperties({ type, definitionCategoryByNameMap }) {
// unwrap required type
if (type.kind === astNodeKind.NON_NULL_TYPE) {
const innerType = getTypeProperties({ type: type.type, definitionCategoryByNameMap });
return {
...innerType,
required: true,
};
}

if (type.kind === astNodeKind.LIST_TYPE) {
const innerType = getTypeProperties({ type: type.type, definitionCategoryByNameMap });
return {
type: 'List',
items: [innerType],
required: false,
};
}

if (type.kind === astNodeKind.NAMED_TYPE) {
const typeName = type.name.value;
const isScalar = isBuiltInScalar({ typeName });

if (isScalar) {
return {
type: typeName,
required: false,
};
}

const definitionCategoryName = definitionCategoryByNameMap[typeName];
if (definitionCategoryName) {
return {
'$ref': `#model/definitions/${definitionCategoryName}/${typeName}`,
required: false,
};
}
}

// fallback
return {
type: 'string',
required: false,
};
}

/**
* Checks if a type name is a built-in scalar
*
* @param {object} params
* @param {string} params.typeName - The type name to check
* @returns {boolean} True if the type is a built-in scalar, false otherwise
*/
function isBuiltInScalar({ typeName }) {
return BUILT_IN_SCALAR_LIST.includes(typeName);
}

module.exports = {
mapField,
};
7 changes: 6 additions & 1 deletion reverse_engineering/mappers/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ function getMappedSchema({ schemaItems, graphName, logger, fieldsOrder }) {
rootSchemaNode: findNodesByKind({ nodes: schemaItems, kind: Kind.SCHEMA_DEFINITION })[0],
graphName,
});
const rootTypeNames = [
container.schemaRootTypes?.rootQuery || 'Query',
container.schemaRootTypes?.rootMutation || 'Mutation',
container.schemaRootTypes?.rootSubscription || 'Subscription',
];

const typeDefinitions = getTypeDefinitions({ typeDefinitions: schemaItems, fieldsOrder });
const typeDefinitions = getTypeDefinitions({ typeDefinitions: schemaItems, fieldsOrder, rootTypeNames });

return [
// TODO: remove test collection
Expand Down
58 changes: 58 additions & 0 deletions reverse_engineering/mappers/typeDefinitions/objectType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* @import {ObjectTypeDefinitionNode} from "graphql"
* @import {DefinitionNameToTypeNameMap, FieldsOrder, REObjectTypeDefinition, REPropertiesSchema} from "../../../shared/types/types"
*/

const { sortByName } = require('../../helpers/sortByName');
const { mapDirectivesUsage } = require('../directiveUsage');
const { mapField } = require('../field');

/**
* Maps object type definitions
*
* @param {object} params
* @param {ObjectTypeDefinitionNode[]} params.objectTypes - The object types
* @param {DefinitionNameToTypeNameMap} params.definitionCategoryByNameMap - The definition category by name map
* @param {FieldsOrder} params.fieldsOrder - The fields order
* @returns {REObjectTypeDefinition[]} The mapped object type definitions
*/
function getObjectTypeDefinitions({ objectTypes = [], definitionCategoryByNameMap, fieldsOrder }) {
return objectTypes.map(objectType => mapObjectType({ objectType, definitionCategoryByNameMap, fieldsOrder }));
}

/**
* Maps a single object type definition
*
* @param {object} params
* @param {ObjectTypeDefinitionNode} params.objectType - The object type to map
* @param {DefinitionNameToTypeNameMap} params.definitionCategoryByNameMap - The definition category by name map
* @param {FieldsOrder} params.fieldsOrder - The fields order
* @returns {REObjectTypeDefinition} The mapped object type definition
*/
function mapObjectType({ objectType, definitionCategoryByNameMap, fieldsOrder }) {
const properties = objectType.fields
? objectType.fields.map(field => mapField({ field, definitionCategoryByNameMap }))
: [];
const required = properties.filter(property => property.required).map(property => property.name);
const convertedProperties = sortByName({ items: properties, fieldsOrder }).reduce(
(acc, property) => {
acc[property.name] = property;
return acc;
},
/** @type {REPropertiesSchema} */ {},
);

return {
type: 'object',
name: objectType.name.value,
properties: convertedProperties,
required,
description: objectType.description?.value || '',
typeDirectives: mapDirectivesUsage({ directives: [...(objectType.directives || [])] }),
// TODO: add interfaces
};
}

module.exports = {
getObjectTypeDefinitions,
};
Loading