UNPKG

openapi-to-graphql-harshith

Version:

Generates a GraphQL schema for a given OpenAPI Specification (OAS)

1 lines 406 kB
{"version":3,"file":"index.mjs","sources":["../../dist/openapi-to-graphql/src/types/graphql.js","../../dist/openapi-to-graphql/src/types/operation.js","../../dist/openapi-to-graphql/src/utils.js","../../dist/openapi-to-graphql/src/oas_3_tools.js","../../dist/openapi-to-graphql/src/resolver_builder.js","../../dist/openapi-to-graphql/src/preprocessor.js","../../dist/openapi-to-graphql/src/schema_builder.js","../../dist/openapi-to-graphql/src/graphql_tools.js","../../dist/openapi-to-graphql/src/auth_builder.js","../../dist/openapi-to-graphql/src/index.js"],"sourcesContent":["// Copyright IBM Corp. 2018. All Rights Reserved.\n// Node module: openapi-to-graphql\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\nexport var GraphQLOperationType;\n(function (GraphQLOperationType) {\n GraphQLOperationType[GraphQLOperationType[\"Query\"] = 0] = \"Query\";\n GraphQLOperationType[GraphQLOperationType[\"Mutation\"] = 1] = \"Mutation\";\n GraphQLOperationType[GraphQLOperationType[\"Subscription\"] = 2] = \"Subscription\";\n})(GraphQLOperationType || (GraphQLOperationType = {}));\n","// Copyright IBM Corp. 2018. All Rights Reserved.\n// Node module: openapi-to-graphql\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\nexport var TargetGraphQLType;\n(function (TargetGraphQLType) {\n // scalars\n TargetGraphQLType[\"string\"] = \"string\";\n TargetGraphQLType[\"integer\"] = \"integer\";\n TargetGraphQLType[\"float\"] = \"float\";\n TargetGraphQLType[\"boolean\"] = \"boolean\";\n TargetGraphQLType[\"id\"] = \"id\";\n TargetGraphQLType[\"bigint\"] = \"bigint\";\n TargetGraphQLType[\"upload\"] = \"upload\";\n // JSON\n TargetGraphQLType[\"json\"] = \"json\";\n // non-scalars\n TargetGraphQLType[\"object\"] = \"object\";\n TargetGraphQLType[\"list\"] = \"list\";\n TargetGraphQLType[\"enum\"] = \"enum\";\n TargetGraphQLType[\"anyOfObject\"] = \"anyOfObject\";\n TargetGraphQLType[\"oneOfUnion\"] = \"oneOfUnion\";\n})(TargetGraphQLType || (TargetGraphQLType = {}));\n","// Copyright IBM Corp. 2018. All Rights Reserved.\n// Node module: openapi-to-graphql\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\nexport var MitigationTypes;\n(function (MitigationTypes) {\n /**\n * Problems with the OAS\n *\n * Should be caught by the module oas-validator\n */\n MitigationTypes[\"INVALID_OAS\"] = \"INVALID_OAS\";\n MitigationTypes[\"UNNAMED_PARAMETER\"] = \"UNNAMED_PARAMETER\";\n // General problems\n MitigationTypes[\"AMBIGUOUS_UNION_MEMBERS\"] = \"AMBIGUOUS_UNION_MEMBERS\";\n MitigationTypes[\"CANNOT_GET_FIELD_TYPE\"] = \"CANNOT_GET_FIELD_TYPE\";\n MitigationTypes[\"COMBINE_SCHEMAS\"] = \"COMBINE_SCHEMAS\";\n MitigationTypes[\"DUPLICATE_FIELD_NAME\"] = \"DUPLICATE_FIELD_NAME\";\n MitigationTypes[\"DUPLICATE_LINK_KEY\"] = \"DUPLICATE_LINK_KEY\";\n MitigationTypes[\"INVALID_HTTP_METHOD\"] = \"INVALID_HTTP_METHOD\";\n MitigationTypes[\"INPUT_UNION\"] = \"INPUT_UNION\";\n MitigationTypes[\"MISSING_RESPONSE_SCHEMA\"] = \"MISSING_RESPONSE_SCHEMA\";\n MitigationTypes[\"MISSING_SCHEMA\"] = \"MISSING_SCHEMA\";\n MitigationTypes[\"MULTIPLE_RESPONSES\"] = \"MULTIPLE_RESPONSES\";\n MitigationTypes[\"NON_APPLICATION_JSON_SCHEMA\"] = \"NON_APPLICATION_JSON_SCHEMA\";\n MitigationTypes[\"OBJECT_MISSING_PROPERTIES\"] = \"OBJECT_MISSING_PROPERTIES\";\n MitigationTypes[\"UNKNOWN_TARGET_TYPE\"] = \"UNKNOWN_TARGET_TYPE\";\n MitigationTypes[\"UNRESOLVABLE_SCHEMA\"] = \"UNRESOLVABLE_SCHEMA\";\n MitigationTypes[\"UNSUPPORTED_HTTP_SECURITY_SCHEME\"] = \"UNSUPPORTED_HTTP_SECURITY_SCHEME\";\n MitigationTypes[\"UNSUPPORTED_JSON_SCHEMA_KEYWORD\"] = \"UNSUPPORTED_JSON_SCHEMA_KEYWORD\";\n MitigationTypes[\"CALLBACKS_MULTIPLE_OPERATION_OBJECTS\"] = \"CALLBACKS_MULTIPLE_OPERATION_OBJECTS\";\n // Links\n MitigationTypes[\"AMBIGUOUS_LINK\"] = \"AMBIGUOUS_LINK\";\n MitigationTypes[\"LINK_NAME_COLLISION\"] = \"LINK_NAME_COLLISION\";\n MitigationTypes[\"UNRESOLVABLE_LINK\"] = \"UNRESOLVABLE_LINK\";\n // Multiple OAS\n MitigationTypes[\"DUPLICATE_OPERATIONID\"] = \"DUPLICATE_OPERATIONID\";\n MitigationTypes[\"DUPLICATE_SECURITY_SCHEME\"] = \"DUPLICATE_SECURITY_SCHEME\";\n MitigationTypes[\"MULTIPLE_OAS_SAME_TITLE\"] = \"MULTIPLE_OAS_SAME_TITLE\";\n // Options\n MitigationTypes[\"CUSTOM_RESOLVER_UNKNOWN_OAS\"] = \"CUSTOM_RESOLVER_UNKNOWN_OAS\";\n MitigationTypes[\"CUSTOM_RESOLVER_UNKNOWN_PATH_METHOD\"] = \"CUSTOM_RESOLVER_UNKNOWN_PATH_METHOD\";\n MitigationTypes[\"LIMIT_ARGUMENT_NAME_COLLISION\"] = \"LIMIT_ARGUMENT_NAME_COLLISION\";\n // Miscellaneous\n MitigationTypes[\"OAUTH_SECURITY_SCHEME\"] = \"OAUTH_SECURITY_SCHEME\";\n})(MitigationTypes || (MitigationTypes = {}));\nexport const mitigations = {\n /**\n * Problems with the OAS\n *\n * Should be caught by the module oas-validator\n */\n INVALID_OAS: 'Ignore issue and continue.',\n UNNAMED_PARAMETER: 'Ignore parameter.',\n // General problems\n AMBIGUOUS_UNION_MEMBERS: 'Ignore issue and continue.',\n CANNOT_GET_FIELD_TYPE: 'Ignore field and continue.',\n COMBINE_SCHEMAS: 'Ignore combine schema keyword and continue.',\n DUPLICATE_FIELD_NAME: 'Ignore field and maintain preexisting field.',\n DUPLICATE_LINK_KEY: 'Ignore link and maintain preexisting link.',\n INPUT_UNION: 'The data will be stored in an arbitrary JSON type.',\n INVALID_HTTP_METHOD: 'Ignore operation and continue.',\n MISSING_RESPONSE_SCHEMA: 'Ignore operation.',\n MISSING_SCHEMA: 'Use arbitrary JSON type.',\n MULTIPLE_RESPONSES: 'Select first response object with successful status code (200-299).',\n NON_APPLICATION_JSON_SCHEMA: 'Ignore schema',\n OBJECT_MISSING_PROPERTIES: 'The (sub-)object will be stored in an arbitrary JSON type.',\n UNKNOWN_TARGET_TYPE: 'The data will be stored in an arbitrary JSON type.',\n UNRESOLVABLE_SCHEMA: 'Ignore and continue. May lead to unexpected behavior.',\n UNSUPPORTED_HTTP_SECURITY_SCHEME: 'Ignore security scheme.',\n UNSUPPORTED_JSON_SCHEMA_KEYWORD: 'Ignore keyword and continue.',\n CALLBACKS_MULTIPLE_OPERATION_OBJECTS: 'Select arbitrary operation object',\n // Links\n AMBIGUOUS_LINK: `Use first occurance of '#/'.`,\n LINK_NAME_COLLISION: 'Ignore link and maintain preexisting field.',\n UNRESOLVABLE_LINK: 'Ignore link.',\n // Multiple OAS\n DUPLICATE_OPERATIONID: 'Ignore operation and maintain preexisting operation.',\n DUPLICATE_SECURITY_SCHEME: 'Ignore security scheme and maintain preexisting scheme.',\n MULTIPLE_OAS_SAME_TITLE: 'Ignore issue and continue.',\n // Options\n CUSTOM_RESOLVER_UNKNOWN_OAS: 'Ignore this set of custom resolvers.',\n CUSTOM_RESOLVER_UNKNOWN_PATH_METHOD: 'Ignore this set of custom resolvers.',\n LIMIT_ARGUMENT_NAME_COLLISION: `Do not override existing 'limit' argument.`,\n // Miscellaneous\n OAUTH_SECURITY_SCHEME: `Do not create OAuth viewer. OAuth support is provided using the 'tokenJSONpath' option.`\n};\n/**\n * Utilities that are specific to OpenAPI-to-GraphQL\n */\nexport function handleWarning({ mitigationType, message, mitigationAddendum, path, data, log }) {\n const mitigation = mitigations[mitigationType];\n const warning = {\n type: mitigationType,\n message,\n mitigation: mitigationAddendum\n ? `${mitigation} ${mitigationAddendum}`\n : mitigation\n };\n if (path) {\n warning['path'] = path;\n }\n if (data.options.strict) {\n throw new Error(`${warning.type} - ${warning.message}`);\n }\n else {\n const output = `Warning: ${warning.message} - ${warning.mitigation}`;\n if (typeof log === 'function') {\n log(output);\n }\n else {\n console.log(output);\n }\n data.options.report.warnings.push(warning);\n }\n}\n// Code provided by codename- from StackOverflow\n// Link: https://stackoverflow.com/a/29622653\nexport function sortObject(o) {\n return Object.keys(o)\n .sort()\n .reduce((r, k) => ((r[k] = o[k]), r), {});\n}\n/**\n * Finds the common property names between two objects\n */\nexport function getCommonPropertyNames(object1, object2) {\n return Object.keys(object1).filter((propertyName) => {\n return propertyName in object2;\n });\n}\n","// Copyright IBM Corp. 2018. All Rights Reserved.\n// Node module: openapi-to-graphql\n// This file is licensed under the MIT License.\n// License text available at https://opensource.org/licenses/MIT\nimport { TargetGraphQLType } from './types/operation';\n// Imports:\nimport * as Swagger2OpenAPI from 'swagger2openapi';\nimport * as OASValidator from 'oas-validator';\nimport debug from 'debug';\nimport { handleWarning, MitigationTypes } from './utils';\nimport * as jsonptr from 'json-ptr';\nimport * as pluralize from 'pluralize';\nconst httpLog = debug('http');\nconst preprocessingLog = debug('preprocessing');\nconst translationLog = debug('translation');\n// OAS constants\nexport var HTTP_METHODS;\n(function (HTTP_METHODS) {\n HTTP_METHODS[\"get\"] = \"get\";\n HTTP_METHODS[\"put\"] = \"put\";\n HTTP_METHODS[\"post\"] = \"post\";\n HTTP_METHODS[\"patch\"] = \"patch\";\n HTTP_METHODS[\"delete\"] = \"delete\";\n HTTP_METHODS[\"options\"] = \"options\";\n HTTP_METHODS[\"head\"] = \"head\";\n})(HTTP_METHODS || (HTTP_METHODS = {}));\nexport const SUCCESS_STATUS_RX = /2[0-9]{2}|2XX/;\nexport var OAS_GRAPHQL_EXTENSIONS;\n(function (OAS_GRAPHQL_EXTENSIONS) {\n OAS_GRAPHQL_EXTENSIONS[\"TypeName\"] = \"x-graphql-type-name\";\n OAS_GRAPHQL_EXTENSIONS[\"FieldName\"] = \"x-graphql-field-name\";\n OAS_GRAPHQL_EXTENSIONS[\"EnumMapping\"] = \"x-graphql-enum-mapping\";\n})(OAS_GRAPHQL_EXTENSIONS || (OAS_GRAPHQL_EXTENSIONS = {}));\n/**\n * Given an HTTP method, convert it to the HTTP_METHODS enum\n */\nexport function methodToHttpMethod(method) {\n switch (method.toLowerCase()) {\n case 'get':\n return HTTP_METHODS.get;\n case 'put':\n return HTTP_METHODS.put;\n case 'post':\n return HTTP_METHODS.post;\n case 'patch':\n return HTTP_METHODS.patch;\n case 'delete':\n return HTTP_METHODS.delete;\n case 'options':\n return HTTP_METHODS.options;\n case 'head':\n return HTTP_METHODS.head;\n default:\n throw new Error(`Invalid HTTP method '${method}'`);\n }\n}\nexport function isOas2(spec) {\n return typeof spec.swagger === 'string' && /^2/.test(spec.swagger);\n}\nexport function isOas3(spec) {\n return typeof spec.openapi === 'string' && /^3/.test(spec.openapi);\n}\n/**\n * Resolves on a validated OAS 3 for the given spec (OAS 2 or OAS 3), or rejects\n * if errors occur.\n */\nexport async function getValidOAS3(spec, oasValidatorOptions, swagger2OpenAPIOptions) {\n // CASE: translate\n if (isOas2(spec)) {\n preprocessingLog(`Received Swagger - going to translate to OpenAPI Specification...`);\n try {\n const { openapi } = await Swagger2OpenAPI.convertObj(spec, swagger2OpenAPIOptions);\n return openapi;\n }\n catch (error) {\n throw new Error(`Could not convert Swagger '${spec.info.title}' to OpenAPI Specification. ${error.message}`);\n }\n // CASE: validate\n }\n else if (isOas3(spec)) {\n preprocessingLog(`Received OpenAPI Specification - going to validate...`);\n await OASValidator.validate(spec, oasValidatorOptions);\n }\n else {\n throw new Error(`Invalid specification provided`);\n }\n return spec;\n}\n/**\n * Counts the number of operations in an OAS.\n */\nexport function countOperations(oas) {\n let numOps = 0;\n for (let path in oas.paths) {\n for (let method in oas.paths[path]) {\n if (isHttpMethod(method)) {\n numOps++;\n if (oas.paths[path][method].callbacks) {\n for (let cbName in oas.paths[path][method].callbacks) {\n for (let cbPath in oas.paths[path][method].callbacks[cbName]) {\n numOps++;\n }\n }\n }\n }\n }\n }\n return numOps;\n}\n/**\n * Counts the number of operations that translate to queries in an OAS.\n */\nexport function countOperationsQuery(oas) {\n let numOps = 0;\n for (let path in oas.paths) {\n for (let method in oas.paths[path]) {\n if (isHttpMethod(method) && method.toLowerCase() === HTTP_METHODS.get) {\n numOps++;\n }\n }\n }\n return numOps;\n}\n/**\n * Counts the number of operations that translate to mutations in an OAS.\n */\nexport function countOperationsMutation(oas) {\n let numOps = 0;\n for (let path in oas.paths) {\n for (let method in oas.paths[path]) {\n if (isHttpMethod(method) && method.toLowerCase() !== HTTP_METHODS.get) {\n numOps++;\n }\n }\n }\n return numOps;\n}\n/**\n * Counts the number of operations that translate to subscriptions in an OAS.\n */\nexport function countOperationsSubscription(oas) {\n let numOps = 0;\n for (let path in oas.paths) {\n for (let method in oas.paths[path]) {\n if (isHttpMethod(method) &&\n method.toLowerCase() !== HTTP_METHODS.get &&\n oas.paths[path][method].callbacks) {\n for (let cbName in oas.paths[path][method].callbacks) {\n for (let cbPath in oas.paths[path][method].callbacks[cbName]) {\n numOps++;\n }\n }\n }\n }\n }\n return numOps;\n}\n/**\n * Counts the number of operations with a payload definition in an OAS.\n */\nexport function countOperationsWithPayload(oas) {\n let numOps = 0;\n for (let path in oas.paths) {\n for (let method in oas.paths[path]) {\n if (isHttpMethod(method) &&\n typeof oas.paths[path][method].requestBody === 'object') {\n numOps++;\n }\n }\n }\n return numOps;\n}\n/**\n * Resolves the given reference in the given object.\n */\nexport function resolveRef(ref, oas) {\n return jsonptr.JsonPointer.get(oas, ref);\n}\n/**\n * Recursively traverse a schema and resolve allOf by appending the data to the\n * parent schema\n */\nexport function resolveAllOf(schema, references, data, oas) {\n // Dereference schema\n if ('$ref' in schema && typeof schema.$ref === 'string') {\n if (schema.$ref in references) {\n return references[schema.$ref];\n }\n const reference = schema.$ref;\n schema = resolveRef(schema.$ref, oas);\n references[reference] = schema;\n }\n /**\n * TODO: Is there a better method to copy the schema?\n *\n * Copy the schema\n */\n const collapsedSchema = JSON.parse(JSON.stringify(schema));\n // Resolve allOf\n if (Array.isArray(collapsedSchema.allOf)) {\n collapsedSchema.allOf.forEach((memberSchema) => {\n const collapsedMemberSchema = resolveAllOf(memberSchema, references, data, oas);\n // Collapse type if applicable\n if (collapsedMemberSchema.type) {\n if (!collapsedSchema.type) {\n collapsedSchema.type = collapsedMemberSchema.type;\n // Check for incompatible schema type\n }\n else if (collapsedSchema.type !== collapsedMemberSchema.type) {\n handleWarning({\n mitigationType: MitigationTypes.UNRESOLVABLE_SCHEMA,\n message: `Resolving 'allOf' field in schema '${JSON.stringify(collapsedSchema)}' ` + `results in incompatible schema type.`,\n data,\n log: preprocessingLog\n });\n }\n }\n // Collapse properties if applicable\n if ('properties' in collapsedMemberSchema) {\n if (!('properties' in collapsedSchema)) {\n collapsedSchema.properties = {};\n }\n Object.entries(collapsedMemberSchema.properties).forEach(([propertyName, property]) => {\n if (!(propertyName in collapsedSchema.properties)) {\n collapsedSchema.properties[propertyName] = property;\n // Conflicting property\n }\n else {\n handleWarning({\n mitigationType: MitigationTypes.UNRESOLVABLE_SCHEMA,\n message: `Resolving 'allOf' field in schema '${JSON.stringify(collapsedSchema)}' ` +\n `results in incompatible property field '${propertyName}'.`,\n data,\n log: preprocessingLog\n });\n }\n });\n }\n // Collapse oneOf if applicable\n if ('oneOf' in collapsedMemberSchema) {\n if (!('oneOf' in collapsedSchema)) {\n collapsedSchema.oneOf = [];\n }\n collapsedMemberSchema.oneOf.forEach((oneOfProperty) => {\n collapsedSchema.oneOf.push(oneOfProperty);\n });\n }\n // Collapse anyOf if applicable\n if ('anyOf' in collapsedMemberSchema) {\n if (!('anyOf' in collapsedSchema)) {\n collapsedSchema.anyOf = [];\n }\n collapsedMemberSchema.anyOf.forEach((anyOfProperty) => {\n collapsedSchema.anyOf.push(anyOfProperty);\n });\n }\n // Collapse required if applicable\n if ('required' in collapsedMemberSchema) {\n if (!('required' in collapsedSchema)) {\n collapsedSchema.required = [];\n }\n collapsedMemberSchema.required.forEach((requiredProperty) => {\n if (!collapsedSchema.required.includes(requiredProperty)) {\n collapsedSchema.required.push(requiredProperty);\n }\n });\n }\n });\n }\n return collapsedSchema;\n}\n/**\n * Returns the base URL to use for the given operation.\n */\nexport function getBaseUrl(operation) {\n // Check for servers:\n if (!Array.isArray(operation.servers) || operation.servers.length === 0) {\n throw new Error(`No servers defined for operation '${operation.operationString}'`);\n }\n // Check for local servers\n if (Array.isArray(operation.servers) && operation.servers.length > 0) {\n const url = buildUrl(operation.servers[0]);\n if (Array.isArray(operation.servers) && operation.servers.length > 1) {\n httpLog(`Warning: Randomly selected first server '${url}'`);\n }\n return url.replace(/\\/$/, '');\n }\n const oas = operation.oas;\n if (Array.isArray(oas.servers) && oas.servers.length > 0) {\n const url = buildUrl(oas.servers[0]);\n if (Array.isArray(oas.servers) && oas.servers.length > 1) {\n httpLog(`Warning: Randomly selected first server '${url}'`);\n }\n return url.replace(/\\/$/, '');\n }\n throw new Error('Cannot find a server to call');\n}\n/**\n * Returns the default URL for a given OAS server object.\n */\nfunction buildUrl(server) {\n let url = server.url;\n // Replace with variable defaults, if applicable\n if (typeof server.variables === 'object' &&\n Object.keys(server.variables).length > 0) {\n for (let variableKey in server.variables) {\n // TODO: check for default? Would be invalid OAS\n url = url.replace(`{${variableKey}}`, server.variables[variableKey].default.toString());\n }\n }\n return url;\n}\n/**\n * Returns object/array/scalar where all object keys (if applicable) are\n * sanitized.\n */\nexport function sanitizeObjectKeys(obj, // obj does not necessarily need to be an object\ncaseStyle = CaseStyle.camelCase) {\n const cleanKeys = (obj) => {\n // Case: no (response) data\n if (obj === null || typeof obj === 'undefined') {\n return null;\n // Case: array\n }\n else if (Array.isArray(obj)) {\n return obj.map(cleanKeys);\n // Case: object\n }\n else if (typeof obj === 'object') {\n const res = {};\n for (const key in obj) {\n const saneKey = sanitize(key, caseStyle);\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n res[saneKey] = cleanKeys(obj[key]);\n }\n }\n return res;\n // Case: scalar\n }\n else {\n return obj;\n }\n };\n return cleanKeys(obj);\n}\n/**\n * Desanitizes keys in given object by replacing them with the keys stored in\n * the given mapping.\n */\nexport function desanitizeObjectKeys(obj, mapping = {}) {\n const replaceKeys = (obj) => {\n if (obj === null) {\n return null;\n }\n else if (Array.isArray(obj)) {\n return obj.map(replaceKeys);\n }\n else if (typeof obj === 'object') {\n const res = {};\n for (let key in obj) {\n if (key in mapping) {\n const rawKey = mapping[key];\n if (Object.prototype.hasOwnProperty.call(obj, key)) {\n res[rawKey] = replaceKeys(obj[key]);\n }\n }\n else {\n res[key] = replaceKeys(obj[key]);\n }\n }\n return res;\n }\n else {\n return obj;\n }\n };\n return replaceKeys(obj);\n}\n/**\n * Returns the GraphQL type that the provided schema should be made into\n */\nexport function getSchemaTargetGraphQLType(schemaOrRef, data, oas) {\n let schema;\n if ('$ref' in schemaOrRef && typeof schemaOrRef.$ref === 'string') {\n schema = resolveRef(schemaOrRef.$ref, oas);\n }\n else {\n schema = schemaOrRef;\n }\n // TODO: Need to resolve allOf here as well.\n // CASE: Check for nested or concurrent anyOf and oneOf\n if (\n // TODO: Should also consider if the member schema contains type data\n (Array.isArray(schema.anyOf) && Array.isArray(schema.oneOf)) || // anyOf and oneOf used concurrently\n hasNestedAnyOfUsage(schema, oas) ||\n hasNestedOneOfUsage(schema, oas)) {\n handleWarning({\n mitigationType: MitigationTypes.COMBINE_SCHEMAS,\n message: `Schema '${JSON.stringify(schema)}' contains either both ` +\n `'anyOf' and 'oneOf' or nested 'anyOf' and 'oneOf' which ` +\n `is currently not supported.`,\n mitigationAddendum: `Use arbitrary JSON type instead.`,\n data,\n log: preprocessingLog\n });\n return TargetGraphQLType.json;\n }\n if (Array.isArray(schema.anyOf)) {\n return GetAnyOfTargetGraphQLType(schema, data, oas);\n }\n if (Array.isArray(schema.oneOf)) {\n return GetOneOfTargetGraphQLType(schema, data, oas);\n }\n // CASE: enum\n if (Array.isArray(schema.enum)) {\n return TargetGraphQLType.enum;\n }\n // CASE: object\n if (schema.type === 'object' || typeof schema.properties === 'object') {\n // TODO: additionalProperties is more like a flag than a type itself\n // CASE: arbitrary JSON\n if (typeof schema.additionalProperties === 'object') {\n return TargetGraphQLType.json;\n }\n else {\n return TargetGraphQLType.object;\n }\n }\n // CASE: array\n if (schema.type === 'array' || 'items' in schema) {\n return TargetGraphQLType.list;\n }\n // Special edge cases involving the schema format\n if (typeof schema.format === 'string') {\n if (schema.type === 'integer' && schema.format === 'int64') {\n return TargetGraphQLType.bigint;\n // CASE: file upload\n }\n else if (schema.type === 'string' && schema.format === 'binary') {\n return TargetGraphQLType.upload;\n // CASE: id\n }\n else if (schema.type === 'string' &&\n (schema.format === 'uuid' ||\n // Custom ID format\n (Array.isArray(data.options.idFormats) &&\n data.options.idFormats.includes(schema.format)))) {\n return TargetGraphQLType.id;\n }\n }\n switch (schema.type) {\n case 'string':\n return TargetGraphQLType.string;\n case 'number':\n return TargetGraphQLType.float;\n case 'integer':\n return TargetGraphQLType.integer;\n case 'boolean':\n return TargetGraphQLType.boolean;\n default:\n // Error: unsupported schema type\n }\n return null;\n}\n/**\n * Check to see if there are cases of nested oneOf fields in the member schemas\n *\n * We currently cannot handle complex cases of oneOf and anyOf\n */\nfunction hasNestedOneOfUsage(schema, oas) {\n // TODO: Should also consider if the member schema contains type data\n return (Array.isArray(schema.oneOf) &&\n schema.oneOf.some((memberSchemaOrRef) => {\n let memberSchema;\n if ('$ref' in memberSchemaOrRef &&\n typeof memberSchemaOrRef.$ref === 'string') {\n memberSchema = resolveRef(memberSchemaOrRef.$ref, oas);\n }\n else {\n memberSchema = memberSchemaOrRef;\n }\n return (\n /**\n * anyOf and oneOf are nested\n *\n * Nested oneOf would result in nested unions which are not allowed by\n * GraphQL\n */\n Array.isArray(memberSchema.anyOf) || Array.isArray(memberSchema.oneOf));\n }));\n}\n/**\n * Check to see if there are cases of nested anyOf fields in the member schemas\n *\n * We currently cannot handle complex cases of oneOf and anyOf\n */\nfunction hasNestedAnyOfUsage(schema, oas) {\n // TODO: Should also consider if the member schema contains type data\n return (Array.isArray(schema.anyOf) &&\n schema.anyOf.some((memberSchemaOrRef) => {\n let memberSchema;\n if ('$ref' in memberSchemaOrRef &&\n typeof memberSchemaOrRef.$ref === 'string') {\n memberSchema = resolveRef(memberSchemaOrRef.$ref, oas);\n }\n else {\n memberSchema = memberSchemaOrRef;\n }\n return (\n // anyOf and oneOf are nested\n Array.isArray(memberSchema.anyOf) || Array.isArray(memberSchema.oneOf));\n }));\n}\nfunction GetAnyOfTargetGraphQLType(schema, data, oas) {\n // Identify the type of the base schema, meaning ignoring the anyOf\n const schemaWithNoAnyOf = { ...schema };\n delete schemaWithNoAnyOf.anyOf;\n const baseTargetType = getSchemaTargetGraphQLType(schemaWithNoAnyOf, data, oas);\n // Target GraphQL types of all the member schemas\n const memberTargetTypes = [];\n schema.anyOf.forEach((memberSchema) => {\n const memberTargetType = getSchemaTargetGraphQLType(memberSchema, data, oas);\n if (memberTargetType !== null) {\n memberTargetTypes.push(memberTargetType);\n }\n });\n if (memberTargetTypes.length > 0) {\n const firstMemberTargetType = memberTargetTypes[0];\n const consistentMemberTargetTypes = memberTargetTypes.every((targetType) => {\n return targetType === firstMemberTargetType;\n });\n if (consistentMemberTargetTypes) {\n if (baseTargetType !== null) {\n if (baseTargetType === firstMemberTargetType) {\n if (baseTargetType === 'object') {\n // Base schema and member schema types are object types\n return TargetGraphQLType.anyOfObject;\n }\n else {\n // Base schema and member schema types but no object types\n return baseTargetType;\n }\n }\n else {\n // Base schema and member schema types are not consistent\n return TargetGraphQLType.json;\n }\n }\n else {\n if (firstMemberTargetType === TargetGraphQLType.object) {\n return TargetGraphQLType.anyOfObject;\n }\n else {\n return firstMemberTargetType;\n }\n }\n }\n else {\n // Member schema types are not consistent\n return TargetGraphQLType.json;\n }\n }\n else {\n // No member schema types, therefore use the base schema type\n return baseTargetType;\n }\n}\nfunction GetOneOfTargetGraphQLType(schema, data, oas) {\n // Identify the type of the base schema, meaning ignoring the oneOf\n const schemaWithNoOneOf = { ...schema };\n delete schemaWithNoOneOf.oneOf;\n const baseTargetType = getSchemaTargetGraphQLType(schemaWithNoOneOf, data, oas);\n // Target GraphQL types of all the member schemas\n const memberTargetTypes = [];\n schema.oneOf.forEach((memberSchema) => {\n const collapsedMemberSchema = resolveAllOf(memberSchema, {}, data, oas);\n const memberTargetType = getSchemaTargetGraphQLType(collapsedMemberSchema, data, oas);\n if (memberTargetType !== null) {\n memberTargetTypes.push(memberTargetType);\n }\n });\n if (memberTargetTypes.length > 0) {\n const firstMemberTargetType = memberTargetTypes[0];\n const consistentMemberTargetTypes = memberTargetTypes.every((targetType) => {\n return targetType === firstMemberTargetType;\n });\n if (consistentMemberTargetTypes) {\n if (baseTargetType !== null) {\n if (baseTargetType === firstMemberTargetType) {\n if (baseTargetType === 'object') {\n // Base schema and member schema types are object types\n return TargetGraphQLType.oneOfUnion;\n }\n else {\n // Base schema and member schema types but no object types\n return baseTargetType;\n }\n }\n else {\n // Base schema and member schema types are not consistent\n return TargetGraphQLType.json;\n }\n }\n else {\n if (firstMemberTargetType === TargetGraphQLType.object) {\n return TargetGraphQLType.oneOfUnion;\n }\n else {\n return firstMemberTargetType;\n }\n }\n }\n else {\n // Member schema types are not consistent\n return TargetGraphQLType.json;\n }\n }\n else {\n // No member schema types, therefore use the base schema type\n return baseTargetType;\n }\n}\n/**\n * Identifies common path components in the given list of paths. Returns these\n * components as well as an updated list of paths where the common prefix was\n * removed.\n */\nfunction extractBasePath(paths) {\n if (paths.length <= 1) {\n return {\n basePath: '/',\n updatedPaths: paths\n };\n }\n let basePathComponents = paths[0].split('/');\n for (let path of paths) {\n if (basePathComponents.length === 0) {\n break;\n }\n const pathComponents = path.split('/');\n for (let i = 0; i < pathComponents.length; i++) {\n if (i < basePathComponents.length) {\n if (pathComponents[i] !== basePathComponents[i]) {\n basePathComponents = basePathComponents.slice(0, i);\n }\n }\n else {\n break;\n }\n }\n }\n const updatedPaths = paths.map((path) => path.split('/').slice(basePathComponents.length).join('/'));\n let basePath = basePathComponents.length === 0 ||\n (basePathComponents.length === 1 && basePathComponents[0] === '')\n ? '/'\n : basePathComponents.join('/');\n return {\n basePath,\n updatedPaths\n };\n}\nfunction isIdParam(part) {\n return /^{.*(id|name|key).*}$/gi.test(part);\n}\nfunction isSingularParam(part, nextPart) {\n return `\\{${pluralize.singular(part)}\\}` === nextPart;\n}\n/**\n * Infers a resource name from the given URL path.\n *\n * For example, turns \"/users/{userId}/car\" into \"userCar\".\n */\nexport function inferResourceNameFromPath(path) {\n const parts = path.split('/');\n let pathNoPathParams = parts.reduce((path, part, i) => {\n if (!/{/g.test(part)) {\n if (parts[i + 1] &&\n (isIdParam(parts[i + 1]) || isSingularParam(part, parts[i + 1]))) {\n return path + capitalize(pluralize.singular(part));\n }\n else {\n return path + capitalize(part);\n }\n }\n else {\n return path;\n }\n }, '');\n return pathNoPathParams;\n}\n/**\n * Returns the request schema (if any) for the given operation,\n * a dictionary of names from different sources (if available), and whether the\n * request schema is required for the operation.\n */\nexport function getRequestSchemaAndNames(path, method, operation, oas) {\n var _a;\n let payloadContentType; // randomly selected content-type, prioritizing application/json\n let requestBodyObject; // request object\n let payloadSchema; // request schema with given content-type\n let payloadSchemaNames; // dictionary of names\n let payloadRequired = false;\n // Get request body\n const requestBodyObjectOrRef = operation === null || operation === void 0 ? void 0 : operation.requestBody;\n if (typeof requestBodyObjectOrRef === 'object' &&\n requestBodyObjectOrRef !== null) {\n // Resolve reference if applicable. Make sure we have a RequestBodyObject:\n if ('$ref' in requestBodyObjectOrRef &&\n typeof requestBodyObjectOrRef.$ref === 'string') {\n requestBodyObject = resolveRef(requestBodyObjectOrRef.$ref, oas);\n }\n else {\n requestBodyObject = requestBodyObjectOrRef;\n }\n if (typeof requestBodyObject === 'object' && requestBodyObject !== null) {\n // Determine if request body is required:\n payloadRequired =\n typeof (requestBodyObject === null || requestBodyObject === void 0 ? void 0 : requestBodyObject.required) === 'boolean'\n ? requestBodyObject === null || requestBodyObject === void 0 ? void 0 : requestBodyObject.required\n : false;\n // Determine content-type\n const content = requestBodyObject === null || requestBodyObject === void 0 ? void 0 : requestBodyObject.content;\n if (typeof content === 'object' &&\n content !== null &&\n Object.keys(content).length > 0) {\n // Prioritize content-type JSON\n if ('application/json' in content) {\n payloadContentType = 'application/json';\n }\n else if ('application/x-www-form-urlencoded' in content) {\n payloadContentType = 'application/x-www-form-urlencoded';\n }\n else {\n // Pick first (random) content type\n const randomContentType = Object.keys(content)[0];\n payloadContentType = randomContentType;\n }\n if (payloadContentType === 'application/json' ||\n payloadContentType === '*/*' ||\n payloadContentType === 'application/x-www-form-urlencoded' ||\n payloadContentType === 'multipart/form-data') {\n // Name extracted from a reference, if applicable\n let fromRef;\n // Determine payload schema\n const payloadSchemaOrRef = (_a = content === null || content === void 0 ? void 0 : content[payloadContentType]) === null || _a === void 0 ? void 0 : _a.schema;\n if (typeof payloadSchemaOrRef === 'object' &&\n payloadSchemaOrRef !== null) {\n // Resolve payload schema reference if applicable\n if ('$ref' in payloadSchemaOrRef &&\n typeof payloadSchemaOrRef.$ref === 'string') {\n fromRef = payloadSchemaOrRef.$ref.split('/').pop();\n payloadSchema = resolveRef(payloadSchemaOrRef.$ref, oas);\n }\n else {\n payloadSchema = payloadSchemaOrRef;\n }\n }\n // Determine possible schema names\n payloadSchemaNames = {\n fromExtension: payloadSchema[OAS_GRAPHQL_EXTENSIONS.TypeName],\n fromRef,\n fromSchema: payloadSchema === null || payloadSchema === void 0 ? void 0 : payloadSchema.title,\n fromPath: inferResourceNameFromPath(path)\n };\n /**\n * Edge case: if request body content-type is not application/json or\n * application/x-www-form-urlencoded, do not parse it.\n *\n * Instead, treat the request body as a black box and send it as a string\n * with the proper content-type header\n */\n }\n else {\n const saneContentTypeName = uncapitalize(payloadContentType.split('/').reduce((name, term) => {\n return name + capitalize(term);\n }));\n let description = `String represents payload of content type '${payloadContentType}'`;\n if (typeof (payloadSchema === null || payloadSchema === void 0 ? void 0 : payloadSchema.description) === 'string') {\n description += `\\n\\nOriginal top level description: '${payloadSchema.description}'`;\n }\n // Replacement schema to avoid parsing\n payloadSchema = {\n description,\n type: 'string'\n };\n // Determine possible schema names\n payloadSchemaNames = {\n fromPath: saneContentTypeName\n };\n }\n }\n }\n }\n return {\n payloadContentType,\n payloadSchema,\n payloadSchemaNames,\n payloadRequired\n };\n}\n/**\n * Returns the response schema for the given operation,\n * a successful status code, and a dictionary of names from different sources\n * (if available).\n */\nexport function getResponseSchemaAndNames(path, method, operation, oas, data, options) {\n var _a, _b, _c;\n let responseContentType; // randomly selected content-type, prioritizing application/json\n let responseObject; // response object\n let responseSchema; // response schema with given content-type\n let responseSchemaNames; // dictionary of names\n const statusCode = getResponseStatusCode(path, method, operation, oas, data);\n // Get response object\n const responseObjectOrRef = (_a = operation === null || operation === void 0 ? void 0 : operation.responses) === null || _a === void 0 ? void 0 : _a[statusCode];\n if (typeof responseObjectOrRef === 'object' && responseObjectOrRef !== null) {\n if ('$ref' in responseObjectOrRef &&\n typeof responseObjectOrRef.$ref === 'string') {\n responseObject = resolveRef(responseObjectOrRef.$ref, oas);\n }\n else {\n responseObject = responseObjectOrRef;\n }\n // Determine content-type\n if (typeof responseObject === 'object' && responseObject !== null) {\n const content = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content;\n if (typeof content === 'object' &&\n content !== null &&\n Object.keys(content).length > 0) {\n // Prioritize content-type JSON\n if ('application/json' in content) {\n responseContentType = 'application/json';\n }\n else {\n // Pick first (random) content type\n const randomContentType = Object.keys(content)[0];\n responseContentType = randomContentType;\n }\n if (responseContentType === 'application/json' ||\n responseContentType === '*/*') {\n // Name from reference, if applicable\n let fromRef;\n // Determine response schema\n const responseSchemaOrRef = (_c = (_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b[responseContentType]) === null || _c === void 0 ? void 0 : _c.schema;\n // Resolve response schema reference if applicable\n if ('$ref' in responseSchemaOrRef &&\n typeof responseSchemaOrRef.$ref === 'string') {\n fromRef = responseSchemaOrRef.$ref.split('/').pop();\n responseSchema = resolveRef(responseSchemaOrRef.$ref, oas);\n }\n else {\n responseSchema = responseSchemaOrRef;\n }\n // Determine possible schema names\n responseSchemaNames = {\n fromExtension: responseSchema[OAS_GRAPHQL_EXTENSIONS.TypeName],\n fromRef,\n fromSchema: responseSchema === null || responseSchema === void 0 ? void 0 : responseSchema.title,\n fromPath: inferResourceNameFromPath(path)\n };\n /**\n * Edge case: if response body content-type is not application/json,\n * do not parse.\n */\n }\n else {\n let description = 'Placeholder to access non-application/json response bodies';\n if (typeof (responseSchema === null || responseSchema === void 0 ? void 0 : responseSchema.description) === 'string') {\n description += `\\n\\nOriginal top level description: '${responseSchema.description}'`;\n }\n // Replacement schema to avoid parsing\n responseSchema = {\n description,\n type: 'string'\n };\n // Determine possible schema names\n responseSchemaNames = {\n fromExtension: responseSchema === null || responseSchema === void 0 ? void 0 : responseSchema[OAS_GRAPHQL_EXTENSIONS.TypeName],\n fromSchema: responseSchema === null || responseSchema === void 0 ? void 0 : responseSchema.title,\n fromPath: inferResourceNameFromPath(path)\n };\n }\n return {\n responseContentType,\n responseSchema,\n responseSchemaNames,\n statusCode\n };\n }\n }\n }\n // No response schema\n if (options.fillEmptyResponses) {\n return {\n responseSchemaNames: {\n fromPath: inferResourceNameFromPath(path)\n },\n responseSchema: {\n description: 'Placeholder to support operations with no response schema',\n type: 'string'\n }\n };\n }\n else {\n return {};\n }\n}\n/**\n * Returns a success status code for the given operation\n */\nexport function getResponseStatusCode(path, method, operation, oas, data) {\n if (typeof operation.responses === 'object' && operation.responses !== null) {\n const codes = Object.keys(operation.responses);\n const successCodes = codes.filter((code) => {\n return SUCCESS_STATUS_RX.test(code);\n });\n if (successCodes.length === 1) {\n return successCodes[0];\n }\n else if (successCodes.length > 1) {\n // Select a random success code\n handleWarning({\n mitigationType: MitigationTypes.MULTIPLE_RESPONSES,\n message: `Operation '${formatOperationString(method, path, oas.info.title)}' ` +\n `contains multiple possible successful response object ` +\n `(HTTP code 200-299 or 2XX). Only one can be chosen.`,\n mitigationAddendum: `The response object with the HTTP code ` +\n `${successCodes[0]} will be selected`,\n data,\n log: translationLog\n });\n return successCodes[0];\n }\n }\n}\n/**\n * Returns a hash containing the links in the given operation.\n */\nexport function getLinks(path, method, operation, oas, data) {\n const links = {};\n const statusCode = getResponseStatusCode(path, method, operation, oas, data);\n if (!statusCode) {\n return links;\n }\n if (typeof operation.responses === 'object') {\n const responses = operation.responses;\n if (typeof responses[statusCode] === 'object') {\n const responseObjectOrRef = responses[statusCode];\n let response;\n if ('$ref' in responseObjectOrRef &&\n typeof responseObjectOrRef.$ref === 'string') {\n response = resolveRef(responseObjectOrRef.$ref, oas);\n }\n else {\n response = responseObjectOrRef;\n }\n if (typeof response.links === 'object') {\n const epLinks = response.links;\n for (let linkKey in epLinks) {\n const linkObjectOrRef = epLinks[linkKey];\n let link;\n if ('$ref' in linkObjectOrRef &&\n typeof linkObjectOrRef.$ref === 'string') {\n link = resolveRef(linkObjectOrRef.$ref, oas);\n }\n else {\n link = linkObjectOrRef;\n }\n links[linkKey] = link;\n }\n }\n }\n }\n return links;\n}\n/**\n * Returns the list of parameters in the given operation.\n */\nexport function getParameters(path, method, operation, pathItem, oas) {\n let parameters = [];\n if (!isHttpMethod(method)) {\n translationLog(`Warning: attempted to get parameters for ${method} ${path}, ` +\n `which is not an operation.`);\n return parameters;\n }\n // First, consider parameters in Path Item Object:\n const pathParams = pathItem.parameters;\n if (Array.isArray(pathParams)) {\n const pathItemParameters = pathParams.map((p) => {\n if ('$ref' in p && typeof p.$ref === 'string') {\n // Here we know we have a parameter object:\n return resolveRef(p.$ref, oas);\n }\n else {\n // Here we know we have a parameter object:\n return p;\n }\n });\n parameters = parameters.concat(pathItemParameters);\n }\n // Second, consider parameters in Operation Object:\n const opObjectParameters = operation.parameters;\n if (Array.isArray(opObjectParameters)) {\n const operationParameters = opObjectParameters.map((p) => {\n if ('$ref' in p && typeof p.$ref === 'string') {\n // Here we know we have a parameter object:\n return resolveRef(p.$ref, oas);\n }\n else {\n // Here we know we have a parameter object:\n return p;\n }\n });\n parameters = parameters.concat(operationParameters);\n }\n return parameters;\n}\n/**\n * Returns an array of server objects for the operation at the given path and\n * method. Considers in the following order: global server definitions,\n * definitions at the path item, definitions at the operation, or