egregious 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in egregious.gemspec
4
+ gemspec
5
+
data/README ADDED
@@ -0,0 +1,47 @@
1
+ Egregious is a rails based exception handling gem for well defined http exception handling for json, xml and html.
2
+
3
+ If you have a json or xml api into your rails application, you probably have added your own exception handling to map
4
+ exceptions to a http status and formatting your json and xml output. We did to. We were tired of doing it over and
5
+ over. We decided to create egregious. One of the major goals is to start providing a more consistent api error
6
+ experience for all rails applications. As of the creation of egregious the behavior of rails was to return html when an
7
+ exception is thrown with the status code of 500. With egregious proper json and html of the error will be returned
8
+ with a good default mapping of exceptions to http status codes. This allows api developers to respond to the status
9
+ code properly, instead of scratching their head with 500's coming back all the time. If the problem was yours then the
10
+ result codes are in the 300 range. If the problem was the server then the status codes are in the 500's. With the
11
+ message and exception type providing more context information.
12
+
13
+ What egregious can do:
14
+
15
+ * Defines default exception handling for most common ruby, rails, warden and cancan exceptions.
16
+ (warden and cancan are optional)
17
+ * Catches defined exceptions using a rescue_with returning the status code defined for each exception
18
+ and well structured json, xml
19
+ * For html production requests attempts to load the html error pages for the mapped status code,
20
+ falling back to the 500.html page.
21
+ * Defines exceptions for all http status codes allowing you to throw these exceptions anywhere in your code.
22
+ * Allows you to change the exception mapping to fit your needs, adding exceptions and changing status mapping.
23
+
24
+
25
+ REQUIRES:
26
+ Rails 3.x
27
+
28
+ USAGE:
29
+ 1) Add to your Gemfile:
30
+ gem 'egregious'
31
+
32
+ 2) In your ApplicationController add:
33
+ include Egregious
34
+
35
+ Optionally, to configure create an initializer and add:
36
+
37
+ Egregious.exception_codes.merge!({NameError => :bad_request})
38
+
39
+ This will either add a new mapping or replace the existing mapping to a new status code.
40
+ You can pass the status code as a symbol, integer or string.
41
+
42
+ In your code if you want to send an error back just throw an exception. For example:
43
+
44
+ raise Egregious::BadRequest.new("You can not created an order without a customer.") unless customer_id
45
+
46
+ All the http status codes have exception classes named after them in the Egregious module. You can throw any exception,
47
+ or define your own exceptions. You can find a list in the Rack::Utils::HTTP_STATUS_CODES class.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/egregious.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "egregious/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "egregious"
7
+ s.version = Egregious::VERSION
8
+ s.authors = ["Russell Edens"]
9
+ s.email = ["rx@voomify.com"]
10
+ s.homepage = "http://github.com/voomify/egregious"
11
+ s.summary = %q{Egregious is a rails based exception handling gem for well defined http exception handling for json, xml and html}
12
+ s.description = %q{Requires Rails 3.x.}
13
+
14
+ s.rubyforge_project = "egregious"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "json"
24
+ s.add_development_dependency "hpricot"
25
+ s.add_development_dependency "rails", '~> 3.1.0'
26
+ s.add_development_dependency "rails", '~> 3.1.0'
27
+ s.add_development_dependency "warden"
28
+ s.add_development_dependency "cancan"
29
+
30
+ s.add_runtime_dependency "rack"
31
+ end
data/lib/egregious.rb ADDED
@@ -0,0 +1,159 @@
1
+ require "egregious/version"
2
+ require "egregious/extensions/exception"
3
+ require 'rack'
4
+
5
+
6
+ module Egregious
7
+
8
+ # use these exception to control the code you are throwing from you code
9
+ # all http statuses have an exception defined for them
10
+ Rack::Utils::HTTP_STATUS_CODES.each do |key, value|
11
+ class_eval "class #{value.gsub(/\s|-/,'')} < StandardError; end"
12
+ end
13
+
14
+ def self.status_code(status)
15
+ if status.is_a?(Symbol)
16
+ Rack::Utils::HTTP_STATUS_CODES[status] || 500
17
+ else
18
+ status.to_i
19
+ end
20
+ end
21
+
22
+ # internal method that loads the exception codes
23
+ def self._load_exception_codes
24
+
25
+ # by default if there is not mapping they map to 500/internal server error
26
+ exception_codes = {
27
+ SecurityError=>status_code(:forbidden)
28
+ }
29
+ # all status codes have a exception class defined
30
+ Rack::Utils::HTTP_STATUS_CODES.each do |key, value|
31
+ exception_codes.merge!(eval("Egregious::#{value.gsub(/\s|-/,'')}")=>value.downcase.gsub(/\s|-/, '_').to_sym)
32
+ end
33
+
34
+ if defined?(ActionController)
35
+ exception_codes.merge!({
36
+ AbstractController::ActionNotFound=>status_code(:bad_request),
37
+ ActionController::InvalidAuthenticityToken=>status_code(:bad_request),
38
+ ActionController::MethodNotAllowed=>status_code(:not_allowed),
39
+ ActionController::MissingFile=>status_code(:not_found),
40
+ ActionController::RoutingError=>status_code(:bad_request),
41
+ ActionController::UnknownController=>status_code(:bad_request),
42
+ ActionController::UnknownHttpMethod=>status_code(:not_allowed)
43
+ #ActionController::MissingTemplate=>status_code(:not_found)
44
+ })
45
+ end
46
+
47
+ if defined?(ActiveModel)
48
+ exception_codes.merge!({
49
+ ActiveModel::MissingAttributeError=>status_code(:bad_request)})
50
+ end
51
+
52
+ if defined?(ActiveRecord)
53
+ exception_codes.merge!({
54
+ ActiveRecord::AttributeAssignmentError=>status_code(:bad_request),
55
+ ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded=>status_code(:bad_request),
56
+ ActiveRecord::MultiparameterAssignmentErrors=>status_code(:bad_request),
57
+ ActiveRecord::ReadOnlyAssociation=>status_code(:forbidden),
58
+ ActiveRecord::ReadOnlyRecord=>status_code(:forbidden),
59
+ ActiveRecord::RecordInvalid=>status_code(:bad_request),
60
+ ActiveRecord::RecordNotFound=>status_code(:not_found),
61
+ ActiveRecord::UnknownAttributeError=>status_code(:bad_request)
62
+ })
63
+ end
64
+
65
+ if defined?(Warden)
66
+ exception_codes.merge!({
67
+ Warden::NotAuthenticated=>status_code(:unauthorized),
68
+ })
69
+ end
70
+
71
+ if defined?(CanCan)
72
+ # technically this should be forbidden, but for some reason cancan returns AccessDenied when you are not logged in
73
+ exception_codes.merge!({CanCan::AccessDenied=>status_code(:unauthorized)})
74
+ exception_codes.merge!({CanCan::AuthorizationNotPerformed => status_code(:unauthorized)})
75
+ end
76
+
77
+ @@exception_codes = exception_codes
78
+ end
79
+
80
+ @@exception_codes = self._load_exception_codes
81
+ @@root = defined?(Rail) ? Rails.root : nil
82
+
83
+ # exposes the root of the app
84
+ def self.root
85
+ @@root
86
+ end
87
+
88
+ # set the root directory and stack traces will be cleaned up
89
+ def self.root=(root)
90
+ @@root=root
91
+ end
92
+
93
+ # a little helper to help us clean up the backtrace
94
+ # if root is defined it removes that, for rails it takes care of that
95
+ def clean_backtrace(exception)
96
+ if backtrace = exception.backtrace
97
+ if Egregious.root
98
+ backtrace.map { |line| line.sub Egregious.root, '' }
99
+ else
100
+ backtrace
101
+ end
102
+ end
103
+ end
104
+
105
+ # this method exposes the @@exception_codes class variable
106
+ # allowing someone to re-configure the mapping. For example in a rails initializer:
107
+ # Egregious.exception_codes = {NameError => "503"} or
108
+ # If you want the default mapping and then want to modify it you should call the following:
109
+ # Egregious.exception_codes.merge!({MyCustomException=>"500"})
110
+ def self.exception_codes
111
+ @@exception_codes
112
+ end
113
+
114
+ # this method exposes the @@exception_codes class variable
115
+ # allowing someone to re-configure the mapping. For example in a rails initializer:
116
+ # Egregious.exception_codes = {NameError => "503"} or
117
+ # If you want the default mapping and then want to modify it you should call the following:
118
+ # Egregious.exception_codes.merge!({MyCustomException=>"500"})
119
+ def self.exception_codes=(exception_codes)
120
+ @@exception_codes=exception_codes
121
+ end
122
+
123
+ # this method will auto load the exception codes if they are not set
124
+ # by an external configuration call to self.exception_code already
125
+ # it is called by the status_code_for_exception method
126
+ def exception_codes
127
+ return Egregious.exception_codes
128
+ end
129
+
130
+ # this method will lookup the exception code for a given exception class
131
+ # if the exception is not in our map then it will return 500
132
+ def status_code_for_exception(exception)
133
+ self.exception_codes[exception.class] ? self.exception_codes[exception.class] : '500'
134
+ end
135
+
136
+ def egregious_exception_handler(exception)
137
+ flash.now[:alert] = exception.message
138
+ logger.fatal(
139
+ "\n\n" + exception.class.to_s + ' (' + exception.message.to_s + '):\n ' +
140
+ clean_backtrace(exception).join("\n ") +
141
+ "\n\n")
142
+ HoptoadNotifier.notify(exception) if defined?(HoptoadNotifier)
143
+ respond_to do |format|
144
+ status = status_code_for_exception(exception)
145
+ format.xml { render :xml=> exception.to_xml, :status => status }
146
+ format.json { render :json=> exception.to_json, :status => status }
147
+ # render the html page for the status we are returning it exists...if not then render the 500.html page.
148
+ format.html { render :file => File.exists?(Rails.root + '/public/' + status + '.html') ?
149
+ Rails.root + '/public/' + status + '.html' : Rails.root + '/public/500.html'}
150
+ end
151
+ end
152
+
153
+
154
+ def self.included(base)
155
+ base.class_eval do
156
+ rescue_from 'Exception' , :with => :egregious_exception_handler
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,17 @@
1
+ #
2
+ # We are opening up Exception and adding to_xml and to_json.
3
+ #
4
+ class Exception
5
+
6
+ def exception_type
7
+ self.class.to_s ? self.class.to_s.split("::").last : 'None'
8
+ end
9
+
10
+ def to_xml
11
+ "<errors><error>#{self.message}</error><type>#{self.exception_type}</type></errors>"
12
+ end
13
+
14
+ def to_json
15
+ "{\"error\":\"#{self.message.gsub(/\r/, ' ').gsub(/\n/, ' ').squeeze(' ')}\", \"type\":\"#{self.exception_type}\"}"
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Egregious
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ def rescue_from(exception, options)
4
+ end
5
+
6
+ include Egregious
7
+
8
+ describe Egregious do
9
+ describe 'clean_backtrace ' do
10
+ it "should return nil" do
11
+ clean_backtrace(Exception.new).should == nil
12
+ end
13
+
14
+ it "should return a stack trace" do
15
+ begin
16
+ raise Exception.new
17
+ rescue Exception=>exception
18
+ clean_backtrace(exception).size.should be > 0
19
+ end
20
+ end
21
+
22
+ it "should remove the beginning of the backtrace" do
23
+ begin
24
+ raise Exception.new
25
+ rescue Exception=>exception
26
+ class Rails
27
+ def self.root
28
+ __FILE__
29
+ end
30
+ end
31
+ clean_backtrace(exception)[0].should match /:\d/
32
+ end
33
+ end
34
+ end
35
+
36
+ #
37
+ # Once these codes are published and used in the gem, they should not change.
38
+ # it will break other peoples apis. This test ensures that.
39
+ # client should use a configuration file to change the mappings themselves
40
+ #
41
+ describe 'exception_codes' do
42
+
43
+ it "should return forbidden for SecurityError's'" do
44
+ exception_codes[SecurityError].should == Egregious.status_code(:forbidden)
45
+ end
46
+
47
+ if defined?(ActionController)
48
+ it "should return expected errors for ActionController" do
49
+ exception_codes[AbstractController::ActionNotFound].should == Egregious.status_code(:forbidden)
50
+ exception_codes[AbstractController::ActionNotFound].should == Egregious.status_code(:bad_request)
51
+ exception_codes[ActionController::InvalidAuthenticityToken].should == Egregious.status_code(:bad_request)
52
+ exception_codes[ActionController::MethodNotAllowed].should == Egregious.status_code(:not_allowed)
53
+ exception_codes[ActionController::MissingFile].should == Egregious.status_code(:not_found)
54
+ exception_codes[ActionController::RoutingError].should == Egregious.status_code(:bad_request)
55
+ exception_codes[ActionController::UnknownController].should == Egregious.status_code(:bad_request)
56
+ exception_codes[ActionController::UnknownHttpMethod].should == Egregious.status_code(:not_allowed)
57
+ exception_codes[ActionController::MissingTemplate].should == Egregious.status_code(:not_found)
58
+ end
59
+ end
60
+
61
+ if defined?(ActiveModel)
62
+ it "should return expected errors for ActiveModel" do
63
+ exception_codes[ActiveModel::MissingAttributeError].should == Egregious.status_code(:bad_request)
64
+ end
65
+ end
66
+
67
+ if defined?(ActiveRecord)
68
+ it "should return expected errors for ActiveRecord" do
69
+ exception_codes[ActiveRecord::AttributeAssignmentError].should == Egregious.status_code(:bad_request)
70
+ exception_codes[ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded].should == Egregious.status_code(:bad_request)
71
+ exception_codes[ActiveRecord::MultiparameterAssignmentErrors].should == Egregious.status_code(:bad_request)
72
+ exception_codes[ActiveRecord::ReadOnlyAssociation].should == Egregious.status_code(:forbidden)
73
+ exception_codes[ActiveRecord::ReadOnlyRecord].should == Egregious.status_code(:forbidden)
74
+ exception_codes[ActiveRecord::RecordInvalid].should == Egregious.status_code(:bad_request)
75
+ exception_codes[ActiveRecord::RecordNotFound].should == Egregious.status_code(:not_found)
76
+ exception_codes[ActiveRecord::UnknownAttributeError].should == Egregious.status_code(:bad_request)
77
+ end
78
+ end
79
+
80
+ if defined?(Warden)
81
+ it "should return expected errors for Warden" do
82
+ exception_codes[Warden::NotAuthenticated].should == Egregious.status_code(:unauthorized)
83
+ end
84
+ end
85
+
86
+ if defined?(CanCan)
87
+ it "should return expected errors for CanCan" do
88
+ # technically this should be forbidden, but for some reason cancan returns AccessDenied when you are not logged in
89
+ exception_codes[CanCan::AccessDenied].should == Egregious.status_code(:unauthorized)
90
+ exception_codes[CanCan::AuthorizationNotPerformed].should == Egregious.status_code(:unauthorized)
91
+ end
92
+ end
93
+ end
94
+
95
+ describe "status_code_for_exception" do
96
+ it 'should return 500 for non-mapped exceptions'do
97
+ exception_codes[Exception].should == nil
98
+ status_code_for_exception(Exception.new).should=='500'
99
+ end
100
+ it 'should allow configuration of exception codes' do
101
+ Egregious.exception_codes.merge!({NameError => "999"})
102
+ status_code_for_exception(NameError.new).should=="999"
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'json'
3
+ require 'hpricot'
4
+
5
+ describe Exception do
6
+ it "should output valid xml on to_xml" do
7
+ doc = Hpricot.XML(Exception.new("Yes").to_xml)
8
+ (doc/:errors).each do |error|
9
+ (error/:error).inner_html.should=='Yes'
10
+ (error/:type).inner_html.should=='Exception'
11
+ end
12
+ end
13
+
14
+ it "should output be valid json on to_json" do
15
+ result = JSON.parse(Exception.new("Yes").to_json)
16
+ result['error'].should == "Yes"
17
+ result['type'].should == "Exception"
18
+ end
19
+
20
+ it "should parse module names out" do
21
+ module X
22
+ module Y
23
+ class Z < Exception
24
+ end
25
+ end
26
+ end
27
+ X::Y::Z.new.exception_type.should == 'Z'
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'bundler'
4
+ Bundler.setup
5
+
6
+ ENV["RAILS_ENV"] ||= "test"
7
+ require 'active_support'
8
+ require 'active_support/test_case'
9
+ require 'warden'
10
+ require 'cancan'
11
+
12
+ require 'egregious' # and any other gems you need
13
+
14
+ RSpec.configure do |config|
15
+ # some (optional) config here
16
+ end
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: egregious
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Russell Edens
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-27 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: hpricot
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: rails
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 3
74
+ - 1
75
+ - 0
76
+ version: 3.1.0
77
+ type: :development
78
+ version_requirements: *id004
79
+ - !ruby/object:Gem::Dependency
80
+ name: rails
81
+ prerelease: false
82
+ requirement: &id005 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 3
90
+ - 1
91
+ - 0
92
+ version: 3.1.0
93
+ type: :development
94
+ version_requirements: *id005
95
+ - !ruby/object:Gem::Dependency
96
+ name: warden
97
+ prerelease: false
98
+ requirement: &id006 !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ type: :development
108
+ version_requirements: *id006
109
+ - !ruby/object:Gem::Dependency
110
+ name: cancan
111
+ prerelease: false
112
+ requirement: &id007 !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ type: :development
122
+ version_requirements: *id007
123
+ - !ruby/object:Gem::Dependency
124
+ name: rack
125
+ prerelease: false
126
+ requirement: &id008 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ hash: 3
132
+ segments:
133
+ - 0
134
+ version: "0"
135
+ type: :runtime
136
+ version_requirements: *id008
137
+ description: Requires Rails 3.x.
138
+ email:
139
+ - rx@voomify.com
140
+ executables: []
141
+
142
+ extensions: []
143
+
144
+ extra_rdoc_files: []
145
+
146
+ files:
147
+ - .gitignore
148
+ - Gemfile
149
+ - README
150
+ - Rakefile
151
+ - egregious.gemspec
152
+ - lib/egregious.rb
153
+ - lib/egregious/extensions/exception.rb
154
+ - lib/egregious/version.rb
155
+ - spec/egregious_spec.rb
156
+ - spec/exceptions_spec.rb
157
+ - spec/spec_helper.rb
158
+ has_rdoc: true
159
+ homepage: http://github.com/voomify/egregious
160
+ licenses: []
161
+
162
+ post_install_message:
163
+ rdoc_options: []
164
+
165
+ require_paths:
166
+ - lib
167
+ required_ruby_version: !ruby/object:Gem::Requirement
168
+ none: false
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ hash: 3
173
+ segments:
174
+ - 0
175
+ version: "0"
176
+ required_rubygems_version: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ hash: 3
182
+ segments:
183
+ - 0
184
+ version: "0"
185
+ requirements: []
186
+
187
+ rubyforge_project: egregious
188
+ rubygems_version: 1.5.2
189
+ signing_key:
190
+ specification_version: 3
191
+ summary: Egregious is a rails based exception handling gem for well defined http exception handling for json, xml and html
192
+ test_files:
193
+ - spec/egregious_spec.rb
194
+ - spec/exceptions_spec.rb
195
+ - spec/spec_helper.rb