"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
var graphql_1 = require("graphql");
var values_1 = require("graphql/execution/values");
var hasOwn = Object.prototype.hasOwnProperty;
// Abstract base class of any visitor implementation, defining the available
// visitor methods along with their parameter types, and providing a static
// helper function for determining whether a subclass implements a given
// visitor method, as opposed to inheriting one of the stubs defined here.
var SchemaVisitor = /** @class */ (function () {
    function SchemaVisitor() {
    }
    // Determine if this SchemaVisitor (sub)class implements a particular
    // visitor method.
    SchemaVisitor.implementsVisitorMethod = function (methodName) {
        if (!methodName.startsWith('visit')) {
            return false;
        }
        var method = this.prototype[methodName];
        if (typeof method !== 'function') {
            return false;
        }
        if (this === SchemaVisitor) {
            // The SchemaVisitor class implements every visitor method.
            return true;
        }
        var stub = SchemaVisitor.prototype[methodName];
        if (method === stub) {
            // If this.prototype[methodName] was just inherited from SchemaVisitor,
            // then this class does not really implement the method.
            return false;
        }
        return true;
    };
    // Concrete subclasses of SchemaVisitor should override one or more of these
    // visitor methods, in order to express their interest in handling certain
    // schema types/locations. Each method may return null to remove the given
    // type from the schema, a non-null value of the same type to update the
    // type in the schema, or nothing to leave the type as it was.
    /* tslint:disable:no-empty */
    SchemaVisitor.prototype.visitSchema = function (schema) { };
    SchemaVisitor.prototype.visitScalar = function (scalar) { };
    SchemaVisitor.prototype.visitObject = function (object) { };
    SchemaVisitor.prototype.visitFieldDefinition = function (field, details) { };
    SchemaVisitor.prototype.visitArgumentDefinition = function (argument, details) { };
    SchemaVisitor.prototype.visitInterface = function (iface) { };
    SchemaVisitor.prototype.visitUnion = function (union) { };
    SchemaVisitor.prototype.visitEnum = function (type) { };
    SchemaVisitor.prototype.visitEnumValue = function (value, details) { };
    SchemaVisitor.prototype.visitInputObject = function (object) { };
    SchemaVisitor.prototype.visitInputFieldDefinition = function (field, details) { };
    return SchemaVisitor;
}());
exports.SchemaVisitor = SchemaVisitor;
// Generic function for visiting GraphQLSchema objects.
function visitSchema(schema, 
    // To accommodate as many different visitor patterns as possible, the
    // visitSchema function does not simply accept a single instance of the
    // SchemaVisitor class, but instead accepts a function that takes the
    // current VisitableSchemaType object and the name of a visitor method and
    // returns an array of SchemaVisitor instances that implement the visitor
    // method and have an interest in handling the given VisitableSchemaType
    // object. In the simplest case, this function can always return an array
    // containing a single visitor object, without even looking at the type or
    // methodName parameters. In other cases, this function might sometimes
    // return an empty array to indicate there are no visitors that should be
    // applied to the given VisitableSchemaType object. For an example of a
    // visitor pattern that benefits from this abstraction, see the
    // SchemaDirectiveVisitor class below.
    visitorSelector) {
    // Helper function that calls visitorSelector and applies the resulting
    // visitors to the given type, with arguments [type, ...args].
    function callMethod(methodName, type) {
        var args = [];
        for (var _i = 2; _i < arguments.length; _i++) {
            args[_i - 2] = arguments[_i];
        }
        visitorSelector(type, methodName).every(function (visitor) {
            var newType = visitor[methodName].apply(visitor, [type].concat(args));
            if (typeof newType === 'undefined') {
                // Keep going without modifying type.
                return true;
            }
            if (methodName === 'visitSchema' ||
                type instanceof graphql_1.GraphQLSchema) {
                throw new Error("Method " + methodName + " cannot replace schema with " + newType);
            }
            if (newType === null) {
                // Stop the loop and return null form callMethod, which will cause
                // the type to be removed from the schema.
                type = null;
                return false;
            }
            // Update type to the new type returned by the visitor method, so that
            // later directives will see the new type, and callMethod will return
            // the final type.
            type = newType;
        });
        // If there were no directives for this type object, or if all visitor
        // methods returned nothing, type will be returned unmodified.
        return type;
    }
    // Recursive helper function that calls any appropriate visitor methods for
    // each object in the schema, then traverses the object's children (if any).
    function visit(type) {
        if (type instanceof graphql_1.GraphQLSchema) {
            // Unlike the other types, the root GraphQLSchema object cannot be
            // replaced by visitor methods, because that would make life very hard
            // for SchemaVisitor subclasses that rely on the original schema object.
            callMethod('visitSchema', type);
            updateEachKey(type.getTypeMap(), function (namedType, typeName) {
                if (!typeName.startsWith('__')) {
                    // Call visit recursively to let it determine which concrete
                    // subclass of GraphQLNamedType we found in the type map. Because
                    // we're using updateEachKey, the result of visit(namedType) may
                    // cause the type to be removed or replaced.
                    return visit(namedType);
                }
            });
            return type;
        }
        if (type instanceof graphql_1.GraphQLObjectType) {
            // Note that callMethod('visitObject', type) may not actually call any
            // methods, if there are no @directive annotations associated with this
            // type, or if this SchemaDirectiveVisitor subclass does not override
            // the visitObject method.
            var newObject = callMethod('visitObject', type);
            if (newObject) {
                visitFields(newObject);
            }
            return newObject;
        }
        if (type instanceof graphql_1.GraphQLInterfaceType) {
            var newInterface = callMethod('visitInterface', type);
            if (newInterface) {
                visitFields(newInterface);
            }
            return newInterface;
        }
        if (type instanceof graphql_1.GraphQLInputObjectType) {
            var newInputObject_1 = callMethod('visitInputObject', type);
            if (newInputObject_1) {
                updateEachKey(newInputObject_1.getFields(), function (field) {
                    // Since we call a different method for input object fields, we
                    // can't reuse the visitFields function here.
                    return callMethod('visitInputFieldDefinition', field, {
                        objectType: newInputObject_1,
                    });
                });
            }
            return newInputObject_1;
        }
        if (type instanceof graphql_1.GraphQLScalarType) {
            return callMethod('visitScalar', type);
        }
        if (type instanceof graphql_1.GraphQLUnionType) {
            return callMethod('visitUnion', type);
        }
        if (type instanceof graphql_1.GraphQLEnumType) {
            var newEnum_1 = callMethod('visitEnum', type);
            if (newEnum_1) {
                updateEachKey(newEnum_1.getValues(), function (value) {
                    return callMethod('visitEnumValue', value, {
                        enumType: newEnum_1,
                    });
                });
            }
            return newEnum_1;
        }
        throw new Error("Unexpected schema type: " + type);
    }
    function visitFields(type) {
        updateEachKey(type.getFields(), function (field) {
            // It would be nice if we could call visit(field) recursively here, but
            // GraphQLField is merely a type, not a value that can be detected using
            // an instanceof check, so we have to visit the fields in this lexical
            // context, so that TypeScript can validate the call to
            // visitFieldDefinition.
            var newField = callMethod('visitFieldDefinition', field, {
                // While any field visitor needs a reference to the field object, some
                // field visitors may also need to know the enclosing (parent) type,
                // perhaps to determine if the parent is a GraphQLObjectType or a
                // GraphQLInterfaceType. To obtain a reference to the parent, a
                // visitor method can have a second parameter, which will be an object
                // with an .objectType property referring to the parent.
                objectType: type,
            });
            if (newField && newField.args) {
                updateEachKey(newField.args, function (arg) {
                    return callMethod('visitArgumentDefinition', arg, {
                        // Like visitFieldDefinition, visitArgumentDefinition takes a
                        // second parameter that provides additional context, namely the
                        // parent .field and grandparent .objectType. Remember that the
                        // current GraphQLSchema is always available via this.schema.
                        field: newField,
                        objectType: type,
                    });
                });
            }
            return newField;
        });
    }
    visit(schema);
    // Return the original schema for convenience, even though it cannot have
    // been replaced or removed by the code above.
    return schema;
}
exports.visitSchema = visitSchema;
// Update any references to named schema types that disagree with the named
// types found in schema.getTypeMap().
function healSchema(schema) {
    heal(schema);
    return schema;
    function heal(type) {
        if (type instanceof graphql_1.GraphQLSchema) {
            var originalTypeMap_1 = type.getTypeMap();
            var actualNamedTypeMap_1 = Object.create(null);
            // If any of the .name properties of the GraphQLNamedType objects in
            // schema.getTypeMap() have changed, the keys of the type map need to
            // be updated accordingly.
            each(originalTypeMap_1, function (namedType, typeName) {
                if (typeName.startsWith('__')) {
                    return;
                }
                var actualName = namedType.name;
                if (actualName.startsWith('__')) {
                    return;
                }
                if (hasOwn.call(actualNamedTypeMap_1, actualName)) {
                    throw new Error("Duplicate schema type name " + actualName);
                }
                actualNamedTypeMap_1[actualName] = namedType;
                // Note: we are deliberately leaving namedType in the schema by its
                // original name (which might be different from actualName), so that
                // references by that name can be healed.
            });
            // Now add back every named type by its actual name.
            each(actualNamedTypeMap_1, function (namedType, typeName) {
                originalTypeMap_1[typeName] = namedType;
            });
            // Directive declaration argument types can refer to named types.
            each(type.getDirectives(), function (decl) {
                if (decl.args) {
                    each(decl.args, function (arg) {
                        arg.type = healType(arg.type);
                    });
                }
            });
            each(originalTypeMap_1, function (namedType, typeName) {
                if (!typeName.startsWith('__')) {
                    heal(namedType);
                }
            });
            updateEachKey(originalTypeMap_1, function (namedType, typeName) {
                // Dangling references to renamed types should remain in the schema
                // during healing, but must be removed now, so that the following
                // invariant holds for all names: schema.getType(name).name === name
                if (!typeName.startsWith('__') &&
                    !hasOwn.call(actualNamedTypeMap_1, typeName)) {
                    return null;
                }
            });
        }
        else if (type instanceof graphql_1.GraphQLObjectType) {
            healFields(type);
            each(type.getInterfaces(), function (iface) { return heal(iface); });
        }
        else if (type instanceof graphql_1.GraphQLInterfaceType) {
            healFields(type);
        }
        else if (type instanceof graphql_1.GraphQLInputObjectType) {
            each(type.getFields(), function (field) {
                field.type = healType(field.type);
            });
        }
        else if (type instanceof graphql_1.GraphQLScalarType) {
            // Nothing to do.
        }
        else if (type instanceof graphql_1.GraphQLUnionType) {
            updateEachKey(type.getTypes(), function (t) { return healType(t); });
        }
        else if (type instanceof graphql_1.GraphQLEnumType) {
            // Nothing to do.
        }
        else {
            throw new Error("Unexpected schema type: " + type);
        }
    }
    function healFields(type) {
        each(type.getFields(), function (field) {
            field.type = healType(field.type);
            if (field.args) {
                each(field.args, function (arg) {
                    arg.type = healType(arg.type);
                });
            }
        });
    }
    function healType(type) {
        if (type instanceof graphql_1.GraphQLList ||
            type instanceof graphql_1.GraphQLNonNull) {
            // Unwrap the two known wrapper types:
            // https://github.com/graphql/graphql-js/blob/master/src/type/wrappers.js
            type.ofType = healType(type.ofType);
        }
        else if (graphql_1.isNamedType(type)) {
            // If a type annotation on a field or an argument or a union member is
            // any `GraphQLNamedType` with a `name`, then it must end up identical
            // to `schema.getType(name)`, since `schema.getTypeMap()` is the source
            // of truth for all named schema types.
            var namedType = type;
            var officialType = schema.getType(namedType.name);
            if (officialType && namedType !== officialType) {
                return officialType;
            }
        }
        return type;
    }
}
exports.healSchema = healSchema;
// This class represents a reusable implementation of a @directive that may
// appear in a GraphQL schema written in Schema Definition Language.
//
// By overriding one or more visit{Object,Union,...} methods, a subclass
// registers interest in certain schema types, such as GraphQLObjectType,
// GraphQLUnionType, etc. When SchemaDirectiveVisitor.visitSchemaDirectives is
// called with a GraphQLSchema object and a map of visitor subclasses, the
// overidden methods of those subclasses allow the visitors to obtain
// references to any type objects that have @directives attached to them,
// enabling visitors to inspect or modify the schema as appropriate.
//
// For example, if a directive called @rest(url: "...") appears after a field
// definition, a SchemaDirectiveVisitor subclass could provide meaning to that
// directive by overriding the visitFieldDefinition method (which receives a
// GraphQLField parameter), and then the body of that visitor method could
// manipulate the field's resolver function to fetch data from a REST endpoint
// described by the url argument passed to the @rest directive:
//
//   const typeDefs = `
//   type Query {
//     people: [Person] @rest(url: "/api/v1/people")
//   }`;
//
//   const schema = makeExecutableSchema({ typeDefs });
//
//   SchemaDirectiveVisitor.visitSchemaDirectives(schema, {
//     rest: class extends SchemaDirectiveVisitor {
//       public visitFieldDefinition(field: GraphQLField<any, any>) {
//         const { url } = this.args;
//         field.resolve = () => fetch(url);
//       }
//     }
//   });
//
// The subclass in this example is defined as an anonymous class expression,
// for brevity. A truly reusable SchemaDirectiveVisitor would most likely be
// defined in a library using a named class declaration, and then exported for
// consumption by other modules and packages.
//
// See below for a complete list of overridable visitor methods, their
// parameter types, and more details about the properties exposed by instances
// of the SchemaDirectiveVisitor class.
var SchemaDirectiveVisitor = /** @class */ (function (_super) {
    __extends(SchemaDirectiveVisitor, _super);
    // Mark the constructor protected to enforce passing SchemaDirectiveVisitor
    // subclasses (not instances) to visitSchemaDirectives.
    function SchemaDirectiveVisitor(config) {
        var _this = _super.call(this) || this;
        _this.name = config.name;
        _this.args = config.args;
        _this.visitedType = config.visitedType;
        _this.schema = config.schema;
        _this.context = config.context;
        return _this;
    }
    // Override this method to return a custom GraphQLDirective (or modify one
    // already present in the schema) to enforce argument types, provide default
    // argument values, or specify schema locations where this @directive may
    // appear. By default, any declaration found in the schema will be returned.
    SchemaDirectiveVisitor.getDirectiveDeclaration = function (directiveName, schema) {
        return schema.getDirective(directiveName);
    };
    // Call SchemaDirectiveVisitor.visitSchemaDirectives to visit every
    // @directive in the schema and create an appropriate SchemaDirectiveVisitor
    // instance to visit the object decorated by the @directive.
    SchemaDirectiveVisitor.visitSchemaDirectives = function (schema, directiveVisitors, 
        // Optional context object that will be available to all visitor instances
        // via this.context. Defaults to an empty null-prototype object.
        context) {
        // Optional context object that will be available to all visitor instances
        // via this.context. Defaults to an empty null-prototype object.
        if (context === void 0) { context = Object.create(null); }
        // If the schema declares any directives for public consumption, record
        // them here so that we can properly coerce arguments when/if we encounter
        // an occurrence of the directive while walking the schema below.
        var declaredDirectives = this.getDeclaredDirectives(schema, directiveVisitors);
        // Map from directive names to lists of SchemaDirectiveVisitor instances
        // created while visiting the schema.
        var createdVisitors = Object.create(null);
        Object.keys(directiveVisitors).forEach(function (directiveName) {
            createdVisitors[directiveName] = [];
        });
        function visitorSelector(type, methodName) {
            var visitors = [];
            var directiveNodes = type.astNode && type.astNode.directives;
            if (!directiveNodes) {
                return visitors;
            }
            directiveNodes.forEach(function (directiveNode) {
                var directiveName = directiveNode.name.value;
                if (!hasOwn.call(directiveVisitors, directiveName)) {
                    return;
                }
                var visitorClass = directiveVisitors[directiveName];
                // Avoid creating visitor objects if visitorClass does not override
                // the visitor method named by methodName.
                if (!visitorClass.implementsVisitorMethod(methodName)) {
                    return;
                }
                var decl = declaredDirectives[directiveName];
                var args;
                if (decl) {
                    // If this directive was explicitly declared, use the declared
                    // argument types (and any default values) to check, coerce, and/or
                    // supply default values for the given arguments.
                    args = values_1.getArgumentValues(decl, directiveNode);
                }
                else {
                    // If this directive was not explicitly declared, just convert the
                    // argument nodes to their corresponding JavaScript values.
                    args = Object.create(null);
                    directiveNode.arguments.forEach(function (arg) {
                        args[arg.name.value] = valueFromASTUntyped(arg.value);
                    });
                }
                // As foretold in comments near the top of the visitSchemaDirectives
                // method, this is where instances of the SchemaDirectiveVisitor class
                // get created and assigned names. While subclasses could override the
                // constructor method, the constructor is marked as protected, so
                // these are the only arguments that will ever be passed.
                visitors.push(new visitorClass({
                    name: directiveName,
                    args: args,
                    visitedType: type,
                    schema: schema,
                    context: context,
                }));
            });
            if (visitors.length > 0) {
                visitors.forEach(function (visitor) {
                    createdVisitors[visitor.name].push(visitor);
                });
            }
            return visitors;
        }
        visitSchema(schema, visitorSelector);
        // Automatically update any references to named schema types replaced
        // during the traversal, so implementors don't have to worry about that.
        healSchema(schema);
        return createdVisitors;
    };
    SchemaDirectiveVisitor.getDeclaredDirectives = function (schema, directiveVisitors) {
        var declaredDirectives = Object.create(null);
        each(schema.getDirectives(), function (decl) {
            declaredDirectives[decl.name] = decl;
        });
        // If the visitor subclass overrides getDirectiveDeclaration, and it
        // returns a non-null GraphQLDirective, use that instead of any directive
        // declared in the schema itself. Reasoning: if a SchemaDirectiveVisitor
        // goes to the trouble of implementing getDirectiveDeclaration, it should
        // be able to rely on that implementation.
        each(directiveVisitors, function (visitorClass, directiveName) {
            var decl = visitorClass.getDirectiveDeclaration(directiveName, schema);
            if (decl) {
                declaredDirectives[directiveName] = decl;
            }
        });
        each(declaredDirectives, function (decl, name) {
            if (!hasOwn.call(directiveVisitors, name)) {
                // SchemaDirectiveVisitors.visitSchemaDirectives might be called
                // multiple times with partial directiveVisitors maps, so it's not
                // necessarily an error for directiveVisitors to be missing an
                // implementation of a directive that was declared in the schema.
                return;
            }
            var visitorClass = directiveVisitors[name];
            each(decl.locations, function (loc) {
                var visitorMethodName = directiveLocationToVisitorMethodName(loc);
                if (SchemaVisitor.implementsVisitorMethod(visitorMethodName) &&
                    !visitorClass.implementsVisitorMethod(visitorMethodName)) {
                    // While visitor subclasses may implement extra visitor methods,
                    // it's definitely a mistake if the GraphQLDirective declares itself
                    // applicable to certain schema locations, and the visitor subclass
                    // does not implement all the corresponding methods.
                    throw new Error("SchemaDirectiveVisitor for @" + name + " must implement " + visitorMethodName + " method");
                }
            });
        });
        return declaredDirectives;
    };
    return SchemaDirectiveVisitor;
}(SchemaVisitor));
exports.SchemaDirectiveVisitor = SchemaDirectiveVisitor;
// Convert a string like "FIELD_DEFINITION" to "visitFieldDefinition".
function directiveLocationToVisitorMethodName(loc) {
    return 'visit' + loc.replace(/([^_]*)_?/g, function (wholeMatch, part) {
        return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
    });
}
function each(arrayOrObject, callback) {
    Object.keys(arrayOrObject).forEach(function (key) {
        callback(arrayOrObject[key], key);
    });
}
// A more powerful version of each that has the ability to replace or remove
// array or object keys.
function updateEachKey(arrayOrObject, 
    // The callback can return nothing to leave the key untouched, null to remove
    // the key from the array or object, or a non-null V to replace the value.
    callback) {
    var deletedCount = 0;
    Object.keys(arrayOrObject).forEach(function (key) {
        var result = callback(arrayOrObject[key], key);
        if (typeof result === 'undefined') {
            return;
        }
        if (result === null) {
            delete arrayOrObject[key];
            deletedCount++;
            return;
        }
        arrayOrObject[key] = result;
    });
    if (deletedCount > 0 && Array.isArray(arrayOrObject)) {
        // Remove any holes from the array due to deleted elements.
        arrayOrObject.splice(0).forEach(function (elem) {
            arrayOrObject.push(elem);
        });
    }
}
// Similar to the graphql-js function of the same name, slightly simplified:
// https://github.com/graphql/graphql-js/blob/master/src/utilities/valueFromASTUntyped.js
function valueFromASTUntyped(valueNode) {
    switch (valueNode.kind) {
        case graphql_1.Kind.NULL:
            return null;
        case graphql_1.Kind.INT:
            return parseInt(valueNode.value, 10);
        case graphql_1.Kind.FLOAT:
            return parseFloat(valueNode.value);
        case graphql_1.Kind.STRING:
        case graphql_1.Kind.ENUM:
        case graphql_1.Kind.BOOLEAN:
            return valueNode.value;
        case graphql_1.Kind.LIST:
            return valueNode.values.map(valueFromASTUntyped);
        case graphql_1.Kind.OBJECT:
            var obj_1 = Object.create(null);
            valueNode.fields.forEach(function (field) {
                obj_1[field.name.value] = valueFromASTUntyped(field.value);
            });
            return obj_1;
        /* istanbul ignore next */
        default:
            throw new Error('Unexpected value kind: ' + valueNode.kind);
    }
}
//# sourceMappingURL=schemaVisitor.js.map