@ardatan/openapi-to-graphql
Version:
Generates a GraphQL schema for a given OpenAPI Specification (OAS)
980 lines • 40.1 kB
JavaScript
;
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: openapi-to-graphql
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
Object.defineProperty(exports, "__esModule", { value: true });
const graphql_1 = require("graphql");
// Imports:
const GraphQLJSON = require("graphql-type-json");
const Oas3Tools = require("./oas_3_tools");
const resolver_builder_1 = require("./resolver_builder");
const preprocessor_1 = require("./preprocessor");
const debug_1 = require("debug");
const utils_1 = require("./utils");
const translationLog = debug_1.default('translation');
/**
* Creates and returns a GraphQL type for the given JSON schema.
*/
function getGraphQLType({ def, operation, data, iteration = 0, isInputObjectType = false }) {
const name = isInputObjectType
? def.graphQLInputObjectTypeName
: def.graphQLTypeName;
// Avoid excessive iterations
if (iteration === 50) {
throw new Error(`GraphQL type ${name} has excessive nesting of other types`);
}
switch (def.targetGraphQLType) {
// CASE: object - create object type
case 'object':
return createOrReuseOt({
def,
operation,
data,
iteration,
isInputObjectType
});
// CASE: union - create union type
case 'union':
return createOrReuseUnion({
def,
operation,
data,
iteration
});
// CASE: list - create list type
case 'list':
return createOrReuseList({
def,
operation,
data,
iteration,
isInputObjectType
});
// CASE: enum - create enum type
case 'enum':
return createOrReuseEnum({
def,
data
});
// CASE: scalar - return scalar type
default:
return getScalarType({
def,
data
});
}
}
exports.getGraphQLType = getGraphQLType;
/**
* Creates an (input) object type or return an existing one, and stores it
* in data
*
* A returned GraphQLObjectType has the following internal structure:
*
* new GraphQLObjectType({
* name // Optional name of the type
* description // Optional description of type
* fields // REQUIRED returning fields
* type // REQUIRED definition of the field type
* args // Optional definition of types
* resolve // Optional function defining how to obtain this type
* })
*/
function createOrReuseOt({ def, operation, data, iteration, isInputObjectType }) {
// Try to reuse a preexisting (input) object type
// CASE: query - reuse object type
if (!isInputObjectType) {
if (def.graphQLType && typeof def.graphQLType !== 'undefined') {
translationLog(`Reuse object type '${def.graphQLTypeName}'` +
(typeof operation === 'object'
? ` (for operation '${operation.operationString}')`
: ''));
return def.graphQLType;
}
// CASE: mutation - reuse input object type
}
else {
if (def.graphQLInputObjectType &&
typeof def.graphQLInputObjectType !== 'undefined') {
translationLog(`Reuse input object type '${def.graphQLInputObjectTypeName}'` +
(typeof operation === 'object'
? ` (for operation '${operation.operationString}')`
: ''));
return def.graphQLInputObjectType;
}
}
// Cannot reuse preexisting (input) object type, therefore create one
const schema = def.schema;
const description = schema.description;
// CASE: query - create object type
if (!isInputObjectType) {
translationLog(`Create object type '${def.graphQLTypeName}'` +
(typeof operation === 'object'
? ` (for operation '${operation.operationString}')`
: ''));
def.graphQLType = new graphql_1.GraphQLObjectType({
name: def.graphQLTypeName,
description,
fields: () => {
return createFields({
def,
links: def.links,
operation,
data,
iteration,
isInputObjectType: false
});
}
});
return def.graphQLType;
// CASE: mutation - create input object type
}
else {
translationLog(`Create input object type '${def.graphQLInputObjectTypeName}'` +
(typeof operation === 'object'
? ` (for operation '${operation.operationString}')`
: ''));
def.graphQLInputObjectType = new graphql_1.GraphQLInputObjectType({
name: def.graphQLInputObjectTypeName,
description,
// @ts-ignore
fields: () => {
return createFields({
def,
links: {},
operation,
data,
iteration,
isInputObjectType: true
});
}
});
return def.graphQLInputObjectType;
}
}
/**
* Creates a union type or return an existing one, and stores it in data
*/
function createOrReuseUnion({ def, operation, data, iteration }) {
// Try to reuse existing union type
if (typeof def.graphQLType !== 'undefined') {
translationLog(`Reuse union type '${def.graphQLTypeName}'` +
(typeof operation === 'object'
? ` (for operation '${operation.operationString}')`
: ''));
return def.graphQLType;
}
else {
translationLog(`Create union type '${def.graphQLTypeName}'` +
(typeof operation === 'object'
? ` (for operation '${operation.operationString}')`
: ''));
const schema = def.schema;
const description = typeof schema.description !== 'undefined'
? schema.description
: 'No description available.';
const memberTypeDefinitions = def.subDefinitions;
const types = Object.values(memberTypeDefinitions).map(memberTypeDefinition => {
return getGraphQLType({
def: memberTypeDefinition,
operation,
data,
iteration: iteration + 1,
isInputObjectType: false
});
});
/**
* Check for ambiguous member types
*
* i.e. member types that can be confused with each other.
*/
checkAmbiguousMemberTypes(def, types, data);
def.graphQLType = new graphql_1.GraphQLUnionType({
name: def.graphQLTypeName,
description,
types,
resolveType: (source, context, info) => {
const properties = Object.keys(source);
// Remove custom _openAPIToGraphQL property used to pass data
const otgIndex = properties.indexOf('_openAPIToGraphQL');
if (otgIndex !== -1) {
properties.splice(otgIndex, 1);
}
/**
* Find appropriate member type
*
* TODO: currently, the check is performed by only checking the property
* names. In the future, we should also check the types of those
* properties.
*
* TODO: there is a chance a that an intended member type cannot be
* identified if, for whatever reason, the return data is a superset
* of the fields specified in the OAS
*/
return types.find(type => {
const typeFields = Object.keys(type.getFields());
if (properties.length <= typeFields.length) {
for (let i = 0; i < properties.length; i++) {
if (!typeFields.includes(properties[i])) {
return false;
}
}
return true;
}
return false;
});
}
});
return def.graphQLType;
}
}
/**
* Check for ambiguous member types
*
* i.e. member types that can be confused with each other.
*/
function checkAmbiguousMemberTypes(def, types, data) {
types.sort((a, b) => {
const aFieldLength = Object.keys(a.getFields()).length;
const bFieldLength = Object.keys(b.getFields()).length;
if (aFieldLength < bFieldLength) {
return -1;
}
else if (aFieldLength < bFieldLength) {
return 1;
}
else {
return 0;
}
});
for (let i = 0; i < types.length - 1; i++) {
const currentType = types[i];
for (let j = i + 1; j < types.length; j++) {
const otherType = types[j];
// TODO: Check the value, not just the field name
if (Object.keys(currentType.getFields()).every(field => {
return Object.keys(otherType.getFields()).includes(field);
})) {
utils_1.handleWarning({
typeKey: 'AMBIGUOUS_UNION_MEMBERS',
message: `Union created from schema '${JSON.stringify(def)}' contains ` +
`member types such as '${currentType}' and '${otherType}' ` +
`which are ambiguous. Ambiguous member types can cause ` +
`problems when trying to resolve types.`,
data,
log: translationLog
});
return;
}
}
}
}
/**
* Creates a list type or returns an existing one, and stores it in data
*/
function createOrReuseList({ def, operation, iteration, isInputObjectType, data }) {
const name = isInputObjectType
? def.graphQLInputObjectTypeName
: def.graphQLTypeName;
// Try to reuse existing Object Type
if (!isInputObjectType &&
def.graphQLType &&
typeof def.graphQLType !== 'undefined') {
translationLog(`Reuse GraphQLList '${def.graphQLTypeName}'`);
return def.graphQLType;
}
else if (isInputObjectType &&
def.graphQLInputObjectType &&
typeof def.graphQLInputObjectType !== 'undefined') {
translationLog(`Reuse GraphQLList '${def.graphQLInputObjectTypeName}'`);
return def.graphQLInputObjectType;
}
// Create new List Object Type
translationLog(`Create GraphQLList '${def.graphQLTypeName}'`);
// Get definition of the list item, which should be in the sub definitions
const itemDef = def.subDefinitions;
// Equivalent to schema.items
const itemsSchema = itemDef.schema;
// Equivalent to `{name}ListItem`
const itemsName = itemDef.graphQLTypeName;
const itemsType = getGraphQLType({
def: itemDef,
data,
operation,
iteration: iteration + 1,
isInputObjectType
});
if (itemsType !== null) {
const listObjectType = new graphql_1.GraphQLList(itemsType);
// Store newly created list type
if (!isInputObjectType) {
def.graphQLType = listObjectType;
}
else {
def.graphQLInputObjectType = listObjectType;
}
return listObjectType;
}
else {
throw new Error(`Cannot create list item object type '${itemsName}' in list
'${name}' with schema '${JSON.stringify(itemsSchema)}'`);
}
}
/**
* Creates an enum type or returns an existing one, and stores it in data
*/
function createOrReuseEnum({ def, data }) {
/**
* Try to reuse existing enum type
*
* Enum types do not have an input variant so only check def.ot
*/
if (def.graphQLType && typeof def.graphQLType !== 'undefined') {
translationLog(`Reuse GraphQLEnumType '${def.graphQLTypeName}'`);
return def.graphQLType;
}
else {
translationLog(`Create GraphQLEnumType '${def.graphQLTypeName}'`);
const values = {};
def.schema.enum.forEach(e => {
// Force enum values to string and value should be in ALL_CAPS
values[Oas3Tools.sanitize(e.toString(), Oas3Tools.CaseStyle.ALL_CAPS)] = {
value: e
};
});
// Store newly created Enum Object Type
def.graphQLType = new graphql_1.GraphQLEnumType({
name: def.graphQLTypeName,
values
});
return def.graphQLType;
}
}
/**
* Returns the GraphQL scalar type matching the given JSON schema type
*/
function getScalarType({ def, data }) {
switch (def.targetGraphQLType) {
case 'id':
def.graphQLType = graphql_1.GraphQLID;
break;
case 'string':
def.graphQLType = graphql_1.GraphQLString;
break;
case 'integer':
def.graphQLType = graphql_1.GraphQLInt;
break;
case 'number':
def.graphQLType = graphql_1.GraphQLFloat;
break;
case 'float':
def.graphQLType = graphql_1.GraphQLFloat;
break;
case 'boolean':
def.graphQLType = graphql_1.GraphQLBoolean;
break;
case 'json':
def.graphQLType = GraphQLJSON;
break;
default:
throw new Error(`Cannot process schema type '${def.targetGraphQLType}'.`);
}
return def.graphQLType;
}
/**
* Creates the fields object to be used by an (input) object type
*/
function createFields({ def, links, operation, data, iteration, isInputObjectType }) {
let fields = {};
const fieldTypeDefinitions = def.subDefinitions;
// Create fields for properties
for (let fieldTypeKey in fieldTypeDefinitions) {
const fieldTypeDefinition = fieldTypeDefinitions[fieldTypeKey];
const fieldSchema = fieldTypeDefinition.schema;
// Get object type describing the property
const objectType = getGraphQLType({
def: fieldTypeDefinition,
operation,
data,
iteration: iteration + 1,
isInputObjectType
});
const requiredProperty = typeof def.required === 'object' && def.required.includes(fieldTypeKey);
// Finally, add the object type to the fields (using sanitized field name)
if (objectType) {
const saneFieldTypeKey = Oas3Tools.sanitize(fieldTypeKey, !data.options.simpleNames
? Oas3Tools.CaseStyle.camelCase
: Oas3Tools.CaseStyle.simple);
const sanePropName = Oas3Tools.storeSaneName(saneFieldTypeKey, fieldTypeKey, data.saneMap);
fields[sanePropName] = {
type: requiredProperty
? new graphql_1.GraphQLNonNull(objectType)
: objectType,
description: typeof fieldSchema === 'object' ? fieldSchema.description : null
};
}
else {
utils_1.handleWarning({
typeKey: 'CANNOT_GET_FIELD_TYPE',
message: `Cannot obtain GraphQL type for field '${fieldTypeKey}' in ` +
`GraphQL type '${JSON.stringify(def.schema)}'.`,
data,
log: translationLog
});
}
}
if (typeof links === 'object' && // Links are present
!isInputObjectType // Only object type (input object types cannot make use of links)
) {
for (let saneLinkKey in links) {
translationLog(`Create link '${saneLinkKey}'...`);
// Check if key is already in fields
if (saneLinkKey in fields) {
utils_1.handleWarning({
typeKey: 'LINK_NAME_COLLISION',
message: `Cannot create link '${saneLinkKey}' because parent ` +
`object type already contains a field with the same (sanitized) name.`,
data,
log: translationLog
});
}
else {
const link = links[saneLinkKey];
// Get linked operation
let linkedOpId;
// TODO: href is yet another alternative to operationRef and operationId
if (typeof link.operationId === 'string') {
linkedOpId = link.operationId;
}
else if (typeof link.operationRef === 'string') {
linkedOpId = linkOpRefToOpId({
links,
linkKey: saneLinkKey,
operation,
data
});
}
/**
* linkedOpId may not be initialized because operationRef may lead to an
* operation object that does not have an operationId
*/
if (typeof linkedOpId === 'string' && linkedOpId in data.operations) {
const linkedOp = data.operations[linkedOpId];
// Determine parameters provided via link
let argsFromLink = link.parameters;
// Get arguments that are not provided by the linked operation
let dynamicParams = linkedOp.parameters;
if (typeof argsFromLink === 'object') {
dynamicParams = dynamicParams.filter(param => {
return typeof argsFromLink[param.name] === 'undefined';
});
}
// Get resolve function for link
const linkResolver = resolver_builder_1.getResolver({
operation: linkedOp,
argsFromLink: argsFromLink,
data,
baseUrl: data.options.baseUrl,
requestOptions: data.options.requestOptions
});
// Get arguments for link
const args = getArgs({
parameters: dynamicParams,
operation: linkedOp,
data
});
// Get response object type
const resObjectType = linkedOp.responseDefinition.graphQLType !== undefined
? linkedOp.responseDefinition.graphQLType
: getGraphQLType({
def: linkedOp.responseDefinition,
operation,
data,
iteration: iteration + 1,
isInputObjectType: false
});
let description = link.description;
if (data.options.equivalentToMessages && description) {
description += `\n\nEquivalent to ${linkedOp.operationString}`;
}
// Finally, add the object type to the fields (using sanitized field name)
// TODO: check if fields already has this field name
fields[saneLinkKey] = {
type: resObjectType,
resolve: linkResolver,
args,
description
};
}
else {
utils_1.handleWarning({
typeKey: 'UNRESOLVABLE_LINK',
message: `Cannot resolve target of link '${saneLinkKey}'`,
data,
log: translationLog
});
}
}
}
}
fields = utils_1.sortObject(fields);
return fields;
}
/**
* Returns the operationId that an operationRef is associated to
*
* NOTE: If the operation does not natively have operationId, this function
* will try to produce an operationId the same way preprocessor.js does it.
*
* Any changes to constructing operationIds in preprocessor.js should be
* reflected here.
*/
function linkOpRefToOpId({ links, linkKey, operation, data }) {
const link = links[linkKey];
if (typeof link.operationRef === 'string') {
// TODO: external refs
const operationRef = link.operationRef;
let linkLocation;
let linkRelativePathAndMethod;
/**
* Example relative path: '#/paths/~12.0~1repositories~1{username}/get'
* Example absolute path: 'https://na2.gigantic-server.com/#/paths/~12.0~1repositories~1{username}/get'
* Extract relative path from relative path
*/
if (operationRef.substring(0, 8) === '#/paths/') {
linkRelativePathAndMethod = operationRef;
// Extract relative path from absolute path
}
else {
/**
* '#' may exist in other places in the path
* '/#/' is more likely to point to the beginning of the path
*/
const firstPathIndex = operationRef.indexOf('#/paths/');
// Found a relative path candidate
if (firstPathIndex !== -1) {
// Check to see if there are other relative path candidates
const lastPathIndex = operationRef.lastIndexOf('#/paths/');
if (firstPathIndex !== lastPathIndex) {
utils_1.handleWarning({
typeKey: 'AMBIGUOUS_LINK',
message: `The link '${linkKey}' in operation '${operation.operationString}' ` +
`contains an ambiguous operationRef '${operationRef}', ` +
`meaning it has multiple instances of the string '#/paths/'`,
data,
log: translationLog
});
return;
}
linkLocation = operationRef.substring(0, firstPathIndex);
linkRelativePathAndMethod = operationRef.substring(firstPathIndex);
// Cannot find relative path candidate
}
else {
utils_1.handleWarning({
typeKey: 'UNRESOLVABLE_LINK',
message: `The link '${linkKey}' in operation '${operation.operationString}' ` +
`does not contain a valid path in operationRef '${operationRef}', ` +
`meaning it does not contain a string '#/paths/'`,
data,
log: translationLog
});
return;
}
}
// Infer operationId from relative path
if (typeof linkRelativePathAndMethod === 'string') {
let linkPath;
let linkMethod;
/**
* NOTE: I wish we could extract the linkedOpId by matching the
* linkedOpObject with an operation in data and extracting the operationId
* there but that does not seem to be possible especiially because you
* need to know the operationId just to access the operations so what I
* have to do is reconstruct the operationId the same way preprocessing
* does it
*/
/**
* linkPath should be the path followed by the method
*
* Find the slash that divides the path from the method
*/
const pivotSlashIndex = linkRelativePathAndMethod.lastIndexOf('/');
// Check if there are any '/' in the linkPath
if (pivotSlashIndex !== -1) {
// Get method
// Check if there is a method at the end of the linkPath
if (pivotSlashIndex !== linkRelativePathAndMethod.length - 1) {
// Start at +1 because we do not want the starting '/'
linkMethod = linkRelativePathAndMethod.substring(pivotSlashIndex + 1);
// Check if method is a valid method
if (!Oas3Tools.OAS_OPERATIONS.includes(linkMethod)) {
utils_1.handleWarning({
typeKey: 'UNRESOLVABLE_LINK',
message: `The operationRef '${operationRef}' contains an ` +
`invalid HTTP method '${linkMethod}'`,
data,
log: translationLog
});
return;
}
// There is no method at the end of the path
}
else {
utils_1.handleWarning({
typeKey: 'UNRESOLVABLE_LINK',
message: `The operationRef '${operationRef}' does not contain an` +
`HTTP method`,
data,
log: translationLog
});
return;
}
/**
* Get path
*
* Substring starts at index 8 and ends at pivotSlashIndex to exclude
* the '/'s at the ends of the path
*
* TODO: improve removing '/#/paths'?
*/
linkPath = linkRelativePathAndMethod.substring(8, pivotSlashIndex);
/**
* linkPath is currently a JSON Pointer
*
* Revert the escaped '/', represented by '~1', to form intended path
*/
linkPath = linkPath.replace(/~1/g, '/');
// Find the right oas
const oas = typeof linkLocation === 'undefined'
? operation.oas
: getOasFromLinkLocation(linkLocation, link, data);
// If the link was external, make sure that an OAS could be identified
if (typeof oas !== 'undefined') {
if (typeof linkMethod === 'string' && typeof linkPath === 'string') {
let linkedOpId;
if (linkPath in oas.paths && linkMethod in oas.paths[linkPath]) {
const linkedOpObject = oas.paths[linkPath][linkMethod];
if ('operationId' in linkedOpObject) {
linkedOpId = linkedOpObject.operationId;
}
}
if (typeof linkedOpId !== 'string') {
linkedOpId = Oas3Tools.generateOperationId(linkMethod, linkPath);
}
if (linkedOpId in data.operations) {
return linkedOpId;
}
else {
utils_1.handleWarning({
typeKey: 'UNRESOLVABLE_LINK',
message: `The link '${linkKey}' references an operation with ` +
`operationId '${linkedOpId}' but no such operation exists. ` +
`Note that the operationId may be autogenerated but ` +
`regardless, the link could not be matched to an operation.`,
data,
log: translationLog
});
return;
}
// Path and method could not be found
}
else {
utils_1.handleWarning({
typeKey: 'UNRESOLVABLE_LINK',
message: `Cannot identify path and/or method, '${linkPath} and ` +
`'${linkMethod}' respectively, from operationRef ` +
`'${operationRef}' in link '${linkKey}'`,
data,
log: translationLog
});
return;
}
// External link could not be resolved
}
else {
utils_1.handleWarning({
typeKey: 'UNRESOLVABLE_LINK',
message: `The link '${link.operationRef}' references an external OAS ` +
`but it was not provided`,
data,
log: translationLog
});
return;
}
// Cannot split relative path into path and method sections
}
else {
utils_1.handleWarning({
typeKey: 'UNRESOLVABLE_LINK',
message: `Cannot extract path and/or method from operationRef ` +
`'${operationRef}' in link '${linkKey}'`,
data,
log: translationLog
});
return;
}
// Cannot extract relative path from absolute path
}
else {
utils_1.handleWarning({
typeKey: 'UNRESOLVABLE_LINK',
message: `Cannot extract path and/or method from operationRef ` +
`'${operationRef}' in link '${linkKey}'`,
data,
log: translationLog
});
return;
}
}
}
/**
* Creates the arguments for resolving a field
*/
function getArgs({ requestPayloadDef, parameters, operation, data }) {
let args = {};
// Handle params:
for (const parameter of parameters) {
// We need at least a name
if (typeof parameter.name !== 'string') {
utils_1.handleWarning({
typeKey: 'INVALID_OAS',
message: `The operation '${operation.operationString}' contains a ` +
`parameter '${JSON.stringify(parameter)}' with no 'name' property`,
data,
log: translationLog
});
continue;
}
// If this parameter is provided via options, ignore
if (typeof data.options === 'object') {
switch (parameter.in) {
case 'header':
// Check header option
if (typeof data.options.headers === 'object' &&
parameter.name in data.options.headers) {
continue;
}
// Check requestOptions option
if (typeof data.options.requestOptions === 'object' &&
typeof data.options.requestOptions.headers === 'object' &&
parameter.name in data.options.requestOptions.headers) {
continue;
}
break;
case 'query':
// Check header option
if (typeof data.options.qs === 'object' &&
parameter.name in data.options.qs) {
continue;
}
break;
}
}
/**
* Determine type of parameter
*
* The type of the parameter can either be contained in the "schema" field
* or the "content" field (but not both)
*/
let schema;
if (typeof parameter.schema === 'object') {
schema = parameter.schema;
}
else if (typeof parameter.content === 'object') {
if (typeof parameter.content['application/json'] === 'object' &&
typeof parameter.content['application/json'].schema === 'object') {
schema = parameter.content['application/json'].schema;
}
else {
utils_1.handleWarning({
typeKey: 'NON_APPLICATION_JSON_SCHEMA',
message: `The operation '${operation.operationString}' contains a ` +
`parameter '${JSON.stringify(parameter)}' that has a 'content' ` +
`property but no schemas in application/json format. The ` +
`parameter will not be created`,
data,
log: translationLog
});
continue;
}
}
else {
// Invalid OAS according to 3.0.2
utils_1.handleWarning({
typeKey: 'INVALID_OAS',
message: `The operation '${operation.operationString}' contains a ` +
`parameter '${JSON.stringify(parameter)}' with no 'schema' or ` +
`'content' property`,
data,
log: translationLog
});
continue;
}
/**
* Resolving the reference is necessary later in the code and by doing it,
* we can avoid doing it a second time in resolveRev()
*/
if ('$ref' in schema) {
schema = Oas3Tools.resolveRef(schema['$ref'], operation.oas);
}
// TODO: remove
const paramDef = preprocessor_1.createDataDef({ fromSchema: parameter.name }, schema, true, data);
// @ts-ignore
const type = getGraphQLType({
def: paramDef,
operation,
data,
iteration: 0,
isInputObjectType: true
});
/**
* Sanitize the argument name
*
* NOTE: when matching these parameters back to requests, we need to again
* use the real parameter name
*/
const saneName = Oas3Tools.sanitize(parameter.name, !data.options.simpleNames
? Oas3Tools.CaseStyle.camelCase
: Oas3Tools.CaseStyle.simple);
// Parameters are not required when a default exists:
let hasDefault = false;
if (typeof parameter.schema === 'object') {
let schema = parameter.schema;
if (typeof schema.$ref === 'string') {
schema = Oas3Tools.resolveRef(parameter.schema.$ref, operation.oas);
}
if (typeof schema.default !== 'undefined') {
hasDefault = true;
}
}
const paramRequired = parameter.required && !hasDefault;
args[saneName] = {
type: paramRequired ? new graphql_1.GraphQLNonNull(type) : type,
description: parameter.description // Might be undefined
};
}
// Add limit argument
if (data.options.addLimitArgument &&
typeof operation.responseDefinition === 'object' &&
operation.responseDefinition.schema.type === 'array' &&
// Only add limit argument to lists of object types, not to lists of scalar types
(operation.responseDefinition.subDefinitions.schema
.type === 'object' ||
operation.responseDefinition.subDefinitions.schema
.type === 'array')) {
// Make sure slicing arguments will not overwrite preexisting arguments
if ('limit' in args) {
utils_1.handleWarning({
typeKey: 'LIMIT_ARGUMENT_NAME_COLLISION',
message: `The 'limit' argument cannot be added ` +
`because of a preexisting argument in ` +
`operation ${operation.operationString}`,
data,
log: translationLog
});
}
else {
args['limit'] = {
type: graphql_1.GraphQLInt,
description: `Auto-generated argument that limits the size of ` +
`returned list of objects/list, selecting the first \`n\` ` +
`elements of the list`
};
}
}
// Handle request payload (if present):
if (typeof requestPayloadDef === 'object') {
const reqObjectType = getGraphQLType({
def: requestPayloadDef,
data,
operation,
isInputObjectType: true // Request payloads will always be an input object type
});
// Sanitize the argument name
const saneName = data.options.genericPayloadArgName
? 'requestBody'
: Oas3Tools.uncapitalize(requestPayloadDef.graphQLInputObjectTypeName); // Already sanitized
const reqRequired = typeof operation === 'object' &&
typeof operation.payloadRequired === 'boolean'
? operation.payloadRequired
: false;
args[saneName] = {
type: reqRequired ? new graphql_1.GraphQLNonNull(reqObjectType) : reqObjectType,
// TODO: addendum to the description explaining this is the request body
description: requestPayloadDef.schema.description
};
}
args = utils_1.sortObject(args);
return args;
}
exports.getArgs = getArgs;
/**
* Used in the context of links, specifically those using an external operationRef
* If the reference is an absolute reference, determine the type of location
*
* For example, name reference, file path, web-hosted OAS link, etc.
*/
function getLinkLocationType(linkLocation) {
// TODO: currently we only support the title as a link location
return 'title';
}
/**
* Used in the context of links, specifically those using an external operationRef
* Based on the location of the OAS, retrieve said OAS
*/
function getOasFromLinkLocation(linkLocation, link, data) {
// May be an external reference
switch (getLinkLocationType(linkLocation)) {
case 'title':
// Get the possible
const possibleOass = data.oass.filter(oas => {
return oas.info.title === linkLocation;
});
// Check if there are an ambiguous OASs
if (possibleOass.length === 1) {
// No ambiguity
return possibleOass[0];
}
else if (possibleOass.length > 1) {
// Some ambiguity
utils_1.handleWarning({
typeKey: 'AMBIGUOUS_LINK',
message: `The operationRef '${link.operationRef}' references an ` +
`OAS '${linkLocation}' but multiple OASs share the same title`,
data,
log: translationLog
});
}
else {
// No OAS had the expected title
utils_1.handleWarning({
typeKey: 'UNRESOLVABLE_LINK',
message: `The operationRef '${link.operationRef}' references an ` +
`OAS '${linkLocation}' but no such OAS was provided`,
data,
log: translationLog
});
}
break;
// // TODO
// case 'url':
// break
// // TODO
// case 'file':
// break
// TODO: should title be default?
// In cases of names like api.io
default:
utils_1.handleWarning({
typeKey: 'UNRESOLVABLE_LINK',
message: `The link location of the operationRef ` +
`'${link.operationRef}' is currently not supported\n` +
`Currently only the title of the OAS is supported`,
data,
log: translationLog
});
}
}
//# sourceMappingURL=schema_builder.js.map