diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d28dbba..9f0f14e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,20 +1,48 @@ -# This configuration was generated by `rubocop --auto-gen-config` -# on 2015-01-13 18:47:14 -0500 using RuboCop version 0.28.0. +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2017-10-09 10:22:52 -0500 using RuboCop version 0.41.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 25 -# Configuration parameters: AllowURI, URISchemes. +# Offense count: 1 Metrics/AbcSize: - Max: 20 + Max: 18 -# Offense count: 7 +# Offense count: 1 +Metrics/CyclomaticComplexity: + Max: 9 + +# Offense count: 1 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes. +# URISchemes: http, https +Metrics/LineLength: + Max: 87 + +# Offense count: 1 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 18 + +# Offense count: 1 +Metrics/PerceivedComplexity: + Max: 11 + +# Offense count: 5 Style/Documentation: - Enabled: false + Exclude: + - 'spec/**/*' + - 'test/**/*' + - 'lib/grape-active_model_serializers/endpoint_extension.rb' + - 'lib/grape-active_model_serializers/error_formatter.rb' + - 'lib/grape-active_model_serializers/formatter.rb' + - 'lib/grape-active_model_serializers/options_builder.rb' + - 'lib/grape-active_model_serializers/serializer_resolver.rb' # Offense count: 2 -# Configuration parameters: Exclude. +# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts. Style/FileName: - Enabled: false + Exclude: + - 'lib/grape-active_model_serializers.rb' + - 'spec/grape-active_model_serializers_spec.rb' diff --git a/CHANGELOG.md b/CHANGELOG.md index cd75fc8..0ec0782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### 1.5.2 (Next) +* [#76](https://github.com/ruby-grape/grape-active_model_serializers/pull/76): Add custom error formatter - [@xn](https://github.com/xn). * Your contribution here. ### 1.5.1 (April 25, 2017) diff --git a/Gemfile b/Gemfile index 5ebdb78..9572bc4 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' gemspec -case version = ENV['GRAPE_VERSION'] || '~> 0.10.0' +case version = ENV['GRAPE_VERSION'] || '~> 1.0.0' when 'HEAD' gem 'grape', github: 'intridea/grape' else diff --git a/lib/grape-active_model_serializers.rb b/lib/grape-active_model_serializers.rb index e395587..3efcb34 100644 --- a/lib/grape-active_model_serializers.rb +++ b/lib/grape-active_model_serializers.rb @@ -1,6 +1,7 @@ require 'active_model_serializers' require 'grape' require 'grape-active_model_serializers/endpoint_extension' +require 'grape-active_model_serializers/error_formatter' require 'grape-active_model_serializers/formatter' require 'grape-active_model_serializers/serializer_resolver' require 'grape-active_model_serializers/options_builder' diff --git a/lib/grape-active_model_serializers/error_formatter.rb b/lib/grape-active_model_serializers/error_formatter.rb new file mode 100644 index 0000000..a071f2a --- /dev/null +++ b/lib/grape-active_model_serializers/error_formatter.rb @@ -0,0 +1,53 @@ +module Grape + module ErrorFormatter + module ActiveModelSerializers + extend Base + + class << self + def call(message, backtrace, options = {}, env = nil, original_exception = nil) + message = present(message, env) if respond_to?(:present) + message = wrap_message(message) + + rescue_options = options[:rescue_options] || {} + if rescue_options[:backtrace] && backtrace && !backtrace.empty? + message = message.merge(backtrace: backtrace) + end + if rescue_options[:original_exception] && original_exception + message = message + .merge(original_exception: original_exception.inspect) + end + if ::Grape.const_defined? :Json + ::Grape::Json.dump(message) + else + ::MultiJson.dump(message) + end + end + + private + + def wrap_message(message) + if active_model?(message) + ::ActiveModelSerializers::SerializableResource.new( + message, + serializer: ActiveModel::Serializer::ErrorSerializer + ).as_json + elsif ok_to_pass_through?(message) + message + else + { error: message } + end + end + + def active_model?(message) + message.respond_to?(:errors) && + message.errors.is_a?(ActiveModel::Errors) + end + + def ok_to_pass_through?(message) + message.is_a?(Exceptions::ValidationErrors) || + message.is_a?(Hash) + end + end + end + end +end diff --git a/spec/grape-active_model_serializers/error_formatter_spec.rb b/spec/grape-active_model_serializers/error_formatter_spec.rb new file mode 100644 index 0000000..47972f2 --- /dev/null +++ b/spec/grape-active_model_serializers/error_formatter_spec.rb @@ -0,0 +1,92 @@ +require 'spec_helper' +require 'grape-active_model_serializers/error_formatter' + +describe Grape::ErrorFormatter::ActiveModelSerializers do + subject { Grape::ErrorFormatter::ActiveModelSerializers } + let(:backtrace) { ['Line:1'] } + let(:options) { Hash.new } + let(:env) { { 'api.endpoint' => app.endpoints.first } } + let(:original_exception) { StandardError.new('oh noes!') } + + let(:app) { + Class.new(Grape::API) do |app| + app.format :json + app.formatter :jsonapi, Grape::Formatter::ActiveModelSerializers + app.error_formatter :jsonapi, Grape::ErrorFormatter::ActiveModelSerializers + + app.namespace('space') do |ns| + ns.get('/', root: false) do + error!(message) + end + end + end + } + let(:foo) { + Class.new { + include ActiveModel::Model + + attr_accessor :name + + def initialize(attributes = {}) + super + errors.add(:name, 'We don\'t like bears') + end + } + } + + before do + ActiveModel::Serializer.config.adapter = :json_api + end + + after do + ActiveModel::Serializer.config.adapter = :json + end + + describe '#call' do + context 'message is an activemodel' do + let(:message) { + foo.new(name: 'bar') + } + it 'formats the error' do + result = subject + .call(message, backtrace, options, env, original_exception) + json_hash = JSON.parse(result) + + expected_result = { + 'errors' => [ + { + 'source' => { + 'pointer' => '/data/attributes/name' + }, + 'detail' => 'We don\'t like bears' + } + ] + } + + expect(json_hash == expected_result).to eq(true) + end + end + + context 'message is hash like' do + let(:message) { { 'errors' => ['error'] } } + it 'passes the message through' do + result = subject + .call(message, backtrace, options, env, original_exception) + json_hash = JSON.parse(result) + + expect(json_hash == message).to eq(true) + end + end + + context 'message is text' do + let(:message) { 'error' } + it 'wraps the error' do + result = subject + .call(message, backtrace, options, env, original_exception) + json_hash = JSON.parse(result) + + expect(json_hash == { 'error' => message }).to eq(true) + end + end + end +end