Skip to content

Commit

Permalink
Merge pull request #24 from BouyguesTelecom/refactor/things
Browse files Browse the repository at this point in the history
♻️ Refactor code
  • Loading branch information
arnaud authored Jul 15, 2024
2 parents 214f5a7 + 0c13615 commit f806174
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 142 deletions.
21 changes: 0 additions & 21 deletions packages/graphql-mesh/patches/@graphql-mesh+cli+0.88.5.patch

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
diff --git a/node_modules/@graphql-mesh/transform-rename/cjs/bareRename.js b/node_modules/@graphql-mesh/transform-rename/cjs/bareRename.js
index 7e430cf..76a818e 100644
index 7e430cf..3192400 100644
--- a/node_modules/@graphql-mesh/transform-rename/cjs/bareRename.js
+++ b/node_modules/@graphql-mesh/transform-rename/cjs/bareRename.js
@@ -84,6 +84,9 @@ class BareRename {
if (!mapKeys?.length)
return null;
return mapKeys.reduce((newName, mapKey) => {
+ if (!newName) {
+ return null
+ return null;
+ }
if (mapKeyIsString) {
const str = map.get(mapKey);
Expand Down
71 changes: 38 additions & 33 deletions packages/graphql-mesh/patches/@graphql-tools+stitch+9.0.3.patch
Original file line number Diff line number Diff line change
Expand Up @@ -12,96 +12,101 @@ index 5e752ad..3b487f5 100644
const currentNamedType = (0, graphql_1.getNamedType)(c.type);
if (finalNamedType.toString() !== currentNamedType.toString()) {
diff --git a/node_modules/@graphql-tools/stitch/cjs/typeCandidates.js b/node_modules/@graphql-tools/stitch/cjs/typeCandidates.js
old mode 100644
new mode 100755
index c915942..fd09fc4
index c915942..ae923fe 100644
--- a/node_modules/@graphql-tools/stitch/cjs/typeCandidates.js
+++ b/node_modules/@graphql-tools/stitch/cjs/typeCandidates.js
@@ -119,6 +119,29 @@ function buildTypes({ typeCandidates, directives, stitchingInfo, rootTypeNames,
@@ -119,6 +119,33 @@ function buildTypes({ typeCandidates, directives, stitchingInfo, rootTypeNames,
(typeof mergeTypes === 'function' && mergeTypes(typeCandidates[typeName], typeName)) ||
(Array.isArray(mergeTypes) && mergeTypes.includes(typeName)) ||
(stitchingInfo != null && typeName in stitchingInfo.mergedTypes)) {
+
+ // Custom object-interface stitching
+ // Special handling for object-interface stitching
+ const candidates = typeCandidates[typeName].sort((a, b) => a.subschema.name.localeCompare(b.subschema.name));
+ // Check for candidates with different constructors (e.g., object and interface)
+ if (candidates.some(candidate => candidate.type.constructor !== candidates[0].type.constructor)) {
+ const candidatesI = candidates.filter((c) => c.type.constructor.name === "GraphQLInterfaceType");
+ const candidatesObj = candidates.filter((c) => c.type.constructor.name === "GraphQLObjectType");
+ // If all the candidates are either interfaces or objects
+ if (candidatesI.length + candidatesObj.length === candidates.length) {
+ let finalI = candidatesI[0];
+ const otherCandidates = candidatesI.slice(1, candidatesI.length).concat(candidatesObj);
+ otherCandidates.forEach((otherCandidate) => {
+ // Add fields from other candidates to the final interface
+ Object.keys(otherCandidate.type._fields).forEach(field => {
+ if (!Object.keys(finalI.type._fields).includes(field)) {
+ if (finalI.type._fields[field] === undefined) {
+ finalI.type._fields[field] = otherCandidate.type._fields[field];
+ }
+ })
+ });
+ typeCandidates[typeName] = [finalI];
+ }
+ // Special handling for Date scalar type if it appears among the candidates
+ if (typeName === "Date") {
+ typeCandidates[typeName] = candidates.filter((c) => c.type.constructor.name === "GraphQLScalarType")
+ typeCandidates[typeName] = candidates.filter((c) => c.type.constructor.name === "GraphQLScalarType");
+ }
+ }
+
typeMap[typeName] = (0, mergeCandidates_js_1.mergeCandidates)(typeName, typeCandidates[typeName], typeMergingOptions);
}
else {
@@ -128,6 +151,58 @@ function buildTypes({ typeCandidates, directives, stitchingInfo, rootTypeNames,
@@ -128,6 +155,61 @@ function buildTypes({ typeCandidates, directives, stitchingInfo, rootTypeNames,
typeMap[typeName] = candidateSelector(typeCandidates[typeName]).type;
}
}
+
+ /**
+ * When an object implements an interface, it needs to have every property
+ * of this interface. Because interfaces have been possibly merged earlier
+ * in this file, this statement is sometimes not true anymore.
+ */
+ // Handle edge case: object implementing more than one interface
+ Object.values(typeMap).forEach((type) => {
+ if (type.constructor.name === "GraphQLObjectType") {
+ const typeInterfaces = type.getInterfaces()
+
+ if (typeInterfaces.length !== 0) {
+ type._fields = type.getFields()
+
+ typeInterfaces.forEach(i => {
+ const iFields = typeMap[i.name].getFields()
+ Object.keys(iFields).forEach(keyName => {
+ type._fields[keyName] = iFields[keyName]
+ })
+ })
+ }
+ const typeInterfaces = type.getInterfaces();
+
+ if (typeInterfaces.length >= 2) {
+ let uniqueFields = {}
+ let duplicateFields = {}
+ let uniqueFields = {};
+ let duplicateFields = {};
+
+ typeInterfaces.forEach(i => {
+ const iFields = typeMap[i.name].getFields()
+ const iFields = typeMap[i.name].getFields();
+ Object.keys(iFields).forEach(keyName => {
+ if (uniqueFields[keyName] === undefined) {
+ uniqueFields[keyName] = "defined"
+ uniqueFields[keyName] = "defined";
+ }
+ else {
+ duplicateFields[keyName] = iFields[keyName]
+ duplicateFields[keyName] = iFields[keyName];
+ }
+ })
+ })
+ // Ensure duplicate fields are consistent across implementations
+ Object.keys(duplicateFields).forEach(field => {
+ if (type.getFields()[field] !== undefined) {
+ type._fields[field] = duplicateFields[field]
+ type._fields[field] = duplicateFields[field];
+ }
+ typeInterfaces.forEach(i => {
+ const iFields = typeMap[i.name].getFields()
+ const iFields = typeMap[i.name].getFields();
+
+ if (iFields[field] !== undefined) {
+ typeMap[i.name]._fields[field] = duplicateFields[field]
+ typeMap[i.name]._fields[field] = duplicateFields[field];
+ }
+ })
+ })
+ }
+ }
+ });
+ // Ensure object types implement all fields from their interfaces
+ Object.values(typeMap).forEach((type) => {
+ if (type.constructor.name === "GraphQLObjectType") {
+ const typeInterfaces = type.getInterfaces();
+
+ if (typeInterfaces.length !== 0) {
+ type._fields = type.getFields();
+
+ typeInterfaces.forEach(i => {
+ const iFields = typeMap[i.name].getFields();
+ Object.keys(iFields).forEach(keyName => {
+ type._fields[keyName] = iFields[keyName];
+ })
+ })
+ }
+ }
+ })
+
return (0, utils_1.rewireTypes)(typeMap, directives);
}
Expand Down
24 changes: 2 additions & 22 deletions packages/graphql-mesh/patches/@omnigraph+openapi+0.97.5.patch
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
diff --git a/node_modules/@omnigraph/openapi/cjs/getJSONSchemaOptionsFromOpenAPIOptions.js b/node_modules/@omnigraph/openapi/cjs/getJSONSchemaOptionsFromOpenAPIOptions.js
index e815992..e7a949c 100644
index e815992..9084b8f 100644
--- a/node_modules/@omnigraph/openapi/cjs/getJSONSchemaOptionsFromOpenAPIOptions.js
+++ b/node_modules/@omnigraph/openapi/cjs/getJSONSchemaOptionsFromOpenAPIOptions.js
@@ -31,6 +31,19 @@ async function getJSONSchemaOptionsFromOpenAPIOptions(name, { source, fallbackFo
readFileOrUrl: readFileOrUrlForJsonMachete,
debugLogFn: logger.debug.bind(logger),
}));
+ // Add a "properties" key if a schema of object type doesn't have one
+ const schemas = oasOrSwagger.components?.schemas
+ for (const schemaName in schemas) {
+ try {
+ if (!JSON.stringify(schemas[schemaName]).includes('"properties"') &&
+ schemas[schemaName].type === "object") {
+ schemas[schemaName]["properties"] = {"_null": {}}
+ }
+ }
+ catch (e) {
+ // ignore
+ }
+ }
}
else {
oasOrSwagger = await (0, json_machete_1.dereferenceObject)(source, {
@@ -278,7 +291,7 @@ async function getJSONSchemaOptionsFromOpenAPIOptions(name, { source, fallbackFo
@@ -278,7 +278,7 @@ async function getJSONSchemaOptionsFromOpenAPIOptions(name, { source, fallbackFo
operationConfig.headers = operationConfig.headers || {};
operationConfig.headers.Accept = methodObj.produces.join(', ');
}
Expand Down
6 changes: 2 additions & 4 deletions packages/graphql-mesh/patches/json-machete+0.97.1.patch
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
diff --git a/node_modules/json-machete/cjs/healJSONSchema.js b/node_modules/json-machete/cjs/healJSONSchema.js
old mode 100644
new mode 100755
index 5d7e615..7d4ed0c
index 5d7e615..88ae5fb 100644
--- a/node_modules/json-machete/cjs/healJSONSchema.js
+++ b/node_modules/json-machete/cjs/healJSONSchema.js
@@ -40,12 +40,28 @@ exports.AnySchema = {
Expand Down Expand Up @@ -39,7 +37,7 @@ index 5d7e615..7d4ed0c
subSchema.enum) {
- debugLogFn?.(`${path} has a pattern or maxLength or minLength or enum but no title. Setting it to ${pathBasedName}`);
- subSchema.title = pathBasedName;
+ // Change enum names to be able to merge them
+ // Change the default enum names to be able to merge them
+ const newEnumName = `ENUM_${maybeDefinitionBasedPath.split('/')[maybeDefinitionBasedPath.split('/').length-1]}`
+ debugLogFn?.(`${path} has a enum but no title. Setting it to ${newEnumName}`);
+ subSchema.title = newEnumName;
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql-mesh/scripts/download-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const downSwaggerFromUrl = async (url: string | undefined, index: string): Promi
importFn: (mod) => import(mod),
logger
})
let fileName = getFileName(url) || `${index}-${url.split('/').pop()}`
let fileName = `${index}-${getFileName(url)}`
if (!fileName.endsWith('.json')) {
fileName += '.json'
}
Expand Down
92 changes: 80 additions & 12 deletions packages/graphql-mesh/utils/ConfigFromSwaggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ export default class ConfigFromSwaggers {
this.specs = this.swaggers.map(
(swagger) => <Spec>JSON.parse(readFileSync(swagger, { encoding: 'utf-8' }))
)
/**
* The following code builds a comprehensive catalog of all operations defined in a list of Swagger specifications.
* The resulting catalog is an object where:
* - Keys are operation paths (e.g. "foo/bar/endpoint").
* - Values are objects with details about each operation, including:
* - operationIds: a list of operation IDs corresponding to this path.
* - type: the type returned by the 200 response of this path.
* - swaggers: a list of Swagger definitions where this path is present.
*/
this.catalog = this.specs.reduce((acc, spec, i) => {
Object.keys(spec.paths).forEach((path) => {
const query = spec.paths[path]?.get
Expand All @@ -47,7 +56,29 @@ export default class ConfigFromSwaggers {
}, {} as Catalog)
}

getInterfacesWithChildren() {
/**
* Extracts and returns all available schema types from the Swagger specifications.
*
* @returns {string[]} An array of schema type names.
*
* This function flattens the list of schemas from all Swagger specifications
* and extracts the keys (schema names) from the components' schemas.
*/
getAvailableTypes(): string[] {
return this.specs.flatMap((spec) => Object.keys(spec.components?.schemas ?? {}))
}

/**
* Identifies and returns a map of interface along with their child types.
*
* @returns {Record<string, string[]>} An object where keys are interface names
* and values are arrays of child schema names.
*
* This function iterates through all Swagger specifications to find schemas that
* use discriminators. For each schema with a discriminator, it collects the mapping
* of the discriminator and associates child schemas to the parent schema.
*/
getInterfacesWithChildren(): Record<string, string[]> {
this.specs.forEach((s) => {
const { schemas } = s.components || {}
const entries = Object.entries(schemas || {}).filter(([_, value]) =>
Expand Down Expand Up @@ -77,26 +108,37 @@ export default class ConfigFromSwaggers {
return this.interfacesWithChildren
}

/**
* Creates and returns GraphQL type definitions and resolvers based on the Swagger specifications.
* The function processes each Swagger specification and extracts or generates GraphQL types and resolvers.
*
* @returns {ConfigExtension} An object containing the type definitions and resolvers.
*
* This function supports configurations that allow for schema renaming based on the Swagger version.
*/
createTypeDefsAndResolvers() {
if (this.config.sources) {
this.specs.forEach((spec, index) => {
// Suffix each schema name by the swagger version if there is a "rename" transform
// Apply naming transformations if specified in the configuration
if (
spec.components &&
this.config.sources[index]?.transforms?.find(
(transform) => transform.rename !== undefined
)
) {
// Extract the major version from the Swagger specification
const xVersion = spec.info.version.split('.')[0]
// Create a new object to store schemas with version suffixes
const schemasWithSuffix = {}
// Suffix each schema name with the major version number
Object.entries(spec.components.schemas).forEach(([key, schema]) => {
schemasWithSuffix[`${key}_v${xVersion}`] = schema
})
// Replace the original schemas with the suffixed schemas
spec.components.schemas = schemasWithSuffix
}
})
}
const availableTypes = this.getAvailableTypes(this.specs)
const typeDefs = /* GraphQL */ `
type LinkItem {
rel: String
Expand All @@ -107,14 +149,21 @@ export default class ConfigFromSwaggers {
action: String
}
`

const availableTypes = this.getAvailableTypes()
const interfacesWithChildren = this.getInterfacesWithChildren()
const catalog = this.catalog
const config = this.config

// Reduce over the specifications to generate and accumulate type definitions and resolvers
return this.specs.reduce(
(acc, spec) => {
const { typeDefs, resolvers } = generateTypeDefsAndResolversFromSwagger(
spec,
availableTypes,
this.getInterfacesWithChildren(),
this.catalog,
this.config
interfacesWithChildren,
catalog,
config
)
acc.typeDefs += typeDefs
acc.resolvers = mergeObjects(acc.resolvers, resolvers)
Expand All @@ -124,6 +173,15 @@ export default class ConfigFromSwaggers {
)
}

/**
* Generates and returns an array of OpenAPI sources configured for the project.
* Each source includes a name, handler configurations, and possible transformations.
*
* @returns {OpenApiSource[]} An array of configured OpenAPI sources.
*
* This function maps over the `this.swaggers` array and constructs an object for each source,
* incorporating relevant configurations such as endpoint and headers.
*/
getOpenApiSources() {
return (
this.swaggers.map((source) => ({
Expand All @@ -145,7 +203,14 @@ export default class ConfigFromSwaggers {
)
}

// Get sources that are not openapi
/**
* Filters and returns an array of sources that are not OpenAPI sources from the configuration.
*
* @returns {OtherSource[]} An array of sources that do not use OpenAPI handlers.
*
* This function checks the `this.config.sources` array and filters out any source
* that has an OpenAPI handler configuration.
*/
getOtherSources() {
return (
this.config.sources?.filter(
Expand All @@ -154,7 +219,14 @@ export default class ConfigFromSwaggers {
)
}

// Create Mesh config
/**
* Constructs and returns a complete Mesh configuration object based on the Swagger specifications and custom sources.
*
* @returns {MeshConfig} An object containing the default configuration, additional type definitions, resolvers, and sources.
*
* This function integrates type definitions and resolvers generated from the Swagger specifications
* and combines OpenAPI sources with other custom sources from the configuration.
*/
getMeshConfigFromSwaggers(): {
defaultConfig: any
additionalTypeDefs: string
Expand All @@ -169,8 +241,4 @@ export default class ConfigFromSwaggers {
sources: [...this.getOpenApiSources(), ...this.getOtherSources()]
}
}

// Get all schema names from all swaggers
getAvailableTypes = (specs: Spec[]) =>
specs.flatMap((spec) => Object.keys(spec.components?.schemas ?? {}))
}
Loading

0 comments on commit f806174

Please sign in to comment.