@adpt/cloud
Version:
AdaptJS cloud component library
295 lines • 11.8 kB
JavaScript
;
/*
* 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