lib/graphql/subscriptions.rb in graphql-1.8.0.pre10 vs lib/graphql/subscriptions.rb in graphql-1.8.0.pre11

- old
+ new

@@ -7,57 +7,69 @@ require "graphql/subscriptions/action_cable_subscriptions" end module GraphQL class Subscriptions + # Raised when either: + # - the triggered `event_name` doesn't match a field in the schema; or + # - one or more arguments don't match the field arguments + class InvalidTriggerError < GraphQL::Error + end + + # @see {Subscriptions#initialize} for options, concrete implementations may add options. def self.use(defn, options = {}) schema = defn.target options[:schema] = schema schema.subscriptions = self.new(options) instrumentation = Subscriptions::Instrumentation.new(schema: schema) defn.instrument(:field, instrumentation) defn.instrument(:query, instrumentation) nil end - def initialize(kwargs) - @schema = kwargs[:schema] + # @param schema [Class] the GraphQL schema this manager belongs to + def initialize(schema:, **rest) + @schema = schema end # Fetch subscriptions matching this field + arguments pair # And pass them off to the queue. # @param event_name [String] # @param args [Hash<String, Symbol => Object] # @param object [Object] # @param scope [Symbol, String] # @return [void] def trigger(event_name, args, object, scope: nil) + event_name = event_name.to_s + + # Try with the verbatim input first: field = @schema.get_field("Subscription", event_name) - if !field - raise "No subscription matching trigger: #{event_name}" - end - # Normalize symbol-keyed args to strings - if args.any? - stringified_args = {} - args.each { |k, v| stringified_args[k.to_s] = v } - args = stringified_args + if field.nil? + # And if it wasn't found, normalize it: + normalized_event_name = normalize_name(event_name) + field = @schema.get_field("Subscription", normalized_event_name) + if field.nil? + raise InvalidTriggerError, "No subscription matching trigger: #{event_name} (looked for #{@schema.subscription.graphql_name}.#{normalized_event_name})" + end + else + # Since we found a field, the original input was already normalized + normalized_event_name = event_name end + # Normalize symbol-keyed args to strings, try camelizing them + normalized_args = normalize_arguments(normalized_event_name, field, args) + event = Subscriptions::Event.new( - name: event_name, - arguments: args, + name: normalized_event_name, + arguments: normalized_args, field: field, scope: scope, ) execute_all(event, object) end - def initialize(schema:, **rest) - @schema = schema - end - # `event` was triggered on `object`, and `subscription_id` was subscribed, # so it should be updated. # # Load `subscription_id`'s GraphQL data, re-evaluate the query, and deliver the result. # @@ -136,8 +148,70 @@ end # @return [String] A new unique identifier for a subscription def build_id SecureRandom.uuid + end + + # Convert a user-provided event name or argument + # to the equivalent in GraphQL. + # + # By default, it converts the identifier to camelcase. + # Override this in a subclass to change the transformation. + # + # @param event_or_arg_name [String, Symbol] + # @return [String] + def normalize_name(event_or_arg_name) + Schema::Member::BuildType.camelize(event_or_arg_name.to_s) + end + + private + + # Recursively normalize `args` as belonging to `arg_owner`: + # - convert symbols to strings, + # - if needed, camelize the string (using {#normalize_name}) + # @param arg_owner [GraphQL::Field, GraphQL::BaseType] + # @param args [Hash, Array, Any] some GraphQL input value to coerce as `arg_owner` + # @return [Any] normalized arguments value + def normalize_arguments(event_name, arg_owner, args) + case arg_owner + when GraphQL::Field, GraphQL::InputObjectType + normalized_args = {} + missing_arg_names = [] + args.each do |k, v| + arg_name = k.to_s + arg_defn = arg_owner.arguments[arg_name] + if arg_defn + normalized_arg_name = arg_name + else + normalized_arg_name = normalize_name(arg_name) + arg_defn = arg_owner.arguments[normalized_arg_name] + end + + if arg_defn + normalized_args[normalized_arg_name] = normalize_arguments(event_name, arg_defn.type, v) + else + # Couldn't find a matching argument definition + missing_arg_names << arg_name + end + end + + if missing_arg_names.any? + arg_owner_name = if arg_owner.is_a?(GraphQL::Field) + "Subscription.#{arg_owner.name}" + else + arg_owner.to_s + end + raise InvalidTriggerError, "Can't trigger Subscription.#{event_name}, received undefined arguments: #{missing_arg_names.join(", ")}. (Should match arguments of #{arg_owner_name}.)" + end + + normalized_args + when GraphQL::ListType + args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a) } + when GraphQL::NonNullType + normalize_arguments(event_name, arg_owner.of_type, args) + else + args + end end end end