better_errors 2.5.1 → 2.9.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +130 -0
- data/.github/workflows/release.yml +64 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -1
- data/README.md +34 -2
- data/better_errors.gemspec +3 -1
- data/gemfiles/pry010.gemfile +2 -1
- data/gemfiles/pry011.gemfile +2 -1
- data/gemfiles/pry09.gemfile +2 -1
- data/gemfiles/rack.gemfile +2 -1
- data/gemfiles/rack_boc.gemfile +2 -1
- data/gemfiles/rails42.gemfile +3 -1
- data/gemfiles/rails42_boc.gemfile +3 -1
- data/gemfiles/rails42_haml.gemfile +3 -1
- data/gemfiles/rails50.gemfile +3 -1
- data/gemfiles/rails50_boc.gemfile +3 -1
- data/gemfiles/rails50_haml.gemfile +3 -1
- data/gemfiles/rails51.gemfile +3 -1
- data/gemfiles/rails51_boc.gemfile +3 -1
- data/gemfiles/rails51_haml.gemfile +3 -1
- data/gemfiles/rails52.gemfile +3 -1
- data/gemfiles/rails52_boc.gemfile +3 -1
- data/gemfiles/rails52_haml.gemfile +3 -1
- data/gemfiles/rails60.gemfile +8 -0
- data/gemfiles/rails60_boc.gemfile +9 -0
- data/gemfiles/rails60_haml.gemfile +9 -0
- data/lib/better_errors.rb +20 -33
- data/lib/better_errors/editor.rb +99 -0
- data/lib/better_errors/error_page.rb +31 -4
- data/lib/better_errors/exception_hint.rb +29 -0
- data/lib/better_errors/middleware.rb +59 -12
- data/lib/better_errors/raised_exception.rb +25 -4
- data/lib/better_errors/stack_frame.rb +8 -2
- data/lib/better_errors/templates/main.erb +61 -17
- data/lib/better_errors/templates/text.erb +5 -2
- data/lib/better_errors/templates/variable_info.erb +9 -2
- data/lib/better_errors/version.rb +1 -1
- metadata +28 -7
- data/.travis.yml +0 -62
data/gemfiles/rails52.gemfile
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
2
|
|
3
3
|
gem "rails", "~> 5.2.0"
|
4
|
+
gem 'i18n', '< 1.5.2' if RUBY_VERSION < '2.3'
|
4
5
|
gem "binding_of_caller"
|
5
6
|
|
6
|
-
gem '
|
7
|
+
gem 'simplecov', require: false
|
8
|
+
gem 'simplecov-lcov', require: false
|
7
9
|
|
8
10
|
gemspec path: "../"
|
data/lib/better_errors.rb
CHANGED
@@ -3,6 +3,7 @@ require "erubi"
|
|
3
3
|
require "coderay"
|
4
4
|
require "uri"
|
5
5
|
|
6
|
+
require "better_errors/version"
|
6
7
|
require "better_errors/code_formatter"
|
7
8
|
require "better_errors/inspectable_value"
|
8
9
|
require "better_errors/error_page"
|
@@ -10,20 +11,9 @@ require "better_errors/middleware"
|
|
10
11
|
require "better_errors/raised_exception"
|
11
12
|
require "better_errors/repl"
|
12
13
|
require "better_errors/stack_frame"
|
13
|
-
require "better_errors/
|
14
|
+
require "better_errors/editor"
|
14
15
|
|
15
16
|
module BetterErrors
|
16
|
-
POSSIBLE_EDITOR_PRESETS = [
|
17
|
-
{ symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
|
18
|
-
{ symbols: [:macvim, :mvim], sniff: /vim/i, url: proc { |file, line| "mvim://open?url=file://#{file}&line=#{line}" } },
|
19
|
-
{ symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
|
20
|
-
{ symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
|
21
|
-
{ symbols: [:idea], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
|
22
|
-
{ symbols: [:rubymine], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
|
23
|
-
{ symbols: [:vscode, :code], sniff: /code/i, url: "vscode://file/%{file}:%{line}" },
|
24
|
-
{ symbols: [:atom], sniff: /atom/i, url: "atom://core/open/file?filename=%{file}&line=%{line}" },
|
25
|
-
]
|
26
|
-
|
27
17
|
class << self
|
28
18
|
# The path to the root of the application. Better Errors uses this property
|
29
19
|
# to determine if a file in a backtrace should be considered an application
|
@@ -54,21 +44,27 @@ module BetterErrors
|
|
54
44
|
# the variable won't be returned.
|
55
45
|
# @return int
|
56
46
|
attr_accessor :maximum_variable_inspect_size
|
47
|
+
|
48
|
+
# List of classes that are excluded from inspection.
|
49
|
+
# @return [Array]
|
50
|
+
attr_accessor :ignored_classes
|
57
51
|
end
|
58
52
|
@ignored_instance_variables = []
|
59
53
|
@maximum_variable_inspect_size = 100_000
|
54
|
+
@ignored_classes = ['ActionDispatch::Request', 'ActionDispatch::Response']
|
60
55
|
|
61
|
-
# Returns
|
56
|
+
# Returns an object which responds to #url, which when called with
|
57
|
+
# a filename and line number argument,
|
62
58
|
# returns a URL to open the filename and line in the selected editor.
|
63
59
|
#
|
64
60
|
# Generates TextMate URLs by default.
|
65
61
|
#
|
66
|
-
# BetterErrors.editor
|
62
|
+
# BetterErrors.editor.url("/some/file", 123)
|
67
63
|
# # => txmt://open?url=file:///some/file&line=123
|
68
64
|
#
|
69
65
|
# @return [Proc]
|
70
66
|
def self.editor
|
71
|
-
@editor
|
67
|
+
@editor ||= default_editor
|
72
68
|
end
|
73
69
|
|
74
70
|
# Configures how Better Errors generates open-in-editor URLs.
|
@@ -109,20 +105,15 @@ module BetterErrors
|
|
109
105
|
# @param [Proc] proc
|
110
106
|
#
|
111
107
|
def self.editor=(editor)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
self.editor = proc { |file, line| editor % { file: URI.encode_www_form_component(file), line: line } }
|
108
|
+
if editor.is_a? Symbol
|
109
|
+
@editor = Editor.editor_from_symbol(editor)
|
110
|
+
raise(ArgumentError, "Symbol #{editor} is not a symbol in the list of supported errors.") unless editor
|
111
|
+
elsif editor.is_a? String
|
112
|
+
@editor = Editor.for_formatting_string(editor)
|
113
|
+
elsif editor.respond_to? :call
|
114
|
+
@editor = Editor.for_proc(editor)
|
120
115
|
else
|
121
|
-
|
122
|
-
@editor = editor
|
123
|
-
else
|
124
|
-
raise TypeError, "Expected editor to be a valid editor key, a format string or a callable."
|
125
|
-
end
|
116
|
+
raise ArgumentError, "Expected editor to be a valid editor key, a format string or a callable."
|
126
117
|
end
|
127
118
|
end
|
128
119
|
|
@@ -139,12 +130,8 @@ module BetterErrors
|
|
139
130
|
#
|
140
131
|
# @return [Symbol]
|
141
132
|
def self.default_editor
|
142
|
-
|
143
|
-
ENV["EDITOR"] =~ config[:sniff]
|
144
|
-
}[:url] || :textmate
|
133
|
+
Editor.default_editor
|
145
134
|
end
|
146
|
-
|
147
|
-
BetterErrors.editor = default_editor
|
148
135
|
end
|
149
136
|
|
150
137
|
begin
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module BetterErrors
|
4
|
+
class Editor
|
5
|
+
KNOWN_EDITORS = [
|
6
|
+
{ symbols: [:atom], sniff: /atom/i, url: "atom://core/open/file?filename=%{file}&line=%{line}" },
|
7
|
+
{ symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
|
8
|
+
{ symbols: [:idea], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
|
9
|
+
{ symbols: [:macvim, :mvim], sniff: /vim/i, url: "mvim://open?url=file://%{file_unencoded}&line=%{line}" },
|
10
|
+
{ symbols: [:rubymine], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
|
11
|
+
{ symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
|
12
|
+
{ symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
|
13
|
+
{ symbols: [:vscode, :code], sniff: /code/i, url: "vscode://file/%{file}:%{line}" },
|
14
|
+
{ symbols: [:vscodium, :codium], sniff: /codium/i, url: "vscodium://file/%{file}:%{line}" },
|
15
|
+
]
|
16
|
+
|
17
|
+
def self.for_formatting_string(formatting_string)
|
18
|
+
new proc { |file, line|
|
19
|
+
formatting_string % { file: URI.encode_www_form_component(file), file_unencoded: file, line: line }
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.for_proc(url_proc)
|
24
|
+
new url_proc
|
25
|
+
end
|
26
|
+
|
27
|
+
# Automatically sniffs a default editor preset based on
|
28
|
+
# environment variables.
|
29
|
+
#
|
30
|
+
# @return [Symbol]
|
31
|
+
def self.default_editor
|
32
|
+
editor_from_environment_formatting_string ||
|
33
|
+
editor_from_environment_editor ||
|
34
|
+
editor_from_symbol(:textmate)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.editor_from_environment_editor
|
38
|
+
if ENV["BETTER_ERRORS_EDITOR"]
|
39
|
+
editor = editor_from_command(ENV["BETTER_ERRORS_EDITOR"])
|
40
|
+
return editor if editor
|
41
|
+
puts "BETTER_ERRORS_EDITOR environment variable is not recognized as a supported Better Errors editor."
|
42
|
+
end
|
43
|
+
if ENV["EDITOR"]
|
44
|
+
editor = editor_from_command(ENV["EDITOR"])
|
45
|
+
return editor if editor
|
46
|
+
puts "EDITOR environment variable is not recognized as a supported Better Errors editor. Using TextMate by default."
|
47
|
+
else
|
48
|
+
puts "Since there is no EDITOR or BETTER_ERRORS_EDITOR environment variable, using Textmate by default."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.editor_from_command(editor_command)
|
53
|
+
env_preset = KNOWN_EDITORS.find { |preset| editor_command =~ preset[:sniff] }
|
54
|
+
for_formatting_string(env_preset[:url]) if env_preset
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.editor_from_environment_formatting_string
|
58
|
+
return unless ENV['BETTER_ERRORS_EDITOR_URL']
|
59
|
+
|
60
|
+
for_formatting_string(ENV['BETTER_ERRORS_EDITOR_URL'])
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.editor_from_symbol(symbol)
|
64
|
+
KNOWN_EDITORS.each do |preset|
|
65
|
+
return for_formatting_string(preset[:url]) if preset[:symbols].include?(symbol)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(url_proc)
|
70
|
+
@url_proc = url_proc
|
71
|
+
end
|
72
|
+
|
73
|
+
def url(raw_path, line)
|
74
|
+
if virtual_path && raw_path.start_with?(virtual_path)
|
75
|
+
if host_path
|
76
|
+
file = raw_path.sub(%r{\A#{virtual_path}}, host_path)
|
77
|
+
else
|
78
|
+
file = raw_path.sub(%r{\A#{virtual_path}/}, '')
|
79
|
+
end
|
80
|
+
else
|
81
|
+
file = raw_path
|
82
|
+
end
|
83
|
+
|
84
|
+
url_proc.call(file, line)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
attr_reader :url_proc
|
90
|
+
|
91
|
+
def virtual_path
|
92
|
+
@virtual_path ||= ENV['BETTER_ERRORS_VIRTUAL_PATH']
|
93
|
+
end
|
94
|
+
|
95
|
+
def host_path
|
96
|
+
@host_path ||= ENV['BETTER_ERRORS_HOST_PATH']
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -26,8 +26,13 @@ module BetterErrors
|
|
26
26
|
@id ||= SecureRandom.hex(8)
|
27
27
|
end
|
28
28
|
|
29
|
-
def render(template_name = "main")
|
29
|
+
def render(template_name = "main", csrf_token = nil)
|
30
30
|
binding.eval(self.class.template(template_name).src)
|
31
|
+
rescue => e
|
32
|
+
# Fix the backtrace, which doesn't identify the template that failed (within Better Errors).
|
33
|
+
# We don't know the line number, so just injecting the template path has to be enough.
|
34
|
+
e.backtrace.unshift "#{self.class.template_path(template_name)}:0"
|
35
|
+
raise
|
31
36
|
end
|
32
37
|
|
33
38
|
def do_variables(opts)
|
@@ -59,7 +64,23 @@ module BetterErrors
|
|
59
64
|
end
|
60
65
|
|
61
66
|
def exception_message
|
62
|
-
exception.message.
|
67
|
+
exception.message.strip.gsub(/(\r?\n\s*\r?\n)+/, "\n")
|
68
|
+
end
|
69
|
+
|
70
|
+
def exception_hint
|
71
|
+
exception.hint
|
72
|
+
end
|
73
|
+
|
74
|
+
def active_support_actions
|
75
|
+
return [] unless defined?(ActiveSupport::ActionableError)
|
76
|
+
|
77
|
+
ActiveSupport::ActionableError.actions(exception.type)
|
78
|
+
end
|
79
|
+
|
80
|
+
def action_dispatch_action_endpoint
|
81
|
+
return unless defined?(ActionDispatch::ActionableExceptions)
|
82
|
+
|
83
|
+
ActionDispatch::ActionableExceptions.endpoint
|
63
84
|
end
|
64
85
|
|
65
86
|
def application_frames
|
@@ -73,7 +94,7 @@ module BetterErrors
|
|
73
94
|
private
|
74
95
|
|
75
96
|
def editor_url(frame)
|
76
|
-
BetterErrors.editor
|
97
|
+
BetterErrors.editor.url(frame.filename, frame.line)
|
77
98
|
end
|
78
99
|
|
79
100
|
def rack_session
|
@@ -105,7 +126,13 @@ module BetterErrors
|
|
105
126
|
end
|
106
127
|
|
107
128
|
def inspect_value(obj)
|
108
|
-
|
129
|
+
if BetterErrors.ignored_classes.include? obj.class.name
|
130
|
+
"<span class='unsupported'>(Instance of ignored class. "\
|
131
|
+
"#{obj.class.name ? "Remove #{CGI.escapeHTML(obj.class.name)} from" : "Modify"}"\
|
132
|
+
" BetterErrors.ignored_classes if you need to see it.)</span>"
|
133
|
+
else
|
134
|
+
InspectableValue.new(obj).to_html
|
135
|
+
end
|
109
136
|
rescue BetterErrors::ValueLargerThanConfiguredMaximum
|
110
137
|
"<span class='unsupported'>(Object too large. "\
|
111
138
|
"#{obj.class.name ? "Modify #{CGI.escapeHTML(obj.class.name)}#inspect or a" : "A"}"\
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module BetterErrors
|
2
|
+
class ExceptionHint
|
3
|
+
def initialize(exception)
|
4
|
+
@exception = exception
|
5
|
+
end
|
6
|
+
|
7
|
+
def hint
|
8
|
+
case exception
|
9
|
+
when NoMethodError
|
10
|
+
/\Aundefined method `(?<method>[^']+)' for (?<val>[^:]+):(?<klass>\w+)/.match(exception.message) do |match|
|
11
|
+
if match[:val] == "nil"
|
12
|
+
return "Something is `nil` when it probably shouldn't be."
|
13
|
+
elsif !match[:klass].start_with? '0x'
|
14
|
+
return "`#{match[:method]}` is being called on a `#{match[:klass]}` object, "\
|
15
|
+
"which might not be the type of object you were expecting."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
when NameError
|
19
|
+
/\Aundefined local variable or method `(?<method>[^']+)' for/.match(exception.message) do |match|
|
20
|
+
return "`#{match[:method]}` is probably misspelled."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :exception
|
28
|
+
end
|
29
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require "json"
|
2
2
|
require "ipaddr"
|
3
|
+
require "securerandom"
|
3
4
|
require "set"
|
4
5
|
require "rack"
|
5
6
|
|
@@ -33,12 +34,14 @@ module BetterErrors
|
|
33
34
|
# Adds an address to the set of IP addresses allowed to access Better
|
34
35
|
# Errors.
|
35
36
|
def self.allow_ip!(addr)
|
36
|
-
ALLOWED_IPS << IPAddr.new(addr)
|
37
|
+
ALLOWED_IPS << (addr.is_a?(IPAddr) ? addr : IPAddr.new(addr))
|
37
38
|
end
|
38
39
|
|
39
40
|
allow_ip! "127.0.0.0/8"
|
40
41
|
allow_ip! "::1/128" rescue nil # windows ruby doesn't have ipv6 support
|
41
42
|
|
43
|
+
CSRF_TOKEN_COOKIE_NAME = "BetterErrors-#{BetterErrors::VERSION}-CSRF-Token"
|
44
|
+
|
42
45
|
# A new instance of BetterErrors::Middleware
|
43
46
|
#
|
44
47
|
# @param app The Rack app/middleware to wrap with Better Errors
|
@@ -72,7 +75,7 @@ module BetterErrors
|
|
72
75
|
def better_errors_call(env)
|
73
76
|
case env["PATH_INFO"]
|
74
77
|
when %r{/__better_errors/(?<id>.+?)/(?<method>\w+)\z}
|
75
|
-
internal_call
|
78
|
+
internal_call(env, $~[:id], $~[:method])
|
76
79
|
when %r{/__better_errors/?\z}
|
77
80
|
show_error_page env
|
78
81
|
else
|
@@ -89,11 +92,14 @@ module BetterErrors
|
|
89
92
|
end
|
90
93
|
|
91
94
|
def show_error_page(env, exception=nil)
|
95
|
+
request = Rack::Request.new(env)
|
96
|
+
csrf_token = request.cookies[CSRF_TOKEN_COOKIE_NAME] || SecureRandom.uuid
|
97
|
+
|
92
98
|
type, content = if @error_page
|
93
99
|
if text?(env)
|
94
100
|
[ 'plain', @error_page.render('text') ]
|
95
101
|
else
|
96
|
-
[ 'html', @error_page.render ]
|
102
|
+
[ 'html', @error_page.render('main', csrf_token) ]
|
97
103
|
end
|
98
104
|
else
|
99
105
|
[ 'html', no_errors_page ]
|
@@ -104,12 +110,22 @@ module BetterErrors
|
|
104
110
|
status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
|
105
111
|
end
|
106
112
|
|
107
|
-
|
113
|
+
response = Rack::Response.new(content, status_code, { "Content-Type" => "text/#{type}; charset=utf-8" })
|
114
|
+
|
115
|
+
unless request.cookies[CSRF_TOKEN_COOKIE_NAME]
|
116
|
+
response.set_cookie(CSRF_TOKEN_COOKIE_NAME, value: csrf_token, path: "/", httponly: true, same_site: :strict)
|
117
|
+
end
|
118
|
+
|
119
|
+
# In older versions of Rack, the body returned here is actually a Rack::BodyProxy which seems to be a bug.
|
120
|
+
# (It contains status, headers and body and does not act like an array of strings.)
|
121
|
+
# Since we already have status code and body here, there's no need to use the ones in the Rack::Response.
|
122
|
+
(_status_code, headers, _body) = response.finish
|
123
|
+
[status_code, headers, [content]]
|
108
124
|
end
|
109
125
|
|
110
126
|
def text?(env)
|
111
127
|
env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" ||
|
112
|
-
|
128
|
+
!env["HTTP_ACCEPT"].to_s.include?('html')
|
113
129
|
end
|
114
130
|
|
115
131
|
def log_exception
|
@@ -129,13 +145,22 @@ module BetterErrors
|
|
129
145
|
end
|
130
146
|
end
|
131
147
|
|
132
|
-
def internal_call(env,
|
148
|
+
def internal_call(env, id, method)
|
149
|
+
return not_found_json_response unless %w[variables eval].include?(method)
|
133
150
|
return no_errors_json_response unless @error_page
|
134
|
-
return invalid_error_json_response if
|
151
|
+
return invalid_error_json_response if id != @error_page.id
|
152
|
+
|
153
|
+
request = Rack::Request.new(env)
|
154
|
+
return invalid_csrf_token_json_response unless request.cookies[CSRF_TOKEN_COOKIE_NAME]
|
155
|
+
|
156
|
+
request.body.rewind
|
157
|
+
body = JSON.parse(request.body.read)
|
158
|
+
return invalid_csrf_token_json_response unless request.cookies[CSRF_TOKEN_COOKIE_NAME] == body['csrfToken']
|
159
|
+
|
160
|
+
return not_acceptable_json_response unless request.content_type == 'application/json'
|
135
161
|
|
136
|
-
|
137
|
-
|
138
|
-
[200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(response)]]
|
162
|
+
response = @error_page.send("do_#{method}", body)
|
163
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(response)]]
|
139
164
|
end
|
140
165
|
|
141
166
|
def no_errors_page
|
@@ -157,18 +182,40 @@ module BetterErrors
|
|
157
182
|
"The application has been restarted since this page loaded, " +
|
158
183
|
"or the framework is reloading all gems before each request "
|
159
184
|
end
|
160
|
-
[200, { "Content-Type" => "
|
185
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
161
186
|
error: 'No exception information available',
|
162
187
|
explanation: explanation,
|
163
188
|
)]]
|
164
189
|
end
|
165
190
|
|
166
191
|
def invalid_error_json_response
|
167
|
-
[200, { "Content-Type" => "
|
192
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
168
193
|
error: "Session expired",
|
169
194
|
explanation: "This page was likely opened from a previous exception, " +
|
170
195
|
"and the exception is no longer available in memory.",
|
171
196
|
)]]
|
172
197
|
end
|
198
|
+
|
199
|
+
def invalid_csrf_token_json_response
|
200
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
201
|
+
error: "Invalid CSRF Token",
|
202
|
+
explanation: "The browser session might have been cleared, " +
|
203
|
+
"or something went wrong.",
|
204
|
+
)]]
|
205
|
+
end
|
206
|
+
|
207
|
+
def not_found_json_response
|
208
|
+
[404, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
209
|
+
error: "Not found",
|
210
|
+
explanation: "Not a recognized internal call.",
|
211
|
+
)]]
|
212
|
+
end
|
213
|
+
|
214
|
+
def not_acceptable_json_response
|
215
|
+
[406, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
216
|
+
error: "Request not acceptable",
|
217
|
+
explanation: "The internal request did not match an acceptable content type.",
|
218
|
+
)]]
|
219
|
+
end
|
173
220
|
end
|
174
221
|
end
|