forked from ruby-grape/grape
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathformatter.rb
176 lines (152 loc) · 5.72 KB
/
formatter.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
require 'grape/middleware/base'
module Grape
module Middleware
class Formatter < Base
CHUNKED = 'chunked'.freeze
def default_options
{
default_format: :txt,
formatters: {},
parsers: {}
}
end
def before
negotiate_content_type
read_body_input
end
def after
return unless @app_response
status, headers, bodies = *@app_response
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
@app_response
else
build_formatted_response(status, headers, bodies)
end
end
private
def build_formatted_response(status, headers, bodies)
headers = ensure_content_type(headers)
if bodies.is_a?(Grape::ServeFile::FileResponse)
Grape::ServeFile::SendfileResponse.new([], status, headers) do |resp|
resp.body = bodies.file
end
else
# Allow content-type to be explicitly overwritten
formatter = fetch_formatter(headers, options)
bodymap = bodies.collect { |body| formatter.call(body, env) }
Rack::Response.new(bodymap, status, headers)
end
rescue Grape::Exceptions::InvalidFormatter => e
throw :error, status: 500, message: e.message
end
def fetch_formatter(headers, options)
api_format = mime_types[headers[Grape::Http::Headers::CONTENT_TYPE]] || env[Grape::Env::API_FORMAT]
Grape::Formatter.formatter_for(api_format, options)
end
# Set the content type header for the API format if it is not already present.
#
# @param headers [Hash]
# @return [Hash]
def ensure_content_type(headers)
if headers[Grape::Http::Headers::CONTENT_TYPE]
headers
else
headers.merge(Grape::Http::Headers::CONTENT_TYPE => content_type_for(env[Grape::Env::API_FORMAT]))
end
end
def request
@request ||= Rack::Request.new(env)
end
# store read input in env['api.request.input']
def read_body_input
return unless
(request.post? || request.put? || request.patch? || request.delete?) &&
(!request.form_data? || !request.media_type) &&
!request.parseable_data? &&
(request.content_length.to_i > 0 || request.env[Grape::Http::Headers::HTTP_TRANSFER_ENCODING] == CHUNKED)
return unless (input = env[Grape::Env::RACK_INPUT])
input.rewind
body = env[Grape::Env::API_REQUEST_INPUT] = input.read
begin
read_rack_input(body) if body && !body.empty?
ensure
input.rewind
end
end
# store parsed input in env['api.request.body']
def read_rack_input(body)
fmt = mime_types[request.media_type] if request.media_type
fmt ||= options[:default_format]
if content_type_for(fmt)
parser = Grape::Parser.parser_for fmt, options
if parser
begin
body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))
if body.is_a?(Hash)
env[Grape::Env::RACK_REQUEST_FORM_HASH] = if env[Grape::Env::RACK_REQUEST_FORM_HASH]
env[Grape::Env::RACK_REQUEST_FORM_HASH].merge(body)
else
body
end
env[Grape::Env::RACK_REQUEST_FORM_INPUT] = env[Grape::Env::RACK_INPUT]
end
rescue Grape::Exceptions::Base => e
raise e
rescue StandardError => e
throw :error, status: 400, message: e.message
end
else
env[Grape::Env::API_REQUEST_BODY] = body
end
else
throw :error, status: 406, message: "The requested content-type '#{request.media_type}' is not supported."
end
end
def negotiate_content_type
fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
if content_type_for(fmt)
env[Grape::Env::API_FORMAT] = fmt
else
throw :error, status: 406, message: "The requested format '#{fmt}' is not supported."
end
end
def format_from_extension
parts = request.path.split('.')
if parts.size > 1
extension = parts.last
# avoid symbol memory leak on an unknown format
return extension.to_sym if content_type_for(extension)
end
nil
end
def format_from_params
fmt = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[Grape::Http::Headers::FORMAT]
# avoid symbol memory leak on an unknown format
return fmt.to_sym if content_type_for(fmt)
fmt
end
def format_from_header
mime_array.each do |t|
return mime_types[t] if mime_types.key?(t)
end
nil
end
def mime_array
accept = env[Grape::Http::Headers::HTTP_ACCEPT]
return [] unless accept
accept_into_mime_and_quality = %r{
(
\w+/[\w+.-]+) # eg application/vnd.example.myformat+xml
(?:
(?:;[^,]*?)? # optionally multiple formats in a row
;\s*q=([\d.]+) # optional "quality" preference (eg q=0.5)
)?
}x
vendor_prefix_pattern = /vnd\.[^+]+\+/
accept.scan(accept_into_mime_and_quality)
.sort_by { |_, quality_preference| -quality_preference.to_f }
.flat_map { |mime, _| [mime, mime.sub(vendor_prefix_pattern, '')] }
end
end
end
end