grape 0.15.0 → 0.16.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -1
- data/Gemfile.lock +16 -15
- data/README.md +41 -47
- data/UPGRADING.md +62 -0
- data/gemfiles/rails_3.gemfile.lock +225 -0
- data/grape.gemspec +2 -2
- data/lib/grape.rb +31 -26
- data/lib/grape/api.rb +39 -23
- data/lib/grape/dsl/inside_route.rb +8 -4
- data/lib/grape/dsl/routing.rb +2 -1
- data/lib/grape/endpoint.rb +43 -62
- data/lib/grape/error_formatter.rb +4 -2
- data/lib/grape/error_formatter/base.rb +10 -6
- data/lib/grape/formatter.rb +4 -2
- data/lib/grape/http/headers.rb +1 -0
- data/lib/grape/middleware/formatter.rb +2 -2
- data/lib/grape/middleware/versioner/accept_version_header.rb +2 -2
- data/lib/grape/middleware/versioner/header.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +2 -2
- data/lib/grape/namespace.rb +1 -1
- data/lib/grape/parser.rb +4 -2
- data/lib/grape/path.rb +3 -3
- data/lib/grape/request.rb +2 -2
- data/lib/grape/router.rb +156 -0
- data/lib/grape/router/attribute_translator.rb +40 -0
- data/lib/grape/router/pattern.rb +55 -0
- data/lib/grape/router/route.rb +105 -0
- data/lib/grape/serve_file/file_body.rb +34 -0
- data/lib/grape/{util → serve_file}/file_response.rb +1 -1
- data/lib/grape/{util → serve_file}/sendfile_response.rb +1 -1
- data/lib/grape/util/env.rb +1 -1
- data/lib/grape/util/registrable.rb +13 -0
- data/lib/grape/validations/types/custom_type_coercer.rb +2 -0
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/invalid_format_spec.rb +43 -0
- data/spec/grape/api/recognize_path_spec.rb +21 -0
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +26 -0
- data/spec/grape/api_spec.rb +110 -38
- data/spec/grape/dsl/inside_route_spec.rb +267 -240
- data/spec/grape/endpoint_spec.rb +10 -0
- data/spec/grape/entity_spec.rb +2 -2
- data/spec/grape/middleware/formatter_spec.rb +23 -4
- data/spec/grape/middleware/versioner/header_spec.rb +1 -1
- data/spec/grape/middleware/versioner/path_spec.rb +1 -1
- data/spec/grape/parser_spec.rb +82 -0
- data/spec/grape/request_spec.rb +2 -2
- data/spec/grape/validations/params_scope_spec.rb +2 -2
- data/spec/grape/validations/validators/coerce_spec.rb +51 -0
- data/spec/grape/validations_spec.rb +1 -1
- data/tmp/Gemfile.lock +63 -0
- metadata +70 -55
- data/lib/grape/route.rb +0 -32
data/grape.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.license = 'MIT'
|
14
14
|
|
15
15
|
s.add_runtime_dependency 'rack', '>= 1.3.0'
|
16
|
-
s.add_runtime_dependency '
|
16
|
+
s.add_runtime_dependency 'mustermann19', '~> 0.4.3'
|
17
17
|
s.add_runtime_dependency 'rack-accept'
|
18
18
|
s.add_runtime_dependency 'activesupport'
|
19
19
|
s.add_runtime_dependency 'multi_json', '>= 1.3.2'
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
|
|
23
23
|
s.add_runtime_dependency 'builder'
|
24
24
|
|
25
25
|
s.add_development_dependency 'grape-entity', '>= 0.4.4'
|
26
|
-
s.add_development_dependency 'rake'
|
26
|
+
s.add_development_dependency 'rake', '~> 10'
|
27
27
|
s.add_development_dependency 'maruku'
|
28
28
|
s.add_development_dependency 'yard'
|
29
29
|
s.add_development_dependency 'rack-test'
|
data/lib/grape.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'rack'
|
3
|
-
require 'rack/mount'
|
4
3
|
require 'rack/builder'
|
5
4
|
require 'rack/accept'
|
6
5
|
require 'rack/auth/basic'
|
@@ -33,8 +32,8 @@ module Grape
|
|
33
32
|
eager_autoload do
|
34
33
|
autoload :API
|
35
34
|
autoload :Endpoint
|
35
|
+
autoload :Router
|
36
36
|
|
37
|
-
autoload :Route
|
38
37
|
autoload :Namespace
|
39
38
|
|
40
39
|
autoload :Path
|
@@ -78,28 +77,6 @@ module Grape
|
|
78
77
|
autoload :MethodNotAllowed
|
79
78
|
end
|
80
79
|
|
81
|
-
module ErrorFormatter
|
82
|
-
extend ActiveSupport::Autoload
|
83
|
-
autoload :Base
|
84
|
-
autoload :Json
|
85
|
-
autoload :Txt
|
86
|
-
autoload :Xml
|
87
|
-
end
|
88
|
-
|
89
|
-
module Formatter
|
90
|
-
extend ActiveSupport::Autoload
|
91
|
-
autoload :Json
|
92
|
-
autoload :SerializableHash
|
93
|
-
autoload :Txt
|
94
|
-
autoload :Xml
|
95
|
-
end
|
96
|
-
|
97
|
-
module Parser
|
98
|
-
extend ActiveSupport::Autoload
|
99
|
-
autoload :Json
|
100
|
-
autoload :Xml
|
101
|
-
end
|
102
|
-
|
103
80
|
module Middleware
|
104
81
|
extend ActiveSupport::Autoload
|
105
82
|
autoload :Base
|
@@ -131,8 +108,29 @@ module Grape
|
|
131
108
|
autoload :StackableValues
|
132
109
|
autoload :InheritableSetting
|
133
110
|
autoload :StrictHashConfiguration
|
134
|
-
autoload :
|
135
|
-
|
111
|
+
autoload :Registrable
|
112
|
+
end
|
113
|
+
|
114
|
+
module ErrorFormatter
|
115
|
+
extend ActiveSupport::Autoload
|
116
|
+
autoload :Base
|
117
|
+
autoload :Json
|
118
|
+
autoload :Txt
|
119
|
+
autoload :Xml
|
120
|
+
end
|
121
|
+
|
122
|
+
module Formatter
|
123
|
+
extend ActiveSupport::Autoload
|
124
|
+
autoload :Json
|
125
|
+
autoload :SerializableHash
|
126
|
+
autoload :Txt
|
127
|
+
autoload :Xml
|
128
|
+
end
|
129
|
+
|
130
|
+
module Parser
|
131
|
+
extend ActiveSupport::Autoload
|
132
|
+
autoload :Json
|
133
|
+
autoload :Xml
|
136
134
|
end
|
137
135
|
|
138
136
|
module DSL
|
@@ -163,6 +161,13 @@ module Grape
|
|
163
161
|
extend ActiveSupport::Autoload
|
164
162
|
autoload :Presenter
|
165
163
|
end
|
164
|
+
|
165
|
+
module ServeFile
|
166
|
+
extend ActiveSupport::Autoload
|
167
|
+
autoload :FileResponse
|
168
|
+
autoload :FileBody
|
169
|
+
autoload :SendfileResponse
|
170
|
+
end
|
166
171
|
end
|
167
172
|
|
168
173
|
require 'grape/util/content_types'
|
data/lib/grape/api.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'grape/router'
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
# The API class is the primary entry point for creating Grape APIs. Users
|
3
5
|
# should subclass this class in order to build an API.
|
@@ -52,6 +54,12 @@ module Grape
|
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
57
|
+
# see Grape::Router#recognize_path
|
58
|
+
def recognize_path(path)
|
59
|
+
LOCK.synchronize { compile } unless instance
|
60
|
+
instance.router.recognize_path(path)
|
61
|
+
end
|
62
|
+
|
55
63
|
protected
|
56
64
|
|
57
65
|
def prepare_routes
|
@@ -84,27 +92,30 @@ module Grape
|
|
84
92
|
end
|
85
93
|
end
|
86
94
|
|
95
|
+
attr_reader :router
|
96
|
+
|
87
97
|
# Builds the routes from the defined endpoints, effectively compiling
|
88
98
|
# this API into a usable form.
|
89
99
|
def initialize
|
90
|
-
@
|
100
|
+
@router = Router.new
|
91
101
|
add_head_not_allowed_methods_and_options_methods
|
92
102
|
self.class.endpoints.each do |endpoint|
|
93
|
-
endpoint.mount_in(@
|
103
|
+
endpoint.mount_in(@router)
|
94
104
|
end
|
95
105
|
|
96
|
-
@
|
106
|
+
@router.compile!
|
107
|
+
@router.freeze
|
97
108
|
end
|
98
109
|
|
99
110
|
# Handle a request. See Rack documentation for what `env` is.
|
100
111
|
def call(env)
|
101
|
-
result = @
|
112
|
+
result = @router.call(env)
|
102
113
|
result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
|
103
114
|
result
|
104
115
|
end
|
105
116
|
|
106
117
|
# Some requests may return a HTTP 404 error if grape cannot find a matching
|
107
|
-
# route. In this case,
|
118
|
+
# route. In this case, Grape::Router adds a X-Cascade header to the response
|
108
119
|
# and sets it to 'pass', indicating to grape's parents they should keep
|
109
120
|
# looking for a matching route on other resources.
|
110
121
|
#
|
@@ -126,20 +137,24 @@ module Grape
|
|
126
137
|
# will return an HTTP 405 response for any HTTP method that the resource
|
127
138
|
# cannot handle.
|
128
139
|
def add_head_not_allowed_methods_and_options_methods
|
129
|
-
|
140
|
+
routes_map = {}
|
130
141
|
|
131
142
|
self.class.endpoints.each do |endpoint|
|
132
143
|
routes = endpoint.routes
|
133
144
|
routes.each do |route|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
145
|
+
# using the :any shorthand produces [nil] for route methods, substitute all manually
|
146
|
+
route_key = route.pattern.to_regexp
|
147
|
+
routes_map[route_key] ||= {}
|
148
|
+
route_settings = routes_map[route_key]
|
149
|
+
route_settings[:pattern] = route.pattern
|
150
|
+
route_settings[:requirements] = route.requirements
|
151
|
+
route_settings[:path] = route.origin
|
152
|
+
route_settings[:methods] ||= []
|
153
|
+
route_settings[:methods] << route.request_method
|
154
|
+
route_settings[:endpoint] = route.app
|
140
155
|
|
141
156
|
# using the :any shorthand produces [nil] for route methods, substitute all manually
|
142
|
-
|
157
|
+
route_settings[:methods] = %w(GET PUT POST DELETE PATCH HEAD OPTIONS) if route_settings[:methods].include?('ANY')
|
143
158
|
end
|
144
159
|
end
|
145
160
|
|
@@ -149,7 +164,9 @@ module Grape
|
|
149
164
|
# informations again.
|
150
165
|
without_root_prefix do
|
151
166
|
without_versioning do
|
152
|
-
|
167
|
+
routes_map.each do |_, config|
|
168
|
+
methods = config[:methods]
|
169
|
+
path = config[:path]
|
153
170
|
allowed_methods = methods.dup
|
154
171
|
|
155
172
|
unless self.class.namespace_inheritable(:do_not_route_head)
|
@@ -159,18 +176,19 @@ module Grape
|
|
159
176
|
allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
|
160
177
|
|
161
178
|
unless self.class.namespace_inheritable(:do_not_route_options)
|
162
|
-
generate_options_method(path, allow_header) unless allowed_methods.include?(Grape::Http::Headers::OPTIONS)
|
179
|
+
generate_options_method(path, allow_header, config) unless allowed_methods.include?(Grape::Http::Headers::OPTIONS)
|
163
180
|
end
|
164
181
|
|
165
|
-
|
182
|
+
attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
|
183
|
+
generate_not_allowed_method(config[:pattern], attributes)
|
166
184
|
end
|
167
185
|
end
|
168
186
|
end
|
169
187
|
end
|
170
188
|
|
171
189
|
# Generate an 'OPTIONS' route for a pre-exisiting user defined route
|
172
|
-
def generate_options_method(path, allow_header)
|
173
|
-
self.class.options(path,
|
190
|
+
def generate_options_method(path, allow_header, options = {})
|
191
|
+
self.class.options(path, options) do
|
174
192
|
header 'Allow', allow_header
|
175
193
|
status 204
|
176
194
|
''
|
@@ -179,15 +197,13 @@ module Grape
|
|
179
197
|
|
180
198
|
# Generate a route that returns an HTTP 405 response for a user defined
|
181
199
|
# path on methods not specified
|
182
|
-
def generate_not_allowed_method(
|
183
|
-
not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - allowed_methods
|
200
|
+
def generate_not_allowed_method(pattern, attributes = {})
|
201
|
+
not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - attributes[:allowed_methods]
|
184
202
|
not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
|
185
203
|
|
186
204
|
return if not_allowed_methods.empty?
|
187
205
|
|
188
|
-
|
189
|
-
fail Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allow_header)
|
190
|
-
end
|
206
|
+
@router.associate_routes(pattern, attributes.merge(not_allowed_methods: not_allowed_methods))
|
191
207
|
end
|
192
208
|
|
193
209
|
# Allows definition of endpoints that ignore the versioning configuration
|
@@ -191,8 +191,12 @@ module Grape
|
|
191
191
|
#
|
192
192
|
# GET /file # => "contents of file"
|
193
193
|
def file(value = nil)
|
194
|
-
if value
|
195
|
-
|
194
|
+
if value.is_a?(String)
|
195
|
+
file_body = Grape::ServeFile::FileBody.new(value)
|
196
|
+
@file = Grape::ServeFile::FileResponse.new(file_body)
|
197
|
+
elsif !value.is_a?(NilClass)
|
198
|
+
warn '[DEPRECATION] Argument as FileStreamer-like object is deprecated. Use path to file instead.'
|
199
|
+
@file = Grape::ServeFile::FileResponse.new(value)
|
196
200
|
else
|
197
201
|
@file
|
198
202
|
end
|
@@ -269,10 +273,10 @@ module Grape
|
|
269
273
|
#
|
270
274
|
# desc "Returns the route description."
|
271
275
|
# get '/' do
|
272
|
-
# route.
|
276
|
+
# route.description
|
273
277
|
# end
|
274
278
|
def route
|
275
|
-
env[Grape::Env::
|
279
|
+
env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]
|
276
280
|
end
|
277
281
|
|
278
282
|
# Attempt to locate the Entity class for a given object, if not given
|
data/lib/grape/dsl/routing.rb
CHANGED
@@ -82,7 +82,7 @@ module Grape
|
|
82
82
|
in_setting = inheritable_setting
|
83
83
|
|
84
84
|
if app.respond_to?(:inheritable_setting, true)
|
85
|
-
mount_path =
|
85
|
+
mount_path = Grape::Router.normalize_path(path)
|
86
86
|
app.top_level_setting.namespace_stackable[:mount_path] = mount_path
|
87
87
|
|
88
88
|
app.inherit_settings(inheritable_setting)
|
@@ -98,6 +98,7 @@ module Grape
|
|
98
98
|
method: :any,
|
99
99
|
path: path,
|
100
100
|
app: app,
|
101
|
+
forward_match: !app.respond_to?(:inheritable_setting),
|
101
102
|
for: self
|
102
103
|
)
|
103
104
|
end
|
data/lib/grape/endpoint.rb
CHANGED
@@ -110,7 +110,7 @@ module Grape
|
|
110
110
|
end
|
111
111
|
|
112
112
|
def routes
|
113
|
-
@routes ||= endpoints ? endpoints.collect(&:routes).flatten :
|
113
|
+
@routes ||= endpoints ? endpoints.collect(&:routes).flatten : to_routes
|
114
114
|
end
|
115
115
|
|
116
116
|
def reset_routes!
|
@@ -119,29 +119,36 @@ module Grape
|
|
119
119
|
@routes = nil
|
120
120
|
end
|
121
121
|
|
122
|
-
def mount_in(
|
122
|
+
def mount_in(router)
|
123
123
|
if endpoints
|
124
|
-
endpoints.each
|
125
|
-
e.mount_in(route_set)
|
126
|
-
end
|
124
|
+
endpoints.each { |e| e.mount_in(router) }
|
127
125
|
else
|
128
126
|
reset_routes!
|
129
|
-
|
130
127
|
routes.each do |route|
|
131
|
-
methods = [route.
|
132
|
-
if !namespace_inheritable(:do_not_route_head) && route.
|
128
|
+
methods = [route.request_method]
|
129
|
+
if !namespace_inheritable(:do_not_route_head) && route.request_method == Grape::Http::Headers::GET
|
133
130
|
methods << Grape::Http::Headers::HEAD
|
134
131
|
end
|
135
132
|
methods.each do |method|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
133
|
+
unless route.request_method.to_s.upcase == method
|
134
|
+
route = Grape::Router::Route.new(method, route.origin, route.attributes.to_h)
|
135
|
+
end
|
136
|
+
router.append(route.apply(self))
|
140
137
|
end
|
141
138
|
end
|
142
139
|
end
|
143
140
|
end
|
144
141
|
|
142
|
+
def to_routes
|
143
|
+
route_options = prepare_default_route_attributes
|
144
|
+
map_routes do |method, path|
|
145
|
+
path = prepare_path(path)
|
146
|
+
params = merge_route_options(route_options.merge(suffix: path.suffix))
|
147
|
+
route = Router::Route.new(method, path.path, params)
|
148
|
+
route.apply(self)
|
149
|
+
end.flatten
|
150
|
+
end
|
151
|
+
|
145
152
|
def prepare_routes_requirements
|
146
153
|
endpoint_requirements = options[:route_options][:requirements] || {}
|
147
154
|
all_requirements = (namespace_stackable(:namespace).map(&:requirements) << endpoint_requirements)
|
@@ -150,41 +157,30 @@ module Grape
|
|
150
157
|
end
|
151
158
|
end
|
152
159
|
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
+
def prepare_default_route_attributes
|
161
|
+
{
|
162
|
+
namespace: namespace,
|
163
|
+
version: prepare_version,
|
164
|
+
requirements: prepare_routes_requirements,
|
165
|
+
prefix: namespace_inheritable(:root_prefix),
|
166
|
+
anchor: options[:route_options].fetch(:anchor, true),
|
167
|
+
settings: inheritable_setting.route.except(:saved_declared_params, :saved_validations),
|
168
|
+
forward_match: options[:forward_match]
|
169
|
+
}
|
170
|
+
end
|
160
171
|
|
161
|
-
|
162
|
-
|
163
|
-
|
172
|
+
def prepare_version
|
173
|
+
version = namespace_inheritable(:version) || []
|
174
|
+
return if version.length == 0
|
175
|
+
version.length == 1 ? version.first.to_s : version
|
176
|
+
end
|
164
177
|
|
165
|
-
|
178
|
+
def merge_route_options(default = {})
|
179
|
+
options[:route_options].clone.reverse_merge(default)
|
166
180
|
end
|
167
181
|
|
168
|
-
def
|
169
|
-
options[:method].map
|
170
|
-
options[:path].map do |path|
|
171
|
-
prepared_path = prepare_path(path)
|
172
|
-
anchor = options[:route_options].fetch(:anchor, true)
|
173
|
-
path = compile_path(prepared_path, anchor && !options[:app], prepare_routes_requirements)
|
174
|
-
request_method = (method.to_s.upcase unless method == :any)
|
175
|
-
|
176
|
-
Route.new(options[:route_options].clone.merge(
|
177
|
-
prefix: namespace_inheritable(:root_prefix),
|
178
|
-
version: namespace_inheritable(:version) ? namespace_inheritable(:version).join('|') : nil,
|
179
|
-
namespace: namespace,
|
180
|
-
method: request_method,
|
181
|
-
path: prepared_path,
|
182
|
-
params: prepare_routes_path_params(path),
|
183
|
-
compiled: path,
|
184
|
-
settings: inheritable_setting.route.except(:saved_declared_params, :saved_validations)
|
185
|
-
))
|
186
|
-
end
|
187
|
-
end.flatten
|
182
|
+
def map_routes
|
183
|
+
options[:method].map { |method| options[:path].map { |path| yield method, path } }
|
188
184
|
end
|
189
185
|
|
190
186
|
def prepare_path(path)
|
@@ -196,13 +192,6 @@ module Grape
|
|
196
192
|
@namespace ||= Namespace.joined_space_path(namespace_stackable(:namespace))
|
197
193
|
end
|
198
194
|
|
199
|
-
def compile_path(prepared_path, anchor = true, requirements = {})
|
200
|
-
endpoint_options = {}
|
201
|
-
endpoint_options[:version] = /#{namespace_inheritable(:version).join('|')}/ if namespace_inheritable(:version)
|
202
|
-
endpoint_options.merge!(requirements)
|
203
|
-
Rack::Mount::Strexp.compile(prepared_path, endpoint_options, %w( / . ? ), anchor)
|
204
|
-
end
|
205
|
-
|
206
195
|
def call(env)
|
207
196
|
lazy_initialize!
|
208
197
|
dup.call!(env)
|
@@ -242,7 +231,7 @@ module Grape
|
|
242
231
|
|
243
232
|
run_filters before_validations, :before_validation
|
244
233
|
|
245
|
-
run_validators validations, request
|
234
|
+
run_validators validations, request unless @method_not_allowed
|
246
235
|
|
247
236
|
run_filters after_validations, :after_validation
|
248
237
|
|
@@ -275,11 +264,7 @@ module Grape
|
|
275
264
|
(namespace_stackable(:middleware) || []).each do |m|
|
276
265
|
m = m.dup
|
277
266
|
block = m.pop if m.last.is_a?(Proc)
|
278
|
-
|
279
|
-
b.use(*m, &block)
|
280
|
-
else
|
281
|
-
b.use(*m)
|
282
|
-
end
|
267
|
+
block ? b.use(*m, &block) : b.use(*m)
|
283
268
|
end
|
284
269
|
|
285
270
|
if namespace_inheritable(:version)
|
@@ -305,9 +290,7 @@ module Grape
|
|
305
290
|
def build_helpers
|
306
291
|
helpers = namespace_stackable(:helpers) || []
|
307
292
|
Module.new do
|
308
|
-
helpers.each
|
309
|
-
include mod_to_include
|
310
|
-
end
|
293
|
+
helpers.each { |mod_to_include| include mod_to_include }
|
311
294
|
end
|
312
295
|
end
|
313
296
|
|
@@ -348,9 +331,7 @@ module Grape
|
|
348
331
|
|
349
332
|
def run_filters(filters, type = :other)
|
350
333
|
ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do
|
351
|
-
(filters || []).each
|
352
|
-
instance_eval(&filter)
|
353
|
-
end
|
334
|
+
(filters || []).each { |filter| instance_eval(&filter) }
|
354
335
|
end
|
355
336
|
post_extension = DSL::InsideRoute.post_filter_methods(type)
|
356
337
|
extend post_extension if post_extension
|