UNPKG

@adpt/cloud

Version:
295 lines 11.8 kB
"use strict"; /* * Copyright 2018-2019 Unbounded Systems, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const graphql_1 = require("graphql"); const utils_1 = require("@adpt/utils"); const graphql_type_json_1 = tslib_1.__importDefault(require("graphql-type-json")); const ld = tslib_1.__importStar(require("lodash")); const util = tslib_1.__importStar(require("util")); const swagger_types_1 = require("../../src/swagger2gql/swagger_types"); const type_resolver_1 = require("../../src/swagger2gql/type_resolver"); function isComplexType(schema) { return schema.type === "object" || schema.type === "array"; } function buildFieldsFromSchema(baseName, schema, tyResolver, inputType) { const properties = schema.properties; //FIXME(manishv) add a unit test for these two cases below, do not rely on k8s tests if (properties === undefined) return graphql_type_json_1.default; if (Object.keys(properties).length === 0) return graphql_type_json_1.default; return () => { const ret = {}; const required = schema.required ? schema.required : []; for (const propName in properties) { if (!Object.hasOwnProperty.call(properties, propName)) continue; const prop = properties[propName]; const nonNull = required.find((val) => val === propName) !== undefined; const baseType = getOrBuildType(baseName + "_" + propName, prop, tyResolver, inputType); const type = nonNull ? new graphql_1.GraphQLNonNull(baseType) : baseType; ret[makeGQLFieldName(propName)] = { description: schema.description, type }; } if (Object.keys(ret).length === 0) { throw new Error("Internal Error: no fields, how'd we get here??"); } return ret; }; } function makeGQLTypeName(swaggerName) { //FIXME(manishv) make robust removing all legal swagger characters that are illegal in graphql let gqlName = swaggerName.replace("#/definitions/", ""); gqlName = gqlName.replace(/[\/\.-]/g, "_"); return gqlName; } function makeGQLFieldName(swaggerName) { //FIXME(manishv) make robust removing all legal swagger characters that are illegal in graphql const gqlName = swaggerName.replace(/\$/, "dollar_"); return gqlName; } function jsonSchema2GraphQLType(name, schema, tyResolverIn, inputType) { const tyResolver = inputType ? tyResolverIn.input : tyResolverIn.output; if (schema.type === "array") { const items = schema.items; if (!items) throw new Error(`Saw array schema with no items: ${util.inspect(schema)}`); const itemsName = `${name}_items`; if (swagger_types_1.isRef(items) || !isComplexType(items)) { return new graphql_1.GraphQLList(getOrBuildType(itemsName, items, tyResolverIn, inputType)); } return new graphql_1.GraphQLList(jsonSchema2GraphQLType(itemsName, items, tyResolverIn, inputType)); } const primitive = isPrimitive(schema.type) ? tyResolver.getType(schema.type) : undefined; if (primitive) return primitive; const gqlName = makeGQLTypeName(name) + (inputType ? "_input" : "_output"); const consArgs = { name: gqlName, description: schema.description, }; if (inputType) { const fields = buildFieldsFromSchema(name, schema, tyResolverIn, true); if (!ld.isFunction(fields)) return fields; return new graphql_1.GraphQLInputObjectType(Object.assign({}, consArgs, { fields })); } else { //FIXME(manishv) attach resolvers somewhere const fields = buildFieldsFromSchema(name, schema, tyResolverIn, false); if (!ld.isFunction(fields)) return fields; return new graphql_1.GraphQLObjectType(Object.assign({}, consArgs, { fields })); } } function getDef(schema, tyName) { if (!tyName.startsWith("#/definitions/")) return; const defs = schema.definitions; if (!defs) return; const baseTyName = tyName.replace("#/definitions/", ""); return defs[baseTyName]; } function resolveParam(schema, tyName) { const params = schema.parameters; if (!params || !tyName.startsWith("#/parameters/")) { throw new Error(`Unable to look up parameter name ${tyName}`); } const baseTyName = tyName.replace("#/parameters/", ""); const param = params[baseTyName]; if (!param) throw new Error(`Parameter name ${tyName} not found`); return param; } function resolveType(swagger, tyName, tyResolver, input) { const schema = getDef(swagger, tyName); if (schema === undefined) throw new Error(`Unable to find type '${tyName}'`); return jsonSchema2GraphQLType(tyName, schema, tyResolver, input); } const primitiveTypes = { integer: graphql_1.GraphQLInt, number: graphql_1.GraphQLFloat, string: graphql_1.GraphQLString, boolean: graphql_1.GraphQLBoolean, _Empty: { input: graphql_type_json_1.default, output: graphql_type_json_1.default } }; function isPrimitive(tyName) { return Object.hasOwnProperty.call(primitiveTypes, tyName); } function populateBasicSwaggerTypes(tyResolver) { for (const nameI in primitiveTypes) { if (!Object.hasOwnProperty.call(primitiveTypes, nameI)) continue; const name = nameI; const typ = primitiveTypes[name]; const inTyp = "input" in typ ? typ.input : typ; const outTyp = "output" in typ ? typ.output : typ; tyResolver.input.addType(name, inTyp); tyResolver.output.addType(name, outTyp); } } function getOrBuildType(typeName, refOrSchema, tyResolver, input) { const name = swagger_types_1.isRef(refOrSchema) ? refOrSchema.$ref : typeName; try { const type = input ? tyResolver.input.getType(name) : tyResolver.output.getType(name); if (type) return type; } catch (err) { if (!ld.isError(err) || !err.message.startsWith("Unable to find type")) { throw err; } } if (swagger_types_1.isRef(refOrSchema)) throw new Error(`Unable to find ref type ${name}`); return jsonSchema2GraphQLType(typeName, refOrSchema, tyResolver, input); } function getParameterInfo(operationId, param, tyResolver) { let type; if (param.in === "body") { return { type: getOrBuildType(`${operationId}_body`, param.schema, tyResolver, true), required: param.required ? param.required : false, }; } if (param.type === "array") { const items = param.items; if (!items) throw new Error(`Saw array param with no items: ${util.inspect(param)}`); type = new graphql_1.GraphQLList(getOrBuildType(`${operationId}_${param.name}`, items, tyResolver, true)); } else { type = tyResolver.input.getType(param.type); } return { type, required: param.required ? param.required : false, default: param.default }; } function buildArgsForOperation(operationId, parameters, tyResolver) { if (parameters.length === 0) return; if (operationId == null) throw new Error(`operationId is null`); const ret = {}; for (let param of parameters) { if (swagger_types_1.isRef(param)) { param = tyResolver.param.getType(param.$ref); } const info = getParameterInfo(operationId, param, tyResolver); const defaultValue = param.in === "body" ? undefined : param.default; ret[makeGQLTypeName(param.name)] = { type: (info.required ? new graphql_1.GraphQLNonNull(info.type) : info.type), defaultValue }; } return ret; } function responseTypeForOperation(op, tyResolver) { const okResponse = op.responses["200"]; //FIXME(manishv) deal with other non-error responses here if (okResponse === undefined) return tyResolver.output.getType("_Empty"); const schema = okResponse.schema; if (schema === undefined) return graphql_type_json_1.default; return getOrBuildType(op.operationId + "_Response", schema, tyResolver, false); } function buildQueryField(op, itemParams, tyResolver) { const iparams = itemParams ? itemParams : []; const oparams = op.parameters ? op.parameters : []; return { type: responseTypeForOperation(op, tyResolver), args: buildArgsForOperation(op.operationId, [...iparams, ...oparams], tyResolver) }; } const supportedOps = utils_1.tuple("get", "post"); function buildQueryFields(swagger, tyResolver) { const ret = {}; for (const swagPath in swagger.paths) { if (!Object.hasOwnProperty.call(swagger.paths, swagPath)) continue; const pathItem = swagger.paths[swagPath]; for (const opType of supportedOps) { const op = pathItem[opType]; if (op === undefined) continue; if (op.operationId === undefined) continue; //FIXME(manishv) should we compute a name here? ret[makeGQLFieldName(op.operationId)] = buildQueryField(op, pathItem.parameters, tyResolver); } } return ret; } function buildQueryObject(swagger) { const tyResolver = { input: new type_resolver_1.TypeResolver((n) => resolveType(swagger, n, tyResolver, true)), output: new type_resolver_1.TypeResolver((n) => resolveType(swagger, n, tyResolver, false)), param: new type_resolver_1.TypeResolver((n) => resolveParam(swagger, n)), }; populateBasicSwaggerTypes(tyResolver); const fields = buildQueryFields(swagger, tyResolver); return new graphql_1.GraphQLObjectType({ name: "Query", fields }); } function addResolversToFields(seen, obj, getResolver, isQuery = false) { if (seen.has(obj)) return; seen.add(obj); const fields = obj.getFields(); for (const fieldName in fields) { if (!Object.hasOwnProperty.call(fields, fieldName)) continue; const field = fields[fieldName]; const fieldResolvers = getResolver.fieldResolvers; const resolver = fieldResolvers ? fieldResolvers(obj, fieldName, isQuery) : undefined; field.resolve = resolver; const fieldType = field.type; if (graphql_1.isObjectType(fieldType)) { addResolversToFields(seen, fieldType, getResolver, false); } //FIXME(manishv) How to deal with custom scalar types? } } function addResolversToSchema(schema, getResolver) { const queryType = schema.getQueryType(); if (!queryType) return; addResolversToFields(new Set(), queryType, getResolver, true); return; } function buildGraphQLSchema(swagger, getResolver) { const qobj = buildQueryObject(swagger); const schema = new graphql_1.GraphQLSchema({ query: qobj }); if (getResolver) addResolversToSchema(schema, getResolver); return schema; } exports.buildGraphQLSchema = buildGraphQLSchema; //# sourceMappingURL=converter.js.map