<commit_before># frozen_string_literal: true
require "securerandom"
require "graphql/subscriptions/broadcast_analyzer"
require "graphql/subscriptions/event"
require "graphql/subscriptions/instrumentation"
require "graphql/subscriptions/serialize"
require "graphql/subscriptions/action_cable_subscriptions"
require "graphql/subscriptions/default_subscription_resolve_extension"

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

    # Raised when either:
    # - An initial subscription didn't have a value for `context[subscription_scope]`
    # - Or, an update didn't pass `.trigger(..., scope:)`
    # When raised, the initial subscription or update fails completely.
    class SubscriptionScopeMissingError < GraphQL::Error
    end

    # @see {Subscriptions#initialize} for options, concrete implementations may add options.
    def self.use(defn, options = {})
      schema = defn.is_a?(Class) ? defn : defn.target

      if schema.subscriptions(inherited: false)
        raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}"
      end

      instrumentation = Subscriptions::Instrumentation.new(schema: schema)
      defn.instrument(:query, instrumentation)
      options[:schema] = schema
      schema.subscriptions = self.new(**options)
      schema.add_subscription_extension_if_necessary
      nil
    end

    # @param schema [Class] the GraphQL schema this manager belongs to
    # @param validate_update [Boolean] If false, then validation is skipped when executing updates
    def initialize(schema:, validate_update: true, broadcast: false, default_broadcastable: false, **rest)
      if broadcast
        schema.query_analyzer(Subscriptions::BroadcastAnalyzer)
      end
      @default_broadcastable = default_broadcastable
      @schema = schema
      @validate_update = validate_update
    end

    # @return [Boolean] Used when fields don't have `broadcastable:` explicitly set
    attr_reader :default_broadcastable

    # 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]
    # @param context [Hash]
    # @return [void]
    def trigger(event_name, args, object, scope: nil, context: {})
      # Make something as context-like as possible, even though there isn't a current query:
      dummy_query = GraphQL::Query.new(@schema, "", validate: false, context: context)
      context = dummy_query.context
      event_name = event_name.to_s

      # Try with the verbatim input first:
      field = @schema.get_field(@schema.subscription, event_name, context)

      if field.nil?
        # And if it wasn't found, normalize it:
        normalized_event_name = normalize_name(event_name)
        field = @schema.get_field(@schema.subscription, normalized_event_name, context)
        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
      # Should this accept a real context somehow?
      normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext)

      event = Subscriptions::Event.new(
        name: normalized_event_name,
        arguments: normalized_args,
        field: field,
        scope: scope,
      )
      execute_all(event, object)
    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 return the result.
    #
    # @param subscription_id [String]
    # @param event [GraphQL::Subscriptions::Event] The event which was triggered
    # @param object [Object] The value for the subscription field
    # @return [GraphQL::Query::Result]
    def execute_update(subscription_id, event, object)
      # Lookup the saved data for this subscription
      query_data = read_subscription(subscription_id)
      if query_data.nil?
        delete_subscription(subscription_id)
        return nil
      end

      # Fetch the required keys from the saved data
      query_string = query_data.fetch(:query_string)
      variables = query_data.fetch(:variables)
      context = query_data.fetch(:context)
      operation_name = query_data.fetch(:operation_name)
      execute_options = {
        query: query_string,
        context: context,
        subscription_topic: event.topic,
        operation_name: operation_name,
        variables: variables,
        root_value: object,
      }
      execute_options[:validate] = validate_update?(**execute_options)
      result = @schema.execute(**execute_options)
      subscriptions_context = result.context.namespace(:subscriptions)
      if subscriptions_context[:no_update]
        result = nil
      end

      unsubscribed = subscriptions_context[:unsubscribed]

      if unsubscribed
        # `unsubscribe` was called, clean up on our side
        # TODO also send `{more: false}` to client?<commit_msg>Pass and merge subscription's event and query ctxs<commit_after># frozen_string_literal: true
require "securerandom"
require "graphql/subscriptions/broadcast_analyzer"
require "graphql/subscriptions/event"
require "graphql/subscriptions/instrumentation"
require "graphql/subscriptions/serialize"
require "graphql/subscriptions/action_cable_subscriptions"
require "graphql/subscriptions/default_subscription_resolve_extension"

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

    # Raised when either:
    # - An initial subscription didn't have a value for `context[subscription_scope]`
    # - Or, an update didn't pass `.trigger(..., scope:)`
    # When raised, the initial subscription or update fails completely.
    class SubscriptionScopeMissingError < GraphQL::Error
    end

    # @see {Subscriptions#initialize} for options, concrete implementations may add options.
    def self.use(defn, options = {})
      schema = defn.is_a?(Class) ? defn : defn.target

      if schema.subscriptions(inherited: false)
        raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}"
      end

      instrumentation = Subscriptions::Instrumentation.new(schema: schema)
      defn.instrument(:query, instrumentation)
      options[:schema] = schema
      schema.subscriptions = self.new(**options)
      schema.add_subscription_extension_if_necessary
      nil
    end

    # @param schema [Class] the GraphQL schema this manager belongs to
    # @param validate_update [Boolean] If false, then validation is skipped when executing updates
    def initialize(schema:, validate_update: true, broadcast: false, default_broadcastable: false, **rest)
      if broadcast
        schema.query_analyzer(Subscriptions::BroadcastAnalyzer)
      end
      @default_broadcastable = default_broadcastable
      @schema = schema
      @validate_update = validate_update
    end

    # @return [Boolean] Used when fields don't have `broadcastable:` explicitly set
    attr_reader :default_broadcastable

    # 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]
    # @param context [Hash]
    # @return [void]
    def trigger(event_name, args, object, scope: nil, context: {})
      # Make something as context-like as possible, even though there isn't a current query:
      dummy_query = GraphQL::Query.new(@schema, "", validate: false, context: context)
      context = dummy_query.context
      event_name = event_name.to_s

      # Try with the verbatim input first:
      field = @schema.get_field(@schema.subscription, event_name, context)

      if field.nil?
        # And if it wasn't found, normalize it:
        normalized_event_name = normalize_name(event_name)
        field = @schema.get_field(@schema.subscription, normalized_event_name, context)
        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
      # Should this accept a real context somehow?
      normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext)

      event = Subscriptions::Event.new(
        name: normalized_event_name,
        arguments: normalized_args,
        field: field,
        scope: scope,
        context: context,
      )
      execute_all(event, object)
    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 return the result.
    #
    # @param subscription_id [String]
    # @param event [GraphQL::Subscriptions::Event] The event which was triggered
    # @param object [Object] The value for the subscription field
    # @return [GraphQL::Query::Result]
    def execute_update(subscription_id, event, object)
      # Lookup the saved data for this subscription
      query_data = read_subscription(subscription_id)
      if query_data.nil?
        delete_subscription(subscription_id)
        return nil
      end

      # Fetch the required keys from the saved data
      query_string = query_data.fetch(:query_string)
      variables = query_data.fetch(:variables)
      context = query_data.fetch(:context)
      operation_name = query_data.fetch(:operation_name)
      execute_options = {
        query: query_string,
        context: context,
        subscription_topic: event.topic,
        operation_name: operation_name,
        variables: variables,
        root_value: object,
      }
      
       # merge event's and query's context together
      context.merge!(event.context) if event.context&.present?
      
      execute_options[:validate] = validate_update?(**execute_options)
      result = @schema.execute(**execute_options)
      subscriptions_context = result.context.namespace(:subscriptions)
      if subscriptions_context[:no_update]
        result = nil
      end

      unsubscribed = subscriptions_context[:unsubscribed]

      if unsubscribed
        # `unsubscribe` was called, clean up on our side
        # TODO also send `{more: false}` to client?
{'fraction_non_alphanumeric': 0.08361233480176211, 'fraction_numerical': 0.0, 'mean_word_length': 3.404734187039193, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7292512', 'n_tokens_mistral': 3080, 'n_tokens_neox': 3042, 'n_words': 925}