@apollo/federation-internals
Version: 
Apollo Federation internal utilities
590 lines • 23.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.collectVariablesInValue = exports.argumentsFromAST = exports.isValidLeafValue = exports.valueFromASTUntyped = exports.valueFromAST = exports.isValidValueApplication = exports.isValidValue = exports.valueToAST = exports.valueNodeToConstValueNode = exports.withDefaultValues = exports.argumentsEquals = exports.valueEquals = exports.valueToString = void 0;
const definitions_1 = require("./definitions");
const graphql_1 = require("graphql");
const suggestions_1 = require("./suggestions");
const util_1 = require("util");
const types_1 = require("./types");
const utils_1 = require("./utils");
const error_1 = require("./error");
const MAX_INT = 2147483647;
const MIN_INT = -2147483648;
function valueToString(v, expectedType) {
    if (v === undefined || v === null) {
        return "null";
    }
    if (expectedType && (0, definitions_1.isNonNullType)(expectedType)) {
        return valueToString(v, expectedType.ofType);
    }
    if (expectedType && (0, definitions_1.isCustomScalarType)(expectedType)) {
        expectedType = undefined;
    }
    if ((0, definitions_1.isVariable)(v)) {
        return v.toString();
    }
    if (Array.isArray(v)) {
        let elementsType = undefined;
        if (expectedType && (0, definitions_1.isListType)(expectedType)) {
            elementsType = expectedType.ofType;
        }
        return '[' + v.map(e => valueToString(e, elementsType)).join(', ') + ']';
    }
    if (expectedType && (0, definitions_1.isListType)(expectedType)) {
        return valueToString(v, expectedType.ofType);
    }
    if (typeof v === 'object') {
        if (expectedType && !(0, definitions_1.isInputObjectType)(expectedType)) {
            expectedType = undefined;
        }
        return '{' + Object.keys(v).map(k => {
            var _a;
            const valueType = expectedType ? (_a = expectedType.field(k)) === null || _a === void 0 ? void 0 : _a.type : undefined;
            return `${k}: ${valueToString(v[k], valueType)}`;
        }).join(', ') + '}';
    }
    if (typeof v === 'string') {
        if (expectedType) {
            if ((0, definitions_1.isEnumType)(expectedType)) {
                return expectedType.value(v) ? v : JSON.stringify(v);
            }
            if (expectedType === expectedType.schema().idType() && integerStringRegExp.test(v)) {
                return v;
            }
        }
        return JSON.stringify(v);
    }
    return String(v);
}
exports.valueToString = valueToString;
function valueEquals(a, b) {
    if (a === b) {
        return true;
    }
    if (Array.isArray(a)) {
        return Array.isArray(b) && arrayValueEquals(a, b);
    }
    if (a !== null && typeof a === 'object') {
        return b !== null && typeof b === 'object' && objectEquals(a, b);
    }
    return a === b;
}
exports.valueEquals = valueEquals;
function arrayValueEquals(a, b) {
    if (a.length !== b.length) {
        return false;
    }
    for (let i = 0; i < a.length; ++i) {
        if (!valueEquals(a[i], b[i])) {
            return false;
        }
    }
    return true;
}
function objectEquals(a, b) {
    const keys1 = Object.keys(a);
    const keys2 = Object.keys(b);
    if (keys1.length != keys2.length) {
        return false;
    }
    for (const key of keys1) {
        const v1 = a[key];
        const v2 = b[key];
        if (v2 === undefined && !keys2.includes(key)) {
            return false;
        }
        if (!valueEquals(v1, v2)) {
            return false;
        }
    }
    return true;
}
function argumentsEquals(args1, args2) {
    if (args1 === args2) {
        return true;
    }
    return objectEquals(args1, args2);
}
exports.argumentsEquals = argumentsEquals;
function buildError(message) {
    return new Error(message);
}
function applyDefaultValues(value, type) {
    if ((0, definitions_1.isVariable)(value)) {
        return value;
    }
    if (value === null) {
        if ((0, definitions_1.isNonNullType)(type)) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Invalid null value for non-null type ${type} while computing default values`);
        }
        return null;
    }
    if ((0, definitions_1.isNonNullType)(type)) {
        return applyDefaultValues(value, type.ofType);
    }
    if ((0, definitions_1.isListType)(type)) {
        if (Array.isArray(value)) {
            return value.map(v => applyDefaultValues(v, type.ofType));
        }
        else {
            return applyDefaultValues(value, type.ofType);
        }
    }
    if ((0, definitions_1.isInputObjectType)(type)) {
        if (typeof value !== 'object') {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Expected value for type ${type} to be an object, but is ${typeof value}.`);
        }
        const updated = Object.create(null);
        for (const field of type.fields()) {
            if (!field.type) {
                throw buildError(`Cannot compute default value for field ${field.name} of ${type} as the field type is undefined`);
            }
            const fieldValue = value[field.name];
            if (fieldValue === undefined) {
                if (field.defaultValue !== undefined) {
                    updated[field.name] = applyDefaultValues(field.defaultValue, field.type);
                }
                else if (!(0, definitions_1.isNonNullType)(field.type)) {
                    updated[field.name] = null;
                }
                else {
                    throw error_1.ERRORS.INVALID_GRAPHQL.err(`Required field "${field.name}" of type ${type} was not provided.`);
                }
            }
            else {
                updated[field.name] = applyDefaultValues(fieldValue, field.type);
            }
        }
        for (const fieldName of Object.keys(value)) {
            if (!type.field(fieldName)) {
                const suggestions = (0, suggestions_1.suggestionList)(fieldName, type.fields().map(f => f.name));
                throw error_1.ERRORS.INVALID_GRAPHQL.err(`Field "${fieldName}" is not defined by type "${type}".` + (0, suggestions_1.didYouMean)(suggestions));
            }
        }
        return updated;
    }
    return value;
}
function withDefaultValues(value, argument) {
    if (!argument.type) {
        throw buildError(`Cannot compute default value for argument ${argument} as the type is undefined`);
    }
    if (value === undefined) {
        if (argument.defaultValue !== undefined) {
            return applyDefaultValues(argument.defaultValue, argument.type);
        }
        else if (!(0, definitions_1.isNonNullType)(argument.type)) {
            return null;
        }
        else {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Required argument "${argument.coordinate}" was not provided.`);
        }
    }
    return applyDefaultValues(value, argument.type);
}
exports.withDefaultValues = withDefaultValues;
const integerStringRegExp = /^-?(?:0|[1-9][0-9]*)$/;
function objectFieldNodeToConst(field) {
    return { ...field, value: valueNodeToConstValueNode(field.value) };
}
function valueNodeToConstValueNode(value) {
    if (value.kind === graphql_1.Kind.NULL
        || value.kind === graphql_1.Kind.INT
        || value.kind === graphql_1.Kind.FLOAT
        || value.kind === graphql_1.Kind.STRING
        || value.kind === graphql_1.Kind.BOOLEAN
        || value.kind === graphql_1.Kind.ENUM) {
        return value;
    }
    if (value.kind === graphql_1.Kind.LIST) {
        const constValues = value.values.map(v => valueNodeToConstValueNode(v));
        return { ...value, values: constValues };
    }
    if (value.kind === graphql_1.Kind.OBJECT) {
        const constFields = value.fields.map(f => objectFieldNodeToConst(f));
        return { ...value, fields: constFields };
    }
    if (value.kind === graphql_1.Kind.VARIABLE) {
        throw new Error('Unexpected VariableNode in const AST');
    }
    (0, utils_1.assertUnreachable)(value);
}
exports.valueNodeToConstValueNode = valueNodeToConstValueNode;
function valueToAST(value, type) {
    if (value === undefined) {
        return undefined;
    }
    if ((0, definitions_1.isNonNullType)(type)) {
        const astValue = valueToAST(value, type.ofType);
        if ((astValue === null || astValue === void 0 ? void 0 : astValue.kind) === graphql_1.Kind.NULL) {
            throw buildError(`Invalid null value ${valueToString(value)} for non-null type ${type}`);
        }
        return astValue;
    }
    if (value === null) {
        return { kind: graphql_1.Kind.NULL };
    }
    if ((0, definitions_1.isVariable)(value)) {
        return { kind: graphql_1.Kind.VARIABLE, name: { kind: graphql_1.Kind.NAME, value: value.name } };
    }
    if ((0, definitions_1.isCustomScalarType)(type)) {
        return valueToASTUntyped(value);
    }
    if ((0, definitions_1.isListType)(type)) {
        const itemType = type.ofType;
        const items = Array.from(value);
        if (items != null) {
            const valuesNodes = [];
            for (const item of items) {
                const itemNode = valueToAST(item, itemType);
                if (itemNode != null) {
                    valuesNodes.push(itemNode);
                }
            }
            return { kind: graphql_1.Kind.LIST, values: valuesNodes };
        }
        return valueToAST(value, itemType);
    }
    if ((0, definitions_1.isInputObjectType)(type)) {
        if (typeof value !== 'object') {
            throw buildError(`Invalid non-objet value for input type ${type}, cannot be converted to AST: ${(0, util_1.inspect)(value, true, 10, true)}`);
        }
        const fieldNodes = [];
        for (const field of type.fields()) {
            if (!field.type) {
                throw buildError(`Cannot convert value ${valueToString(value)} as field ${field} has no type set`);
            }
            const fieldValue = valueToAST(value[field.name], field.type);
            if (fieldValue) {
                fieldNodes.push({
                    kind: graphql_1.Kind.OBJECT_FIELD,
                    name: { kind: graphql_1.Kind.NAME, value: field.name },
                    value: fieldValue,
                });
            }
        }
        return { kind: graphql_1.Kind.OBJECT, fields: fieldNodes };
    }
    if (typeof value === 'boolean') {
        return { kind: graphql_1.Kind.BOOLEAN, value: value };
    }
    if (typeof value === 'number' && isFinite(value)) {
        const stringNum = String(value);
        return integerStringRegExp.test(stringNum)
            ? { kind: graphql_1.Kind.INT, value: stringNum }
            : { kind: graphql_1.Kind.FLOAT, value: stringNum };
    }
    if (typeof value === 'string') {
        if ((0, definitions_1.isEnumType)(type)) {
            return { kind: graphql_1.Kind.ENUM, value: value };
        }
        if (type === type.schema().idType() && integerStringRegExp.test(value)) {
            return { kind: graphql_1.Kind.INT, value: value };
        }
        return {
            kind: graphql_1.Kind.STRING,
            value: value,
        };
    }
    throw buildError(`Invalid value for type ${type}, cannot be converted to AST: ${(0, util_1.inspect)(value)}`);
}
exports.valueToAST = valueToAST;
function valueToASTUntyped(value) {
    if (value === undefined) {
        return undefined;
    }
    if (value === null) {
        return { kind: graphql_1.Kind.NULL };
    }
    if ((0, definitions_1.isVariable)(value)) {
        return { kind: graphql_1.Kind.VARIABLE, name: { kind: graphql_1.Kind.NAME, value: value.name } };
    }
    if (Array.isArray(value)) {
        const valuesNodes = [];
        for (const item of value) {
            const itemNode = valueToASTUntyped(item);
            if (itemNode !== undefined) {
                valuesNodes.push(itemNode);
            }
        }
        return { kind: graphql_1.Kind.LIST, values: valuesNodes };
    }
    if (typeof value === 'object') {
        const fieldNodes = [];
        for (const key of Object.keys(value)) {
            const fieldValue = valueToASTUntyped(value[key]);
            if (fieldValue) {
                fieldNodes.push({
                    kind: graphql_1.Kind.OBJECT_FIELD,
                    name: { kind: graphql_1.Kind.NAME, value: key },
                    value: fieldValue,
                });
            }
        }
        return { kind: graphql_1.Kind.OBJECT, fields: fieldNodes };
    }
    if (typeof value === 'boolean') {
        return { kind: graphql_1.Kind.BOOLEAN, value: value };
    }
    if (typeof value === 'number' && isFinite(value)) {
        const stringNum = String(value);
        return integerStringRegExp.test(stringNum)
            ? { kind: graphql_1.Kind.INT, value: stringNum }
            : { kind: graphql_1.Kind.FLOAT, value: stringNum };
    }
    if (typeof value === 'string') {
        return { kind: graphql_1.Kind.STRING, value: value };
    }
    throw buildError(`Invalid value, cannot be converted to AST: ${(0, util_1.inspect)(value, true, 10, true)}`);
}
function isValidVariable(variable, locationType, locationDefault) {
    const variableType = variable.type;
    if ((0, definitions_1.isNonNullType)(locationType) && !(0, definitions_1.isNonNullType)(variableType)) {
        const hasVariableDefault = variable.defaultValue !== undefined && variable.defaultValue !== null;
        const hasLocationDefault = locationDefault !== undefined;
        if (!hasVariableDefault && !hasLocationDefault) {
            return false;
        }
        return areTypesCompatible(variableType, locationType.ofType);
    }
    return areTypesCompatible(variableType, locationType);
}
function areTypesCompatible(variableType, locationType) {
    if ((0, definitions_1.isNonNullType)(locationType)) {
        if (!(0, definitions_1.isNonNullType)(variableType)) {
            return false;
        }
        return areTypesCompatible(variableType.ofType, locationType.ofType);
    }
    if ((0, definitions_1.isNonNullType)(variableType)) {
        return areTypesCompatible(variableType.ofType, locationType);
    }
    if ((0, definitions_1.isListType)(locationType)) {
        if (!(0, definitions_1.isListType)(variableType)) {
            return false;
        }
        return areTypesCompatible(variableType.ofType, locationType.ofType);
    }
    return !(0, definitions_1.isListType)(variableType) && (0, types_1.sameType)(variableType, locationType);
}
function isValidValue(value, argument, variableDefinitions) {
    return isValidValueApplication(value, argument.type, argument.defaultValue, variableDefinitions);
}
exports.isValidValue = isValidValue;
function isValidValueApplication(value, locationType, locationDefault, variableDefinitions) {
    if ((0, definitions_1.isVariable)(value)) {
        const definition = variableDefinitions.definition(value);
        return !!definition && isValidVariable(definition, locationType, locationDefault);
    }
    if ((0, definitions_1.isNonNullType)(locationType)) {
        return value !== null && isValidValueApplication(value, locationType.ofType, undefined, variableDefinitions);
    }
    if (value === null || value === undefined) {
        return true;
    }
    if ((0, definitions_1.isListType)(locationType)) {
        const itemType = locationType.ofType;
        if (Array.isArray(value)) {
            return value.every(item => isValidValueApplication(item, itemType, undefined, variableDefinitions));
        }
        return isValidValueApplication(value, itemType, locationDefault, variableDefinitions);
    }
    if ((0, definitions_1.isInputObjectType)(locationType)) {
        if (typeof value !== 'object') {
            return false;
        }
        const valueKeys = new Set(Object.keys(value));
        const fieldsAreValid = locationType.fields().every(field => {
            valueKeys.delete(field.name);
            return isValidValueApplication(value[field.name], field.type, field.defaultValue, variableDefinitions);
        });
        const hasUnexpectedField = valueKeys.size !== 0;
        return fieldsAreValid && !hasUnexpectedField;
    }
    return isValidLeafValue(locationType.schema(), value, locationType);
}
exports.isValidValueApplication = isValidValueApplication;
function valueFromAST(node, expectedType) {
    if (node.kind === graphql_1.Kind.NULL) {
        if ((0, definitions_1.isNonNullType)(expectedType)) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Invalid null value for non-null type "${expectedType}"`);
        }
        return null;
    }
    if (node.kind === graphql_1.Kind.VARIABLE) {
        return new definitions_1.Variable(node.name.value);
    }
    if ((0, definitions_1.isNonNullType)(expectedType)) {
        expectedType = expectedType.ofType;
    }
    if ((0, definitions_1.isListType)(expectedType)) {
        const baseType = expectedType.ofType;
        if (node.kind === graphql_1.Kind.LIST) {
            return node.values.map(v => valueFromAST(v, baseType));
        }
        return [valueFromAST(node, baseType)];
    }
    if ((0, definitions_1.isIntType)(expectedType)) {
        if (node.kind !== graphql_1.Kind.INT) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Int cannot represent non-integer value ${(0, graphql_1.print)(node)}.`);
        }
        const i = parseInt(node.value, 10);
        if (i > MAX_INT || i < MIN_INT) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Int cannot represent non 32-bit signed integer value ${i}.`);
        }
        return i;
    }
    if ((0, definitions_1.isFloatType)(expectedType)) {
        let parsed;
        if (node.kind === graphql_1.Kind.INT) {
            parsed = parseInt(node.value, 10);
        }
        else if (node.kind === graphql_1.Kind.FLOAT) {
            parsed = parseFloat(node.value);
        }
        else {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Float can only represent integer or float value, but got a ${node.kind}.`);
        }
        if (!isFinite(parsed)) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Float cannot represent non numeric value ${parsed}.`);
        }
        return parsed;
    }
    if ((0, definitions_1.isBooleanType)(expectedType)) {
        if (node.kind !== graphql_1.Kind.BOOLEAN) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Boolean cannot represent a non boolean value ${(0, graphql_1.print)(node)}.`);
        }
        return node.value;
    }
    if ((0, definitions_1.isStringType)(expectedType)) {
        if (node.kind !== graphql_1.Kind.STRING) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`String cannot represent non string value ${(0, graphql_1.print)(node)}.`);
        }
        return node.value;
    }
    if ((0, definitions_1.isIDType)(expectedType)) {
        if (node.kind !== graphql_1.Kind.STRING && node.kind !== graphql_1.Kind.INT) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`ID cannot represent value ${(0, graphql_1.print)(node)}.`);
        }
        return node.value;
    }
    if ((0, definitions_1.isScalarType)(expectedType)) {
        return valueFromASTUntyped(node);
    }
    if ((0, definitions_1.isInputObjectType)(expectedType)) {
        if (node.kind !== graphql_1.Kind.OBJECT) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Input Object Type ${expectedType} cannot represent non-object value ${(0, graphql_1.print)(node)}.`);
        }
        const obj = Object.create(null);
        for (const f of node.fields) {
            const name = f.name.value;
            const field = expectedType.field(name);
            if (!field) {
                throw error_1.ERRORS.INVALID_GRAPHQL.err(`Unknown field "${name}" found in value for Input Object Type "${expectedType}".`);
            }
            obj[name] = valueFromAST(f.value, field.type);
        }
        return obj;
    }
    if ((0, definitions_1.isEnumType)(expectedType)) {
        if (node.kind !== graphql_1.Kind.STRING && node.kind !== graphql_1.Kind.ENUM) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Enum Type ${expectedType} cannot represent value ${(0, graphql_1.print)(node)}.`);
        }
        if (!expectedType.value(node.value)) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Enum Type ${expectedType} has no value ${node.value}.`);
        }
        return node.value;
    }
    (0, utils_1.assert)(false, () => `Unexpected input type ${expectedType} of kind ${expectedType.kind}.`);
}
exports.valueFromAST = valueFromAST;
function valueFromASTUntyped(node) {
    switch (node.kind) {
        case graphql_1.Kind.NULL:
            return null;
        case graphql_1.Kind.INT:
            return parseInt(node.value, 10);
        case graphql_1.Kind.FLOAT:
            return parseFloat(node.value);
        case graphql_1.Kind.STRING:
        case graphql_1.Kind.ENUM:
        case graphql_1.Kind.BOOLEAN:
            return node.value;
        case graphql_1.Kind.LIST:
            return node.values.map(valueFromASTUntyped);
        case graphql_1.Kind.OBJECT:
            const obj = Object.create(null);
            node.fields.forEach(f => obj[f.name.value] = valueFromASTUntyped(f.value));
            return obj;
        case graphql_1.Kind.VARIABLE:
            return new definitions_1.Variable(node.name.value);
    }
}
exports.valueFromASTUntyped = valueFromASTUntyped;
function isValidLeafValue(schema, value, type) {
    if ((0, definitions_1.isCustomScalarType)(type)) {
        return true;
    }
    if (typeof value === 'boolean') {
        return type === schema.booleanType();
    }
    if (typeof value === 'number' && isFinite(value)) {
        const stringNum = String(value);
        if (type === schema.intType() || type === schema.idType()) {
            return integerStringRegExp.test(stringNum);
        }
        return type === schema.floatType();
    }
    if (typeof value === 'string') {
        if ((0, definitions_1.isEnumType)(type)) {
            return type.value(value) !== undefined;
        }
        return type !== schema.booleanType()
            && type !== schema.intType()
            && type !== schema.floatType();
    }
    return false;
}
exports.isValidLeafValue = isValidLeafValue;
function argumentsFromAST(context, args, argsDefiner) {
    var _a;
    if (!args || args.length === 0) {
        return undefined;
    }
    const values = Object.create(null);
    for (const argNode of args) {
        const name = argNode.name.value;
        const expectedType = (_a = argsDefiner.argument(name)) === null || _a === void 0 ? void 0 : _a.type;
        if (!expectedType) {
            throw error_1.ERRORS.INVALID_GRAPHQL.err(`Unknown argument "${name}" found in value: "${context}" has no argument named "${name}"`);
        }
        try {
            values[name] = valueFromAST(argNode.value, expectedType);
        }
        catch (e) {
            if (e instanceof graphql_1.GraphQLError) {
                throw error_1.ERRORS.INVALID_GRAPHQL.err(`Invalid value for argument "${name}": ${e.message}`);
            }
            throw e;
        }
    }
    return values;
}
exports.argumentsFromAST = argumentsFromAST;
function collectVariablesInValue(value, collector) {
    if ((0, definitions_1.isVariable)(value)) {
        collector.add(value);
        return;
    }
    if (!value) {
        return;
    }
    if (Array.isArray(value)) {
        value.forEach(v => collectVariablesInValue(v, collector));
    }
    if (typeof value === 'object') {
        Object.keys(value).forEach(k => collectVariablesInValue(value[k], collector));
    }
}
exports.collectVariablesInValue = collectVariablesInValue;
//# sourceMappingURL=values.js.map