UNPKG

openapi-to-graphql-harshith

Version:

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

313 lines (282 loc) 8.87 kB
// 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 /** * Functions to create viewers that allow users to pass credentials to resolve * functions used by OpenAPI-to-GraphQL. */ // Type imports: import { GraphQLString, GraphQLObjectType, GraphQLNonNull, GraphQLFieldConfigMap, GraphQLFieldResolver, GraphQLFieldConfig } from 'graphql' import { Args, GraphQLOperationType } from './types/graphql' import { PreprocessingData, ProcessedSecurityScheme } from './types/preprocessing_data' // Imports: import { getGraphQLType } from './schema_builder' import * as Oas3Tools from './oas_3_tools' import debug from 'debug' import { handleWarning, sortObject, MitigationTypes } from './utils' import { createDataDef } from './preprocessor' import crossFetch from 'cross-fetch' const translationLog = debug('translation') /** * Load the field object in the appropriate root object * * i.e. inside either rootQueryFields/rootMutationFields or inside * rootQueryFields/rootMutationFields for further processing */ export function createAndLoadViewer<TSource, TContext, TArgs>( queryFields: object, operationType: GraphQLOperationType, data: PreprocessingData<TSource, TContext, TArgs>, fetch: typeof crossFetch ): { [key: string]: GraphQLFieldConfig<TSource, TContext, TArgs> } { const results = {} /** * To ensure that viewers have unique names, we add a numerical postfix. * * This object keeps track of what the postfix should be. * * The key is the security scheme type and the value is * the current highest postfix used for viewers of that security scheme type. */ const viewerNamePostfix: { [key: string]: number } = {} /** * Used to collect all fields in the given querFields object, no matter which * protocol. Used to populate anyAuthViewer. */ const anyAuthFields = {} for (let protocolName in queryFields) { Object.assign(anyAuthFields, queryFields[protocolName]) /** * Check if the name has already been used (i.e. in the list) * if so, create a new name and add it to the list */ const securityType = data.security[protocolName].def.type let viewerType: string /** * HTTP is not an authentication protocol * HTTP covers a number of different authentication type * change the typeName to match the exact authentication type (e.g. basic * authentication) */ if (securityType === 'http') { let scheme = data.security[protocolName].def.scheme switch (scheme) { case 'basic': viewerType = 'basicAuth' break case 'bearer': viewerType = 'bearerAuth' break default: handleWarning({ mitigationType: MitigationTypes.UNSUPPORTED_HTTP_SECURITY_SCHEME, message: `Currently unsupported HTTP authentication protocol ` + `type 'http' and scheme '${scheme}'`, data, log: translationLog }) continue } } else { viewerType = securityType } // Create name for the viewer let viewerName = operationType === GraphQLOperationType.Query ? Oas3Tools.sanitize( `viewer ${viewerType}`, Oas3Tools.CaseStyle.camelCase ) : operationType === GraphQLOperationType.Mutation ? Oas3Tools.sanitize( `mutation viewer ${viewerType}`, Oas3Tools.CaseStyle.camelCase ) : Oas3Tools.sanitize( `subscription viewer ${viewerType}`, Oas3Tools.CaseStyle.camelCase ) // Ensure unique viewer name // If name already exists, append a number at the end of the name if (!(viewerType in viewerNamePostfix)) { viewerNamePostfix[viewerType] = 1 } else { viewerName += ++viewerNamePostfix[viewerType] } // Add the viewer object type to the specified root query object type results[viewerName] = getViewerOT( viewerName, protocolName, securityType, queryFields[protocolName], data ) } // Create name for the AnyAuth viewer const anyAuthObjectName = operationType === GraphQLOperationType.Query ? 'viewerAnyAuth' : operationType === GraphQLOperationType.Mutation ? 'mutationViewerAnyAuth' : 'subscriptionViewerAnyAuth' // Add the AnyAuth object type to the specified root query object type results[anyAuthObjectName] = getViewerAnyAuthOT( anyAuthObjectName, anyAuthFields, data, fetch ) return results } /** * Get the viewer object, resolve function, and arguments */ function getViewerOT<TSource, TContext, TArgs>( name: string, protocolName: string, securityType: string, queryFields: GraphQLFieldConfigMap<any, any>, data: PreprocessingData<TSource, TContext, TArgs> ): GraphQLFieldConfig<TSource, TContext, TArgs> { const scheme: ProcessedSecurityScheme = data.security[protocolName] // Resolve function: const resolve: GraphQLFieldResolver<TSource, TContext, TArgs> = ( source, args, context, info ) => { const security = {} const saneProtocolName = Oas3Tools.sanitize( protocolName, Oas3Tools.CaseStyle.camelCase ) security[ Oas3Tools.storeSaneName(saneProtocolName, protocolName, data.saneMap) ] = args /** * Viewers are always root, so we can instantiate _openAPIToGraphQL here without * previously checking for its existence */ return { _openAPIToGraphQL: { security } } } // Arguments: /** * Do not sort because they are already "sorted" in preprocessing. * Otherwise, for basic auth, "password" will appear before "username" */ const args = {} if (typeof scheme === 'object') { for (let parameterName in scheme.parameters) { // The parameter name should be already sane as it is provided by OpenAPI-to-GraphQL const saneParameterName = Oas3Tools.sanitize( parameterName, Oas3Tools.CaseStyle.camelCase ) args[saneParameterName] = { type: new GraphQLNonNull(GraphQLString) } } } let typeDescription = `A viewer for security scheme '${protocolName}'` /** * HTTP authentication uses different schemes. It is not sufficient to name * only the security type */ let description = securityType === 'http' ? `A viewer that wraps all operations authenticated via security scheme ` + `'${protocolName}', which is of type 'http' '${scheme.def.scheme}'` : `A viewer that wraps all operations authenticated via security scheme ` + `'${protocolName}', which is of type '${securityType}'` if (data.oass.length !== 1) { typeDescription += ` in OAS '${scheme.oas.info.title}'` description = `, in OAS '${scheme.oas.info.title}` } return { type: new GraphQLObjectType({ name: Oas3Tools.capitalize(name), // Should already be sanitized and in camelCase description: typeDescription, fields: () => queryFields }), resolve, args, description } } /** * Create an object containing an AnyAuth viewer, its resolve function, * and its args. */ function getViewerAnyAuthOT<TSource, TContext, TArgs>( name: string, queryFields: GraphQLFieldConfigMap<any, any>, data: PreprocessingData<TSource, TContext, TArgs>, fetch: typeof crossFetch ): GraphQLFieldConfig<TSource, TContext, TArgs> { // Resolve function: const resolve: GraphQLFieldResolver<TSource, TContext, TArgs> = ( source, args, context, info ) => { return { _openAPIToGraphQL: { security: args } } } // Arguments: let args = {} for (let protocolName in data.security) { // Create input object types for the viewer arguments const def = createDataDef( { fromRef: protocolName }, data.security[protocolName].schema, true, data, data.security[protocolName].oas ) const type = getGraphQLType({ def, data, isInputObjectType: true, fetch }) const saneProtocolName = Oas3Tools.sanitize( protocolName, Oas3Tools.CaseStyle.camelCase ) args[ Oas3Tools.storeSaneName(saneProtocolName, protocolName, data.saneMap) ] = { type } } args = sortObject(args) return { type: new GraphQLObjectType({ name: Oas3Tools.capitalize(name), // Should already be GraphQL safe description: 'Warning: Not every request will work with this viewer type', fields: () => queryFields }), resolve, args, description: `A viewer that wraps operations for all available ` + `authentication mechanisms` } }