egregious 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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