UNPKG

210 kBJavaScriptView Raw
1import { readFileOrUrlWithCache, loadFromModuleExportExpression, getInterpolatedStringFactory, getInterpolatedHeadersFactory, getHeadersObject, parseInterpolationStrings } from '@graphql-mesh/utils';
2import { GraphQLError, GraphQLObjectType, GraphQLInputObjectType, GraphQLUnionType, GraphQLList, GraphQLEnumType, GraphQLBoolean, GraphQLFloat, GraphQLInt, GraphQLString, GraphQLID, GraphQLNonNull, GraphQLSchema } from 'graphql';
3import { GraphQLJSON, GraphQLBigInt } from 'graphql-scalars';
4import { convertObj } from 'swagger2openapi';
5import { JsonPointer } from 'json-ptr';
6import { singular } from 'pluralize';
7import { JSONPath } from 'jsonpath-plus';
8import formurlencoded from 'form-urlencoded';
9import urlJoin from 'url-join';
10import deepEqual from 'deep-equal';
11import { fetchache, Request } from 'fetchache';
12
13/* eslint-disable @typescript-eslint/ban-types */
14// Copyright IBM Corp. 2018. All Rights Reserved.
15// Node module: openapi-to-graphql
16// This file is licensed under the MIT License.
17// License text available at https://opensource.org/licenses/MIT
18var GraphQLOperationType;
19(function (GraphQLOperationType) {
20 GraphQLOperationType[GraphQLOperationType["Query"] = 0] = "Query";
21 GraphQLOperationType[GraphQLOperationType["Mutation"] = 1] = "Mutation";
22 GraphQLOperationType[GraphQLOperationType["Subscription"] = 2] = "Subscription";
23})(GraphQLOperationType || (GraphQLOperationType = {}));
24
25/* eslint-disable @typescript-eslint/ban-types */
26/* eslint-disable no-sequences */
27// Copyright IBM Corp. 2018. All Rights Reserved.
28// Node module: openapi-to-graphql
29// This file is licensed under the MIT License.
30// License text available at https://opensource.org/licenses/MIT
31var MitigationTypes;
32(function (MitigationTypes) {
33 /**
34 * Problems with the OAS
35 *
36 * Should be caught by the module oas-validator
37 */
38 MitigationTypes["INVALID_OAS"] = "INVALID_OAS";
39 MitigationTypes["UNNAMED_PARAMETER"] = "UNNAMED_PARAMETER";
40 // General problems
41 MitigationTypes["AMBIGUOUS_UNION_MEMBERS"] = "AMBIGUOUS_UNION_MEMBERS";
42 MitigationTypes["CANNOT_GET_FIELD_TYPE"] = "CANNOT_GET_FIELD_TYPE";
43 MitigationTypes["COMBINE_SCHEMAS"] = "COMBINE_SCHEMAS";
44 MitigationTypes["DUPLICATE_FIELD_NAME"] = "DUPLICATE_FIELD_NAME";
45 MitigationTypes["DUPLICATE_LINK_KEY"] = "DUPLICATE_LINK_KEY";
46 MitigationTypes["INVALID_HTTP_METHOD"] = "INVALID_HTTP_METHOD";
47 MitigationTypes["INPUT_UNION"] = "INPUT_UNION";
48 MitigationTypes["MISSING_RESPONSE_SCHEMA"] = "MISSING_RESPONSE_SCHEMA";
49 MitigationTypes["MISSING_SCHEMA"] = "MISSING_SCHEMA";
50 MitigationTypes["MULTIPLE_RESPONSES"] = "MULTIPLE_RESPONSES";
51 MitigationTypes["NON_APPLICATION_JSON_SCHEMA"] = "NON_APPLICATION_JSON_SCHEMA";
52 MitigationTypes["OBJECT_MISSING_PROPERTIES"] = "OBJECT_MISSING_PROPERTIES";
53 MitigationTypes["UNKNOWN_TARGET_TYPE"] = "UNKNOWN_TARGET_TYPE";
54 MitigationTypes["UNRESOLVABLE_SCHEMA"] = "UNRESOLVABLE_SCHEMA";
55 MitigationTypes["UNSUPPORTED_HTTP_SECURITY_SCHEME"] = "UNSUPPORTED_HTTP_SECURITY_SCHEME";
56 MitigationTypes["UNSUPPORTED_JSON_SCHEMA_KEYWORD"] = "UNSUPPORTED_JSON_SCHEMA_KEYWORD";
57 MitigationTypes["CALLBACKS_MULTIPLE_OPERATION_OBJECTS"] = "CALLBACKS_MULTIPLE_OPERATION_OBJECTS";
58 // Links
59 MitigationTypes["AMBIGUOUS_LINK"] = "AMBIGUOUS_LINK";
60 MitigationTypes["LINK_NAME_COLLISION"] = "LINK_NAME_COLLISION";
61 MitigationTypes["UNRESOLVABLE_LINK"] = "UNRESOLVABLE_LINK";
62 // Multiple OAS
63 MitigationTypes["DUPLICATE_OPERATIONID"] = "DUPLICATE_OPERATIONID";
64 MitigationTypes["DUPLICATE_SECURITY_SCHEME"] = "DUPLICATE_SECURITY_SCHEME";
65 MitigationTypes["MULTIPLE_OAS_SAME_TITLE"] = "MULTIPLE_OAS_SAME_TITLE";
66 // Options
67 MitigationTypes["CUSTOM_RESOLVER_UNKNOWN_OAS"] = "CUSTOM_RESOLVER_UNKNOWN_OAS";
68 MitigationTypes["CUSTOM_RESOLVER_UNKNOWN_PATH_METHOD"] = "CUSTOM_RESOLVER_UNKNOWN_PATH_METHOD";
69 MitigationTypes["LIMIT_ARGUMENT_NAME_COLLISION"] = "LIMIT_ARGUMENT_NAME_COLLISION";
70 // Miscellaneous
71 MitigationTypes["OAUTH_SECURITY_SCHEME"] = "OAUTH_SECURITY_SCHEME";
72})(MitigationTypes || (MitigationTypes = {}));
73const mitigations = {
74 /**
75 * Problems with the OAS
76 *
77 * Should be caught by the module oas-validator
78 */
79 INVALID_OAS: 'Ignore issue and continue.',
80 UNNAMED_PARAMETER: 'Ignore parameter.',
81 // General problems
82 AMBIGUOUS_UNION_MEMBERS: 'Ignore issue and continue.',
83 CANNOT_GET_FIELD_TYPE: 'Ignore field and continue.',
84 COMBINE_SCHEMAS: 'Ignore combine schema keyword and continue.',
85 DUPLICATE_FIELD_NAME: 'Ignore field and maintain preexisting field.',
86 DUPLICATE_LINK_KEY: 'Ignore link and maintain preexisting link.',
87 INPUT_UNION: 'The data will be stored in an arbitrary JSON type.',
88 INVALID_HTTP_METHOD: 'Ignore operation and continue.',
89 MISSING_RESPONSE_SCHEMA: 'Ignore operation.',
90 MISSING_SCHEMA: 'Use arbitrary JSON type.',
91 MULTIPLE_RESPONSES: 'Select first response object with successful status code (200-299).',
92 NON_APPLICATION_JSON_SCHEMA: 'Ignore schema',
93 OBJECT_MISSING_PROPERTIES: 'The (sub-)object will be stored in an arbitray JSON type.',
94 UNKNOWN_TARGET_TYPE: 'The data will be stored in an arbitrary JSON type.',
95 UNRESOLVABLE_SCHEMA: 'Ignore and continue. May lead to unexpected behavior.',
96 UNSUPPORTED_HTTP_SECURITY_SCHEME: 'Ignore security scheme.',
97 UNSUPPORTED_JSON_SCHEMA_KEYWORD: 'Ignore keyword and continue.',
98 CALLBACKS_MULTIPLE_OPERATION_OBJECTS: 'Select arbitrary operation object',
99 // Links
100 AMBIGUOUS_LINK: `Use first occurance of '#/'.`,
101 LINK_NAME_COLLISION: 'Ignore link and maintain preexisting field.',
102 UNRESOLVABLE_LINK: 'Ignore link.',
103 // Multiple OAS
104 DUPLICATE_OPERATIONID: 'Ignore operation and maintain preexisting operation.',
105 DUPLICATE_SECURITY_SCHEME: 'Ignore security scheme and maintain preexisting scheme.',
106 MULTIPLE_OAS_SAME_TITLE: 'Ignore issue and continue.',
107 // Options
108 CUSTOM_RESOLVER_UNKNOWN_OAS: 'Ignore this set of custom resolvers.',
109 CUSTOM_RESOLVER_UNKNOWN_PATH_METHOD: 'Ignore this set of custom resolvers.',
110 LIMIT_ARGUMENT_NAME_COLLISION: `Do not override existing 'limit' argument.`,
111 // Miscellaneous
112 OAUTH_SECURITY_SCHEME: `Ignore security scheme`,
113};
114/**
115 * Utilities that are specific to OpenAPI-to-GraphQL
116 */
117function handleWarning({ mitigationType, message, mitigationAddendum, path, data, log, }) {
118 const mitigation = mitigations[mitigationType];
119 const warning = {
120 type: mitigationType,
121 message,
122 mitigation: mitigationAddendum ? `${mitigation} ${mitigationAddendum}` : mitigation,
123 };
124 if (path) {
125 warning.path = path;
126 }
127 if (data.options.strict) {
128 throw new Error(`${warning.type} - ${warning.message}`);
129 }
130 else {
131 const output = `Warning: ${warning.message} - ${warning.mitigation}`;
132 if (typeof log === 'function') {
133 log(output);
134 }
135 else {
136 console.log(output);
137 }
138 data.options.report.warnings.push(warning);
139 }
140}
141// Code provided by codename- from StackOverflow
142// Link: https://stackoverflow.com/a/29622653
143function sortObject(o) {
144 return Object.keys(o)
145 .sort()
146 .reduce((r, k) => ((r[k] = o[k]), r), {});
147}
148/**
149 * Finds the common property names between two objects
150 */
151function getCommonPropertyNames(object1, object2) {
152 return Object.keys(object1).filter(propertyName => {
153 return propertyName in object2;
154 });
155}
156// TODO: replace this with Mesh's logger
157function mockDebug(...args1) {
158 // do nothing
159 return (...args2) => {
160 if (process.env.DEBUG) {
161 console.debug(...args1, ...args2);
162 }
163 };
164}
165
166/* eslint-disable no-useless-escape */
167const httpLog = mockDebug('http');
168const translationLog = mockDebug('translation');
169// OAS constants
170var HTTP_METHODS;
171(function (HTTP_METHODS) {
172 HTTP_METHODS["get"] = "get";
173 HTTP_METHODS["put"] = "put";
174 HTTP_METHODS["post"] = "post";
175 HTTP_METHODS["patch"] = "patch";
176 HTTP_METHODS["delete"] = "delete";
177 HTTP_METHODS["options"] = "options";
178 HTTP_METHODS["head"] = "head";
179})(HTTP_METHODS || (HTTP_METHODS = {}));
180const SUCCESS_STATUS_RX = /2[0-9]{2}|2XX/;
181/**
182 * Given an HTTP method, convert it to the HTTP_METHODS enum
183 */
184function methodToHttpMethod(method) {
185 switch (method.toLowerCase()) {
186 case 'get':
187 return HTTP_METHODS.get;
188 case 'put':
189 return HTTP_METHODS.put;
190 case 'post':
191 return HTTP_METHODS.post;
192 case 'patch':
193 return HTTP_METHODS.patch;
194 case 'delete':
195 return HTTP_METHODS.delete;
196 case 'options':
197 return HTTP_METHODS.options;
198 case 'head':
199 return HTTP_METHODS.head;
200 default:
201 throw new Error(`Invalid HTTP method '${method}'`);
202 }
203}
204/**
205 * Resolves on a validated OAS 3 for the given spec (OAS 2 or OAS 3), or rejects
206 * if errors occur.
207 */
208async function getValidOAS3(spec) {
209 if (typeof spec.swagger === 'string' && spec.swagger === '2.0') {
210 const { openapi } = await convertObj(spec, {
211 patch: true,
212 warnOnly: true,
213 });
214 return openapi;
215 }
216 else {
217 return spec;
218 }
219}
220/**
221 * Counts the number of operations in an OAS.
222 */
223function countOperations(oas) {
224 let numOps = 0;
225 for (const path in oas.paths) {
226 for (const method in oas.paths[path]) {
227 if (isHttpMethod(method)) {
228 numOps++;
229 if (oas.paths[path][method].callbacks) {
230 for (const cbName in oas.paths[path][method].callbacks) {
231 for (const _ in oas.paths[path][method].callbacks[cbName]) {
232 numOps++;
233 }
234 }
235 }
236 }
237 }
238 }
239 return numOps;
240}
241/**
242 * Counts the number of operations that translate to queries in an OAS.
243 */
244function countOperationsQuery(oas) {
245 let numOps = 0;
246 for (const path in oas.paths) {
247 for (const method in oas.paths[path]) {
248 if (isHttpMethod(method) && method.toLowerCase() === HTTP_METHODS.get) {
249 numOps++;
250 }
251 }
252 }
253 return numOps;
254}
255/**
256 * Counts the number of operations that translate to mutations in an OAS.
257 */
258function countOperationsMutation(oas) {
259 let numOps = 0;
260 for (const path in oas.paths) {
261 for (const method in oas.paths[path]) {
262 if (isHttpMethod(method) && method.toLowerCase() !== HTTP_METHODS.get) {
263 numOps++;
264 }
265 }
266 }
267 return numOps;
268}
269/**
270 * Counts the number of operations that translate to subscriptions in an OAS.
271 */
272function countOperationsSubscription(oas) {
273 let numOps = 0;
274 for (const path in oas.paths) {
275 for (const method in oas.paths[path]) {
276 if (isHttpMethod(method) && method.toLowerCase() !== HTTP_METHODS.get && oas.paths[path][method].callbacks) {
277 for (const cbName in oas.paths[path][method].callbacks) {
278 for (const _ in oas.paths[path][method].callbacks[cbName]) {
279 numOps++;
280 }
281 }
282 }
283 }
284 }
285 return numOps;
286}
287/**
288 * Resolves the given reference in the given object.
289 */
290function resolveRef(ref, oas) {
291 return JsonPointer.get(oas, ref);
292}
293/**
294 * Returns the base URL to use for the given operation.
295 */
296function getBaseUrl(operation) {
297 // Check for servers:
298 if (!Array.isArray(operation.servers) || operation.servers.length === 0) {
299 throw new Error(`No servers defined for operation '${operation.operationString}'`);
300 }
301 // Check for local servers
302 if (Array.isArray(operation.servers) && operation.servers.length > 0) {
303 const url = buildUrl(operation.servers[0]);
304 if (Array.isArray(operation.servers) && operation.servers.length > 1) {
305 httpLog(`Warning: Randomly selected first server '${url}'`);
306 }
307 return url.replace(/\/$/, '');
308 }
309 const oas = operation.oas;
310 if (Array.isArray(oas.servers) && oas.servers.length > 0) {
311 const url = buildUrl(oas.servers[0]);
312 if (Array.isArray(oas.servers) && oas.servers.length > 1) {
313 httpLog(`Warning: Randomly selected first server '${url}'`);
314 }
315 return url.replace(/\/$/, '');
316 }
317 throw new Error('Cannot find a server to call');
318}
319/**
320 * Returns the default URL for a given OAS server object.
321 */
322function buildUrl(server) {
323 let url = server.url;
324 // Replace with variable defaults, if applicable
325 if (typeof server.variables === 'object' && Object.keys(server.variables).length > 0) {
326 for (const variableKey in server.variables) {
327 // TODO: check for default? Would be invalid OAS
328 url = url.replace(`{${variableKey}}`, server.variables[variableKey].default.toString());
329 }
330 }
331 return url;
332}
333/**
334 * Returns object/array/scalar where all object keys (if applicable) are
335 * sanitized.
336 */
337function sanitizeObjectKeys(obj, // obj does not necessarily need to be an object
338caseStyle = CaseStyle.camelCase) {
339 const cleanKeys = (obj) => {
340 // Case: no (response) data
341 if (obj === null || typeof obj === 'undefined') {
342 return null;
343 // Case: array
344 }
345 else if (Array.isArray(obj)) {
346 return obj.map(cleanKeys);
347 // Case: object
348 }
349 else if (typeof obj === 'object') {
350 const res = {};
351 for (const key in obj) {
352 const saneKey = sanitize(key, caseStyle);
353 if (Object.prototype.hasOwnProperty.call(obj, key)) {
354 res[saneKey] = cleanKeys(obj[key]);
355 }
356 }
357 return res;
358 // Case: scalar
359 }
360 else {
361 return obj;
362 }
363 };
364 return cleanKeys(obj);
365}
366/**
367 * Desanitizes keys in given object by replacing them with the keys stored in
368 * the given mapping.
369 */
370function desanitizeObjectKeys(obj, mapping = {}) {
371 const replaceKeys = (obj) => {
372 if (obj === null) {
373 return null;
374 }
375 else if (Array.isArray(obj)) {
376 return obj.map(replaceKeys);
377 }
378 else if (typeof obj === 'object') {
379 const res = {};
380 for (const key in obj) {
381 if (key in mapping) {
382 const rawKey = mapping[key];
383 if (Object.prototype.hasOwnProperty.call(obj, key)) {
384 res[rawKey] = replaceKeys(obj[key]);
385 }
386 }
387 else {
388 res[key] = replaceKeys(obj[key]);
389 }
390 }
391 return res;
392 }
393 else {
394 return obj;
395 }
396 };
397 return replaceKeys(obj);
398}
399/**
400 * Returns the GraphQL type that the provided schema should be made into
401 *
402 * Does not consider allOf, anyOf, oneOf, or not (handled separately)
403 */
404function getSchemaTargetGraphQLType(schema, data) {
405 // CASE: object
406 if (schema.type === 'object' || typeof schema.properties === 'object') {
407 // TODO: additionalProperties is more like a flag than a type itself
408 // CASE: arbitrary JSON
409 if (typeof schema.additionalProperties === 'object') {
410 return 'json';
411 }
412 else {
413 return 'object';
414 }
415 }
416 // CASE: array
417 if (schema.type === 'array' || 'items' in schema) {
418 return 'list';
419 }
420 // CASE: enum
421 if (Array.isArray(schema.enum)) {
422 return 'enum';
423 }
424 // CASE: a type is present
425 if (typeof schema.type === 'string') {
426 // Special edge cases involving the schema format
427 if (typeof schema.format === 'string') {
428 /**
429 * CASE: 64 bit int - return number instead of integer, leading to use of
430 * GraphQLFloat, which can support 64 bits:
431 */
432 if (schema.type === 'integer' && schema.format === 'int64') {
433 return 'number';
434 // CASE: id
435 }
436 else if (schema.type === 'string' &&
437 (schema.format === 'uuid' ||
438 // Custom ID format
439 (Array.isArray(data.options.idFormats) && data.options.idFormats.includes(schema.format)))) {
440 return 'id';
441 }
442 }
443 return schema.type;
444 }
445 return null;
446}
447function isIdParam(part) {
448 return /^{.*(id|name|key).*}$/gi.test(part);
449}
450function isSingularParam(part, nextPart) {
451 return `\{${singular(part)}\}` === nextPart;
452}
453/**
454 * Infers a resource name from the given URL path.
455 *
456 * For example, turns "/users/{userId}/car" into "userCar".
457 */
458function inferResourceNameFromPath(path) {
459 const parts = path.split('/');
460 const pathNoPathParams = parts.reduce((path, part, i) => {
461 if (!/{/g.test(part)) {
462 if (parts[i + 1] && (isIdParam(parts[i + 1]) || isSingularParam(part, parts[i + 1]))) {
463 return path + capitalize(singular(part));
464 }
465 else {
466 return path + capitalize(part);
467 }
468 }
469 else {
470 return path;
471 }
472 }, '');
473 return pathNoPathParams;
474}
475/**
476 * Returns JSON-compatible schema required by the given operation
477 */
478function getRequestBodyObject(operation, oas) {
479 if (typeof operation.requestBody === 'object') {
480 let requestBodyObject = operation.requestBody;
481 // Make sure we have a RequestBodyObject:
482 if (typeof requestBodyObject.$ref === 'string') {
483 requestBodyObject = resolveRef(requestBodyObject.$ref, oas);
484 }
485 else {
486 requestBodyObject = requestBodyObject;
487 }
488 if (typeof requestBodyObject.content === 'object') {
489 const content = requestBodyObject.content;
490 // Prioritize content-type JSON
491 if (Object.keys(content).includes('application/json')) {
492 return {
493 payloadContentType: 'application/json',
494 requestBodyObject,
495 };
496 }
497 else if (Object.keys(content).includes('application/x-www-form-urlencoded')) {
498 return {
499 payloadContentType: 'application/x-www-form-urlencoded',
500 requestBodyObject,
501 };
502 }
503 else {
504 // Pick first (random) content type
505 const randomContentType = Object.keys(content)[0].toString();
506 return {
507 payloadContentType: randomContentType,
508 requestBodyObject,
509 };
510 }
511 }
512 }
513 return { payloadContentType: null, requestBodyObject: null };
514}
515/**
516 * Returns the request schema (if any) for the given operation,
517 * a dictionary of names from different sources (if available), and whether the
518 * request schema is required for the operation.
519 */
520function getRequestSchemaAndNames(path, operation, oas) {
521 const { payloadContentType, requestBodyObject } = getRequestBodyObject(operation, oas);
522 if (payloadContentType) {
523 let payloadSchema = requestBodyObject.content[payloadContentType].schema;
524 // Get resource name from different sources
525 let fromRef;
526 if ('$ref' in payloadSchema) {
527 fromRef = payloadSchema.$ref.split('/').pop();
528 payloadSchema = resolveRef(payloadSchema.$ref, oas);
529 }
530 let payloadSchemaNames = {
531 fromRef,
532 fromSchema: payloadSchema.title,
533 fromPath: inferResourceNameFromPath(path),
534 };
535 // Determine if request body is required:
536 const payloadRequired = typeof requestBodyObject.required === 'boolean' ? requestBodyObject.required : false;
537 /**
538 * Edge case: if request body content-type is not application/json or
539 * application/x-www-form-urlencoded, do not parse it.
540 *
541 * Instead, treat the request body as a black box and send it as a string
542 * with the proper content-type header
543 */
544 if (payloadContentType !== 'application/json' && payloadContentType !== 'application/x-www-form-urlencoded') {
545 const saneContentTypeName = uncapitalize(payloadContentType.split('/').reduce((name, term) => {
546 return name + capitalize(term);
547 }));
548 payloadSchemaNames = {
549 fromPath: saneContentTypeName,
550 };
551 let description = `String represents payload of content type '${payloadContentType}'`;
552 if ('description' in payloadSchema && typeof payloadSchema.description === 'string') {
553 description += `\n\nOriginal top level description: '${payloadSchema.description}'`;
554 }
555 payloadSchema = {
556 description: description,
557 type: 'string',
558 };
559 }
560 return {
561 payloadContentType,
562 payloadSchema,
563 payloadSchemaNames,
564 payloadRequired,
565 };
566 }
567 return {
568 payloadRequired: false,
569 };
570}
571/**
572 * Returns JSON-compatible schema produced by the given operation
573 */
574function getResponseObject(operation, statusCode, oas) {
575 if (typeof operation.responses === 'object') {
576 const responses = operation.responses;
577 if (typeof responses[statusCode] === 'object') {
578 let responseObject = responses[statusCode];
579 // Make sure we have a ResponseObject:
580 if (typeof responseObject.$ref === 'string') {
581 responseObject = resolveRef(responseObject.$ref, oas);
582 }
583 else {
584 responseObject = responseObject;
585 }
586 if (responseObject.content && typeof responseObject.content !== 'undefined') {
587 const content = responseObject.content;
588 // Prioritize content-type JSON
589 if (Object.keys(content).includes('application/json')) {
590 return {
591 responseContentType: 'application/json',
592 responseObject,
593 };
594 }
595 else {
596 // Pick first (random) content type
597 const randomContentType = Object.keys(content)[0].toString();
598 return {
599 responseContentType: randomContentType,
600 responseObject,
601 };
602 }
603 }
604 }
605 }
606 return { responseContentType: null, responseObject: null };
607}
608/**
609 * Returns the response schema for the given operation,
610 * a successful status code, and a dictionary of names from different sources
611 * (if available).
612 */
613function getResponseSchemaAndNames(path, method, operation, oas, data, options) {
614 const statusCode = getResponseStatusCode(path, method, operation, oas, data);
615 if (!statusCode) {
616 return {};
617 }
618 const { responseContentType, responseObject } = getResponseObject(operation, statusCode, oas);
619 if (responseContentType) {
620 let responseSchema = responseObject.content[responseContentType].schema;
621 let fromRef;
622 if ('$ref' in responseSchema) {
623 fromRef = responseSchema.$ref.split('/').pop();
624 responseSchema = resolveRef(responseSchema.$ref, oas);
625 }
626 const responseSchemaNames = {
627 fromRef,
628 fromSchema: responseSchema.title,
629 fromPath: inferResourceNameFromPath(path),
630 };
631 /**
632 * Edge case: if response body content-type is not application/json, do not
633 * parse.
634 */
635 if (responseContentType !== 'application/json') {
636 let description = 'Placeholder to access non-application/json response bodies';
637 if ('description' in responseSchema && typeof responseSchema.description === 'string') {
638 description += `\n\nOriginal top level description: '${responseSchema.description}'`;
639 }
640 responseSchema = {
641 description: description,
642 type: 'string',
643 };
644 }
645 return {
646 responseContentType,
647 responseSchema,
648 responseSchemaNames,
649 statusCode,
650 };
651 }
652 else {
653 /**
654 * GraphQL requires that objects must have some properties.
655 *
656 * To allow some operations (such as those with a 204 HTTP code) to be
657 * included in the GraphQL interface, we added the fillEmptyResponses
658 * option, which will simply create a placeholder to allow access.
659 */
660 if (options.fillEmptyResponses) {
661 return {
662 responseSchemaNames: {
663 fromPath: inferResourceNameFromPath(path),
664 },
665 responseContentType: 'application/json',
666 responseSchema: {
667 description: 'Placeholder to support operations with no response schema',
668 type: 'object',
669 },
670 };
671 }
672 return {};
673 }
674}
675/**
676 * Returns a success status code for the given operation
677 */
678function getResponseStatusCode(path, method, operation, oas, data) {
679 var _a;
680 if (typeof operation.responses === 'object') {
681 const codes = Object.keys(operation.responses);
682 const successCodes = codes.filter(code => {
683 return SUCCESS_STATUS_RX.test(code.toString());
684 });
685 if (successCodes.length === 1) {
686 return successCodes[0].toString();
687 }
688 else if (successCodes.length > 1) {
689 handleWarning({
690 mitigationType: MitigationTypes.MULTIPLE_RESPONSES,
691 message: `Operation '${formatOperationString(method, path, (_a = oas.info) === null || _a === void 0 ? void 0 : _a.title)}' ` +
692 `contains multiple possible successful response object ` +
693 `(HTTP code 200-299 or 2XX). Only one can be chosen.`,
694 mitigationAddendum: `The response object with the HTTP code ` + `${successCodes[0]} will be selected`,
695 data,
696 log: translationLog,
697 });
698 return successCodes[0].toString();
699 }
700 }
701 return null;
702}
703/**
704 * Returns a hash containing the links in the given operation.
705 */
706function getLinks(path, method, operation, oas, data) {
707 const links = {};
708 const statusCode = getResponseStatusCode(path, method, operation, oas, data);
709 if (!statusCode) {
710 return links;
711 }
712 if (typeof operation.responses === 'object') {
713 const responses = operation.responses;
714 if (typeof responses[statusCode] === 'object') {
715 let response = responses[statusCode];
716 if (typeof response.$ref === 'string') {
717 response = resolveRef(response.$ref, oas);
718 }
719 // Here, we can be certain we have a ResponseObject:
720 response = response;
721 if (typeof response.links === 'object') {
722 const epLinks = response.links;
723 for (const linkKey in epLinks) {
724 let link = epLinks[linkKey];
725 // Make sure we have LinkObjects:
726 if (typeof link.$ref === 'string') {
727 link = resolveRef(link.$ref, oas);
728 }
729 else {
730 link = link;
731 }
732 links[linkKey] = link;
733 }
734 }
735 }
736 }
737 return links;
738}
739/**
740 * Returns the list of parameters in the given operation.
741 */
742function getParameters(path, method, operation, pathItem, oas) {
743 let parameters = [];
744 if (!isHttpMethod(method)) {
745 translationLog(`Warning: attempted to get parameters for ${method} ${path}, ` + `which is not an operation.`);
746 return parameters;
747 }
748 // First, consider parameters in Path Item Object:
749 const pathParams = pathItem.parameters;
750 if (Array.isArray(pathParams)) {
751 const pathItemParameters = pathParams.map(p => {
752 if (typeof p.$ref === 'string') {
753 // Here we know we have a parameter object:
754 return resolveRef(p.$ref, oas);
755 }
756 else {
757 // Here we know we have a parameter object:
758 return p;
759 }
760 });
761 parameters = parameters.concat(pathItemParameters);
762 }
763 // Second, consider parameters in Operation Object:
764 const opObjectParameters = operation.parameters;
765 if (Array.isArray(opObjectParameters)) {
766 const operationParameters = opObjectParameters.map(p => {
767 if (typeof p.$ref === 'string') {
768 // Here we know we have a parameter object:
769 return resolveRef(p.$ref, oas);
770 }
771 else {
772 // Here we know we have a parameter object:
773 return p;
774 }
775 });
776 parameters = parameters.concat(operationParameters);
777 }
778 return parameters;
779}
780/**
781 * Returns an array of server objects for the operation at the given path and
782 * method. Considers in the following order: global server definitions,
783 * definitions at the path item, definitions at the operation, or the OAS
784 * default.
785 */
786function getServers(operation, pathItem, oas) {
787 let servers = [];
788 // Global server definitions:
789 if (Array.isArray(oas.servers) && oas.servers.length > 0) {
790 servers = oas.servers;
791 }
792 // First, consider servers defined on the path
793 if (Array.isArray(pathItem.servers) && pathItem.servers.length > 0) {
794 servers = pathItem.servers;
795 }
796 // Second, consider servers defined on the operation
797 if (Array.isArray(operation.servers) && operation.servers.length > 0) {
798 servers = operation.servers;
799 }
800 // Default, in case there is no server:
801 if (servers.length === 0) {
802 const server = {
803 url: '/',
804 };
805 servers.push(server);
806 }
807 return servers;
808}
809/**
810 * Returns a map of Security Scheme definitions, identified by keys. Resolves
811 * possible references.
812 */
813function getSecuritySchemes(oas) {
814 // Collect all security schemes:
815 const securitySchemes = {};
816 if (typeof oas.components === 'object' && typeof oas.components.securitySchemes === 'object') {
817 for (const schemeKey in oas.components.securitySchemes) {
818 const obj = oas.components.securitySchemes[schemeKey];
819 // Ensure we have actual SecuritySchemeObject:
820 if (typeof obj.$ref === 'string') {
821 // Result of resolution will be SecuritySchemeObject:
822 securitySchemes[schemeKey] = resolveRef(obj.$ref, oas);
823 }
824 else {
825 // We already have a SecuritySchemeObject:
826 securitySchemes[schemeKey] = obj;
827 }
828 }
829 }
830 return securitySchemes;
831}
832/**
833 * Returns the list of sanitized keys of non-OAuth2 security schemes
834 * required by the operation at the given path and method.
835 */
836function getSecurityRequirements(operation, securitySchemes, oas) {
837 const results = [];
838 // First, consider global requirements
839 const globalSecurity = oas.security;
840 if (globalSecurity && typeof globalSecurity !== 'undefined') {
841 for (const secReq of globalSecurity) {
842 for (const schemaKey in secReq) {
843 if (securitySchemes[schemaKey] &&
844 typeof securitySchemes[schemaKey] === 'object' &&
845 securitySchemes[schemaKey].def.type !== 'oauth2') {
846 results.push(schemaKey);
847 }
848 }
849 }
850 }
851 // Second, consider operation requirements
852 const localSecurity = operation.security;
853 if (localSecurity && typeof localSecurity !== 'undefined') {
854 for (const secReq of localSecurity) {
855 for (const schemaKey in secReq) {
856 if (securitySchemes[schemaKey] &&
857 typeof securitySchemes[schemaKey] === 'object' &&
858 securitySchemes[schemaKey].def.type !== 'oauth2') {
859 if (!results.includes(schemaKey)) {
860 results.push(schemaKey);
861 }
862 }
863 }
864 }
865 }
866 return results;
867}
868var CaseStyle;
869(function (CaseStyle) {
870 CaseStyle[CaseStyle["simple"] = 0] = "simple";
871 CaseStyle[CaseStyle["PascalCase"] = 1] = "PascalCase";
872 CaseStyle[CaseStyle["camelCase"] = 2] = "camelCase";
873 CaseStyle[CaseStyle["ALL_CAPS"] = 3] = "ALL_CAPS";
874})(CaseStyle || (CaseStyle = {}));
875/**
876 * First sanitizes given string and then also camel-cases it.
877 */
878function sanitize(str, caseStyle) {
879 /**
880 * Used in conjunction to simpleNames, which only removes illegal
881 * characters and preserves casing
882 */
883 if (caseStyle === CaseStyle.simple) {
884 return str.replace(/[^a-zA-Z0-9_]/gi, '');
885 }
886 /**
887 * Remove all GraphQL unsafe characters
888 */
889 const regex = caseStyle === CaseStyle.ALL_CAPS
890 ? /[^a-zA-Z0-9_]/g // ALL_CAPS has underscores
891 : /[^a-zA-Z0-9]/g;
892 const operators = {
893 '<=': 'less_than_or_equal_to',
894 '>=': 'greater_than_or_equal_to',
895 '<': 'less_than',
896 '>': 'greater_than',
897 '=': 'equal_to',
898 };
899 for (const operator in operators) {
900 str = str.replace(operator, operators[operator]);
901 }
902 let sanitized = str.split(regex).reduce((path, part) => {
903 if (caseStyle === CaseStyle.ALL_CAPS) {
904 return path + '_' + part;
905 }
906 else {
907 return path + capitalize(part);
908 }
909 });
910 switch (caseStyle) {
911 case CaseStyle.PascalCase:
912 // The first character in PascalCase should be uppercase
913 sanitized = capitalize(sanitized);
914 break;
915 case CaseStyle.camelCase:
916 // The first character in camelCase should be lowercase
917 sanitized = uncapitalize(sanitized);
918 break;
919 case CaseStyle.ALL_CAPS:
920 sanitized = sanitized.toUpperCase();
921 break;
922 }
923 // Special case: we cannot start with number, and cannot be empty:
924 if (/^[0-9]/.test(sanitized) || sanitized === '') {
925 sanitized = '_' + sanitized;
926 }
927 return sanitized;
928}
929/**
930 * Sanitizes the given string and stores the sanitized-to-original mapping in
931 * the given mapping.
932 */
933function storeSaneName(saneStr, str, mapping) {
934 if (saneStr in mapping && str !== mapping[saneStr]) {
935 // TODO: Follow warning model
936 translationLog(`Warning: '${str}' and '${mapping[saneStr]}' both sanitize ` +
937 `to '${saneStr}' - collision possible. Desanitize to '${str}'.`);
938 let appendix = 2;
939 while (`${saneStr}${appendix}` in mapping) {
940 appendix++;
941 }
942 return storeSaneName(`${saneStr}${appendix}`, str, mapping);
943 }
944 mapping[saneStr] = str;
945 return saneStr;
946}
947/**
948 * Stringifies and possibly trims the given string to the provided length.
949 */
950function trim(str, length) {
951 if (typeof str !== 'string') {
952 str = JSON.stringify(str);
953 }
954 if (str && str.length > length) {
955 str = `${str.substring(0, length)}...`;
956 }
957 return str;
958}
959/**
960 * Determines if the given "method" is indeed an operation. Alternatively, the
961 * method could point to other types of information (e.g., parameters, servers).
962 */
963function isHttpMethod(method) {
964 return Object.keys(HTTP_METHODS).includes(method.toLowerCase());
965}
966/**
967 * Formats a string that describes an operation in the form:
968 * {name of OAS} {HTTP method in ALL_CAPS} {operation path}
969 *
970 * Also used in preprocessing.ts where Operation objects are being constructed
971 */
972function formatOperationString(method, path, title) {
973 if (title) {
974 return `${title} ${method.toUpperCase()} ${path}`;
975 }
976 else {
977 return `${method.toUpperCase()} ${path}`;
978 }
979}
980/**
981 * Capitalizes a given string
982 */
983function capitalize(str) {
984 return str.charAt(0).toUpperCase() + str.slice(1);
985}
986/**
987 * Uncapitalizes a given string
988 */
989function uncapitalize(str) {
990 return str.charAt(0).toLowerCase() + str.slice(1);
991}
992/**
993 * For operations that do not have an operationId, generate one
994 */
995function generateOperationId(method, path) {
996 return sanitize(`${method} ${path}`, CaseStyle.camelCase);
997}
998
999/* eslint-disable no-case-declarations */
1000const translationLog$1 = mockDebug('translation');
1001const httpLog$1 = mockDebug('http');
1002const pubsubLog = mockDebug('pubsub');
1003function headersToObject(headers) {
1004 const headersObj = {};
1005 headers.forEach((value, key) => {
1006 headersObj[key] = value;
1007 });
1008 return headersObj;
1009}
1010/*
1011 * If operationType is Subscription, creates and returns a resolver object that
1012 * contains subscribe to perform subscription and resolve to execute payload
1013 * transformation
1014 */
1015function getSubscribe({ operation, payloadName, data, baseUrl, connectOptions, pubsub, }) {
1016 var _a;
1017 // Determine the appropriate URL:
1018 if (typeof baseUrl === 'undefined') {
1019 baseUrl = getBaseUrl(operation);
1020 }
1021 // Return custom resolver if it is defined
1022 const customResolvers = data.options.customSubscriptionResolvers;
1023 const title = (_a = operation.oas.info) === null || _a === void 0 ? void 0 : _a.title;
1024 const path = operation.path;
1025 const method = operation.method;
1026 if (typeof customResolvers === 'object' &&
1027 typeof customResolvers[title] === 'object' &&
1028 typeof customResolvers[title][path] === 'object' &&
1029 typeof customResolvers[title][path][method] === 'object' &&
1030 typeof customResolvers[title][path][method].subscribe === 'function') {
1031 translationLog$1(`Use custom publish resolver for ${operation.operationString}`);
1032 return customResolvers[title][path][method].subscribe;
1033 }
1034 return (root, args, context, info) => {
1035 try {
1036 /**
1037 * Determine possible topic(s) by resolving callback path
1038 *
1039 * GraphQL produces sanitized payload names, so we have to sanitize before
1040 * lookup here
1041 */
1042 const paramName = sanitize(payloadName, CaseStyle.camelCase);
1043 const resolveData = {};
1044 if (payloadName && typeof payloadName === 'string') {
1045 // The option genericPayloadArgName will change the payload name to "requestBody"
1046 const sanePayloadName = data.options.genericPayloadArgName
1047 ? 'requestBody'
1048 : sanitize(payloadName, CaseStyle.camelCase);
1049 if (sanePayloadName in args) {
1050 if (typeof args[sanePayloadName] === 'object') {
1051 const rawPayload = desanitizeObjectKeys(args[sanePayloadName], data.saneMap);
1052 resolveData.usedPayload = rawPayload;
1053 }
1054 else {
1055 const rawPayload = JSON.parse(args[sanePayloadName]);
1056 resolveData.usedPayload = rawPayload;
1057 }
1058 }
1059 }
1060 if (connectOptions) {
1061 resolveData.usedRequestOptions = connectOptions;
1062 }
1063 else {
1064 resolveData.usedRequestOptions = {
1065 method: resolveData.usedPayload.method ? resolveData.usedPayload.method : method.toUpperCase(),
1066 };
1067 }
1068 pubsubLog(`Subscription schema: ${JSON.stringify(resolveData.usedPayload)}`);
1069 let value = path;
1070 let paramNameWithoutLocation = paramName;
1071 if (paramName.indexOf('.') !== -1) {
1072 paramNameWithoutLocation = paramName.split('.')[1];
1073 }
1074 // See if the callback path contains constants expression
1075 if (value.search(/{|}/) === -1) {
1076 args[paramNameWithoutLocation] = isRuntimeExpression(value)
1077 ? resolveRuntimeExpression(paramName, value, resolveData, root, args)
1078 : value;
1079 }
1080 else {
1081 // Replace callback expression with appropriate values
1082 const cbParams = value.match(/{([^}]*)}/g);
1083 pubsubLog(`Analyzing subscription path: ${cbParams.toString()}`);
1084 cbParams.forEach(cbParam => {
1085 value = value.replace(cbParam, resolveRuntimeExpression(paramName, cbParam.substring(1, cbParam.length - 1), resolveData, root, args));
1086 });
1087 args[paramNameWithoutLocation] = value;
1088 }
1089 const topic = args[paramNameWithoutLocation] || 'test';
1090 pubsubLog(`Subscribing to: ${topic}`);
1091 return pubsub.asyncIterator(topic);
1092 }
1093 catch (e) {
1094 console.error(e);
1095 throw e;
1096 }
1097 };
1098}
1099/*
1100 * If operationType is Subscription, creates and returns a resolver function
1101 * triggered after a message has been published to the corresponding subscribe
1102 * topic(s) to execute payload transformation
1103 */
1104function getPublishResolver({ operation, responseName, data, }) {
1105 var _a;
1106 // Return custom resolver if it is defined
1107 const customResolvers = data.options.customSubscriptionResolvers;
1108 const title = (_a = operation.oas.info) === null || _a === void 0 ? void 0 : _a.title;
1109 const path = operation.path;
1110 const method = operation.method;
1111 if (typeof customResolvers === 'object' &&
1112 typeof customResolvers[title] === 'object' &&
1113 typeof customResolvers[title][path] === 'object' &&
1114 typeof customResolvers[title][path][method] === 'object' &&
1115 typeof customResolvers[title][path][method].resolve === 'function') {
1116 translationLog$1(`Use custom publish resolver for ${operation.operationString}`);
1117 return customResolvers[title][path][method].resolve;
1118 }
1119 return (payload, args, context, info) => {
1120 // Validate and format based on operation.responseDefinition
1121 const typeOfResponse = operation.responseDefinition.targetGraphQLType;
1122 pubsubLog(`Message received: ${responseName}, ${typeOfResponse}, ${JSON.stringify(payload)}`);
1123 let responseBody;
1124 let saneData;
1125 if (typeof payload === 'object') {
1126 if (typeOfResponse === 'object') {
1127 if (Buffer.isBuffer(payload)) {
1128 try {
1129 responseBody = JSON.parse(payload.toString());
1130 }
1131 catch (e) {
1132 const errorString = `Cannot JSON parse payload` +
1133 `operation ${operation.operationString} ` +
1134 `even though it has content-type 'application/json'`;
1135 pubsubLog(errorString);
1136 return null;
1137 }
1138 }
1139 else {
1140 responseBody = payload;
1141 }
1142 saneData = sanitizeObjectKeys(payload);
1143 }
1144 else if ((Buffer.isBuffer(payload) || Array.isArray(payload)) && typeOfResponse === 'string') {
1145 saneData = payload.toString();
1146 }
1147 }
1148 else if (typeof payload === 'string') {
1149 if (typeOfResponse === 'object') {
1150 try {
1151 responseBody = JSON.parse(payload);
1152 saneData = sanitizeObjectKeys(responseBody);
1153 }
1154 catch (e) {
1155 const errorString = `Cannot JSON parse payload` +
1156 `operation ${operation.operationString} ` +
1157 `even though it has content-type 'application/json'`;
1158 pubsubLog(errorString);
1159 return null;
1160 }
1161 }
1162 else if (typeOfResponse === 'string') {
1163 saneData = payload;
1164 }
1165 }
1166 pubsubLog(`Message forwarded: ${JSON.stringify(saneData || payload)}`);
1167 return saneData || payload;
1168 };
1169}
1170/**
1171 * Creates and returns a resolver function that performs API requests for the
1172 * given GraphQL query
1173 */
1174function getResolver(getResolverParams) {
1175 var _a;
1176 let { operation, argsFromLink = {}, payloadName, data, baseUrl, requestOptions, fetch: fetchFn = data.options.fetch, qs: customQs, } = getResolverParams();
1177 // Determine the appropriate URL:
1178 if (typeof baseUrl === 'undefined') {
1179 baseUrl = getBaseUrl(operation);
1180 }
1181 // Return custom resolver if it is defined
1182 const customResolvers = data.options.customResolvers;
1183 const title = (_a = operation.oas.info) === null || _a === void 0 ? void 0 : _a.title;
1184 const path = operation.path;
1185 const method = operation.method;
1186 if (typeof customResolvers === 'object' &&
1187 typeof customResolvers[title] === 'object' &&
1188 typeof customResolvers[title][path] === 'object' &&
1189 typeof customResolvers[title][path][method] === 'function') {
1190 translationLog$1(`Use custom resolver for ${operation.operationString}`);
1191 return customResolvers[title][path][method];
1192 }
1193 // Return resolve function:
1194 return async (root, args, ctx, info = {}) => {
1195 /**
1196 * Retch resolveData from possibly existing _openAPIToGraphQL
1197 *
1198 * NOTE: _openAPIToGraphQL is an object used to pass security info and data
1199 * from previous resolvers
1200 */
1201 let resolveData = {};
1202 if (root &&
1203 typeof root === 'object' &&
1204 typeof root._openAPIToGraphQL === 'object' &&
1205 typeof root._openAPIToGraphQL.data === 'object') {
1206 const parentIdentifier = getParentIdentifier(info);
1207 if (!(parentIdentifier.length === 0) && parentIdentifier in root._openAPIToGraphQL.data) {
1208 /**
1209 * Resolving link params may change the usedParams, but these changes
1210 * should not be present in the parent _openAPIToGraphQL, therefore copy
1211 * the object
1212 */
1213 resolveData = JSON.parse(JSON.stringify(root._openAPIToGraphQL.data[parentIdentifier]));
1214 }
1215 }
1216 if (typeof resolveData.usedParams === 'undefined') {
1217 resolveData.usedParams = {};
1218 }
1219 /**
1220 * Handle default values of parameters, if they have not yet been defined by
1221 * the user.
1222 */
1223 operation.parameters.forEach(param => {
1224 const paramName = sanitize(param.name, !data.options.simpleNames ? CaseStyle.camelCase : CaseStyle.simple);
1225 if (typeof args[paramName] === 'undefined' && param.schema && typeof param.schema === 'object') {
1226 let schema = param.schema;
1227 if (schema && schema.$ref && typeof schema.$ref === 'string') {
1228 schema = resolveRef(schema.$ref, operation.oas);
1229 }
1230 if (schema && schema.default && typeof schema.default !== 'undefined') {
1231 args[paramName] = schema.default;
1232 }
1233 }
1234 });
1235 // Handle arguments provided by links
1236 for (const paramName in argsFromLink) {
1237 const saneParamName = sanitize(paramName, !data.options.simpleNames ? CaseStyle.camelCase : CaseStyle.simple);
1238 let value = argsFromLink[paramName];
1239 /**
1240 * see if the link parameter contains constants that are appended to the link parameter
1241 *
1242 * e.g. instead of:
1243 * $response.body#/employerId
1244 *
1245 * it could be:
1246 * abc_{$response.body#/employerId}
1247 */
1248 if (value.search(/{|}/) === -1) {
1249 args[saneParamName] = isRuntimeExpression(value)
1250 ? resolveLinkParameter(paramName, value, resolveData, root)
1251 : value;
1252 }
1253 else {
1254 // Replace link parameters with appropriate values
1255 const linkParams = value.match(/{([^}]*)}/g);
1256 linkParams.forEach(linkParam => {
1257 value = value.replace(linkParam, resolveLinkParameter(paramName, linkParam.substring(1, linkParam.length - 1), resolveData, root));
1258 });
1259 args[saneParamName] = value;
1260 }
1261 }
1262 // Stored used parameters to future requests:
1263 resolveData.usedParams = Object.assign(resolveData.usedParams, args);
1264 // Build URL (i.e., fill in path parameters):
1265 const { path, qs: query, headers } = extractRequestDataFromArgs(operation.path, operation.parameters, args, data);
1266 const urlObject = new URL(urlJoin(baseUrl, path));
1267 /**
1268 * The Content-type and accept property should not be changed because the
1269 * object type has already been created and unlike these properties, it
1270 * cannot be easily changed
1271 *
1272 * NOTE: This may cause the user to encounter unexpected changes
1273 */
1274 if (operation.method !== 'get') {
1275 /**
1276 * the check is done on the 'get' operation to determine if there is a payload because
1277 * operation.payloadRequired is not always correctly initialized (at least during the unit test)
1278 */
1279 headers['content-type'] =
1280 typeof operation.payloadContentType !== 'undefined' ? operation.payloadContentType : 'application/json';
1281 }
1282 headers.accept =
1283 typeof operation.responseContentType !== 'undefined' ? operation.responseContentType : 'application/json';
1284 let options;
1285 if (requestOptions) {
1286 options = { ...requestOptions };
1287 options.method = operation.method;
1288 options.headers = options.headers || {};
1289 for (const headerName in headers) {
1290 const headerValue = headers[headerName];
1291 if (headerValue) {
1292 options.headers[headerName] = headerValue;
1293 }
1294 }
1295 }
1296 else {
1297 options = {
1298 method: operation.method,
1299 headers: headers,
1300 };
1301 }
1302 for (const paramName in query) {
1303 const val = query[paramName];
1304 if (val) {
1305 urlObject.searchParams.set(paramName, val);
1306 }
1307 }
1308 /**
1309 * Determine possible payload
1310 *
1311 * GraphQL produces sanitized payload names, so we have to sanitize before
1312 * lookup here
1313 */
1314 resolveData.usedPayload = undefined;
1315 if (typeof payloadName === 'string') {
1316 // The option genericPayloadArgName will change the payload name to "requestBody"
1317 const sanePayloadName = data.options.genericPayloadArgName
1318 ? 'requestBody'
1319 : sanitize(payloadName, CaseStyle.camelCase);
1320 let rawPayload;
1321 if (operation.payloadContentType === 'application/json') {
1322 rawPayload = JSON.stringify(desanitizeObjectKeys(args[sanePayloadName], data.saneMap));
1323 }
1324 else if (operation.payloadContentType === 'application/x-www-form-urlencoded') {
1325 rawPayload = formurlencoded(desanitizeObjectKeys(args[sanePayloadName], data.saneMap));
1326 }
1327 else {
1328 // Payload is not an object
1329 rawPayload = args[sanePayloadName];
1330 }
1331 options.body = rawPayload;
1332 resolveData.usedPayload = rawPayload;
1333 }
1334 /**
1335 * Pass on OpenAPI-to-GraphQL options
1336 */
1337 if (typeof data.options === 'object') {
1338 // Headers:
1339 if (typeof data.options.headers === 'object') {
1340 for (const header in data.options.headers) {
1341 const val = data.options.headers[header];
1342 if (val) {
1343 options.headers[header] = val;
1344 }
1345 }
1346 }
1347 // Query string:
1348 if (typeof data.options.qs === 'object') {
1349 for (const query in data.options.qs) {
1350 const val = data.options.qs[query];
1351 if (val) {
1352 urlObject.searchParams.set(query, val);
1353 }
1354 }
1355 }
1356 }
1357 if (typeof customQs === 'object') {
1358 for (const query in customQs) {
1359 const val = customQs[query];
1360 if (val) {
1361 urlObject.searchParams.set(query, val);
1362 }
1363 }
1364 }
1365 // Get authentication headers and query parameters
1366 if (root && typeof root === 'object' && typeof root._openAPIToGraphQL === 'object') {
1367 const { authHeaders, authQs, authCookie } = getAuthOptions(operation, root._openAPIToGraphQL, data);
1368 // ...and pass them to the options
1369 for (const headerName in authHeaders) {
1370 const headerValue = authHeaders[headerName];
1371 if (headerValue) {
1372 options.headers[headerName] = headerValue;
1373 }
1374 }
1375 for (const query in authQs) {
1376 const val = authQs[query];
1377 if (val) {
1378 urlObject.searchParams.set(query, val);
1379 }
1380 }
1381 // Add authentication cookie if created
1382 if (authCookie !== null) {
1383 const cookieHeaderName = 'cookie';
1384 options.headers[cookieHeaderName] = authCookie;
1385 }
1386 }
1387 // Extract OAuth token from context (if available)
1388 if (data.options.sendOAuthTokenInQuery) {
1389 const oauthQueryObj = createOAuthQS(data, ctx);
1390 for (const query in oauthQueryObj) {
1391 const val = oauthQueryObj[query];
1392 if (val) {
1393 urlObject.searchParams.set(query, val);
1394 }
1395 }
1396 }
1397 else {
1398 const oauthHeader = createOAuthHeader(data, ctx);
1399 for (const headerName in oauthHeader) {
1400 const headerValue = oauthHeader[headerName];
1401 if (headerValue) {
1402 options.headers[headerName] = headerValue;
1403 }
1404 }
1405 }
1406 const urlWithoutQuery = urlObject.href.replace(urlObject.search, '');
1407 resolveData.url = urlWithoutQuery;
1408 resolveData.usedRequestOptions = options;
1409 resolveData.usedStatusCode = operation.statusCode;
1410 // Make the call
1411 httpLog$1(`Call ${options.method.toUpperCase()} ${urlWithoutQuery}?${urlObject.search}\n` +
1412 `headers: ${JSON.stringify(options.headers)}\n` +
1413 `request body: ${options.body}`);
1414 let response;
1415 try {
1416 response = await fetchFn(urlObject.href, options);
1417 }
1418 catch (err) {
1419 httpLog$1(err);
1420 throw err;
1421 }
1422 const body = await response.text();
1423 if (response.status < 200 || response.status > 299) {
1424 httpLog$1(`${response.status} - ${trim(body, 100)}`);
1425 const errorString = `Could not invoke operation ${operation.operationString}`;
1426 if (data.options.provideErrorExtensions) {
1427 let responseBody;
1428 try {
1429 responseBody = JSON.parse(body);
1430 }
1431 catch (e) {
1432 responseBody = body;
1433 }
1434 const extensions = {
1435 method: operation.method,
1436 path: operation.path,
1437 statusText: response.statusText,
1438 statusCode: response.status,
1439 responseHeaders: headersToObject(response.headers),
1440 responseBody,
1441 };
1442 throw graphQLErrorWithExtensions(errorString, extensions);
1443 }
1444 else {
1445 throw new Error(errorString);
1446 }
1447 // Successful response 200-299
1448 }
1449 else {
1450 httpLog$1(`${response.status} - ${trim(body, 100)}`);
1451 if (response.headers.get('content-type')) {
1452 /**
1453 * If the response body is type JSON, then parse it
1454 *
1455 * content-type may not be necessarily 'application/json' it can be
1456 * 'application/json; charset=utf-8' for example
1457 */
1458 if (response.headers.get('content-type').includes('json')) {
1459 let responseBody;
1460 try {
1461 responseBody = JSON.parse(body);
1462 }
1463 catch (e) {
1464 const errorString = `Cannot JSON parse response body of ` +
1465 `operation ${operation.operationString} ` +
1466 `even though it has content-type '${response.headers.get('content-type')}'`;
1467 httpLog$1(errorString);
1468 throw errorString;
1469 }
1470 resolveData.responseHeaders = headersToObject(response.headers);
1471 // Deal with the fact that the server might send unsanitized data
1472 let saneData = sanitizeObjectKeys(responseBody, !data.options.simpleNames ? CaseStyle.camelCase : CaseStyle.simple);
1473 // Pass on _openAPIToGraphQL to subsequent resolvers
1474 if (saneData && typeof saneData === 'object') {
1475 if (Array.isArray(saneData)) {
1476 saneData.forEach(element => {
1477 if (typeof element._openAPIToGraphQL === 'undefined') {
1478 element._openAPIToGraphQL = {
1479 data: {},
1480 };
1481 }
1482 if (root && typeof root === 'object' && typeof root._openAPIToGraphQL === 'object') {
1483 Object.assign(element._openAPIToGraphQL, root._openAPIToGraphQL);
1484 }
1485 element._openAPIToGraphQL.data[getIdentifier(info)] = resolveData;
1486 });
1487 }
1488 else {
1489 if (typeof saneData._openAPIToGraphQL === 'undefined') {
1490 saneData._openAPIToGraphQL = {
1491 data: {},
1492 };
1493 }
1494 if (root && typeof root === 'object' && typeof root._openAPIToGraphQL === 'object') {
1495 Object.assign(saneData._openAPIToGraphQL, root._openAPIToGraphQL);
1496 }
1497 saneData._openAPIToGraphQL.data[getIdentifier(info)] = resolveData;
1498 }
1499 }
1500 // Apply limit argument
1501 if (data.options.addLimitArgument &&
1502 /**
1503 * NOTE: Does not differentiate between autogenerated args and
1504 * preexisting args
1505 *
1506 * Ensure that there is not preexisting 'limit' argument
1507 */
1508 !operation.parameters.find(parameter => {
1509 return parameter.name === 'limit';
1510 }) &&
1511 // Only array data
1512 Array.isArray(saneData) &&
1513 // Only array of objects/arrays
1514 saneData.some(data => {
1515 return typeof data === 'object';
1516 }) &&
1517 'limit' in args) {
1518 let arraySaneData = saneData;
1519 const limit = args.limit;
1520 if (limit >= 0) {
1521 arraySaneData = arraySaneData.slice(0, limit);
1522 }
1523 else {
1524 throw new Error(`Auto-generated 'limit' argument must be greater than or equal to 0`);
1525 }
1526 saneData = arraySaneData;
1527 }
1528 return saneData;
1529 }
1530 else {
1531 // TODO: Handle YAML
1532 return body;
1533 }
1534 }
1535 else {
1536 /**
1537 * Check to see if there is not supposed to be a response body,
1538 * if that is the case, that would explain why there is not
1539 * a content-type
1540 */
1541 const { responseContentType } = getResponseObject(operation, operation.statusCode, operation.oas);
1542 if (responseContentType === null) {
1543 return null;
1544 }
1545 else {
1546 const errorString = 'Response does not have a Content-Type property';
1547 httpLog$1(errorString);
1548 throw errorString;
1549 }
1550 }
1551 }
1552 };
1553}
1554/**
1555 * Attempts to create an object to become an OAuth query string by extracting an
1556 * OAuth token from the ctx based on the JSON path provided in the options.
1557 */
1558function createOAuthQS(data, context) {
1559 return typeof data.options.tokenJSONpath !== 'string' ? {} : extractToken(data, context);
1560}
1561function extractToken(data, ctx) {
1562 const tokenJSONpath = data.options.tokenJSONpath;
1563 const tokens = JSONPath({ path: tokenJSONpath, json: ctx });
1564 if (Array.isArray(tokens) && tokens.length > 0) {
1565 const token = tokens[0];
1566 return {
1567 access_token: token,
1568 };
1569 }
1570 else {
1571 httpLog$1(`Warning: could not extract OAuth token from context at '${tokenJSONpath}'`);
1572 return {};
1573 }
1574}
1575/**
1576 * Attempts to create an OAuth authorization header by extracting an OAuth token
1577 * from the ctx based on the JSON path provided in the options.
1578 */
1579function createOAuthHeader(data, ctx) {
1580 if (typeof data.options.tokenJSONpath !== 'string') {
1581 return {};
1582 }
1583 // Extract token
1584 const tokenJSONpath = data.options.tokenJSONpath;
1585 const tokens = JSONPath({ path: tokenJSONpath, json: ctx });
1586 if (Array.isArray(tokens) && tokens.length > 0) {
1587 const token = tokens[0];
1588 return {
1589 Authorization: `Bearer ${token}`,
1590 'User-Agent': 'openapi-to-graphql',
1591 };
1592 }
1593 else {
1594 httpLog$1(`Warning: could not extract OAuth token from context at ` + `'${tokenJSONpath}'`);
1595 return {};
1596 }
1597}
1598/**
1599 * Returns the headers and query strings to authenticate a request (if any).
1600 * Object containing authHeader and authQs object,
1601 * which hold headers and query parameters respectively to authentication a
1602 * request.
1603 */
1604function getAuthOptions(operation, _openAPIToGraphQL, data) {
1605 const authHeaders = {};
1606 const authQs = {};
1607 let authCookie = null;
1608 /**
1609 * Determine if authentication is required, and which protocol (if any) we can
1610 * use
1611 */
1612 const { authRequired, sanitizedSecurityRequirement } = getAuthReqAndProtcolName(operation, _openAPIToGraphQL);
1613 const securityRequirement = data.saneMap[sanitizedSecurityRequirement];
1614 // Possibly, we don't need to do anything:
1615 if (!authRequired) {
1616 return { authHeaders, authQs, authCookie };
1617 }
1618 // If authentication is required, but we can't fulfill the protocol, throw:
1619 if (authRequired && typeof securityRequirement !== 'string') {
1620 throw new Error(`Missing information to authenticate API request.`);
1621 }
1622 if (typeof securityRequirement === 'string') {
1623 const security = data.security[securityRequirement];
1624 switch (security.def.type) {
1625 case 'apiKey':
1626 const apiKey = _openAPIToGraphQL.security[sanitizedSecurityRequirement].apiKey;
1627 if ('in' in security.def) {
1628 if (typeof security.def.name === 'string') {
1629 if (security.def.in === 'header') {
1630 authHeaders[security.def.name] = apiKey;
1631 }
1632 else if (security.def.in === 'query') {
1633 authQs[security.def.name] = apiKey;
1634 }
1635 else if (security.def.in === 'cookie') {
1636 authCookie = `${security.def.name}=${apiKey}`;
1637 }
1638 }
1639 else {
1640 throw new Error(`Cannot send API key in '${JSON.stringify(security.def.in)}'`);
1641 }
1642 }
1643 break;
1644 case 'http':
1645 switch (security.def.scheme) {
1646 case 'basic':
1647 const username = _openAPIToGraphQL.security[sanitizedSecurityRequirement].username;
1648 const password = _openAPIToGraphQL.security[sanitizedSecurityRequirement].password;
1649 const credentials = `${username}:${password}`;
1650 authHeaders.Authorization = `Basic ${Buffer.from(credentials).toString('base64')}`;
1651 break;
1652 default:
1653 throw new Error(`Cannot recognize http security scheme ` + `'${JSON.stringify(security.def.scheme)}'`);
1654 }
1655 break;
1656 case 'oauth2':
1657 break;
1658 case 'openIdConnect':
1659 break;
1660 default:
1661 throw new Error(`Cannot recognize security type '${security.def.type}'`);
1662 }
1663 }
1664 return { authHeaders, authQs, authCookie };
1665}
1666/**
1667 * Determines whether given operation requires authentication, and which of the
1668 * (possibly multiple) authentication protocols can be used based on the data
1669 * present in the given context.
1670 */
1671function getAuthReqAndProtcolName(operation, _openAPIToGraphQL) {
1672 let authRequired = false;
1673 if (Array.isArray(operation.securityRequirements) && operation.securityRequirements.length > 0) {
1674 authRequired = true;
1675 for (const securityRequirement of operation.securityRequirements) {
1676 const sanitizedSecurityRequirement = sanitize(securityRequirement, CaseStyle.camelCase);
1677 if (typeof _openAPIToGraphQL.security[sanitizedSecurityRequirement] === 'object') {
1678 return {
1679 authRequired,
1680 sanitizedSecurityRequirement,
1681 };
1682 }
1683 }
1684 }
1685 return {
1686 authRequired,
1687 };
1688}
1689/**
1690 * Given a link parameter, determine the value
1691 *
1692 * The link parameter is a reference to data contained in the
1693 * url/method/statuscode or response/request body/query/path/header
1694 */
1695function resolveLinkParameter(paramName, value, resolveData, root, args) {
1696 if (value === '$url') {
1697 return resolveData.url;
1698 }
1699 else if (value === '$method') {
1700 return resolveData.usedRequestOptions.method;
1701 }
1702 else if (value === '$statusCode') {
1703 return resolveData.usedStatusCode;
1704 }
1705 else if (value.startsWith('$request.')) {
1706 // CASE: parameter is previous body
1707 if (value === '$request.body') {
1708 return resolveData.usedPayload;
1709 // CASE: parameter in previous body
1710 }
1711 else if (value.startsWith('$request.body#')) {
1712 const tokens = JSONPath({
1713 path: value.split('body#/')[1],
1714 json: resolveData.usedPayload,
1715 });
1716 if (Array.isArray(tokens) && tokens.length > 0) {
1717 return tokens[0];
1718 }
1719 else {
1720 httpLog$1(`Warning: could not extract parameter '${paramName}' from link`);
1721 }
1722 // CASE: parameter in previous query parameter
1723 }
1724 else if (value.startsWith('$request.query')) {
1725 return resolveData.usedParams[sanitize(value.split('query.')[1], CaseStyle.camelCase)];
1726 // CASE: parameter in previous path parameter
1727 }
1728 else if (value.startsWith('$request.path')) {
1729 return resolveData.usedParams[sanitize(value.split('path.')[1], CaseStyle.camelCase)];
1730 // CASE: parameter in previous header parameter
1731 }
1732 else if (value.startsWith('$request.header')) {
1733 return resolveData.usedRequestOptions.headers[value.split('header.')[1]];
1734 }
1735 }
1736 else if (value.startsWith('$response.')) {
1737 /**
1738 * CASE: parameter is body
1739 *
1740 * NOTE: may not be used because it implies that the operation does not
1741 * return a JSON object and OpenAPI-to-GraphQL does not create GraphQL
1742 * objects for non-JSON data and links can only exists between objects.
1743 */
1744 if (value === '$response.body') {
1745 const result = JSON.parse(JSON.stringify(root));
1746 /**
1747 * _openAPIToGraphQL contains data used by OpenAPI-to-GraphQL to create the GraphQL interface
1748 * and should not be exposed
1749 */
1750 result._openAPIToGraphQL = undefined;
1751 return result;
1752 // CASE: parameter in body
1753 }
1754 else if (value.startsWith('$response.body#')) {
1755 const tokens = JSONPath({
1756 path: value.split('body#/')[1],
1757 json: root,
1758 });
1759 if (Array.isArray(tokens) && tokens.length > 0) {
1760 return tokens[0];
1761 }
1762 else {
1763 httpLog$1(`Warning: could not extract parameter '${paramName}' from link`);
1764 }
1765 // CASE: parameter in query parameter
1766 }
1767 else if (value.startsWith('$response.query')) {
1768 // NOTE: handled the same way $request.query is handled
1769 return resolveData.usedParams[sanitize(value.split('query.')[1], CaseStyle.camelCase)];
1770 // CASE: parameter in path parameter
1771 }
1772 else if (value.startsWith('$response.path')) {
1773 // NOTE: handled the same way $request.path is handled
1774 return resolveData.usedParams[sanitize(value.split('path.')[1], CaseStyle.camelCase)];
1775 // CASE: parameter in header parameter
1776 }
1777 else if (value.startsWith('$response.header')) {
1778 return resolveData.responseHeaders[value.split('header.')[1]];
1779 }
1780 }
1781 throw new Error(`Cannot create link because '${value}' is an invalid runtime expression`);
1782}
1783/**
1784 * Given a link parameter or callback path, determine the value from the runtime
1785 * expression
1786 *
1787 * The link parameter or callback path is a reference to data contained in the
1788 * url/method/statuscode or response/request body/query/path/header
1789 */
1790function resolveRuntimeExpression(paramName, value, resolveData, root, args) {
1791 if (value === '$url') {
1792 return resolveData.usedRequestOptions.url;
1793 }
1794 else if (value === '$method') {
1795 return resolveData.usedRequestOptions.method;
1796 }
1797 else if (value === '$statusCode') {
1798 return resolveData.usedStatusCode;
1799 }
1800 else if (value.startsWith('$request.')) {
1801 // CASE: parameter is previous body
1802 if (value === '$request.body') {
1803 return resolveData.usedPayload;
1804 // CASE: parameter in previous body
1805 }
1806 else if (value.startsWith('$request.body#')) {
1807 const tokens = JSONPath({
1808 path: value.split('body#/')[1],
1809 json: resolveData.usedPayload,
1810 });
1811 if (Array.isArray(tokens) && tokens.length > 0) {
1812 return tokens[0];
1813 }
1814 else {
1815 httpLog$1(`Warning: could not extract parameter '${paramName}' from link`);
1816 }
1817 // CASE: parameter in previous query parameter
1818 }
1819 else if (value.startsWith('$request.query')) {
1820 return resolveData.usedParams[sanitize(value.split('query.')[1], CaseStyle.camelCase)];
1821 // CASE: parameter in previous path parameter
1822 }
1823 else if (value.startsWith('$request.path')) {
1824 return resolveData.usedParams[sanitize(value.split('path.')[1], CaseStyle.camelCase)];
1825 // CASE: parameter in previous header parameter
1826 }
1827 else if (value.startsWith('$request.header')) {
1828 return resolveData.usedRequestOptions.headers[value.split('header.')[1]];
1829 }
1830 }
1831 else if (value.startsWith('$response.')) {
1832 /**
1833 * CASE: parameter is body
1834 *
1835 * NOTE: may not be used because it implies that the operation does not
1836 * return a JSON object and OpenAPI-to-GraphQL does not create GraphQL
1837 * objects for non-JSON data and links can only exists between objects.
1838 */
1839 if (value === '$response.body') {
1840 const result = JSON.parse(JSON.stringify(root));
1841 /**
1842 * _openAPIToGraphQL contains data used by OpenAPI-to-GraphQL to create the GraphQL interface
1843 * and should not be exposed
1844 */
1845 result._openAPIToGraphQL = undefined;
1846 return result;
1847 // CASE: parameter in body
1848 }
1849 else if (value.startsWith('$response.body#')) {
1850 const tokens = JSONPath({
1851 path: value.split('body#/')[1],
1852 json: root,
1853 });
1854 if (Array.isArray(tokens) && tokens.length > 0) {
1855 return tokens[0];
1856 }
1857 else {
1858 httpLog$1(`Warning: could not extract parameter '${paramName}' from link`);
1859 }
1860 // CASE: parameter in query parameter
1861 }
1862 else if (value.startsWith('$response.query')) {
1863 // NOTE: handled the same way $request.query is handled
1864 return resolveData.usedParams[sanitize(value.split('query.')[1], CaseStyle.camelCase)];
1865 // CASE: parameter in path parameter
1866 }
1867 else if (value.startsWith('$response.path')) {
1868 // NOTE: handled the same way $request.path is handled
1869 return resolveData.usedParams[sanitize(value.split('path.')[1], CaseStyle.camelCase)];
1870 // CASE: parameter in header parameter
1871 }
1872 else if (value.startsWith('$response.header')) {
1873 return resolveData.responseHeaders[value.split('header.')[1]];
1874 }
1875 }
1876 throw new Error(`Cannot create link because '${value}' is an invalid runtime expression`);
1877}
1878/**
1879 * Check if a string is a runtime expression in the context of link parameters
1880 */
1881function isRuntimeExpression(str) {
1882 const references = ['header.', 'query.', 'path.', 'body'];
1883 if (str === '$url' || str === '$method' || str === '$statusCode') {
1884 return true;
1885 }
1886 else if (str.startsWith('$request.')) {
1887 for (let i = 0; i < references.length; i++) {
1888 if (str.startsWith(`$request.${references[i]}`)) {
1889 return true;
1890 }
1891 }
1892 }
1893 else if (str.startsWith('$response.')) {
1894 for (let i = 0; i < references.length; i++) {
1895 if (str.startsWith(`$response.${references[i]}`)) {
1896 return true;
1897 }
1898 }
1899 }
1900 return false;
1901}
1902/**
1903 * From the info object provided by the resolver, get a unique identifier, which
1904 * is the path formed from the nested field names (or aliases if provided)
1905 *
1906 * Used to store and retrieve the _openAPIToGraphQL of parent field
1907 */
1908function getIdentifier(info) {
1909 return getIdentifierRecursive(info.path);
1910}
1911/**
1912 * From the info object provided by the resolver, get the unique identifier of
1913 * the parent object
1914 */
1915function getParentIdentifier(info) {
1916 return getIdentifierRecursive(info.path.prev);
1917}
1918/**
1919 * Get the path of nested field names (or aliases if provided)
1920 */
1921function getIdentifierRecursive(path) {
1922 return typeof path.prev === 'undefined'
1923 ? path.key.toString()
1924 : /**
1925 * Check if the identifier contains array indexing, if so remove.
1926 *
1927 * i.e. instead of 0/friends/1/friends/2/friends/user, create
1928 * friends/friends/friends/user
1929 */
1930 isNaN(parseInt(path.key.toString()))
1931 ? `${path.key}/${getIdentifierRecursive(path.prev)}`
1932 : getIdentifierRecursive(path.prev);
1933}
1934/**
1935 * Create a new GraphQLError with an extensions field
1936 */
1937function graphQLErrorWithExtensions(message, extensions) {
1938 return new GraphQLError(message, null, null, null, null, null, extensions);
1939}
1940/**
1941 * Extracts data from the GraphQL arguments of a particular field
1942 *
1943 * Replaces the path parameter in the given path with values in the given args.
1944 * Furthermore adds the query parameters for a request.
1945 */
1946function extractRequestDataFromArgs(path, parameters, args, // NOTE: argument keys are sanitized!
1947data) {
1948 const qs = {};
1949 const headers = {};
1950 // Iterate parameters:
1951 for (const param of parameters) {
1952 const sanitizedParamName = sanitize(param.name, !data.options.simpleNames ? CaseStyle.camelCase : CaseStyle.simple);
1953 if (sanitizedParamName && sanitizedParamName in args) {
1954 switch (param.in) {
1955 // Path parameters
1956 case 'path':
1957 path = path.replace(`{${param.name}}`, args[sanitizedParamName]);
1958 break;
1959 // Query parameters
1960 case 'query':
1961 qs[param.name] = args[sanitizedParamName];
1962 break;
1963 // Header parameters
1964 case 'header':
1965 headers[param.name] = args[sanitizedParamName];
1966 break;
1967 // Cookie parameters
1968 case 'cookie':
1969 if (!('cookie' in headers)) {
1970 headers.cookie = '';
1971 }
1972 headers.cookie += `${param.name}=${args[sanitizedParamName]}; `;
1973 break;
1974 default:
1975 httpLog$1(`Warning: The parameter location '${param.in}' in the ` +
1976 `parameter '${param.name}' of operation '${path}' is not ` +
1977 `supported`);
1978 }
1979 }
1980 }
1981 return { path, qs, headers };
1982}
1983
1984// Copyright IBM Corp. 2018. All Rights Reserved.
1985const preprocessingLog = mockDebug('preprocessing');
1986/**
1987 * Given an operation object from the OAS, create an Operation, which contains
1988 * the necessary data to create a GraphQL wrapper for said operation object.
1989 *
1990 * @param path The path of the operation object
1991 * @param method The method of the operation object
1992 * @param operationString A string representation of the path and the method (and the OAS title if applicable)
1993 * @param operationType Whether the operation should be turned into a Query/Mutation/Subscription operation
1994 * @param operation The operation object from the OAS
1995 * @param pathItem The path item object from the OAS from which the operation object is derived from
1996 * @param oas The OAS from which the path item and operation object are derived from
1997 * @param data An assortment of data which at this point is mainly used enable logging
1998 * @param options The options passed by the user
1999 */
2000function processOperation(path, method, operationString, operationType, operation, pathItem, oas, data, options) {
2001 // Determine description
2002 let description = operation.description;
2003 if ((typeof description !== 'string' || description === '') && typeof operation.summary === 'string') {
2004 description = operation.summary;
2005 }
2006 if (data.options.equivalentToMessages) {
2007 // Description may not exist
2008 if (typeof description !== 'string') {
2009 description = '';
2010 }
2011 description += `\n\nEquivalent to ${operationString}`;
2012 }
2013 // Hold on to the operationId
2014 const operationId = typeof operation.operationId !== 'undefined' ? operation.operationId : generateOperationId(method, path);
2015 // Request schema
2016 const { payloadContentType, payloadSchema, payloadSchemaNames, payloadRequired } = getRequestSchemaAndNames(path, operation, oas);
2017 const payloadDefinition = payloadSchema && typeof payloadSchema !== 'undefined'
2018 ? createDataDef(payloadSchemaNames, payloadSchema, true, data, oas)
2019 : undefined;
2020 // Response schema
2021 const { responseContentType, responseSchema, responseSchemaNames, statusCode } = getResponseSchemaAndNames(path, method, operation, oas, data, options);
2022 if (!responseSchema || typeof responseSchema !== 'object') {
2023 handleWarning({
2024 mitigationType: MitigationTypes.MISSING_RESPONSE_SCHEMA,
2025 message: `Operation ${operationString} has no (valid) response schema. ` +
2026 `You can use the fillEmptyResponses option to create a ` +
2027 `placeholder schema`,
2028 data,
2029 log: preprocessingLog,
2030 });
2031 return undefined;
2032 }
2033 // Links
2034 const links = getLinks(path, method, operation, oas, data);
2035 const responseDefinition = createDataDef(responseSchemaNames, responseSchema, false, data, oas, links);
2036 // Parameters
2037 const parameters = getParameters(path, method, operation, pathItem, oas);
2038 // Security protocols
2039 const securityRequirements = options.viewer ? getSecurityRequirements(operation, data.security, oas) : [];
2040 // Servers
2041 const servers = getServers(operation, pathItem, oas);
2042 // Whether to place this operation into an authentication viewer
2043 const inViewer = securityRequirements.length > 0 && data.options.viewer !== false;
2044 return {
2045 operationId,
2046 operationString,
2047 operationType,
2048 description,
2049 path,
2050 method,
2051 payloadContentType,
2052 payloadDefinition,
2053 payloadRequired,
2054 responseContentType,
2055 responseDefinition,
2056 parameters,
2057 securityRequirements,
2058 servers,
2059 inViewer,
2060 statusCode,
2061 oas,
2062 };
2063}
2064/**
2065 * Extract information from the OAS and put it inside a data structure that
2066 * is easier for OpenAPI-to-GraphQL to use
2067 */
2068function preprocessOas(oass, options) {
2069 const data = {
2070 operations: {},
2071 callbackOperations: {},
2072 usedTypeNames: [
2073 'Query',
2074 'Mutation',
2075 'Subscription',
2076 ],
2077 defs: [],
2078 security: {},
2079 saneMap: {},
2080 options,
2081 oass,
2082 };
2083 oass.forEach(oas => {
2084 // Store stats on OAS:
2085 data.options.report.numOps += countOperations(oas);
2086 data.options.report.numOpsMutation += countOperationsMutation(oas);
2087 data.options.report.numOpsQuery += countOperationsQuery(oas);
2088 data.options.report.numOpsSubscription += countOperationsSubscription(oas);
2089 // Get security schemes
2090 const currentSecurity = getProcessedSecuritySchemes(oas, data);
2091 const commonSecurityPropertyName = getCommonPropertyNames(data.security, currentSecurity);
2092 commonSecurityPropertyName.forEach(propertyName => {
2093 var _a;
2094 handleWarning({
2095 mitigationType: MitigationTypes.DUPLICATE_SECURITY_SCHEME,
2096 message: `Multiple OASs share security schemes with the same name '${propertyName}'`,
2097 mitigationAddendum: `The security scheme from OAS ` + `'${(_a = currentSecurity[propertyName].oas.info) === null || _a === void 0 ? void 0 : _a.title}' will be ignored`,
2098 data,
2099 log: preprocessingLog,
2100 });
2101 });
2102 // Do not overwrite preexisting security schemes
2103 data.security = { ...currentSecurity, ...data.security };
2104 // Process all operations
2105 for (const path in oas.paths) {
2106 const pathItem = !('$ref' in oas.paths[path])
2107 ? oas.paths[path]
2108 : resolveRef(oas.paths[path].$ref, oas);
2109 Object.keys(pathItem)
2110 .filter(objectKey => {
2111 /**
2112 * Get only fields that contain operation objects
2113 *
2114 * Can also contain other fields such as summary or description
2115 */
2116 return isHttpMethod(objectKey);
2117 })
2118 .forEach(rawMethod => {
2119 var _a, _b, _c, _d, _e, _f;
2120 const operationString = oass.length === 1
2121 ? formatOperationString(rawMethod, path)
2122 : formatOperationString(rawMethod, path, (_a = oas.info) === null || _a === void 0 ? void 0 : _a.title);
2123 let httpMethod;
2124 try {
2125 httpMethod = methodToHttpMethod(rawMethod);
2126 }
2127 catch (e) {
2128 handleWarning({
2129 mitigationType: MitigationTypes.INVALID_HTTP_METHOD,
2130 message: `Invalid HTTP method '${rawMethod}' in operation '${operationString}'`,
2131 data,
2132 log: preprocessingLog,
2133 });
2134 return;
2135 }
2136 const operation = pathItem[httpMethod];
2137 let operationType = httpMethod === HTTP_METHODS.get ? GraphQLOperationType.Query : GraphQLOperationType.Mutation;
2138 // Option selectQueryOrMutationField can override operation type
2139 if (typeof options.selectQueryOrMutationField === 'object' &&
2140 typeof options.selectQueryOrMutationField[(_b = oas.info) === null || _b === void 0 ? void 0 : _b.title] === 'object' &&
2141 typeof options.selectQueryOrMutationField[(_c = oas.info) === null || _c === void 0 ? void 0 : _c.title][path] === 'object' &&
2142 typeof options.selectQueryOrMutationField[(_d = oas.info) === null || _d === void 0 ? void 0 : _d.title][path][httpMethod] === 'number' // This is an TS enum, which is translated to have a integer value
2143 ) {
2144 operationType =
2145 options.selectQueryOrMutationField[(_e = oas.info) === null || _e === void 0 ? void 0 : _e.title][path][httpMethod] === GraphQLOperationType.Mutation
2146 ? GraphQLOperationType.Mutation
2147 : GraphQLOperationType.Query;
2148 }
2149 const operationData = processOperation(path, httpMethod, operationString, operationType, operation, pathItem, oas, data, options);
2150 if (operationData) {
2151 /**
2152 * Handle operationId property name collision
2153 * May occur if multiple OAS are provided
2154 */
2155 if (operationData && !(operationData.operationId in data.operations)) {
2156 data.operations[operationData.operationId] = operationData;
2157 }
2158 else {
2159 handleWarning({
2160 mitigationType: MitigationTypes.DUPLICATE_OPERATIONID,
2161 message: `Multiple OASs share operations with the same operationId '${operationData.operationId}'`,
2162 mitigationAddendum: `The operation from the OAS '${(_f = operationData.oas.info) === null || _f === void 0 ? void 0 : _f.title}' will be ignored`,
2163 data,
2164 log: preprocessingLog,
2165 });
2166 }
2167 }
2168 // Process all callbacks
2169 if (operation.callbacks) {
2170 Object.entries(operation.callbacks).forEach(([callbackName, callback]) => {
2171 const resolvedCallback = !('$ref' in callback)
2172 ? callback
2173 : resolveRef(callback.$ref, oas);
2174 Object.entries(resolvedCallback).forEach(([callbackExpression, callbackPathItem]) => {
2175 var _a, _b;
2176 const resolvedCallbackPathItem = !('$ref' in callbackPathItem)
2177 ? callbackPathItem
2178 : resolveRef(callbackPathItem.$ref, oas);
2179 const callbackOperationObjectMethods = Object.keys(resolvedCallbackPathItem).filter(objectKey => {
2180 /**
2181 * Get only fields that contain operation objects
2182 *
2183 * Can also contain other fields such as summary or description
2184 */
2185 return isHttpMethod(objectKey.toString());
2186 });
2187 if (callbackOperationObjectMethods.length > 0) {
2188 if (callbackOperationObjectMethods.length > 1) {
2189 handleWarning({
2190 mitigationType: MitigationTypes.CALLBACKS_MULTIPLE_OPERATION_OBJECTS,
2191 message: `Callback '${callbackExpression}' on operation '${operationString}' has multiple operation objects with the methods '${callbackOperationObjectMethods}'. OpenAPI-to-GraphQL can only utilize one of these operation objects.`,
2192 mitigationAddendum: `The operation with the method '${callbackOperationObjectMethods[0].toString()}' will be selected and all others will be ignored.`,
2193 data,
2194 log: preprocessingLog,
2195 });
2196 }
2197 // Select only one of the operation object methods
2198 const callbackRawMethod = callbackOperationObjectMethods[0];
2199 const callbackOperationString = oass.length === 1
2200 ? formatOperationString(httpMethod, callbackName)
2201 : formatOperationString(httpMethod, callbackName, (_a = oas.info) === null || _a === void 0 ? void 0 : _a.title);
2202 let callbackHttpMethod;
2203 try {
2204 callbackHttpMethod = methodToHttpMethod(callbackRawMethod.toString());
2205 }
2206 catch (e) {
2207 handleWarning({
2208 mitigationType: MitigationTypes.INVALID_HTTP_METHOD,
2209 message: `Invalid HTTP method '${rawMethod}' in callback '${callbackOperationString}' in operation '${operationString}'`,
2210 data,
2211 log: preprocessingLog,
2212 });
2213 return;
2214 }
2215 const callbackOperation = processOperation(callbackExpression, callbackHttpMethod, callbackOperationString, GraphQLOperationType.Subscription, resolvedCallbackPathItem[callbackHttpMethod], callbackPathItem, oas, data, options);
2216 callbackOperation.responseContentType = callbackOperation.payloadContentType;
2217 callbackOperation.responseDefinition = callbackOperation.payloadDefinition;
2218 callbackOperation.parameters = operationData.parameters;
2219 callbackOperation.payloadContentType = operationData.payloadContentType;
2220 callbackOperation.payloadDefinition = operationData.payloadDefinition;
2221 callbackOperation.payloadRequired = operationData.payloadRequired;
2222 if (callbackOperation) {
2223 /**
2224 * Handle operationId property name collision
2225 * May occur if multiple OAS are provided
2226 */
2227 if (callbackOperation && !(callbackOperation.operationId in data.callbackOperations)) {
2228 data.callbackOperations[callbackOperation.operationId] = callbackOperation;
2229 }
2230 else {
2231 handleWarning({
2232 mitigationType: MitigationTypes.DUPLICATE_OPERATIONID,
2233 message: `Multiple OASs share callback operations with the same operationId '${callbackOperation.operationId}'`,
2234 mitigationAddendum: `The callback operation from the OAS '${(_b = operationData.oas.info) === null || _b === void 0 ? void 0 : _b.title}' will be ignored`,
2235 data,
2236 log: preprocessingLog,
2237 });
2238 }
2239 }
2240 }
2241 });
2242 });
2243 }
2244 });
2245 }
2246 });
2247 return data;
2248}
2249/**
2250 * Extracts the security schemes from given OAS and organizes the information in
2251 * a data structure that is easier for OpenAPI-to-GraphQL to use
2252 *
2253 * Here is the structure of the data:
2254 * {
2255 * {string} [sanitized name] { Contains information about the security protocol
2256 * {string} rawName Stores the raw security protocol name
2257 * {object} def Definition provided by OAS
2258 * {object} parameters Stores the names of the authentication credentials
2259 * NOTE: Structure will depend on the type of the protocol
2260 * (e.g. basic authentication, API key, etc.)
2261 * NOTE: Mainly used for the AnyAuth viewers
2262 * {object} schema Stores the GraphQL schema to create the viewers
2263 * }
2264 * }
2265 *
2266 * Here is an example:
2267 * {
2268 * MyApiKey: {
2269 * rawName: "My_api_key",
2270 * def: { ... },
2271 * parameters: {
2272 * apiKey: MyKeyApiKey
2273 * },
2274 * schema: { ... }
2275 * }
2276 * MyBasicAuth: {
2277 * rawName: "My_basic_auth",
2278 * def: { ... },
2279 * parameters: {
2280 * username: MyBasicAuthUsername,
2281 * password: MyBasicAuthPassword,
2282 * },
2283 * schema: { ... }
2284 * }
2285 * }
2286 */
2287function getProcessedSecuritySchemes(oas, data) {
2288 var _a, _b, _c, _d, _e;
2289 const result = {};
2290 const security = getSecuritySchemes(oas);
2291 // Loop through all the security protocols
2292 for (const key in security) {
2293 const protocol = security[key];
2294 // Determine the schema and the parameters for the security protocol
2295 let schema;
2296 let parameters = {};
2297 let description;
2298 switch (protocol.type) {
2299 case 'apiKey':
2300 description = `API key credentials for the security protocol '${key}'`;
2301 if (data.oass.length > 1) {
2302 description += ` in ${(_a = oas.info) === null || _a === void 0 ? void 0 : _a.title}`;
2303 }
2304 parameters = {
2305 apiKey: sanitize(`${key}_apiKey`, CaseStyle.camelCase),
2306 };
2307 schema = {
2308 type: 'object',
2309 description,
2310 properties: {
2311 apiKey: {
2312 type: 'string',
2313 },
2314 },
2315 };
2316 break;
2317 case 'http':
2318 switch (protocol.scheme) {
2319 /**
2320 * TODO: HTTP has a number of authentication types
2321 *
2322 * See http://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml
2323 */
2324 case 'basic':
2325 description = `Basic auth credentials for security protocol '${key}'`;
2326 parameters = {
2327 username: sanitize(`${key}_username`, CaseStyle.camelCase),
2328 password: sanitize(`${key}_password`, CaseStyle.camelCase),
2329 };
2330 schema = {
2331 type: 'object',
2332 description,
2333 properties: {
2334 username: {
2335 type: 'string',
2336 },
2337 password: {
2338 type: 'string',
2339 },
2340 },
2341 };
2342 break;
2343 default:
2344 handleWarning({
2345 mitigationType: MitigationTypes.UNSUPPORTED_HTTP_SECURITY_SCHEME,
2346 message: `Currently unsupported HTTP authentication protocol ` +
2347 `type 'http' and scheme '${protocol.scheme}' in OAS ` +
2348 `'${(_b = oas.info) === null || _b === void 0 ? void 0 : _b.title}'`,
2349 data,
2350 log: preprocessingLog,
2351 });
2352 }
2353 break;
2354 // TODO: Implement
2355 case 'openIdConnect':
2356 handleWarning({
2357 mitigationType: MitigationTypes.UNSUPPORTED_HTTP_SECURITY_SCHEME,
2358 message: `Currently unsupported HTTP authentication protocol ` + `type 'openIdConnect' in OAS '${(_c = oas.info) === null || _c === void 0 ? void 0 : _c.title}'`,
2359 data,
2360 log: preprocessingLog,
2361 });
2362 break;
2363 case 'oauth2':
2364 handleWarning({
2365 mitigationType: MitigationTypes.OAUTH_SECURITY_SCHEME,
2366 message: `OAuth security scheme found in OAS '${(_d = oas.info) === null || _d === void 0 ? void 0 : _d.title}'. ` +
2367 `OAuth support is provided using the 'tokenJSONpath' option`,
2368 data,
2369 log: preprocessingLog,
2370 });
2371 // Continue because we do not want to create an OAuth viewer
2372 continue;
2373 default:
2374 handleWarning({
2375 mitigationType: MitigationTypes.UNSUPPORTED_HTTP_SECURITY_SCHEME,
2376 message: `Unsupported HTTP authentication protocol` + `type '${protocol.type}' in OAS '${(_e = oas.info) === null || _e === void 0 ? void 0 : _e.title}'`,
2377 data,
2378 log: preprocessingLog,
2379 });
2380 }
2381 // Add protocol data to the output
2382 result[key] = {
2383 rawName: key,
2384 def: protocol,
2385 parameters,
2386 schema,
2387 oas,
2388 };
2389 }
2390 return result;
2391}
2392/**
2393 * Method to either create a new or reuse an existing, centrally stored data
2394 * definition.
2395 */
2396function createDataDef(names, schema, isInputObjectType, data, oas, links) {
2397 const preferredName = getPreferredName(names);
2398 // Basic validation test
2399 if (typeof schema !== 'object') {
2400 handleWarning({
2401 mitigationType: MitigationTypes.MISSING_SCHEMA,
2402 message: `Could not create data definition for schema with ` +
2403 `preferred name '${preferredName}' and schema '${JSON.stringify(schema)}'`,
2404 data,
2405 log: preprocessingLog,
2406 });
2407 // TODO: Does this change make the option fillEmptyResponses obsolete?
2408 return {
2409 preferredName,
2410 schema: null,
2411 required: [],
2412 links: null,
2413 subDefinitions: null,
2414 graphQLTypeName: null,
2415 graphQLInputObjectTypeName: null,
2416 targetGraphQLType: 'json',
2417 };
2418 }
2419 else {
2420 if ('$ref' in schema) {
2421 schema = resolveRef(schema.$ref, oas);
2422 }
2423 const saneLinks = {};
2424 if (typeof links === 'object') {
2425 Object.keys(links).forEach(linkKey => {
2426 saneLinks[sanitize(linkKey.toString(), !data.options.simpleNames ? CaseStyle.camelCase : CaseStyle.simple)] = links[linkKey];
2427 });
2428 }
2429 // Determine the index of possible existing data definition
2430 const index = getSchemaIndex(preferredName, schema, data.defs);
2431 if (index !== -1) {
2432 // Found existing data definition and fetch it
2433 const existingDataDef = data.defs[index];
2434 /**
2435 * Collapse links if possible, i.e. if the current operation has links,
2436 * combine them with the prexisting ones
2437 */
2438 if (typeof saneLinks !== 'undefined') {
2439 if (typeof existingDataDef.links !== 'undefined') {
2440 // Check if there are any overlapping links
2441 Object.keys(existingDataDef.links).forEach(saneLinkKey => {
2442 if (typeof saneLinks[saneLinkKey] !== 'undefined' &&
2443 !deepEqual(existingDataDef.links[saneLinkKey], saneLinks[saneLinkKey])) {
2444 handleWarning({
2445 mitigationType: MitigationTypes.DUPLICATE_LINK_KEY,
2446 message: `Multiple operations with the same response body share the same sanitized ` +
2447 `link key '${saneLinkKey}' but have different link definitions ` +
2448 `'${JSON.stringify(existingDataDef.links[saneLinkKey])}' and ` +
2449 `'${JSON.stringify(saneLinks[saneLinkKey])}'.`,
2450 data,
2451 log: preprocessingLog,
2452 });
2453 }
2454 });
2455 /**
2456 * Collapse the links
2457 *
2458 * Avoid overwriting preexisting links
2459 */
2460 existingDataDef.links = { ...saneLinks, ...existingDataDef.links };
2461 }
2462 else {
2463 // No preexisting links, so simply assign the links
2464 existingDataDef.links = saneLinks;
2465 }
2466 }
2467 return existingDataDef;
2468 }
2469 else {
2470 // Else, define a new name, store the def, and return it
2471 const usedNames = [...data.usedTypeNames, ...Object.keys(data.saneMap)];
2472 const caseStyle = data.options.simpleNames ? CaseStyle.simple : CaseStyle.PascalCase;
2473 const name = getSchemaName(names, usedNames, caseStyle);
2474 // Store and sanitize the name
2475 let saneName = sanitize(name, caseStyle);
2476 saneName = storeSaneName(saneName, name, data.saneMap);
2477 const saneInputName = capitalize(saneName + 'Input');
2478 /**
2479 * TODO: is there a better way of copying the schema object?
2480 *
2481 * Perhaps, just copy it at the root level (operation schema)
2482 */
2483 const collapsedSchema = resolveAllOf(schema, {}, data, oas);
2484 const targetGraphQLType = getSchemaTargetGraphQLType(collapsedSchema, data);
2485 const def = {
2486 preferredName,
2487 /**
2488 * Note that schema may contain $ref or schema composition (e.g. allOf)
2489 *
2490 * TODO: the schema is used in getSchemaIndex, which allows us to check
2491 * whether a dataDef has already been created for that particular
2492 * schema and name pair. The look up should resolve references but
2493 * currently, it does not.
2494 */
2495 schema,
2496 required: [],
2497 targetGraphQLType,
2498 subDefinitions: undefined,
2499 links: saneLinks,
2500 graphQLTypeName: saneName,
2501 graphQLInputObjectTypeName: saneInputName,
2502 };
2503 // Used type names and defs of union and object types are pushed during creation
2504 if (targetGraphQLType === 'object' || targetGraphQLType === 'list' || targetGraphQLType === 'enum') {
2505 data.usedTypeNames.push(saneName);
2506 data.usedTypeNames.push(saneInputName);
2507 // Add the def to the master list
2508 data.defs.push(def);
2509 }
2510 // We currently only support simple cases of anyOf and oneOf
2511 if (
2512 // TODO: Should also consider if the member schema contains type data
2513 (Array.isArray(collapsedSchema.anyOf) && Array.isArray(collapsedSchema.oneOf)) || // anyOf and oneOf used concurrently
2514 hasNestedAnyOfUsage(collapsedSchema, oas) ||
2515 hasNestedOneOfUsage(collapsedSchema, oas)) {
2516 handleWarning({
2517 mitigationType: MitigationTypes.COMBINE_SCHEMAS,
2518 message: `Schema '${JSON.stringify(schema)}' contains either both ` +
2519 `'anyOf' and 'oneOf' or nested 'anyOf' and 'oneOf' which ` +
2520 `is currently not supported.`,
2521 mitigationAddendum: `Use arbitrary JSON type instead.`,
2522 data,
2523 log: preprocessingLog,
2524 });
2525 def.targetGraphQLType = 'json';
2526 return def;
2527 }
2528 // oneOf will ideally be turned into a union type
2529 if (Array.isArray(collapsedSchema.oneOf)) {
2530 const oneOfDataDef = createDataDefFromOneOf(saneName, saneInputName, collapsedSchema, isInputObjectType, def, data, oas);
2531 if (typeof oneOfDataDef === 'object') {
2532 return oneOfDataDef;
2533 }
2534 }
2535 /**
2536 * anyOf will ideally be turned into an object type
2537 *
2538 * Fields common to all member schemas will be made non-null
2539 */
2540 if (Array.isArray(collapsedSchema.anyOf)) {
2541 const anyOfDataDef = createDataDefFromAnyOf(saneName, saneInputName, collapsedSchema, isInputObjectType, def, data, oas);
2542 if (typeof anyOfDataDef === 'object') {
2543 return anyOfDataDef;
2544 }
2545 }
2546 if (targetGraphQLType) {
2547 switch (targetGraphQLType) {
2548 case 'list':
2549 if (typeof collapsedSchema.items === 'object') {
2550 // Break schema down into component parts
2551 // I.e. if it is an list type, create a reference to the list item type
2552 // Or if it is an object type, create references to all of the field types
2553 const itemsSchema = collapsedSchema.items;
2554 let itemsName = `${name}ListItem`;
2555 if ('$ref' in itemsSchema) {
2556 itemsName = collapsedSchema.items.$ref.split('/').pop();
2557 }
2558 const subDefinition = createDataDef(
2559 // Is this the correct classification for this name? It does not matter in the long run.
2560 { fromRef: itemsName }, itemsSchema, isInputObjectType, data, oas);
2561 // Add list item reference
2562 def.subDefinitions = subDefinition;
2563 }
2564 break;
2565 case 'object':
2566 def.subDefinitions = {};
2567 if (typeof collapsedSchema.properties === 'object' && Object.keys(collapsedSchema.properties).length > 0) {
2568 addObjectPropertiesToDataDef(def, collapsedSchema, def.required, isInputObjectType, data, oas);
2569 }
2570 else {
2571 handleWarning({
2572 mitigationType: MitigationTypes.OBJECT_MISSING_PROPERTIES,
2573 message: `Schema ${JSON.stringify(schema)} does not have ` + `any properties`,
2574 data,
2575 log: preprocessingLog,
2576 });
2577 def.targetGraphQLType = 'json';
2578 }
2579 break;
2580 }
2581 }
2582 else {
2583 // No target GraphQL type
2584 handleWarning({
2585 mitigationType: MitigationTypes.UNKNOWN_TARGET_TYPE,
2586 message: `No GraphQL target type could be identified for schema '${JSON.stringify(schema)}'.`,
2587 data,
2588 log: preprocessingLog,
2589 });
2590 def.targetGraphQLType = 'json';
2591 }
2592 return def;
2593 }
2594 }
2595}
2596/**
2597 * Returns the index of the data definition object in the given list that
2598 * contains the same schema and preferred name as the given one. Returns -1 if
2599 * that schema could not be found.
2600 */
2601function getSchemaIndex(preferredName, schema, dataDefs) {
2602 /**
2603 * TODO: instead of iterating through the whole list every time, create a
2604 * hashing function and store all of the DataDefinitions in a hashmap.
2605 */
2606 for (let index = 0; index < dataDefs.length; index++) {
2607 const def = dataDefs[index];
2608 /**
2609 * TODO: deepEquals is not sufficient. We also need to resolve references.
2610 * However, deepEquals should work for vast majority of cases.
2611 */
2612 if (preferredName === def.preferredName && deepEqual(schema, def.schema)) {
2613 return index;
2614 }
2615 }
2616 // The schema could not be found in the master list
2617 return -1;
2618}
2619/**
2620 * Determines the preferred name to use for schema regardless of name collisions.
2621 *
2622 * In other words, determines the ideal name for a schema.
2623 *
2624 * Similar to getSchemaName() except it does not check if the name has already
2625 * been taken.
2626 */
2627function getPreferredName(names) {
2628 if (typeof names.preferred === 'string') {
2629 return sanitize(names.preferred, CaseStyle.PascalCase); // CASE: preferred name already known
2630 }
2631 else if (typeof names.fromRef === 'string') {
2632 return sanitize(names.fromRef, CaseStyle.PascalCase); // CASE: name from reference
2633 }
2634 else if (typeof names.fromSchema === 'string') {
2635 return sanitize(names.fromSchema, CaseStyle.PascalCase); // CASE: name from schema (i.e., "title" property in schema)
2636 }
2637 else if (typeof names.fromPath === 'string') {
2638 return sanitize(names.fromPath, CaseStyle.PascalCase); // CASE: name from path
2639 }
2640 else {
2641 return 'PlaceholderName'; // CASE: placeholder name
2642 }
2643}
2644/**
2645 * Determines name to use for schema from previously determined schemaNames and
2646 * considering not reusing existing names.
2647 */
2648function getSchemaName(names, usedNames, caseStyle) {
2649 if (Object.keys(names).length === 1 && typeof names.preferred === 'string') {
2650 throw new Error(`Cannot create data definition without name(s), excluding the preferred name.`);
2651 }
2652 let schemaName;
2653 // CASE: name from reference
2654 if (typeof names.fromRef === 'string') {
2655 const saneName = sanitize(names.fromRef, caseStyle);
2656 if (!usedNames.includes(saneName)) {
2657 schemaName = names.fromRef;
2658 }
2659 }
2660 // CASE: name from schema (i.e., "title" property in schema)
2661 if (!schemaName && typeof names.fromSchema === 'string') {
2662 const saneName = sanitize(names.fromSchema, caseStyle);
2663 if (!usedNames.includes(saneName)) {
2664 schemaName = names.fromSchema;
2665 }
2666 }
2667 // CASE: name from path
2668 if (!schemaName && typeof names.fromPath === 'string') {
2669 const saneName = sanitize(names.fromPath, caseStyle);
2670 if (!usedNames.includes(saneName)) {
2671 schemaName = names.fromPath;
2672 }
2673 }
2674 // CASE: all names are already used - create approximate name
2675 if (!schemaName) {
2676 schemaName = sanitize(typeof names.fromRef === 'string'
2677 ? names.fromRef
2678 : typeof names.fromSchema === 'string'
2679 ? names.fromSchema
2680 : typeof names.fromPath === 'string'
2681 ? names.fromPath
2682 : 'PlaceholderName', caseStyle);
2683 }
2684 if (usedNames.includes(schemaName)) {
2685 let appendix = 2;
2686 /**
2687 * GraphQL Objects cannot share the name so if the name already exists in
2688 * the master list append an incremental number until the name does not
2689 * exist anymore.
2690 */
2691 while (usedNames.includes(`${schemaName}${appendix}`)) {
2692 appendix++;
2693 }
2694 schemaName = `${schemaName}${appendix}`;
2695 }
2696 return schemaName;
2697}
2698/**
2699 * Recursively add all of the properties of an object to the data definition
2700 */
2701function addObjectPropertiesToDataDef(def, schema, required, isInputObjectType, data, oas) {
2702 /**
2703 * Resolve all required properties
2704 *
2705 * TODO: required may contain duplicates, which is not necessarily a problem
2706 */
2707 if (Array.isArray(schema.required)) {
2708 schema.required.forEach(requiredProperty => {
2709 required.push(requiredProperty);
2710 });
2711 }
2712 for (const propertyKey in schema.properties) {
2713 let propSchemaName = propertyKey;
2714 let propSchema = schema.properties[propertyKey];
2715 if ('$ref' in propSchema) {
2716 propSchemaName = propSchema.$ref.split('/').pop();
2717 propSchema = resolveRef(propSchema.$ref, oas);
2718 }
2719 if (!(propertyKey in def.subDefinitions)) {
2720 const subDefinition = createDataDef({
2721 fromRef: propSchemaName,
2722 fromSchema: propSchema.title,
2723 }, propSchema, isInputObjectType, data, oas);
2724 // Add field type references
2725 def.subDefinitions[propertyKey] = subDefinition;
2726 }
2727 else {
2728 handleWarning({
2729 mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
2730 message: `By way of resolving 'allOf', multiple schemas contain ` +
2731 `properties with the same name, preventing consolidation. Cannot ` +
2732 `add property '${propertyKey}' from schema '${JSON.stringify(schema)}' ` +
2733 `to dataDefinition '${JSON.stringify(def)}'`,
2734 data,
2735 log: preprocessingLog,
2736 });
2737 }
2738 }
2739}
2740/**
2741 * Recursively traverse a schema and resolve allOf by appending the data to the
2742 * parent schema
2743 */
2744function resolveAllOf(schema, references, data, oas) {
2745 // Dereference schema
2746 if ('$ref' in schema) {
2747 const referenceLocation = schema.$ref;
2748 schema = resolveRef(schema.$ref, oas);
2749 if (referenceLocation in references) {
2750 return references[referenceLocation];
2751 }
2752 else {
2753 // Store references in case of circular allOf
2754 references[referenceLocation] = schema;
2755 }
2756 }
2757 const collapsedSchema = JSON.parse(JSON.stringify(schema));
2758 // Resolve allOf
2759 if (Array.isArray(collapsedSchema.allOf)) {
2760 collapsedSchema.allOf.forEach(memberSchema => {
2761 // Collapse type if applicable
2762 const resolvedSchema = resolveAllOf(memberSchema, references, data, oas);
2763 if (resolvedSchema.type) {
2764 if (!collapsedSchema.type) {
2765 collapsedSchema.type = resolvedSchema.type;
2766 // Add type if applicable
2767 }
2768 else if (collapsedSchema.type !== resolvedSchema.type) {
2769 // Incompatible schema type
2770 handleWarning({
2771 mitigationType: MitigationTypes.UNRESOLVABLE_SCHEMA,
2772 message: `Resolving 'allOf' field in schema '${collapsedSchema}' ` +
2773 `results in incompatible schema type from partial schema '${resolvedSchema}'.`,
2774 data,
2775 log: preprocessingLog,
2776 });
2777 }
2778 }
2779 // Collapse properties if applicable
2780 if ('properties' in resolvedSchema) {
2781 if (!('properties' in collapsedSchema)) {
2782 collapsedSchema.properties = {};
2783 }
2784 Object.entries(resolvedSchema.properties).forEach(([propertyName, property]) => {
2785 if (propertyName in collapsedSchema) {
2786 // Conflicting property
2787 handleWarning({
2788 mitigationType: MitigationTypes.UNRESOLVABLE_SCHEMA,
2789 message: `Resolving 'allOf' field in schema '${collapsedSchema}' ` +
2790 `results in incompatible property field from partial schema '${resolvedSchema}'.`,
2791 data,
2792 log: preprocessingLog,
2793 });
2794 }
2795 else {
2796 collapsedSchema.properties[propertyName] = property;
2797 }
2798 });
2799 }
2800 // Collapse oneOf if applicable
2801 if ('oneOf' in resolvedSchema) {
2802 if (!('oneOf' in collapsedSchema)) {
2803 collapsedSchema.oneOf = [];
2804 }
2805 resolvedSchema.oneOf.forEach(oneOfProperty => {
2806 collapsedSchema.oneOf.push(oneOfProperty);
2807 });
2808 }
2809 // Collapse anyOf if applicable
2810 if ('anyOf' in resolvedSchema) {
2811 if (!('anyOf' in collapsedSchema)) {
2812 collapsedSchema.anyOf = [];
2813 }
2814 resolvedSchema.anyOf.forEach(anyOfProperty => {
2815 collapsedSchema.anyOf.push(anyOfProperty);
2816 });
2817 }
2818 // Collapse required if applicable
2819 if ('required' in resolvedSchema) {
2820 if (!('required' in collapsedSchema)) {
2821 collapsedSchema.required = [];
2822 }
2823 resolvedSchema.required.forEach(requiredProperty => {
2824 if (!collapsedSchema.required.includes(requiredProperty)) {
2825 collapsedSchema.required.push(requiredProperty);
2826 }
2827 });
2828 }
2829 });
2830 }
2831 return collapsedSchema;
2832}
2833/**
2834 * In the context of schemas that use keywords that combine member schemas,
2835 * collect data on certain aspects so it is all in one place for processing.
2836 */
2837function getMemberSchemaData(schemas, data, oas) {
2838 const result = {
2839 allTargetGraphQLTypes: [],
2840 allProperties: [],
2841 allRequired: [],
2842 };
2843 schemas.forEach(schema => {
2844 // Dereference schemas
2845 if ('$ref' in schema) {
2846 schema = resolveRef(schema.$ref, oas);
2847 }
2848 // Consolidate target GraphQL type
2849 const memberTargetGraphQLType = getSchemaTargetGraphQLType(schema, data);
2850 if (memberTargetGraphQLType) {
2851 result.allTargetGraphQLTypes.push(memberTargetGraphQLType);
2852 }
2853 // Consolidate properties
2854 if (schema.properties) {
2855 result.allProperties.push(schema.properties);
2856 }
2857 // Consolidate required
2858 if (schema.required) {
2859 result.allRequired = result.allRequired.concat(schema.required);
2860 }
2861 });
2862 return result;
2863}
2864/**
2865 * Check to see if there are cases of nested oneOf fields in the member schemas
2866 *
2867 * We currently cannot handle complex cases of oneOf and anyOf
2868 */
2869function hasNestedOneOfUsage(collapsedSchema, oas) {
2870 // TODO: Should also consider if the member schema contains type data
2871 return (Array.isArray(collapsedSchema.oneOf) &&
2872 collapsedSchema.oneOf.some(memberSchema => {
2873 // anyOf and oneOf are nested
2874 if ('$ref' in memberSchema) {
2875 memberSchema = resolveRef(memberSchema.$ref, oas);
2876 }
2877 return (Array.isArray(memberSchema.anyOf) || Array.isArray(memberSchema.oneOf) // Nested oneOf would result in nested unions which are not allowed by GraphQL
2878 );
2879 }));
2880}
2881/**
2882 * Check to see if there are cases of nested anyOf fields in the member schemas
2883 *
2884 * We currently cannot handle complex cases of oneOf and anyOf
2885 */
2886function hasNestedAnyOfUsage(collapsedSchema, oas) {
2887 // TODO: Should also consider if the member schema contains type data
2888 return (Array.isArray(collapsedSchema.anyOf) &&
2889 collapsedSchema.anyOf.some(memberSchema => {
2890 // anyOf and oneOf are nested
2891 if ('$ref' in memberSchema) {
2892 memberSchema = resolveRef(memberSchema.$ref, oas);
2893 }
2894 return Array.isArray(memberSchema.anyOf) || Array.isArray(memberSchema.oneOf);
2895 }));
2896}
2897/**
2898 * Create a data definition for anyOf is applicable
2899 *
2900 * anyOf should resolve into an object that contains the superset of all
2901 * properties from the member schemas
2902 */
2903function createDataDefFromAnyOf(saneName, saneInputName, collapsedSchema, isInputObjectType, def, data, oas) {
2904 const anyOfData = getMemberSchemaData(collapsedSchema.anyOf, data, oas);
2905 if (anyOfData.allTargetGraphQLTypes.some(memberTargetGraphQLType => {
2906 return memberTargetGraphQLType === 'object';
2907 })) {
2908 // Every member type should be an object
2909 if (anyOfData.allTargetGraphQLTypes.every(memberTargetGraphQLType => {
2910 return memberTargetGraphQLType === 'object';
2911 }) &&
2912 anyOfData.allProperties.length > 0 // Redundant check
2913 ) {
2914 // Ensure that parent schema is compatible with oneOf
2915 if (def.targetGraphQLType === null || def.targetGraphQLType === 'object') {
2916 const allProperties = {};
2917 const incompatibleProperties = new Set();
2918 /**
2919 * TODO: Check for consistent properties across all member schemas and
2920 * make them into non-nullable properties by manipulating the
2921 * required field
2922 */
2923 if (typeof collapsedSchema.properties === 'object') {
2924 Object.keys(collapsedSchema.properties).forEach(propertyName => {
2925 allProperties[propertyName] = [collapsedSchema.properties[propertyName]];
2926 });
2927 }
2928 // Check if any member schema has conflicting properties
2929 anyOfData.allProperties.forEach(properties => {
2930 Object.keys(properties).forEach(propertyName => {
2931 if (!incompatibleProperties.has(propertyName.toString()) && // Has not been already identified as a problematic property
2932 typeof allProperties[propertyName] === 'object' &&
2933 allProperties[propertyName].some(property => {
2934 // Property does not match a recorded one
2935 return !deepEqual(property, properties[propertyName]);
2936 })) {
2937 incompatibleProperties.add(propertyName.toString());
2938 }
2939 // Add property in the store
2940 if (!(propertyName in allProperties)) {
2941 allProperties[propertyName] = [];
2942 }
2943 allProperties[propertyName].push(properties[propertyName]);
2944 });
2945 });
2946 def.subDefinitions = {};
2947 if (typeof collapsedSchema.properties === 'object' && Object.keys(collapsedSchema.properties).length > 0) {
2948 addObjectPropertiesToDataDef(def, collapsedSchema, def.required, isInputObjectType, data, oas);
2949 }
2950 anyOfData.allProperties.forEach(properties => {
2951 Object.keys(properties).forEach(propertyName => {
2952 if (!incompatibleProperties.has(propertyName.toString())) {
2953 // Dereferenced by processing anyOfData
2954 const propertySchema = properties[propertyName];
2955 const subDefinition = createDataDef({
2956 fromRef: propertyName.toString(),
2957 fromSchema: propertySchema.title,
2958 }, propertySchema, isInputObjectType, data, oas);
2959 /**
2960 * Add field type references
2961 * There should not be any collisions
2962 */
2963 def.subDefinitions[propertyName] = subDefinition;
2964 }
2965 });
2966 });
2967 // Add in incompatible properties
2968 incompatibleProperties.forEach(propertyName => {
2969 // TODO: add description
2970 def.subDefinitions[propertyName] = {
2971 targetGraphQLType: 'json',
2972 };
2973 });
2974 data.usedTypeNames.push(saneName);
2975 data.usedTypeNames.push(saneInputName);
2976 data.defs.push(def);
2977 def.targetGraphQLType = 'object';
2978 return def;
2979 }
2980 else {
2981 // The parent schema is incompatible with the member schemas
2982 handleWarning({
2983 mitigationType: MitigationTypes.COMBINE_SCHEMAS,
2984 message: `Schema '${JSON.stringify(def.schema)}' contains 'anyOf' and ` +
2985 `some member schemas are object types so create a GraphQL ` +
2986 `object type but the parent schema is a non-object type ` +
2987 `so they are not compatible.`,
2988 mitigationAddendum: `Use arbitrary JSON type instead.`,
2989 data,
2990 log: preprocessingLog,
2991 });
2992 def.targetGraphQLType = 'json';
2993 return def;
2994 }
2995 }
2996 else {
2997 // The member schemas are not all object types
2998 handleWarning({
2999 mitigationType: MitigationTypes.COMBINE_SCHEMAS,
3000 message: `Schema '${def.schema}' contains 'anyOf' and ` +
3001 `some member schemas are object types so create a GraphQL ` +
3002 `object type but some member schemas are non-object types ` +
3003 `so they are not compatible.`,
3004 data,
3005 log: preprocessingLog,
3006 });
3007 def.targetGraphQLType = 'json';
3008 return def;
3009 }
3010 }
3011}
3012function createDataDefFromOneOf(saneName, saneInputName, collapsedSchema, isInputObjectType, def, data, oas) {
3013 const oneOfData = getMemberSchemaData(collapsedSchema.oneOf, data, oas);
3014 if (oneOfData.allTargetGraphQLTypes.some(memberTargetGraphQLType => {
3015 return memberTargetGraphQLType === 'object';
3016 })) {
3017 // unions must be created from object types
3018 if (oneOfData.allTargetGraphQLTypes.every(memberTargetGraphQLType => {
3019 return memberTargetGraphQLType === 'object';
3020 }) &&
3021 oneOfData.allProperties.length > 0 // Redundant check
3022 ) {
3023 // Input object types cannot be composed of unions
3024 if (isInputObjectType) {
3025 handleWarning({
3026 mitigationType: MitigationTypes.INPUT_UNION,
3027 message: `Input object types cannot be composed of union types.`,
3028 data,
3029 log: preprocessingLog,
3030 });
3031 def.targetGraphQLType = 'json';
3032 return def;
3033 }
3034 // Ensure that parent schema is compatible with oneOf
3035 if (def.targetGraphQLType === null || def.targetGraphQLType === 'object') {
3036 def.subDefinitions = [];
3037 collapsedSchema.oneOf.forEach(memberSchema => {
3038 // Dereference member schema
3039 let fromRef;
3040 if ('$ref' in memberSchema) {
3041 fromRef = memberSchema.$ref.split('/').pop();
3042 memberSchema = resolveRef(memberSchema.$ref, oas);
3043 }
3044 // Member types of GraphQL unions must be object types
3045 if (getSchemaTargetGraphQLType(memberSchema, data) === 'object') {
3046 const subDefinition = createDataDef({
3047 fromRef,
3048 fromSchema: memberSchema.title,
3049 fromPath: `${saneName}Member`,
3050 }, memberSchema, isInputObjectType, data, oas);
3051 def.subDefinitions.push(subDefinition);
3052 }
3053 else {
3054 handleWarning({
3055 mitigationType: MitigationTypes.COMBINE_SCHEMAS,
3056 message: `Schema '${JSON.stringify(def.schema)}' contains 'oneOf' so ` +
3057 `create a GraphQL union type but member schema '${JSON.stringify(memberSchema)}' ` +
3058 `is not an object type and union member types must be ` +
3059 `object base types.`,
3060 data,
3061 log: preprocessingLog,
3062 });
3063 }
3064 });
3065 // Not all member schemas may have been turned into GraphQL member types
3066 if (def.subDefinitions.length > 0 &&
3067 def.subDefinitions.every(subDefinition => {
3068 return subDefinition.targetGraphQLType === 'object';
3069 })) {
3070 // Ensure all member schemas have been verified as object types
3071 data.usedTypeNames.push(saneName);
3072 data.usedTypeNames.push(saneInputName);
3073 data.defs.push(def);
3074 def.targetGraphQLType = 'union';
3075 return def;
3076 }
3077 else {
3078 handleWarning({
3079 mitigationType: MitigationTypes.COMBINE_SCHEMAS,
3080 message: `Schema '${JSON.stringify(def.schema)}' contains 'oneOf' so ` +
3081 `create a GraphQL union type but all member schemas are not` +
3082 `object types and union member types must be object types.`,
3083 mitigationAddendum: `Use arbitrary JSON type instead.`,
3084 data,
3085 log: preprocessingLog,
3086 });
3087 // Default arbitrary JSON type
3088 def.targetGraphQLType = 'json';
3089 return def;
3090 }
3091 }
3092 else {
3093 // The parent schema is incompatible with the member schemas
3094 handleWarning({
3095 mitigationType: MitigationTypes.COMBINE_SCHEMAS,
3096 message: `Schema '${JSON.stringify(def.schema)}' contains 'oneOf' so create ` +
3097 `a GraphQL union type but the parent schema is a non-object ` +
3098 `type and member types must be object types.`,
3099 mitigationAddendum: `Use arbitrary JSON type instead.`,
3100 data,
3101 log: preprocessingLog,
3102 });
3103 def.targetGraphQLType = 'json';
3104 return def;
3105 }
3106 }
3107 else {
3108 // The member schemas are not all object types
3109 handleWarning({
3110 mitigationType: MitigationTypes.COMBINE_SCHEMAS,
3111 message: `Schema '${JSON.stringify(def.schema)}' contains 'oneOf' so create ` +
3112 `a GraphQL union type but some member schemas are non-object ` +
3113 `types and union member types must be object types.`,
3114 mitigationAddendum: `Use arbitrary JSON type instead.`,
3115 data,
3116 log: preprocessingLog,
3117 });
3118 def.targetGraphQLType = 'json';
3119 return def;
3120 }
3121 }
3122}
3123
3124/* eslint-disable @typescript-eslint/ban-ts-comment */
3125const translationLog$2 = mockDebug('translation');
3126/**
3127 * Creates and returns a GraphQL type for the given JSON schema.
3128 */
3129function getGraphQLType({ def, operation, data, iteration = 0, isInputObjectType = false, includeHttpDetails = false, }) {
3130 const name = isInputObjectType ? def.graphQLInputObjectTypeName : def.graphQLTypeName;
3131 // Avoid excessive iterations
3132 if (iteration === 50) {
3133 throw new Error(`GraphQL type ${name} has excessive nesting of other types`);
3134 }
3135 switch (def.targetGraphQLType) {
3136 // CASE: object - create object type
3137 case 'object':
3138 return createOrReuseOt({
3139 def,
3140 operation,
3141 data,
3142 iteration,
3143 isInputObjectType,
3144 includeHttpDetails,
3145 });
3146 // CASE: union - create union type
3147 case 'union':
3148 return createOrReuseUnion({
3149 def,
3150 operation,
3151 data,
3152 iteration,
3153 includeHttpDetails,
3154 });
3155 // CASE: list - create list type
3156 case 'list':
3157 return createOrReuseList({
3158 def,
3159 operation,
3160 data,
3161 iteration,
3162 isInputObjectType,
3163 includeHttpDetails,
3164 });
3165 // CASE: enum - create enum type
3166 case 'enum':
3167 return createOrReuseEnum({
3168 def,
3169 data,
3170 });
3171 // CASE: scalar - return scalar type
3172 default:
3173 return getScalarType({
3174 def,
3175 data,
3176 });
3177 }
3178}
3179/**
3180 * Creates an (input) object type or return an existing one, and stores it
3181 * in data
3182 *
3183 * A returned GraphQLObjectType has the following internal structure:
3184 *
3185 * new GraphQLObjectType({
3186 * name // Optional name of the type
3187 * description // Optional description of type
3188 * fields // REQUIRED returning fields
3189 * type // REQUIRED definition of the field type
3190 * args // Optional definition of types
3191 * resolve // Optional function defining how to obtain this type
3192 * })
3193 */
3194function createOrReuseOt({ def, operation, data, iteration, isInputObjectType, includeHttpDetails, }) {
3195 // Try to reuse a preexisting (input) object type
3196 // CASE: query - reuse object type
3197 if (!isInputObjectType) {
3198 if (def.graphQLType && typeof def.graphQLType !== 'undefined') {
3199 translationLog$2(`Reuse object type '${def.graphQLTypeName}'` +
3200 (typeof operation === 'object' ? ` (for operation '${operation.operationString}')` : ''));
3201 return def.graphQLType;
3202 }
3203 // CASE: mutation - reuse input object type
3204 }
3205 else {
3206 if (def.graphQLInputObjectType && typeof def.graphQLInputObjectType !== 'undefined') {
3207 translationLog$2(`Reuse input object type '${def.graphQLInputObjectTypeName}'` +
3208 (typeof operation === 'object' ? ` (for operation '${operation.operationString}')` : ''));
3209 return def.graphQLInputObjectType;
3210 }
3211 }
3212 // Cannot reuse preexisting (input) object type, therefore create one
3213 const schema = def.schema;
3214 const description = schema.description;
3215 // CASE: query - create object type
3216 if (!isInputObjectType) {
3217 translationLog$2(`Create object type '${def.graphQLTypeName}'` +
3218 (typeof operation === 'object' ? ` (for operation '${operation.operationString}')` : ''));
3219 def.graphQLType = new GraphQLObjectType({
3220 name: def.graphQLTypeName,
3221 description,
3222 fields: () => {
3223 return createFields({
3224 def,
3225 links: def.links,
3226 operation,
3227 data,
3228 iteration,
3229 isInputObjectType: false,
3230 includeHttpDetails,
3231 });
3232 },
3233 });
3234 return def.graphQLType;
3235 // CASE: mutation - create input object type
3236 }
3237 else {
3238 translationLog$2(`Create input object type '${def.graphQLInputObjectTypeName}'` +
3239 (typeof operation === 'object' ? ` (for operation '${operation.operationString}')` : ''));
3240 def.graphQLInputObjectType = new GraphQLInputObjectType({
3241 name: def.graphQLInputObjectTypeName,
3242 description,
3243 fields: () => {
3244 return createFields({
3245 def,
3246 links: {},
3247 operation,
3248 data,
3249 iteration,
3250 isInputObjectType: true,
3251 includeHttpDetails,
3252 });
3253 },
3254 });
3255 return def.graphQLInputObjectType;
3256 }
3257}
3258/**
3259 * Creates a union type or return an existing one, and stores it in data
3260 */
3261function createOrReuseUnion({ def, operation, data, iteration, includeHttpDetails, }) {
3262 // Try to reuse existing union type
3263 if (typeof def.graphQLType !== 'undefined') {
3264 translationLog$2(`Reuse union type '${def.graphQLTypeName}'` +
3265 (typeof operation === 'object' ? ` (for operation '${operation.operationString}')` : ''));
3266 return def.graphQLType;
3267 }
3268 else {
3269 translationLog$2(`Create union type '${def.graphQLTypeName}'` +
3270 (typeof operation === 'object' ? ` (for operation '${operation.operationString}')` : ''));
3271 const schema = def.schema;
3272 const description = typeof schema.description !== 'undefined' ? schema.description : 'No description available.';
3273 const memberTypeDefinitions = def.subDefinitions;
3274 const types = Object.values(memberTypeDefinitions).map(memberTypeDefinition => {
3275 return getGraphQLType({
3276 def: memberTypeDefinition,
3277 operation,
3278 data,
3279 iteration: iteration + 1,
3280 isInputObjectType: false,
3281 includeHttpDetails,
3282 });
3283 });
3284 /**
3285 * Check for ambiguous member types
3286 *
3287 * i.e. member types that can be confused with each other.
3288 */
3289 checkAmbiguousMemberTypes(def, types, data);
3290 def.graphQLType = new GraphQLUnionType({
3291 name: def.graphQLTypeName,
3292 description,
3293 types,
3294 resolveType: (source, context, info) => {
3295 const properties = Object.keys(source)
3296 // Remove custom _openAPIToGraphQL property used to pass data
3297 .filter(property => property !== '_openAPIToGraphQL');
3298 /**
3299 * Find appropriate member type
3300 *
3301 * TODO: currently, the check is performed by only checking the property
3302 * names. In the future, we should also check the types of those
3303 * properties.
3304 *
3305 * TODO: there is a chance a that an intended member type cannot be
3306 * identified if, for whatever reason, the return data is a superset
3307 * of the fields specified in the OAS
3308 */
3309 return types.find(type => {
3310 const typeFields = Object.keys(type.getFields());
3311 // The type should be a superset of the properties
3312 if (properties.length <= typeFields.length) {
3313 return properties.every(property => typeFields.includes(property.toString()));
3314 }
3315 return false;
3316 });
3317 },
3318 });
3319 return def.graphQLType;
3320 }
3321}
3322/**
3323 * Check for ambiguous member types
3324 *
3325 * i.e. member types that can be confused with each other.
3326 */
3327function checkAmbiguousMemberTypes(def, types, data) {
3328 types.sort((a, b) => {
3329 const aFieldLength = Object.keys(a.getFields()).length;
3330 const bFieldLength = Object.keys(b.getFields()).length;
3331 if (aFieldLength < bFieldLength) {
3332 return -1;
3333 }
3334 else if (aFieldLength < bFieldLength) {
3335 return 1;
3336 }
3337 else {
3338 return 0;
3339 }
3340 });
3341 for (let i = 0; i < types.length - 1; i++) {
3342 const currentType = types[i];
3343 for (let j = i + 1; j < types.length; j++) {
3344 const otherType = types[j];
3345 // TODO: Check the value, not just the field name
3346 if (Object.keys(currentType.getFields()).every(field => {
3347 return Object.keys(otherType.getFields()).includes(field);
3348 })) {
3349 handleWarning({
3350 mitigationType: MitigationTypes.AMBIGUOUS_UNION_MEMBERS,
3351 message: `Union created from schema '${JSON.stringify(def)}' contains ` +
3352 `member types such as '${currentType}' and '${otherType}' ` +
3353 `which are ambiguous. Ambiguous member types can cause ` +
3354 `problems when trying to resolve types.`,
3355 data,
3356 log: translationLog$2,
3357 });
3358 return;
3359 }
3360 }
3361 }
3362}
3363/**
3364 * Creates a list type or returns an existing one, and stores it in data
3365 */
3366function createOrReuseList({ def, operation, iteration, isInputObjectType, data, includeHttpDetails, }) {
3367 const name = isInputObjectType ? def.graphQLInputObjectTypeName : def.graphQLTypeName;
3368 // Try to reuse existing Object Type
3369 if (!isInputObjectType && def.graphQLType && typeof def.graphQLType !== 'undefined') {
3370 translationLog$2(`Reuse GraphQLList '${def.graphQLTypeName}'`);
3371 return def.graphQLType;
3372 }
3373 else if (isInputObjectType && def.graphQLInputObjectType && typeof def.graphQLInputObjectType !== 'undefined') {
3374 translationLog$2(`Reuse GraphQLList '${def.graphQLInputObjectTypeName}'`);
3375 return def.graphQLInputObjectType;
3376 }
3377 // Create new List Object Type
3378 translationLog$2(`Create GraphQLList '${def.graphQLTypeName}'`);
3379 // Get definition of the list item, which should be in the sub definitions
3380 const itemDef = def.subDefinitions;
3381 // Equivalent to schema.items
3382 const itemsSchema = itemDef.schema;
3383 // Equivalent to `{name}ListItem`
3384 const itemsName = itemDef.graphQLTypeName;
3385 const itemsType = getGraphQLType({
3386 def: itemDef,
3387 data,
3388 operation,
3389 iteration: iteration + 1,
3390 isInputObjectType,
3391 includeHttpDetails,
3392 });
3393 if (itemsType !== null) {
3394 const listObjectType = new GraphQLList(itemsType);
3395 // Store newly created list type
3396 if (!isInputObjectType) {
3397 def.graphQLType = listObjectType;
3398 }
3399 else {
3400 def.graphQLInputObjectType = listObjectType;
3401 }
3402 return listObjectType;
3403 }
3404 else {
3405 throw new Error(`Cannot create list item object type '${itemsName}' in list
3406 '${name}' with schema '${JSON.stringify(itemsSchema)}'`);
3407 }
3408}
3409/**
3410 * Creates an enum type or returns an existing one, and stores it in data
3411 */
3412function createOrReuseEnum({ def, }) {
3413 /**
3414 * Try to reuse existing enum type
3415 *
3416 * Enum types do not have an input variant so only check def.ot
3417 */
3418 if (def.graphQLType && typeof def.graphQLType !== 'undefined') {
3419 translationLog$2(`Reuse GraphQLEnumType '${def.graphQLTypeName}'`);
3420 return def.graphQLType;
3421 }
3422 else {
3423 translationLog$2(`Create GraphQLEnumType '${def.graphQLTypeName}'`);
3424 const values = {};
3425 def.schema.enum.forEach(e => {
3426 // Force enum values to string and value should be in ALL_CAPS
3427 values[sanitize(e.toString(), CaseStyle.ALL_CAPS)] = {
3428 value: e,
3429 };
3430 });
3431 // Store newly created Enum Object Type
3432 def.graphQLType = new GraphQLEnumType({
3433 name: def.graphQLTypeName,
3434 values,
3435 });
3436 return def.graphQLType;
3437 }
3438}
3439/**
3440 * Returns the GraphQL scalar type matching the given JSON schema type
3441 */
3442function getScalarType({ def, data, }) {
3443 switch (def.targetGraphQLType) {
3444 case 'id':
3445 def.graphQLType = GraphQLID;
3446 break;
3447 case 'string':
3448 def.graphQLType = GraphQLString;
3449 break;
3450 case 'integer':
3451 def.graphQLType = GraphQLInt;
3452 break;
3453 case 'int64':
3454 def.graphQLType = GraphQLBigInt;
3455 break;
3456 case 'number':
3457 def.graphQLType = GraphQLFloat;
3458 break;
3459 case 'float':
3460 def.graphQLType = GraphQLFloat;
3461 break;
3462 case 'boolean':
3463 def.graphQLType = GraphQLBoolean;
3464 break;
3465 case 'json':
3466 def.graphQLType = GraphQLJSON;
3467 break;
3468 default:
3469 throw new Error(`Cannot process schema type '${def.targetGraphQLType}'.`);
3470 }
3471 return def.graphQLType;
3472}
3473/**
3474 * Creates the fields object to be used by an (input) object type
3475 */
3476function createFields({ def, links, operation, data, iteration, isInputObjectType, includeHttpDetails, }) {
3477 let fields = {};
3478 if (includeHttpDetails && !isInputObjectType) {
3479 fields.httpDetails = {
3480 type: GraphQLJSON,
3481 resolve: root => { var _a; return (_a = root === null || root === void 0 ? void 0 : root._openAPIToGraphQL) === null || _a === void 0 ? void 0 : _a.data; },
3482 };
3483 }
3484 const fieldTypeDefinitions = def.subDefinitions;
3485 // Create fields for properties
3486 for (const fieldTypeKey in fieldTypeDefinitions) {
3487 const fieldTypeDefinition = fieldTypeDefinitions[fieldTypeKey];
3488 const fieldSchema = fieldTypeDefinition.schema;
3489 // Get object type describing the property
3490 const objectType = getGraphQLType({
3491 def: fieldTypeDefinition,
3492 operation,
3493 data,
3494 iteration: iteration + 1,
3495 isInputObjectType,
3496 includeHttpDetails,
3497 });
3498 const requiredProperty = typeof def.required === 'object' && def.required.includes(fieldTypeKey);
3499 // Finally, add the object type to the fields (using sanitized field name)
3500 if (objectType) {
3501 const saneFieldTypeKey = sanitize(fieldTypeKey, !data.options.simpleNames ? CaseStyle.camelCase : CaseStyle.simple);
3502 const sanePropName = storeSaneName(saneFieldTypeKey, fieldTypeKey, data.saneMap);
3503 fields[sanePropName] = {
3504 type: requiredProperty ? new GraphQLNonNull(objectType) : objectType,
3505 description: typeof fieldSchema === 'object' ? fieldSchema.description : null,
3506 };
3507 }
3508 else {
3509 handleWarning({
3510 mitigationType: MitigationTypes.CANNOT_GET_FIELD_TYPE,
3511 message: `Cannot obtain GraphQL type for field '${fieldTypeKey}' in ` +
3512 `GraphQL type '${JSON.stringify(def.schema)}'.`,
3513 data,
3514 log: translationLog$2,
3515 });
3516 }
3517 }
3518 if (typeof links === 'object' && // Links are present
3519 !isInputObjectType // Only object type (input object types cannot make use of links)
3520 ) {
3521 for (const saneLinkKey in links) {
3522 translationLog$2(`Create link '${saneLinkKey}'...`);
3523 // Check if key is already in fields
3524 if (saneLinkKey in fields) {
3525 handleWarning({
3526 mitigationType: MitigationTypes.LINK_NAME_COLLISION,
3527 message: `Cannot create link '${saneLinkKey}' because parent ` +
3528 `object type already contains a field with the same (sanitized) name.`,
3529 data,
3530 log: translationLog$2,
3531 });
3532 }
3533 else {
3534 const link = links[saneLinkKey];
3535 // Get linked operation
3536 let linkedOpId;
3537 // TODO: href is yet another alternative to operationRef and operationId
3538 if (typeof link.operationId === 'string') {
3539 linkedOpId = link.operationId;
3540 }
3541 else if (typeof link.operationRef === 'string') {
3542 linkedOpId = linkOpRefToOpId({
3543 links,
3544 linkKey: saneLinkKey,
3545 operation,
3546 data,
3547 });
3548 }
3549 /**
3550 * linkedOpId may not be initialized because operationRef may lead to an
3551 * operation object that does not have an operationId
3552 */
3553 if (typeof linkedOpId === 'string' && linkedOpId in data.operations) {
3554 const linkedOp = data.operations[linkedOpId];
3555 // Determine parameters provided via link
3556 const argsFromLink = link.parameters;
3557 // Get arguments that are not provided by the linked operation
3558 let dynamicParams = linkedOp.parameters;
3559 if (typeof argsFromLink === 'object') {
3560 dynamicParams = dynamicParams.filter(param => {
3561 return typeof argsFromLink[param.name] === 'undefined';
3562 });
3563 }
3564 // Get resolve function for link
3565 const linkResolver = data.options.resolverMiddleware(() => ({
3566 operation: linkedOp,
3567 argsFromLink: argsFromLink,
3568 data,
3569 baseUrl: data.options.baseUrl,
3570 requestOptions: data.options.requestOptions,
3571 }), getResolver);
3572 // Get arguments for link
3573 const args = getArgs({
3574 parameters: dynamicParams,
3575 operation: linkedOp,
3576 data,
3577 includeHttpDetails,
3578 });
3579 // Get response object type
3580 const resObjectType = linkedOp.responseDefinition.graphQLType !== undefined
3581 ? linkedOp.responseDefinition.graphQLType
3582 : getGraphQLType({
3583 def: linkedOp.responseDefinition,
3584 operation,
3585 data,
3586 iteration: iteration + 1,
3587 isInputObjectType: false,
3588 includeHttpDetails,
3589 });
3590 let description = link.description;
3591 if (data.options.equivalentToMessages && description) {
3592 description += `\n\nEquivalent to ${linkedOp.operationString}`;
3593 }
3594 // Finally, add the object type to the fields (using sanitized field name)
3595 // TODO: check if fields already has this field name
3596 fields[saneLinkKey] = {
3597 type: resObjectType,
3598 resolve: linkResolver,
3599 args,
3600 description,
3601 };
3602 }
3603 else {
3604 handleWarning({
3605 mitigationType: MitigationTypes.UNRESOLVABLE_LINK,
3606 message: `Cannot resolve target of link '${saneLinkKey}'`,
3607 data,
3608 log: translationLog$2,
3609 });
3610 }
3611 }
3612 }
3613 }
3614 fields = sortObject(fields);
3615 return fields;
3616}
3617/**
3618 * Returns the operationId that an operationRef is associated to
3619 *
3620 * NOTE: If the operation does not natively have operationId, this function
3621 * will try to produce an operationId the same way preprocessor.js does it.
3622 *
3623 * Any changes to constructing operationIds in preprocessor.js should be
3624 * reflected here.
3625 */
3626function linkOpRefToOpId({ links, linkKey, operation, data, }) {
3627 const link = links[linkKey];
3628 if (typeof link.operationRef === 'string') {
3629 // TODO: external refs
3630 const operationRef = link.operationRef;
3631 let linkLocation;
3632 let linkRelativePathAndMethod;
3633 /**
3634 * Example relative path: '#/paths/~12.0~1repositories~1{username}/get'
3635 * Example absolute path: 'https://na2.gigantic-server.com/#/paths/~12.0~1repositories~1{username}/get'
3636 * Extract relative path from relative path
3637 */
3638 if (operationRef.substring(0, 8) === '#/paths/') {
3639 linkRelativePathAndMethod = operationRef;
3640 // Extract relative path from absolute path
3641 }
3642 else {
3643 /**
3644 * '#' may exist in other places in the path
3645 * '/#/' is more likely to point to the beginning of the path
3646 */
3647 const firstPathIndex = operationRef.indexOf('#/paths/');
3648 // Found a relative path candidate
3649 if (firstPathIndex !== -1) {
3650 // Check to see if there are other relative path candidates
3651 const lastPathIndex = operationRef.lastIndexOf('#/paths/');
3652 if (firstPathIndex !== lastPathIndex) {
3653 handleWarning({
3654 mitigationType: MitigationTypes.AMBIGUOUS_LINK,
3655 message: `The link '${linkKey}' in operation '${operation.operationString}' ` +
3656 `contains an ambiguous operationRef '${operationRef}', ` +
3657 `meaning it has multiple instances of the string '#/paths/'`,
3658 data,
3659 log: translationLog$2,
3660 });
3661 return null;
3662 }
3663 linkLocation = operationRef.substring(0, firstPathIndex);
3664 linkRelativePathAndMethod = operationRef.substring(firstPathIndex);
3665 // Cannot find relative path candidate
3666 }
3667 else {
3668 handleWarning({
3669 mitigationType: MitigationTypes.UNRESOLVABLE_LINK,
3670 message: `The link '${linkKey}' in operation '${operation.operationString}' ` +
3671 `does not contain a valid path in operationRef '${operationRef}', ` +
3672 `meaning it does not contain a string '#/paths/'`,
3673 data,
3674 log: translationLog$2,
3675 });
3676 return null;
3677 }
3678 }
3679 // Infer operationId from relative path
3680 if (typeof linkRelativePathAndMethod === 'string') {
3681 let linkPath;
3682 let linkMethod;
3683 /**
3684 * NOTE: I wish we could extract the linkedOpId by matching the
3685 * linkedOpObject with an operation in data and extracting the operationId
3686 * there but that does not seem to be possible especiially because you
3687 * need to know the operationId just to access the operations so what I
3688 * have to do is reconstruct the operationId the same way preprocessing
3689 * does it
3690 */
3691 /**
3692 * linkPath should be the path followed by the method
3693 *
3694 * Find the slash that divides the path from the method
3695 */
3696 const pivotSlashIndex = linkRelativePathAndMethod.lastIndexOf('/');
3697 // Check if there are any '/' in the linkPath
3698 if (pivotSlashIndex !== -1) {
3699 // Get method
3700 // Check if there is a method at the end of the linkPath
3701 if (pivotSlashIndex !== linkRelativePathAndMethod.length - 1) {
3702 try {
3703 // Start at +1 because we do not want the starting '/'
3704 linkMethod = methodToHttpMethod(linkRelativePathAndMethod.substring(pivotSlashIndex + 1));
3705 }
3706 catch (_a) {
3707 handleWarning({
3708 mitigationType: MitigationTypes.UNRESOLVABLE_LINK,
3709 message: `The operationRef '${operationRef}' contains an ` + `invalid HTTP method '${linkMethod}'`,
3710 data,
3711 log: translationLog$2,
3712 });
3713 return null;
3714 }
3715 // There is no method at the end of the path
3716 }
3717 else {
3718 handleWarning({
3719 mitigationType: MitigationTypes.UNRESOLVABLE_LINK,
3720 message: `The operationRef '${operationRef}' does not contain an` + `HTTP method`,
3721 data,
3722 log: translationLog$2,
3723 });
3724 return null;
3725 }
3726 /**
3727 * Get path
3728 *
3729 * Substring starts at index 8 and ends at pivotSlashIndex to exclude
3730 * the '/'s at the ends of the path
3731 *
3732 * TODO: improve removing '/#/paths'?
3733 */
3734 linkPath = linkRelativePathAndMethod.substring(8, pivotSlashIndex);
3735 /**
3736 * linkPath is currently a JSON Pointer
3737 *
3738 * Revert the escaped '/', represented by '~1', to form intended path
3739 */
3740 linkPath = linkPath.replace(/~1/g, '/');
3741 // Find the right oas
3742 const oas = typeof linkLocation === 'undefined' ? operation.oas : getOasFromLinkLocation(linkLocation, link, data);
3743 // If the link was external, make sure that an OAS could be identified
3744 if (typeof oas !== 'undefined') {
3745 if (typeof linkMethod === 'string' && typeof linkPath === 'string') {
3746 let linkedOpId;
3747 if (linkPath in oas.paths && linkMethod in oas.paths[linkPath]) {
3748 const linkedOpObject = oas.paths[linkPath][linkMethod];
3749 if ('operationId' in linkedOpObject) {
3750 linkedOpId = linkedOpObject.operationId;
3751 }
3752 }
3753 if (typeof linkedOpId !== 'string') {
3754 linkedOpId = generateOperationId(linkMethod, linkPath);
3755 }
3756 if (linkedOpId in data.operations) {
3757 return linkedOpId;
3758 }
3759 else {
3760 handleWarning({
3761 mitigationType: MitigationTypes.UNRESOLVABLE_LINK,
3762 message: `The link '${linkKey}' references an operation with ` +
3763 `operationId '${linkedOpId}' but no such operation exists. ` +
3764 `Note that the operationId may be autogenerated but ` +
3765 `regardless, the link could not be matched to an operation.`,
3766 data,
3767 log: translationLog$2,
3768 });
3769 return null;
3770 }
3771 // Path and method could not be found
3772 }
3773 else {
3774 handleWarning({
3775 mitigationType: MitigationTypes.UNRESOLVABLE_LINK,
3776 message: `Cannot identify path and/or method, '${linkPath} and ` +
3777 `'${linkMethod}' respectively, from operationRef ` +
3778 `'${operationRef}' in link '${linkKey}'`,
3779 data,
3780 log: translationLog$2,
3781 });
3782 return null;
3783 }
3784 // External link could not be resolved
3785 }
3786 else {
3787 handleWarning({
3788 mitigationType: MitigationTypes.UNRESOLVABLE_LINK,
3789 message: `The link '${link.operationRef}' references an external OAS ` + `but it was not provided`,
3790 data,
3791 log: translationLog$2,
3792 });
3793 return null;
3794 }
3795 // Cannot split relative path into path and method sections
3796 }
3797 else {
3798 handleWarning({
3799 mitigationType: MitigationTypes.UNRESOLVABLE_LINK,
3800 message: `Cannot extract path and/or method from operationRef ` + `'${operationRef}' in link '${linkKey}'`,
3801 data,
3802 log: translationLog$2,
3803 });
3804 return null;
3805 }
3806 // Cannot extract relative path from absolute path
3807 }
3808 else {
3809 handleWarning({
3810 mitigationType: MitigationTypes.UNRESOLVABLE_LINK,
3811 message: `Cannot extract path and/or method from operationRef ` + `'${operationRef}' in link '${linkKey}'`,
3812 data,
3813 log: translationLog$2,
3814 });
3815 return null;
3816 }
3817 }
3818}
3819/**
3820 * Determin if an argument should be created if the argument has already been
3821 * provided through the options
3822 */
3823function skipArg(parameter, operation, data, includeHttpDetails) {
3824 var _a;
3825 if (typeof data.options === 'object') {
3826 switch (parameter.in) {
3827 case 'header':
3828 // Check header option
3829 if (typeof data.options.headers === 'object' && parameter.name in data.options.headers) {
3830 return true;
3831 }
3832 else if (typeof data.options.headers === 'function') {
3833 const headers = data.options.headers(operation.method, operation.path, (_a = operation.oas.info) === null || _a === void 0 ? void 0 : _a.title);
3834 if (typeof headers === 'object') {
3835 return true;
3836 }
3837 // Check requestOptions option
3838 }
3839 else if (typeof data.options.requestOptions === 'object') {
3840 if (typeof data.options.requestOptions.headers === 'object' &&
3841 parameter.name in data.options.requestOptions.headers) {
3842 return true;
3843 } /* else if (
3844 typeof data.options.requestOptions.headers === 'function'
3845 ) {
3846 const headers = data.options.requestOptions.headers(
3847 operation.method,
3848 operation.path,
3849 operation.oas.info?.title
3850 )
3851
3852 if (typeof headers === 'object') {
3853 return true
3854 }
3855 } */
3856 }
3857 break;
3858 case 'query':
3859 // Check header option
3860 if (typeof data.options.qs === 'object' && parameter.name in data.options.qs) {
3861 return true;
3862 // Check requestOptions option
3863 } /* else if (
3864 typeof data.options.requestOptions === 'object' &&
3865 typeof data.options.requestOptions.qs === 'object' &&
3866 parameter.name in data.options.requestOptions.qs
3867 ) {
3868 return true
3869 } */
3870 break;
3871 }
3872 }
3873 return false;
3874}
3875/**
3876 * Creates the arguments for resolving a field
3877 *
3878 * Arguments that are provided via options will be ignored
3879 */
3880function getArgs({ requestPayloadDef, parameters, operation, data, includeHttpDetails, }) {
3881 let args = {};
3882 // Handle params:
3883 parameters.forEach(parameter => {
3884 // We need at least a name
3885 if (typeof parameter.name !== 'string') {
3886 handleWarning({
3887 mitigationType: MitigationTypes.INVALID_OAS,
3888 message: `The operation '${operation.operationString}' contains a ` +
3889 `parameter '${JSON.stringify(parameter)}' with no 'name' property`,
3890 data,
3891 log: translationLog$2,
3892 });
3893 return;
3894 }
3895 // If this parameter is provided via options, ignore
3896 if (skipArg(parameter, operation, data)) {
3897 return;
3898 }
3899 /**
3900 * Determine type of parameter
3901 *
3902 * The type of the parameter can either be contained in the "schema" field
3903 * or the "content" field (but not both)
3904 */
3905 let schema;
3906 if (typeof parameter.schema === 'object') {
3907 schema = parameter.schema;
3908 }
3909 else if (typeof parameter.content === 'object') {
3910 if (typeof parameter.content['application/json'] === 'object' &&
3911 typeof parameter.content['application/json'].schema === 'object') {
3912 schema = parameter.content['application/json'].schema;
3913 }
3914 else {
3915 handleWarning({
3916 mitigationType: MitigationTypes.NON_APPLICATION_JSON_SCHEMA,
3917 message: `The operation '${operation.operationString}' contains a ` +
3918 `parameter '${JSON.stringify(parameter)}' that has a 'content' ` +
3919 `property but no schemas in application/json format. The ` +
3920 `parameter will not be created`,
3921 data,
3922 log: translationLog$2,
3923 });
3924 return;
3925 }
3926 }
3927 else {
3928 // Invalid OAS according to 3.0.2
3929 handleWarning({
3930 mitigationType: MitigationTypes.INVALID_OAS,
3931 message: `The operation '${operation.operationString}' contains a ` +
3932 `parameter '${JSON.stringify(parameter)}' with no 'schema' or ` +
3933 `'content' property`,
3934 data,
3935 log: translationLog$2,
3936 });
3937 return;
3938 }
3939 /**
3940 * Resolving the reference is necessary later in the code and by doing it,
3941 * we can avoid doing it a second time in resolveRev()
3942 */
3943 if ('$ref' in schema) {
3944 schema = resolveRef(schema.$ref, operation.oas);
3945 }
3946 const paramDef = createDataDef({ fromSchema: parameter.name }, schema, true, data, operation.oas);
3947 // @ts-ignore
3948 const type = getGraphQLType({
3949 def: paramDef,
3950 operation,
3951 data,
3952 iteration: 0,
3953 isInputObjectType: true,
3954 });
3955 /**
3956 * Sanitize the argument name
3957 *
3958 * NOTE: when matching these parameters back to requests, we need to again
3959 * use the real parameter name
3960 */
3961 const saneName = sanitize(parameter.name, !data.options.simpleNames ? CaseStyle.camelCase : CaseStyle.simple);
3962 // Parameters are not required when a default exists:
3963 let hasDefault = false;
3964 if (typeof parameter.schema === 'object') {
3965 let schema = parameter.schema;
3966 if (typeof schema.$ref === 'string') {
3967 schema = resolveRef(parameter.schema.$ref, operation.oas);
3968 }
3969 if (typeof schema.default !== 'undefined') {
3970 hasDefault = true;
3971 }
3972 }
3973 const paramRequired = parameter.required && !hasDefault;
3974 args[saneName] = {
3975 type: paramRequired ? new GraphQLNonNull(type) : type,
3976 description: parameter.description,
3977 };
3978 });
3979 // Add limit argument
3980 if (data.options.addLimitArgument &&
3981 typeof operation.responseDefinition === 'object' &&
3982 operation.responseDefinition.schema.type === 'array' &&
3983 // Only add limit argument to lists of object types, not to lists of scalar types
3984 (operation.responseDefinition.subDefinitions.schema.type === 'object' ||
3985 operation.responseDefinition.subDefinitions.schema.type === 'array' ||
3986 (operation.responseDefinition.subDefinitions.schema.type === undefined &&
3987 (operation.responseDefinition.subDefinitions.schema.allOf ||
3988 operation.responseDefinition.subDefinitions.schema.anyOf ||
3989 operation.responseDefinition.subDefinitions.schema.oneOf)))) {
3990 // Make sure slicing arguments will not overwrite preexisting arguments
3991 if ('limit' in args) {
3992 handleWarning({
3993 mitigationType: MitigationTypes.LIMIT_ARGUMENT_NAME_COLLISION,
3994 message: `The 'limit' argument cannot be added ` +
3995 `because of a preexisting argument in ` +
3996 `operation ${operation.operationString}`,
3997 data,
3998 log: translationLog$2,
3999 });
4000 }
4001 else {
4002 args.limit = {
4003 type: GraphQLInt,
4004 description: `Auto-generated argument that limits the size of ` +
4005 `returned list of objects/list, selecting the first \`n\` ` +
4006 `elements of the list`,
4007 };
4008 }
4009 }
4010 // Handle request payload (if present):
4011 if (typeof requestPayloadDef === 'object') {
4012 const reqObjectType = getGraphQLType({
4013 def: requestPayloadDef,
4014 data,
4015 operation,
4016 isInputObjectType: true,
4017 includeHttpDetails,
4018 });
4019 // Sanitize the argument name
4020 const saneName = data.options.genericPayloadArgName
4021 ? 'requestBody'
4022 : uncapitalize(requestPayloadDef.graphQLInputObjectTypeName); // Already sanitized
4023 const reqRequired = typeof operation === 'object' && typeof operation.payloadRequired === 'boolean'
4024 ? operation.payloadRequired
4025 : false;
4026 args[saneName] = {
4027 type: reqRequired ? new GraphQLNonNull(reqObjectType) : reqObjectType,
4028 // TODO: addendum to the description explaining this is the request body
4029 description: requestPayloadDef.schema.description,
4030 };
4031 }
4032 args = sortObject(args);
4033 return args;
4034}
4035/**
4036 * Used in the context of links, specifically those using an external operationRef
4037 * If the reference is an absolute reference, determine the type of location
4038 *
4039 * For example, name reference, file path, web-hosted OAS link, etc.
4040 */
4041function getLinkLocationType(linkLocation) {
4042 // TODO: currently we only support the title as a link location
4043 return 'title';
4044}
4045/**
4046 * Used in the context of links, specifically those using an external operationRef
4047 * Based on the location of the OAS, retrieve said OAS
4048 */
4049function getOasFromLinkLocation(linkLocation, link, data) {
4050 // May be an external reference
4051 switch (getLinkLocationType()) {
4052 case 'title':
4053 // Get the possible
4054 const possibleOass = data.oass.filter(oas => {
4055 var _a;
4056 return ((_a = oas.info) === null || _a === void 0 ? void 0 : _a.title) === linkLocation;
4057 });
4058 // Check if there are an ambiguous OASs
4059 if (possibleOass.length === 1) {
4060 // No ambiguity
4061 return possibleOass[0];
4062 }
4063 else if (possibleOass.length > 1) {
4064 // Some ambiguity
4065 handleWarning({
4066 mitigationType: MitigationTypes.AMBIGUOUS_LINK,
4067 message: `The operationRef '${link.operationRef}' references an ` +
4068 `OAS '${linkLocation}' but multiple OASs share the same title`,
4069 data,
4070 log: translationLog$2,
4071 });
4072 }
4073 else {
4074 // No OAS had the expected title
4075 handleWarning({
4076 mitigationType: MitigationTypes.UNRESOLVABLE_LINK,
4077 message: `The operationRef '${link.operationRef}' references an ` +
4078 `OAS '${linkLocation}' but no such OAS was provided`,
4079 data,
4080 log: translationLog$2,
4081 });
4082 }
4083 break;
4084 // // TODO
4085 // case 'url':
4086 // break
4087 // // TODO
4088 // case 'file':
4089 // break
4090 // TODO: should title be default?
4091 // In cases of names like api.io
4092 default:
4093 handleWarning({
4094 mitigationType: MitigationTypes.UNRESOLVABLE_LINK,
4095 message: `The link location of the operationRef ` +
4096 `'${link.operationRef}' is currently not supported\n` +
4097 `Currently only the title of the OAS is supported`,
4098 data,
4099 log: translationLog$2,
4100 });
4101 }
4102}
4103
4104// Copyright IBM Corp. 2018. All Rights Reserved.
4105/**
4106 * Returns empty GraphQLObjectType.
4107 */
4108function getEmptyObjectType(name) {
4109 return new GraphQLObjectType({
4110 name: name + 'Placeholder',
4111 description: 'Placeholder object',
4112 fields: {
4113 message: {
4114 type: GraphQLString,
4115 description: 'Placeholder field',
4116 resolve: () => {
4117 return 'This is a placeholder field.';
4118 },
4119 },
4120 },
4121 });
4122}
4123
4124// Copyright IBM Corp. 2018. All Rights Reserved.
4125const translationLog$3 = mockDebug('translation');
4126/**
4127 * Load the field object in the appropriate root object
4128 *
4129 * i.e. inside either rootQueryFields/rootMutationFields or inside
4130 * rootQueryFields/rootMutationFields for further processing
4131 */
4132function createAndLoadViewer(queryFields, operationType, data, includeHttpDetails) {
4133 const results = {};
4134 /**
4135 * To ensure that viewers have unique names, we add a numerical postfix.
4136 *
4137 * This object keeps track of what the postfix should be.
4138 *
4139 * The key is the security scheme type and the value is
4140 * the current highest postfix used for viewers of that security scheme type.
4141 */
4142 const viewerNamePostfix = {};
4143 /**
4144 * Used to collect all fields in the given querFields object, no matter which
4145 * protocol. Used to populate anyAuthViewer.
4146 */
4147 const anyAuthFields = {};
4148 for (const protocolName in queryFields) {
4149 Object.assign(anyAuthFields, queryFields[protocolName]);
4150 /**
4151 * Check if the name has already been used (i.e. in the list)
4152 * if so, create a new name and add it to the list
4153 */
4154 const securityType = data.security[protocolName].def.type;
4155 let viewerType;
4156 /**
4157 * HTTP is not an authentication protocol
4158 * HTTP covers a number of different authentication type
4159 * change the typeName to match the exact authentication type (e.g. basic
4160 * authentication)
4161 */
4162 if (securityType === 'http') {
4163 const scheme = data.security[protocolName].def.scheme;
4164 switch (scheme) {
4165 case 'basic':
4166 viewerType = 'basicAuth';
4167 break;
4168 default:
4169 handleWarning({
4170 mitigationType: MitigationTypes.UNSUPPORTED_HTTP_SECURITY_SCHEME,
4171 message: `Currently unsupported HTTP authentication protocol ` + `type 'http' and scheme '${scheme}'`,
4172 data,
4173 log: translationLog$3,
4174 });
4175 continue;
4176 }
4177 }
4178 else {
4179 viewerType = securityType;
4180 }
4181 // Create name for the viewer
4182 let viewerName = operationType === GraphQLOperationType.Query
4183 ? sanitize(`viewer ${viewerType}`, CaseStyle.camelCase)
4184 : operationType === GraphQLOperationType.Mutation
4185 ? sanitize(`mutation viewer ${viewerType}`, CaseStyle.camelCase)
4186 : sanitize(`subscription viewer ${viewerType}`, CaseStyle.camelCase);
4187 // Ensure unique viewer name
4188 // If name already exists, append a number at the end of the name
4189 if (!(viewerType in viewerNamePostfix)) {
4190 viewerNamePostfix[viewerType] = 1;
4191 }
4192 else {
4193 viewerName += ++viewerNamePostfix[viewerType];
4194 }
4195 // Add the viewer object type to the specified root query object type
4196 results[viewerName] = getViewerOT(viewerName, protocolName, securityType, queryFields[protocolName], data);
4197 }
4198 // Create name for the AnyAuth viewer
4199 const anyAuthObjectName = operationType === GraphQLOperationType.Query
4200 ? 'viewerAnyAuth'
4201 : operationType === GraphQLOperationType.Mutation
4202 ? 'mutationViewerAnyAuth'
4203 : 'subscriptionViewerAnyAuth';
4204 // Add the AnyAuth object type to the specified root query object type
4205 results[anyAuthObjectName] = getViewerAnyAuthOT(anyAuthObjectName, anyAuthFields, data, includeHttpDetails);
4206 return results;
4207}
4208/**
4209 * Gets the viewer Object, resolve function, and arguments
4210 */
4211function getViewerOT(name, protocolName, securityType, queryFields, data) {
4212 var _a, _b;
4213 const scheme = data.security[protocolName];
4214 // Resolve function:
4215 const resolve = (root, args, context) => {
4216 const security = {};
4217 const saneProtocolName = sanitize(protocolName, CaseStyle.camelCase);
4218 security[storeSaneName(saneProtocolName, protocolName, data.saneMap)] = args;
4219 /**
4220 * Viewers are always root, so we can instantiate _openAPIToGraphQL here without
4221 * previously checking for its existence
4222 */
4223 return {
4224 _openAPIToGraphQL: {
4225 security,
4226 },
4227 };
4228 };
4229 // Arguments:
4230 /**
4231 * Do not sort because they are already "sorted" in preprocessing.
4232 * Otherwise, for basic auth, "password" will appear before "username"
4233 */
4234 const args = {};
4235 if (typeof scheme === 'object') {
4236 for (const parameterName in scheme.parameters) {
4237 args[parameterName] = { type: new GraphQLNonNull(GraphQLString) };
4238 }
4239 }
4240 let typeDescription = `A viewer for security scheme '${protocolName}'`;
4241 /**
4242 * HTTP authentication uses different schemes. It is not sufficient to name
4243 * only the security type
4244 */
4245 let description = securityType === 'http'
4246 ? `A viewer that wraps all operations authenticated via security scheme ` +
4247 `'${protocolName}', which is of type 'http' '${scheme.def.scheme}'`
4248 : `A viewer that wraps all operations authenticated via security scheme ` +
4249 `'${protocolName}', which is of type '${securityType}'`;
4250 if (data.oass.length !== 1) {
4251 typeDescription += ` in OAS '${(_a = scheme.oas.info) === null || _a === void 0 ? void 0 : _a.title}'`;
4252 description = `, in OAS '${(_b = scheme.oas.info) === null || _b === void 0 ? void 0 : _b.title}`;
4253 }
4254 return {
4255 type: new GraphQLObjectType({
4256 name: capitalize(name),
4257 description: typeDescription,
4258 fields: () => queryFields,
4259 }),
4260 resolve,
4261 args,
4262 description,
4263 };
4264}
4265/**
4266 * Create an object containing an AnyAuth viewer, its resolve function,
4267 * and its args.
4268 */
4269function getViewerAnyAuthOT(name, queryFields, data, includeHttpDetails) {
4270 let args = {};
4271 for (const protocolName in data.security) {
4272 // Create input object types for the viewer arguments
4273 const def = createDataDef({ fromRef: protocolName }, data.security[protocolName].schema, true, data, data.security[protocolName].oas);
4274 const type = getGraphQLType({
4275 def,
4276 data,
4277 isInputObjectType: true,
4278 includeHttpDetails,
4279 });
4280 const saneProtocolName = sanitize(protocolName, CaseStyle.camelCase);
4281 args[storeSaneName(saneProtocolName, protocolName, data.saneMap)] = { type };
4282 }
4283 args = sortObject(args);
4284 // Pass object containing security information to fields
4285 const resolve = (root, args, context) => {
4286 return {
4287 _openAPIToGraphQL: {
4288 security: args,
4289 },
4290 };
4291 };
4292 return {
4293 type: new GraphQLObjectType({
4294 name: capitalize(name),
4295 description: 'Warning: Not every request will work with this viewer type',
4296 fields: () => queryFields,
4297 }),
4298 resolve,
4299 args,
4300 description: `A viewer that wraps operations for all available ` + `authentication mechanisms`,
4301 };
4302}
4303
4304// Copyright IBM Corp. 2018. All Rights Reserved.
4305const translationLog$4 = mockDebug('translation');
4306/**
4307 * Creates a GraphQL interface from the given OpenAPI Specification (2 or 3).
4308 */
4309async function createGraphQLSchema(spec, options = {}) {
4310 // Setting default options
4311 options.strict = typeof options.strict === 'boolean' ? options.strict : false;
4312 // Schema options
4313 options.operationIdFieldNames =
4314 typeof options.operationIdFieldNames === 'boolean' ? options.operationIdFieldNames : false;
4315 options.fillEmptyResponses = typeof options.fillEmptyResponses === 'boolean' ? options.fillEmptyResponses : false;
4316 options.addLimitArgument = typeof options.addLimitArgument === 'boolean' ? options.addLimitArgument : false;
4317 options.genericPayloadArgName =
4318 typeof options.genericPayloadArgName === 'boolean' ? options.genericPayloadArgName : false;
4319 options.simpleNames = typeof options.simpleNames === 'boolean' ? options.simpleNames : false;
4320 options.singularNames = typeof options.singularNames === 'boolean' ? options.singularNames : false;
4321 // Authentication options
4322 options.viewer = typeof options.viewer === 'boolean' ? options.viewer : true;
4323 options.sendOAuthTokenInQuery =
4324 typeof options.sendOAuthTokenInQuery === 'boolean' ? options.sendOAuthTokenInQuery : false;
4325 // Logging options
4326 options.provideErrorExtensions =
4327 typeof options.provideErrorExtensions === 'boolean' ? options.provideErrorExtensions : true;
4328 options.equivalentToMessages =
4329 typeof options.equivalentToMessages === 'boolean' ? options.equivalentToMessages : true;
4330 options.resolverMiddleware =
4331 typeof options.resolverMiddleware === 'function'
4332 ? options.resolverMiddleware
4333 : (resolverFactoryParams, factory) => factory(resolverFactoryParams);
4334 options.report = {
4335 warnings: [],
4336 numOps: 0,
4337 numOpsQuery: 0,
4338 numOpsMutation: 0,
4339 numOpsSubscription: 0,
4340 numQueriesCreated: 0,
4341 numMutationsCreated: 0,
4342 numSubscriptionsCreated: 0,
4343 };
4344 options.includeHttpDetails = typeof options.includeHttpDetails === 'boolean' ? options.includeHttpDetails : false;
4345 let oass;
4346 if (Array.isArray(spec)) {
4347 /**
4348 * Convert all non-OAS 3.0.x into OAS 3.0.x
4349 */
4350 oass = await Promise.all(spec.map(ele => {
4351 return getValidOAS3(ele);
4352 }));
4353 }
4354 else {
4355 /**
4356 * Check if the spec is a valid OAS 3.0.x
4357 * If the spec is OAS 2.0, attempt to translate it into 3.0.x, then try to
4358 * translate the spec into a GraphQL schema
4359 */
4360 oass = [await getValidOAS3(spec)];
4361 }
4362 const { schema, report } = await translateOpenAPIToGraphQL(oass, options);
4363 return {
4364 schema,
4365 report,
4366 };
4367}
4368/**
4369 * Creates a GraphQL interface from the given OpenAPI Specification 3.0.x
4370 */
4371async function translateOpenAPIToGraphQL(oass, { strict, report,
4372// Schema options
4373operationIdFieldNames, fillEmptyResponses, addLimitArgument, idFormats, selectQueryOrMutationField, genericPayloadArgName, simpleNames, singularNames, includeHttpDetails,
4374// Resolver options
4375headers, qs, requestOptions, connectOptions, baseUrl, customResolvers, fetch, resolverMiddleware, pubsub,
4376// Authentication options
4377viewer, tokenJSONpath, sendOAuthTokenInQuery,
4378// Logging options
4379provideErrorExtensions, equivalentToMessages, }) {
4380 const options = {
4381 strict,
4382 report,
4383 // Schema options
4384 operationIdFieldNames,
4385 fillEmptyResponses,
4386 addLimitArgument,
4387 idFormats,
4388 selectQueryOrMutationField,
4389 genericPayloadArgName,
4390 simpleNames,
4391 singularNames,
4392 includeHttpDetails,
4393 // Resolver options
4394 headers,
4395 qs,
4396 requestOptions,
4397 connectOptions,
4398 baseUrl,
4399 customResolvers,
4400 fetch,
4401 resolverMiddleware,
4402 pubsub,
4403 // Authentication options
4404 viewer,
4405 tokenJSONpath,
4406 sendOAuthTokenInQuery,
4407 // Logging options
4408 provideErrorExtensions,
4409 equivalentToMessages,
4410 };
4411 translationLog$4(`Options: ${JSON.stringify(options)}`);
4412 /**
4413 * Extract information from the OASs and put it inside a data structure that
4414 * is easier for OpenAPI-to-GraphQL to use
4415 */
4416 const data = preprocessOas(oass, options);
4417 preliminaryChecks(options, data);
4418 // Query, Mutation, and Subscription fields
4419 let queryFields = {};
4420 let mutationFields = {};
4421 let subscriptionFields = {};
4422 // Authenticated Query, Mutation, and Subscription fields
4423 let authQueryFields = {};
4424 let authMutationFields = {};
4425 let authSubscriptionFields = {};
4426 // Add Query and Mutation fields
4427 Object.entries(data.operations).forEach(([operationId, operation]) => {
4428 translationLog$4(`Process operation '${operation.operationString}'...`);
4429 const field = getFieldForOperation(operation, options.baseUrl, data, requestOptions, connectOptions, includeHttpDetails, pubsub);
4430 const saneOperationId = sanitize(operationId, CaseStyle.camelCase);
4431 // Check if the operation should be added as a Query or Mutation
4432 if (operation.operationType === GraphQLOperationType.Query) {
4433 let fieldName = !singularNames
4434 ? uncapitalize(operation.responseDefinition.graphQLTypeName)
4435 : sanitize(inferResourceNameFromPath(operation.path), CaseStyle.camelCase);
4436 if (operation.inViewer) {
4437 for (const securityRequirement of operation.securityRequirements) {
4438 if (typeof authQueryFields[securityRequirement] !== 'object') {
4439 authQueryFields[securityRequirement] = {};
4440 }
4441 // Avoid overwriting fields that return the same data:
4442 if (fieldName in authQueryFields[securityRequirement] ||
4443 /**
4444 * If the option is set operationIdFieldNames, the fieldName is
4445 * forced to be the operationId
4446 */
4447 operationIdFieldNames) {
4448 fieldName = storeSaneName(saneOperationId, operationId, data.saneMap);
4449 }
4450 if (fieldName in authQueryFields[securityRequirement]) {
4451 handleWarning({
4452 mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
4453 message: `Multiple operations have the same name ` +
4454 `'${fieldName}' and security requirement ` +
4455 `'${securityRequirement}'. GraphQL field names must be ` +
4456 `unique so only one can be added to the authentication ` +
4457 `viewer. Operation '${operation.operationString}' will be ignored.`,
4458 data,
4459 log: translationLog$4,
4460 });
4461 }
4462 else {
4463 authQueryFields[securityRequirement][fieldName] = field;
4464 }
4465 }
4466 }
4467 else {
4468 // Avoid overwriting fields that return the same data:
4469 if (fieldName in queryFields ||
4470 /**
4471 * If the option is set operationIdFieldNames, the fieldName is
4472 * forced to be the operationId
4473 */
4474 operationIdFieldNames) {
4475 fieldName = storeSaneName(saneOperationId, operationId, data.saneMap);
4476 }
4477 if (fieldName in queryFields) {
4478 handleWarning({
4479 mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
4480 message: `Multiple operations have the same name ` +
4481 `'${fieldName}'. GraphQL field names must be ` +
4482 `unique so only one can be added to the Query object. ` +
4483 `Operation '${operation.operationString}' will be ignored.`,
4484 data,
4485 log: translationLog$4,
4486 });
4487 }
4488 else {
4489 queryFields[fieldName] = field;
4490 }
4491 }
4492 }
4493 else {
4494 let saneFieldName;
4495 if (!singularNames) {
4496 /**
4497 * Use operationId to avoid problems differentiating operations with the
4498 * same path but differnet methods
4499 */
4500 saneFieldName = storeSaneName(saneOperationId, operationId, data.saneMap);
4501 }
4502 else {
4503 const fieldName = `${operation.method}${inferResourceNameFromPath(operation.path)}`;
4504 saneFieldName = storeSaneName(sanitize(fieldName, CaseStyle.camelCase), fieldName, data.saneMap);
4505 }
4506 if (operation.inViewer) {
4507 for (const securityRequirement of operation.securityRequirements) {
4508 if (typeof authMutationFields[securityRequirement] !== 'object') {
4509 authMutationFields[securityRequirement] = {};
4510 }
4511 if (saneFieldName in authMutationFields[securityRequirement]) {
4512 handleWarning({
4513 mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
4514 message: `Multiple operations have the same name ` +
4515 `'${saneFieldName}' and security requirement ` +
4516 `'${securityRequirement}'. GraphQL field names must be ` +
4517 `unique so only one can be added to the authentication ` +
4518 `viewer. Operation '${operation.operationString}' will be ignored.`,
4519 data,
4520 log: translationLog$4,
4521 });
4522 }
4523 else {
4524 authMutationFields[securityRequirement][saneFieldName] = field;
4525 }
4526 }
4527 }
4528 else {
4529 if (saneFieldName in mutationFields) {
4530 handleWarning({
4531 mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
4532 message: `Multiple operations have the same name ` +
4533 `'${saneFieldName}'. GraphQL field names must be ` +
4534 `unique so only one can be added to the Mutation object. ` +
4535 `Operation '${operation.operationString}' will be ignored.`,
4536 data,
4537 log: translationLog$4,
4538 });
4539 }
4540 else {
4541 mutationFields[saneFieldName] = field;
4542 }
4543 }
4544 }
4545 });
4546 // Add Subscription fields
4547 Object.entries(data.callbackOperations).forEach(([operationId, operation]) => {
4548 translationLog$4(`Process operation '${operationId}'...`);
4549 const field = getFieldForOperation(operation, options.baseUrl, data, requestOptions, connectOptions, includeHttpDetails, pubsub);
4550 const saneOperationId = sanitize(operationId, CaseStyle.camelCase);
4551 const saneFieldName = storeSaneName(saneOperationId, operationId, data.saneMap);
4552 if (operation.inViewer) {
4553 for (const securityRequirement of operation.securityRequirements) {
4554 if (typeof authSubscriptionFields[securityRequirement] !== 'object') {
4555 authSubscriptionFields[securityRequirement] = {};
4556 }
4557 if (saneFieldName in authSubscriptionFields[securityRequirement]) {
4558 handleWarning({
4559 mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
4560 message: `Multiple operations have the same name ` +
4561 `'${saneFieldName}' and security requirement ` +
4562 `'${securityRequirement}'. GraphQL field names must be ` +
4563 `unique so only one can be added to the authentication ` +
4564 `viewer. Operation '${operation.operationString}' will be ignored.`,
4565 data,
4566 log: translationLog$4,
4567 });
4568 }
4569 else {
4570 authSubscriptionFields[securityRequirement][saneFieldName] = field;
4571 }
4572 }
4573 }
4574 else {
4575 if (saneFieldName in subscriptionFields) {
4576 handleWarning({
4577 mitigationType: MitigationTypes.DUPLICATE_FIELD_NAME,
4578 message: `Multiple operations have the same name ` +
4579 `'${saneFieldName}'. GraphQL field names must be ` +
4580 `unique so only one can be added to the Mutation object. ` +
4581 `Operation '${operation.operationString}' will be ignored.`,
4582 data,
4583 log: translationLog$4,
4584 });
4585 }
4586 else {
4587 subscriptionFields[saneFieldName] = field;
4588 }
4589 }
4590 });
4591 // Sorting fields
4592 queryFields = sortObject(queryFields);
4593 mutationFields = sortObject(mutationFields);
4594 subscriptionFields = sortObject(subscriptionFields);
4595 authQueryFields = sortObject(authQueryFields);
4596 Object.keys(authQueryFields).forEach(key => {
4597 authQueryFields[key] = sortObject(authQueryFields[key]);
4598 });
4599 authMutationFields = sortObject(authMutationFields);
4600 Object.keys(authMutationFields).forEach(key => {
4601 authMutationFields[key] = sortObject(authMutationFields[key]);
4602 });
4603 authSubscriptionFields = sortObject(authSubscriptionFields);
4604 Object.keys(authSubscriptionFields).forEach(key => {
4605 authSubscriptionFields[key] = sortObject(authSubscriptionFields[key]);
4606 });
4607 // Count created Query, Mutation, and Subscription fields
4608 options.report.numQueriesCreated =
4609 Object.keys(queryFields).length +
4610 Object.keys(authQueryFields).reduce((sum, key) => {
4611 return sum + Object.keys(authQueryFields[key]).length;
4612 }, 0);
4613 options.report.numMutationsCreated =
4614 Object.keys(mutationFields).length +
4615 Object.keys(authMutationFields).reduce((sum, key) => {
4616 return sum + Object.keys(authMutationFields[key]).length;
4617 }, 0);
4618 options.report.numSubscriptionsCreated =
4619 Object.keys(subscriptionFields).length +
4620 Object.keys(authSubscriptionFields).reduce((sum, key) => {
4621 return sum + Object.keys(authSubscriptionFields[key]).length;
4622 }, 0);
4623 /**
4624 * Organize authenticated Query, Mutation, and Subscriptions fields into
4625 * viewer objects.
4626 */
4627 if (Object.keys(authQueryFields).length > 0) {
4628 Object.assign(queryFields, createAndLoadViewer(authQueryFields, GraphQLOperationType.Query, data, includeHttpDetails));
4629 }
4630 if (Object.keys(authMutationFields).length > 0) {
4631 Object.assign(mutationFields, createAndLoadViewer(authMutationFields, GraphQLOperationType.Mutation, data, includeHttpDetails));
4632 }
4633 if (Object.keys(authSubscriptionFields).length > 0) {
4634 Object.assign(subscriptionFields, createAndLoadViewer(authSubscriptionFields, GraphQLOperationType.Subscription, data, includeHttpDetails));
4635 }
4636 // Build up the schema
4637 const schemaConfig = {
4638 query: Object.keys(queryFields).length > 0
4639 ? new GraphQLObjectType({
4640 name: 'Query',
4641 fields: queryFields,
4642 })
4643 : getEmptyObjectType('Query'),
4644 mutation: Object.keys(mutationFields).length > 0
4645 ? new GraphQLObjectType({
4646 name: 'Mutation',
4647 fields: mutationFields,
4648 })
4649 : null,
4650 subscription: Object.keys(subscriptionFields).length > 0
4651 ? new GraphQLObjectType({
4652 name: 'Subscription',
4653 fields: subscriptionFields,
4654 })
4655 : null,
4656 };
4657 /**
4658 * Fill in yet undefined object types to avoid GraphQLSchema from breaking.
4659 *
4660 * The reason: once creating the schema, the 'fields' thunks will resolve and
4661 * if a field references an undefined object type, GraphQL will throw.
4662 */
4663 Object.entries(data.operations).forEach(([, operation]) => {
4664 if (typeof operation.responseDefinition.graphQLType === 'undefined') {
4665 operation.responseDefinition.graphQLType = getEmptyObjectType(operation.responseDefinition.graphQLTypeName);
4666 }
4667 });
4668 const schema = new GraphQLSchema(schemaConfig);
4669 return { schema, report: options.report };
4670}
4671/**
4672 * Creates the field object for the given operation.
4673 */
4674function getFieldForOperation(operation, baseUrl, data, requestOptions, connectOptions, includeHttpDetails, pubsub) {
4675 // Create GraphQL Type for response:
4676 const type = getGraphQLType({
4677 def: operation.responseDefinition,
4678 data,
4679 operation,
4680 includeHttpDetails,
4681 });
4682 const payloadSchemaName = operation.payloadDefinition ? operation.payloadDefinition.graphQLInputObjectTypeName : null;
4683 const args = getArgs({
4684 /**
4685 * Even though these arguments seems redundent because of the operation
4686 * argument, the function cannot be refactored because it is also used to
4687 * create arguments for links. The operation argument is really used to pass
4688 * data to other functions.
4689 */
4690 requestPayloadDef: operation.payloadDefinition,
4691 parameters: operation.parameters,
4692 operation,
4693 data,
4694 includeHttpDetails,
4695 });
4696 // Get resolver and subscribe function for Subscription fields
4697 if (operation.operationType === GraphQLOperationType.Subscription) {
4698 const responseSchemaName = operation.responseDefinition ? operation.responseDefinition.graphQLTypeName : null;
4699 const resolve = getPublishResolver({
4700 operation,
4701 responseName: responseSchemaName,
4702 data,
4703 });
4704 const subscribe = getSubscribe({
4705 operation,
4706 payloadName: payloadSchemaName,
4707 data,
4708 baseUrl,
4709 connectOptions,
4710 pubsub,
4711 });
4712 return {
4713 type,
4714 resolve,
4715 subscribe,
4716 args,
4717 description: operation.description,
4718 };
4719 // Get resolver for Query and Mutation fields
4720 }
4721 else {
4722 const resolve = data.options.resolverMiddleware(() => ({
4723 operation,
4724 payloadName: payloadSchemaName,
4725 data,
4726 baseUrl,
4727 requestOptions,
4728 }), getResolver);
4729 return {
4730 type,
4731 resolve,
4732 args,
4733 description: operation.description,
4734 };
4735 }
4736}
4737/**
4738 * Ensure that the customResolvers/customSubscriptionResolvers object is a
4739 * triply nested object using the name of the OAS, the path, and the method
4740 * as keys.
4741 */
4742function checkCustomResolversStructure(customResolvers, data) {
4743 if (typeof customResolvers === 'object') {
4744 // Check that all OASs that are referenced in the customResolvers are provided
4745 Object.keys(customResolvers)
4746 .filter(title => {
4747 // If no OAS contains this title
4748 return !data.oass.some(oas => {
4749 var _a;
4750 return title === ((_a = oas.info) === null || _a === void 0 ? void 0 : _a.title);
4751 });
4752 })
4753 .forEach(title => {
4754 handleWarning({
4755 mitigationType: MitigationTypes.CUSTOM_RESOLVER_UNKNOWN_OAS,
4756 message: `Custom resolvers reference OAS '${title.toString()}' but no such ` + `OAS was provided`,
4757 data,
4758 log: translationLog$4,
4759 });
4760 });
4761 // TODO: Only run the following test on OASs that exist. See previous check.
4762 Object.keys(customResolvers).forEach(title => {
4763 // Get all operations from a particular OAS
4764 const operations = Object.values(data.operations).filter(operation => {
4765 var _a;
4766 return title === ((_a = operation.oas.info) === null || _a === void 0 ? void 0 : _a.title);
4767 });
4768 Object.keys(customResolvers[title]).forEach(path => {
4769 Object.keys(customResolvers[title][path]).forEach(method => {
4770 if (!operations.some(operation => {
4771 return path === operation.path && method === operation.method;
4772 })) {
4773 handleWarning({
4774 mitigationType: MitigationTypes.CUSTOM_RESOLVER_UNKNOWN_PATH_METHOD,
4775 message: `A custom resolver references an operation with ` +
4776 `path '${path.toString()}' and method '${method.toString()}' but no such operation ` +
4777 `exists in OAS '${title.toString()}'`,
4778 data,
4779 log: translationLog$4,
4780 });
4781 }
4782 });
4783 });
4784 });
4785 }
4786}
4787/**
4788 * Ensures that the options are valid
4789 */
4790function preliminaryChecks(options, data) {
4791 // Check if OASs have unique titles
4792 const titles = data.oass.map(oas => {
4793 var _a;
4794 return (_a = oas.info) === null || _a === void 0 ? void 0 : _a.title;
4795 });
4796 // Find duplicates among titles
4797 new Set(titles.filter((title, index) => {
4798 return titles.indexOf(title) !== index;
4799 })).forEach(title => {
4800 handleWarning({
4801 mitigationType: MitigationTypes.MULTIPLE_OAS_SAME_TITLE,
4802 message: `Multiple OAS share the same title '${title}'`,
4803 data,
4804 log: translationLog$4,
4805 });
4806 });
4807 // Check customResolvers
4808 checkCustomResolversStructure(options.customResolvers, data);
4809 // Check customSubscriptionResolvers
4810 checkCustomResolversStructure(options.customSubscriptionResolvers, data);
4811}
4812
4813class OpenAPIHandler {
4814 constructor({ config, cache, pubsub }) {
4815 this.config = config;
4816 this.cache = cache;
4817 this.pubsub = pubsub;
4818 }
4819 async getMeshSource() {
4820 var _a, _b, _c;
4821 const path = this.config.source;
4822 const spec = await readFileOrUrlWithCache(path, this.cache, {
4823 headers: this.config.schemaHeaders,
4824 fallbackFormat: this.config.sourceFormat,
4825 });
4826 let fetch;
4827 if (this.config.customFetch) {
4828 fetch = await loadFromModuleExportExpression(this.config.customFetch, 'default');
4829 }
4830 else {
4831 fetch = (...args) => fetchache(args[0] instanceof Request ? args[0] : new Request(...args), this.cache);
4832 }
4833 const baseUrlFactory = getInterpolatedStringFactory(this.config.baseUrl);
4834 const headersFactory = getInterpolatedHeadersFactory(this.config.operationHeaders);
4835 const queryStringFactoryMap = new Map();
4836 for (const queryName in this.config.qs || {}) {
4837 queryStringFactoryMap.set(queryName, getInterpolatedStringFactory(this.config.qs[queryName]));
4838 }
4839 const searchParamsFactory = (resolverData, searchParams) => {
4840 for (const queryName in this.config.qs || {}) {
4841 searchParams.set(queryName, queryStringFactoryMap.get(queryName)(resolverData));
4842 }
4843 return searchParams;
4844 };
4845 const { schema } = await createGraphQLSchema(spec, {
4846 fetch,
4847 baseUrl: this.config.baseUrl,
4848 operationIdFieldNames: true,
4849 fillEmptyResponses: true,
4850 includeHttpDetails: this.config.includeHttpDetails,
4851 addLimitArgument: this.config.addLimitArgument === undefined ? true : this.config.addLimitArgument,
4852 sendOAuthTokenInQuery: true,
4853 viewer: false,
4854 equivalentToMessages: true,
4855 pubsub: this.pubsub,
4856 resolverMiddleware: (getResolverParams, originalFactory) => (root, args, context, info) => {
4857 const resolverData = { root, args, context, info };
4858 const resolverParams = getResolverParams();
4859 resolverParams.requestOptions = {
4860 headers: getHeadersObject(headersFactory(resolverData)),
4861 };
4862 if (context === null || context === void 0 ? void 0 : context.baseUrl) {
4863 resolverParams.baseUrl = context.baseUrl;
4864 }
4865 if (!resolverParams.baseUrl && this.config.baseUrl) {
4866 resolverParams.baseUrl = baseUrlFactory(resolverData);
4867 }
4868 if (resolverParams.baseUrl) {
4869 const urlObj = new URL(resolverParams.baseUrl);
4870 searchParamsFactory(resolverData, urlObj.searchParams);
4871 }
4872 else {
4873 console.warn(`There is no 'baseUrl' defined for this OpenAPI definition. We recommend you to define one manually!`);
4874 }
4875 if (context === null || context === void 0 ? void 0 : context.fetch) {
4876 resolverParams.fetch = context.fetch;
4877 }
4878 if (context === null || context === void 0 ? void 0 : context.qs) {
4879 resolverParams.qs = context.qs;
4880 }
4881 return originalFactory(() => resolverParams)(root, args, context, info);
4882 },
4883 });
4884 const { args, contextVariables } = parseInterpolationStrings(Object.values(this.config.operationHeaders || {}));
4885 const rootFields = [
4886 ...Object.values(((_a = schema.getQueryType()) === null || _a === void 0 ? void 0 : _a.getFields()) || {}),
4887 ...Object.values(((_b = schema.getMutationType()) === null || _b === void 0 ? void 0 : _b.getFields()) || {}),
4888 ...Object.values(((_c = schema.getSubscriptionType()) === null || _c === void 0 ? void 0 : _c.getFields()) || {}),
4889 ];
4890 for (const rootField of rootFields) {
4891 for (const argName in args) {
4892 const argConfig = args[argName];
4893 rootField.args.push({
4894 name: argName,
4895 description: undefined,
4896 defaultValue: undefined,
4897 extensions: undefined,
4898 astNode: undefined,
4899 deprecationReason: undefined,
4900 ...argConfig,
4901 });
4902 }
4903 }
4904 contextVariables.push('fetch', 'baseUrl');
4905 return {
4906 schema,
4907 contextVariables,
4908 };
4909 }
4910}
4911
4912export default OpenAPIHandler;
4913//# sourceMappingURL=index.esm.js.map