Class: ElasticGraph::QueryRegistry::Registry

Inherits:
Object
  • Object
show all
Defined in:
lib/elastic_graph/query_registry/registry.rb

Overview

An abstraction that implements a registry of GraphQL queries. Callers should use ‘build_and_validate_query` to get `GraphQL::Query` objects so that clients are properly limited in what queries we execute on their behalf.

Note that this class is designed to be as efficient as possible:

  • Registered GraphQL queries are only parsed once, and then the parsed form is used each time that query is submitted. In our benchmarking, parsing of large queries can be significant, taking ~10ms or so.

  • We delay parsing a registered client’s queries until the first time that client sends us a query. That way, we don’t have to pay any parsing cost for queries that were registered by an old client that no longer sends us requests.

  • Likewise, we defer reading a client’s registered query strings off of disk until the first time it submits a request.

In addition, it’s worth noting that we support some basic “fuzzy” matching of query strings, based on the query canonicalization performed by the GraphQL gem. Semantically insignificant changes to the query string from a registered query (such as whitespace differences, or comments) are tolerated.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.build_from_directory(schema, directory, allow_unregistered_clients:, allow_any_query_for_clients:) ⇒ Object

Public factory method, which builds a ‘Registry` instance from the given directory. Subdirectories are treated as client names, and the files in them are treated as individually registered queries.



39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/elastic_graph/query_registry/registry.rb', line 39

def self.build_from_directory(schema, directory, allow_unregistered_clients:, allow_any_query_for_clients:)
  directory = Pathname.new(directory)

  new(
    schema,
    client_names: directory.children.map { |client_dir| client_dir.basename.to_s },
    allow_unregistered_clients: allow_unregistered_clients,
    allow_any_query_for_clients: allow_any_query_for_clients
  ) do |client_name|
    # Lazily read queries off of disk when we need to for a given client.
    (directory / client_name).glob("*.graphql").map { |file| ::File.read(file.to_s) }
  end
end

Instance Method Details

#build_and_validate_query(query_string, client:, variables: {}, operation_name: nil, context: {}) ⇒ Object

Builds a ‘GraphQL::Query` object for the given query string, and validates that it is an allowed query. Returns a list of registry validation errors in addition to the built query object. The list of validation errors will be empty if the query should be allowed. A query can be allowed either by virtue of being registered for usage by the given clent, or by being for a completely unregistered client (if `allow_unregistered_clients` is `true`).

This is also tolerant of some minimal differences in the query string (such as comments and whitespace). If the query differs in a significant way from a registered query, it will not be recognized as registered.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/elastic_graph/query_registry/registry.rb', line 62

def build_and_validate_query(query_string, client:, variables: {}, operation_name: nil, context: {})
  validator =
    if @registered_client_validator.applies_to?(client)
      @registered_client_validator
    else
      @unregistered_client_validator
    end

  validator.build_and_validate_query(query_string, client: client, variables: variables, operation_name: operation_name, context: context) do
    @schema.new_graphql_query(
      query_string,
      variables: variables,
      operation_name: operation_name,
      context: context
    )
  end
end