From 610b07b0035a8a1d47267a677f780d35c4b5861e Mon Sep 17 00:00:00 2001 From: Ben Roesch Date: Tue, 3 Feb 2026 16:05:40 -0500 Subject: [PATCH] Allow access to controller context/methods in exception_reporter so you can access params --- .../scimitar/application_controller.rb | 2 +- config/initializers/scimitar.rb | 4 +- .../scimitar/application_controller_spec.rb | 56 ++++++++++++------- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/app/controllers/scimitar/application_controller.rb b/app/controllers/scimitar/application_controller.rb index fb2ac35..aafba56 100644 --- a/app/controllers/scimitar/application_controller.rb +++ b/app/controllers/scimitar/application_controller.rb @@ -48,7 +48,7 @@ def handle_resource_not_found(exception) # def handle_scim_error(error_response, exception = error_response) unless Scimitar.engine_configuration.exception_reporter.nil? - Scimitar.engine_configuration.exception_reporter.call(exception) + instance_exec(exception, &Scimitar.engine_configuration.exception_reporter) end render json: error_response, status: error_response.status diff --git a/config/initializers/scimitar.rb b/config/initializers/scimitar.rb index 91559b2..c84da90 100644 --- a/config/initializers/scimitar.rb +++ b/config/initializers/scimitar.rb @@ -102,7 +102,9 @@ # JSON response to the API caller. If you want exceptions to also be # reported to a third party system such as sentry.io or raygun.com, you can # configure a Proc to do so. It is passed a Ruby exception subclass object. - # For example, a minimal sentry.io reporter might do this: + # The Proc is called via 'instance_exec' in the controller context, so you + # have access to things like 'request', 'params' and 'action_name'. For + # example, a minimal sentry.io reporter might do this: # # exception_reporter: Proc.new do | exception | # Sentry.capture_exception(exception) diff --git a/spec/controllers/scimitar/application_controller_spec.rb b/spec/controllers/scimitar/application_controller_spec.rb index 69af527..27ade1f 100644 --- a/spec/controllers/scimitar/application_controller_spec.rb +++ b/spec/controllers/scimitar/application_controller_spec.rb @@ -352,8 +352,9 @@ def authenticated? context 'with an exception reporter' do around :each do | example | original_configuration = Scimitar.engine_configuration.exception_reporter + exceptions = @exceptions = [] Scimitar.engine_configuration.exception_reporter = Proc.new do | exception | - @exception = exception + exceptions << exception end example.run() ensure @@ -364,8 +365,8 @@ def authenticated? it 'is invoked' do get :index, params: { format: :scim } - expect(@exception).to be_a(RuntimeError) - expect(@exception.message).to eql('Bang') + expect(@exceptions.first).to be_a(RuntimeError) + expect(@exceptions.first.message).to eql('Bang') end end @@ -379,8 +380,8 @@ def index it 'is invoked' do get :index, params: { format: :scim } - expect(@exception).to be_a(ActiveRecord::RecordNotFound) - expect(@exception.message).to eql('42') + expect(@exceptions.first).to be_a(ActiveRecord::RecordNotFound) + expect(@exceptions.first.message).to eql('42') end end @@ -398,8 +399,8 @@ def index it 'is invoked' do get :index, params: { format: :scim } - expect(@exception).to be_a(ActionDispatch::Http::Parameters::ParseError) - expect(@exception.message).to eql('Hello') + expect(@exceptions.first).to be_a(ActionDispatch::Http::Parameters::ParseError) + expect(@exceptions.first.message).to eql('Hello') end end @@ -412,8 +413,8 @@ def index; end request.headers['Content-Type'] = 'text/plain' get :index - expect(@exception).to be_a(Scimitar::ErrorResponse) - expect(@exception.message).to eql('Only application/scim+json type is accepted.') + expect(@exceptions.first).to be_a(Scimitar::ErrorResponse) + expect(@exceptions.first.message).to eql('Only application/scim+json type is accepted.') end end @@ -423,16 +424,16 @@ def index; end request.headers['User-Agent' ] = 'Google-Auto-Provisioning' get :index - expect(@exception).to be_a(RuntimeError) - expect(@exception.message).to eql('Bang') + expect(@exceptions.first).to be_a(RuntimeError) + expect(@exceptions.first.message).to eql('Bang') end it 'is invoked early for unrecognised agents' do request.headers['Content-Type'] = 'application/json' get :index - expect(@exception).to be_a(Scimitar::ErrorResponse) - expect(@exception.message).to eql('Only application/scim+json type is accepted.') + expect(@exceptions.first).to be_a(Scimitar::ErrorResponse) + expect(@exceptions.first.message).to eql('Only application/scim+json type is accepted.') end end # "context 'and with Google SCIM calls' do" @@ -459,8 +460,8 @@ def index; end request.headers['Content-Type'] = 'application/json+success' get :index - expect(@exception).to be_a(RuntimeError) - expect(@exception.message).to eql('Bang') + expect(@exceptions.first).to be_a(RuntimeError) + expect(@exceptions.first.message).to eql('Bang') expect(request.format == :scim).to eql(true) expect(request.headers['CONTENT_TYPE']).to eql('application/scim+json') @@ -472,8 +473,8 @@ def index; end request.headers['Content-Type'] = 'application/json+preserve' get :index - expect(@exception).to be_a(RuntimeError) - expect(@exception.message).to eql('Bang') + expect(@exceptions.first).to be_a(RuntimeError) + expect(@exceptions.first.message).to eql('Bang') expect(request.format == :html).to eql(true) expect(request.headers['CONTENT_TYPE']).to eql('application/json+preserve') @@ -485,11 +486,28 @@ def index; end request.headers['Content-Type'] = 'application/json+fail' get :index - expect(@exception).to be_a(Scimitar::ErrorResponse) - expect(@exception.message).to eql('Only application/scim+json type is accepted.') + expect(@exceptions.first).to be_a(Scimitar::ErrorResponse) + expect(@exceptions.first.message).to eql('Only application/scim+json type is accepted.') end end # "context 'returning "fail"' do" end # "context 'and with a custom request sanitizer' do" + + context 'evaluated in controller context' do + it 'has access to controller methods like request and params' do + reported_request_method = nil + reported_params = nil + Scimitar.engine_configuration.exception_reporter = Proc.new do | exception | + reported_request_method = request.method + reported_params = params + end + + get :index, params: { format: :scim, foo: 'bar' } + + expect(reported_request_method).to eql('GET') + expect(reported_params['format']).to eql('scim') + expect(reported_params['foo']).to eql('bar') + end + end end # "context 'exception reporter' do" end # "context 'error handling' do" end