class RackGraphql::Middleware
Constants
- DEFAULT_ERROR_STATUS_CODE
- DEFAULT_STATUS_CODE
- NULL_BYTE
Attributes
app_name[R]
context_handler[R]
error_status_code_map[R]
log_exception_backtrace[R]
logger[R]
re_raise_exceptions[R]
schema[R]
Public Class Methods
new( schema:, app_name: nil, context_handler: nil, logger: nil, log_exception_backtrace: RackGraphql.log_exception_backtrace, re_raise_exceptions: false, error_status_code_map: {} )
click to toggle source
# File lib/rack_graphql/middleware.rb, line 7 def initialize( schema:, app_name: nil, context_handler: nil, logger: nil, log_exception_backtrace: RackGraphql.log_exception_backtrace, re_raise_exceptions: false, error_status_code_map: {} ) @schema = schema @app_name = app_name @context_handler = context_handler || ->(_) {} @logger = logger @log_exception_backtrace = log_exception_backtrace @re_raise_exceptions = re_raise_exceptions @error_status_code_map = error_status_code_map end
Public Instance Methods
call(env)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 26 def call(env) return [406, {}, []] unless post_request?(env) params = post_data(env) return [400, {}, []] unless params.is_a?(Hash) variables = ensure_hash(params['variables']) operation_name = params['operationName'] context = context_handler.call(env) log("Executing with params: #{params.inspect}, operationName: #{operation_name}, variables: #{variables.inspect}") result = execute(params: params, operation_name: operation_name, variables: variables, context: context) [ response_status(result), response_headers(result), [response_body(result)] ] rescue AmbiguousParamError => e exception_string = dump_exception(e) log(exception_string) env[Rack::RACK_ERRORS].puts(exception_string) env[Rack::RACK_ERRORS].flush [ 400, { 'Content-Type' => 'application/json' }, [Oj.dump({})] ] rescue StandardError, LoadError, SyntaxError => e # To respect the graphql spec, all errors need to be returned as json. # By default exceptions are not re-raised, # so they cannot be caught by error tracking rack middlewares. # You can change this behavior via `re_raise_exceptions` argument. exception_string = dump_exception(e) log(exception_string) raise e if re_raise_exceptions env[Rack::RACK_ERRORS].puts(exception_string) env[Rack::RACK_ERRORS].flush [ error_status_code_map[e.class] || DEFAULT_ERROR_STATUS_CODE, { 'Content-Type' => 'application/json' }, [Oj.dump('errors' => [exception_hash(e)])] ] ensure ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base) end
Private Instance Methods
dump_exception(exception)
click to toggle source
Based on github.com/rack/rack/blob/master/lib/rack/show_exceptions.rb
# File lib/rack_graphql/middleware.rb, line 185 def dump_exception(exception) string = "#{exception.class}: #{exception.message}\n" string << exception.backtrace.map { |l| "\t#{l}" }.join("\n") if log_exception_backtrace string end
ensure_hash(ambiguous_param)
click to toggle source
Handle form data, JSON body, or a blank value
# File lib/rack_graphql/middleware.rb, line 95 def ensure_hash(ambiguous_param) case ambiguous_param when String return {} if ambiguous_param.empty? begin ensure_hash(Oj.load(ambiguous_param)) rescue Oj::ParseError raise AmbiguousParamError, "Unexpected parameter: #{ambiguous_param}" end when Hash ambiguous_param when nil {} else fail AmbiguousParamError, "Unexpected parameter: #{ambiguous_param}" end end
exception_hash(exception)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 191 def exception_hash(exception) { 'app_name' => app_name, 'message' => "#{exception.class}: #{exception.message}", 'backtrace' => log_exception_backtrace ? exception.backtrace : "[FILTERED]" } end
execute(params:, operation_name:, variables:, context:)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 114 def execute(params:, operation_name:, variables:, context:) if valid_multiplex?(params) execute_multi(params['_json'], operation_name: operation_name, variables: variables, context: context) else execute_single(params['query'], operation_name: operation_name, variables: variables, context: context) end end
execute_multi(queries_params, operation_name:, variables:, context:)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 130 def execute_multi(queries_params, operation_name:, variables:, context:) queries = queries_params.map do |param| { query: param['query'], operation_name: operation_name, variables: variables, context: context } end schema.multiplex(queries) end
execute_single(query, operation_name:, variables:, context:)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 122 def execute_single(query, operation_name:, variables:, context:) schema.execute(query, operation_name: operation_name, variables: variables, context: context) end
log(message)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 179 def log(message) return unless logger logger.debug("[rack-graphql] #{message}") end
post_data(env)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 85 def post_data(env) payload = env['rack.input'].read.to_s return nil if payload.index(NULL_BYTE) ::Oj.load(payload) rescue Oj::ParseError nil end
post_request?(env)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 81 def post_request?(env) env['REQUEST_METHOD'] == 'POST' end
response_body(result = nil)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 159 def response_body(result = nil) if result_subscription?(result) body = result.to_h body["data"] ||= {} body["data"][result.query.operation_name] ||= nil body["data"]["subscriptionId"] = result.context[:subscription_id] elsif result.is_a?(Array) body = result.map(&:to_h) else body = result.to_h end Oj.dump(body) end
response_headers(result = nil)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 143 def response_headers(result = nil) { 'Access-Control-Expose-Headers' => 'X-Subscription-ID', 'Content-Type' => 'application/json' }.tap do |headers| headers['X-Subscription-ID'] = result.context[:subscription_id] if result_subscription?(result) end end
response_status(result)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 152 def response_status(result) return DEFAULT_STATUS_CODE if result.is_a?(Array) errors = result.to_h["errors"] || [] errors.map { |e| e["http_status"] }.compact.first || DEFAULT_STATUS_CODE end
result_subscription?(result)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 173 def result_subscription?(result) return false unless result.is_a?(GraphQL::Query::Result) result.subscription? end
valid_multiplex?(params)
click to toggle source
# File lib/rack_graphql/middleware.rb, line 126 def valid_multiplex?(params) params['_json'].is_a?(Array) && params['_json'].all? { |j| j.is_a?(Hash) } end