Module: Axn::Extras::Strategies::Client
- Defined in:
- lib/axn/extras/strategies/client.rb
Class Method Summary collapse
- .configure(name: :client, prepend_config: nil, debug: false, user_agent: nil, error_handler: nil, **options, &block) ⇒ Object
-
.ensure_execution_context_middleware_defined ⇒ Object
Injects request/response into the action’s set_execution_context under client_strategy__last_request so exception reporting (e.g. on_exception) includes the last client request (url, method, status, etc.).
Class Method Details
.configure(name: :client, prepend_config: nil, debug: false, user_agent: nil, error_handler: nil, **options, &block) ⇒ Object
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/axn/extras/strategies/client.rb', line 50 def self.configure(name: :client, prepend_config: nil, debug: false, user_agent: nil, error_handler: nil, **, &block) # Aliasing to avoid shadowing/any confusion client_name = name error_handler_config = error_handler Module.new do extend ActiveSupport::Concern included do raise ArgumentError, "client strategy: desired client name '#{client_name}' is already taken" if method_defined?(client_name) define_method client_name do # Hydrate options that are callable (e.g. procs), so we can set e.g. per-request expiration # headers and/or other non-static values. = .transform_values do |value| value.respond_to?(:call) ? value.call : value end ::Faraday.new(**) do |conn| conn.headers["Content-Type"] = "application/json" conn.headers["User-Agent"] = user_agent || "#{client_name} / Axn Client Strategy / v#{Axn::VERSION}" # Because middleware is executed in reverse order, downstream user may need flexibility in where to inject configs prepend_config&.call(conn) # Auto-inject request/response into set_execution_context for exception reporting Client.ensure_execution_context_middleware_defined conn.use Client::ExecutionContextMiddleware, self conn.response :raise_error conn.request :url_encoded conn.request :json conn.response :json, content_type: /\bjson$/ # Enable for debugging conn.response :logger if debug # Inject error handler middleware if configured if error_handler_config && defined?(Faraday) unless Client.const_defined?(:ErrorHandlerMiddleware, false) Client.const_set(:ErrorHandlerMiddleware, Class.new(::Faraday::Middleware) do def initialize(app, config) super(app) @config = config end def call(env) @app.call(env).on_complete do |response_env| body = parse_body(response_env.body) condition = @config[:if] || -> { status != 200 } @response_env = response_env @body = body should_handle = instance_exec(&condition) handle_error(response_env, body) if should_handle end end def status @response_env&.status end attr_reader :body, :response_env private def parse_body(body) return {} if body.blank? body.is_a?(String) ? JSON.parse(body) : body rescue JSON::ParserError {} end def handle_error(response_env, body) error = extract_value(body, @config[:error_key]) details = extract_value(body, @config[:detail_key]) if @config[:detail_key] backtrace = extract_value(body, @config[:backtrace_key]) if @config[:backtrace_key] = if @config[:formatter] @config[:formatter].call(error, details, response_env) else (error, details) end prefix = "Error while #{response_env.method.to_s.upcase}ing #{response_env.url}" = .present? ? "#{prefix}: #{}" : prefix exception_class = @config[:exception_class] || ::Faraday::BadRequestError exception = exception_class.new() exception.set_backtrace(backtrace) if backtrace.present? raise exception end def extract_value(data, key) return nil if key.blank? keys = key.split(".") keys.reduce(data) do |current, k| return nil unless current.is_a?(Hash) current[k.to_s] || current[k.to_sym] end end def (error, details) parts = [] parts << error if error if details if @config[:extract_detail] extracted = if details.is_a?(Hash) details.map { |key, value| @config[:extract_detail].call(key, value) }.compact.to_sentence else Array(details).map { |node| @config[:extract_detail].call(node) }.compact.to_sentence end parts << extracted if extracted.present? elsif details.present? raise ArgumentError, "must provide extract_detail when detail_key is set and details is not a string" unless details.is_a?(String) parts << details end end parts.compact.join(" - ") end end) end conn.use Client::ErrorHandlerMiddleware, error_handler_config end block&.call(conn) end end memo client_name end end end |
.ensure_execution_context_middleware_defined ⇒ Object
Injects request/response into the action’s set_execution_context under client_strategy__last_request so exception reporting (e.g. on_exception) includes the last client request (url, method, status, etc.).
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/axn/extras/strategies/client.rb', line 10 def self.ensure_execution_context_middleware_defined return if const_defined?(:ExecutionContextMiddleware, false) const_set(:ExecutionContextMiddleware, Class.new(::Faraday::Middleware) do def initialize(app, action_instance) super(app) @action_instance = action_instance end def call(env) assign_request_context(env) @app.call(env).on_complete { |response_env| assign_response_context(env, response_env) } end private def assign_request_context(env) return unless @action_instance.respond_to?(:set_execution_context, true) @action_instance.send(:set_execution_context, client_strategy__last_request: { url: env.url.to_s, method: env.method.to_s.upcase, }) end def assign_response_context(request_env, response_env) return unless @action_instance.respond_to?(:set_execution_context, true) last_request = { url: request_env.url.to_s, method: request_env.method.to_s.upcase, status: response_env.status, } last_request[:response_content_type] = response_env.response_headers["Content-Type"] if response_env.response_headers["Content-Type"] @action_instance.send(:set_execution_context, client_strategy__last_request: last_request) end end) end |