rails-rfc6570 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +1 -1
- data/lib/rails/rfc6570.rb +15 -201
- data/lib/rails/rfc6570/formatter.rb +50 -0
- data/lib/rails/rfc6570/version.rb +1 -1
- data/lib/rails/rfc6570/visitor.rb +120 -0
- data/spec/dummy/log/test.log +2877 -0
- data/spec/rails/rfc6570/visitor_spec.rb +113 -0
- data/spec/rfc6570_spec.rb +5 -0
- data/spec/spec_helper.rb +2 -3
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb6f9305fdd5bf479a341347ca33add86759ac067ec0ad2bff4e7953d8dcbb4f
|
4
|
+
data.tar.gz: fba3bdf42cd2a5d8d3cfd61030341d1733e9b3806d534fa08bdfee4b33fe61a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d4a88801f28b407ca01c1258d477cf596944c155a2094b67118e970f8ef8430300f6979967855b97c4ebe7786a909567314f5f8eb34360dbd09ac347ba39cef
|
7
|
+
data.tar.gz: c68fa573cfd3ca16c2f8fcdb46550e0137be207e44c611f24ebcbac14169e4affa2bf680a31ca779577abad0531cccc240a548ca6d7b533ef6a8655b09186ca6
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -93,7 +93,7 @@ class UserDecorator < Draper::Decorator
|
|
93
93
|
end
|
94
94
|
```
|
95
95
|
|
96
|
-
This gem does not support every construct possible with route matchers especially nested groups cannot be expressed in URI templates. It also makes some assumptions when converting splat matchers like swallowing a multiple slashes.
|
96
|
+
This gem does not support every construct possible with route matchers especially nested groups cannot be expressed in URI templates. They are expanded into separate groups. It also makes some assumptions when converting splat matchers like swallowing a multiple slashes. An error is raised when routes with OR-clauses are tried to be converted.
|
97
97
|
|
98
98
|
You can also combine **Rails::RFC6570** with [rack-link_headers](https://github.com/jgraichen/rack-link_headers) and provide hypermedia linking everywhere!
|
99
99
|
|
data/lib/rails/rfc6570.rb
CHANGED
@@ -1,168 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
|
5
|
-
module ActionDispatch
|
6
|
-
module Journey
|
7
|
-
module Visitors
|
8
|
-
class RFC6570 < Visitor # rubocop:disable ClassLength
|
9
|
-
DISPATCH_CACHE = {} # rubocop:disable MutableConstant
|
10
|
-
|
11
|
-
def initialize(opts = {})
|
12
|
-
super()
|
13
|
-
|
14
|
-
@opts = opts
|
15
|
-
@stack = []
|
16
|
-
@group_depth = 0
|
17
|
-
end
|
18
|
-
|
19
|
-
def ignore
|
20
|
-
@opts.fetch(:ignore) { %w[format] }
|
21
|
-
end
|
22
|
-
|
23
|
-
def route
|
24
|
-
@route ||= @opts[:route]
|
25
|
-
end
|
26
|
-
|
27
|
-
# rubocop:disable AbcSize
|
28
|
-
def accept(node)
|
29
|
-
str = visit(node)
|
30
|
-
|
31
|
-
if @opts.fetch(:params, true) && route
|
32
|
-
controller = route.defaults[:controller].to_s
|
33
|
-
action = route.defaults[:action].to_s
|
34
|
-
|
35
|
-
if controller.present? && action.present?
|
36
|
-
params = Rails::RFC6570.params_for(controller, action)
|
37
|
-
str += '{?' + params.join(',') + '}' if params&.any?
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
str
|
42
|
-
end
|
43
|
-
# rubocop:enable all
|
44
|
-
|
45
|
-
def visit(node)
|
46
|
-
@stack.unshift node.type
|
47
|
-
send DISPATCH_CACHE.fetch(node.type), node
|
48
|
-
ensure
|
49
|
-
@stack.shift
|
50
|
-
end
|
51
|
-
|
52
|
-
def symbol_name(node)
|
53
|
-
name = node.to_s.tr '*:', ''
|
54
|
-
|
55
|
-
if ignore.include?(name)
|
56
|
-
nil
|
57
|
-
else
|
58
|
-
name
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def placeholder(node, prefix = nil, suffix = nil, pretext = nil)
|
63
|
-
name = symbol_name node
|
64
|
-
if name
|
65
|
-
"#{pretext}{#{prefix}#{name}#{suffix}}"
|
66
|
-
else
|
67
|
-
''
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# rubocop:disable AbcSize
|
72
|
-
# rubocop:disable CyclomaticComplexity
|
73
|
-
# rubocop:disable MethodLength
|
74
|
-
# rubocop:disable PerceivedComplexity
|
75
|
-
def binary(node)
|
76
|
-
case [node.left.type, node.right.type]
|
77
|
-
when %i[DOT SYMBOL]
|
78
|
-
if @stack[0..1] == %i[CAT GROUP]
|
79
|
-
placeholder node.right, '.'
|
80
|
-
else
|
81
|
-
placeholder(node.right, nil, nil, '.')
|
82
|
-
end
|
83
|
-
when %i[SLASH SYMBOL]
|
84
|
-
if @stack[0..1] == %i[CAT GROUP]
|
85
|
-
placeholder(node.right, '/')
|
86
|
-
else
|
87
|
-
placeholder(node.right, nil, nil, '/')
|
88
|
-
end
|
89
|
-
when %i[SLASH STAR]
|
90
|
-
placeholder node.right, '/', '*'
|
91
|
-
when %i[SLASH CAT]
|
92
|
-
if node.right.left.type == :STAR
|
93
|
-
placeholder(node.right.left, '/', '*') +
|
94
|
-
visit(node.right.right)
|
95
|
-
else
|
96
|
-
[visit(node.left), visit(node.right)].join
|
97
|
-
end
|
98
|
-
when %i[CAT STAR]
|
99
|
-
visit(node.left).to_s.gsub(%r{/+$}, '') +
|
100
|
-
placeholder(node.right, '/', '*')
|
101
|
-
else
|
102
|
-
[visit(node.left), visit(node.right)].join
|
103
|
-
end
|
104
|
-
end
|
105
|
-
# rubocop:enable all
|
106
|
-
|
107
|
-
def terminal(node)
|
108
|
-
node.left
|
109
|
-
end
|
110
|
-
|
111
|
-
def nary(node)
|
112
|
-
node.children.each {|c| visit(c) }
|
113
|
-
end
|
114
|
-
|
115
|
-
def unary(node)
|
116
|
-
visit(node.left)
|
117
|
-
end
|
118
|
-
|
119
|
-
# rubocop:disable MethodName
|
120
|
-
def visit_CAT(node)
|
121
|
-
binary(node)
|
122
|
-
end
|
123
|
-
|
124
|
-
def visit_LITERAL(node)
|
125
|
-
terminal(node)
|
126
|
-
end
|
127
|
-
|
128
|
-
def visit_SLASH(node)
|
129
|
-
terminal(node)
|
130
|
-
end
|
3
|
+
require 'action_dispatch/journey'
|
131
4
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
def visit_SYMBOL(node)
|
137
|
-
placeholder(node)
|
138
|
-
end
|
139
|
-
|
140
|
-
def visit_OR(node)
|
141
|
-
nary(node)
|
142
|
-
end
|
143
|
-
|
144
|
-
def visit_STAR(node)
|
145
|
-
unary(node)
|
146
|
-
end
|
147
|
-
|
148
|
-
def visit_GROUP(node)
|
149
|
-
raise 'Cannot transform nested groups.' if @group_depth >= 1
|
150
|
-
|
151
|
-
@group_depth += 1
|
152
|
-
visit node.left
|
153
|
-
ensure
|
154
|
-
@group_depth -= 1
|
155
|
-
end
|
156
|
-
# rubocop:enable MethodName
|
157
|
-
|
158
|
-
instance_methods(true).each do |meth|
|
159
|
-
next unless meth =~ /^visit_(.*)$/
|
160
|
-
DISPATCH_CACHE[Regexp.last_match(1).to_sym] = meth
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
5
|
+
require 'rails/rfc6570/formatter'
|
6
|
+
require 'rails/rfc6570/version'
|
7
|
+
require 'rails/rfc6570/visitor'
|
166
8
|
|
167
9
|
module Rails
|
168
10
|
module RFC6570
|
@@ -170,7 +12,6 @@ module Rails
|
|
170
12
|
class Railtie < ::Rails::Railtie # :nodoc:
|
171
13
|
initializer 'rails-rfc6570', group: :all do |_app|
|
172
14
|
require 'rails/rfc6570/patches'
|
173
|
-
require 'action_dispatch/journey'
|
174
15
|
|
175
16
|
MAJOR = Rails::VERSION::MAJOR
|
176
17
|
MINOR = Rails::VERSION::MINOR
|
@@ -184,9 +25,6 @@ module Rails
|
|
184
25
|
::ActionDispatch::Journey::Route.send :include,
|
185
26
|
Rails::RFC6570::Extensions::JourneyRoute
|
186
27
|
|
187
|
-
::ActionDispatch::Journey::Nodes::Node.send :include,
|
188
|
-
Rails::RFC6570::Extensions::JourneyNode
|
189
|
-
|
190
28
|
::ActiveSupport.on_load(:action_controller) do
|
191
29
|
include Rails::RFC6570::Helper
|
192
30
|
extend Rails::RFC6570::ControllerExtension
|
@@ -207,7 +45,6 @@ module Rails
|
|
207
45
|
Hash[routes.map {|n, r| [n, r.to_rfc6570(opts)] }]
|
208
46
|
end
|
209
47
|
|
210
|
-
# rubocop:disable AbcSize
|
211
48
|
# rubocop:disable MethodLength
|
212
49
|
def define_rfc6570_helpers(name, route, mod, set)
|
213
50
|
rfc6570_name = :"#{name}_rfc6570"
|
@@ -224,11 +61,13 @@ module Rails
|
|
224
61
|
end
|
225
62
|
|
226
63
|
define_method(rfc6570_url_name) do |opts = {}|
|
227
|
-
|
64
|
+
::Rails::RFC6570.build_url_template(self, route,
|
65
|
+
opts.merge(path_only: false))
|
228
66
|
end
|
229
67
|
|
230
68
|
define_method(rfc6570_path_name) do |opts = {}|
|
231
|
-
|
69
|
+
::Rails::RFC6570.build_url_template(self, route,
|
70
|
+
opts.merge(path_only: true))
|
232
71
|
end
|
233
72
|
end
|
234
73
|
|
@@ -248,14 +87,8 @@ module Rails
|
|
248
87
|
|
249
88
|
module JourneyRoute
|
250
89
|
def to_rfc6570(opts = {})
|
251
|
-
|
252
|
-
|
253
|
-
end
|
254
|
-
|
255
|
-
module JourneyNode
|
256
|
-
def to_rfc6570(opts = {})
|
257
|
-
::Addressable::Template.new \
|
258
|
-
::ActionDispatch::Journey::Visitors::RFC6570.new(opts).accept(self)
|
90
|
+
@rfc6570_formatter ||= RFC6570::Formatter.new(self)
|
91
|
+
@rfc6570_formatter.evaluate(opts)
|
259
92
|
end
|
260
93
|
end
|
261
94
|
end
|
@@ -274,7 +107,7 @@ module Rails
|
|
274
107
|
route = Rails.application.routes.named_routes[name]
|
275
108
|
raise KeyError.new "No named routed for `#{name}'." unless route
|
276
109
|
|
277
|
-
|
110
|
+
route.to_rfc6570(**opts, ctx: self)
|
278
111
|
end
|
279
112
|
end
|
280
113
|
|
@@ -292,29 +125,6 @@ module Rails
|
|
292
125
|
end
|
293
126
|
end
|
294
127
|
|
295
|
-
# rubocop:disable MethodLength
|
296
|
-
def build_url_template(t, route, options)
|
297
|
-
template = route.to_rfc6570(options)
|
298
|
-
|
299
|
-
if options.fetch(:path_only, false)
|
300
|
-
template
|
301
|
-
else
|
302
|
-
options = t.url_options.merge(options)
|
303
|
-
options[:path] = template.pattern
|
304
|
-
|
305
|
-
original_script_name = options.delete(:original_script_name)
|
306
|
-
|
307
|
-
if original_script_name
|
308
|
-
options[:script_name] = original_script_name + options[:script_name]
|
309
|
-
end
|
310
|
-
|
311
|
-
url = ActionDispatch::Http::URL.url_for(options)
|
312
|
-
|
313
|
-
::Addressable::Template.new(url)
|
314
|
-
end
|
315
|
-
end
|
316
|
-
# rubocop:enable all
|
317
|
-
|
318
128
|
def params_for(controller, action)
|
319
129
|
ctr = "#{controller.camelize}Controller".constantize
|
320
130
|
ctr.rfc6570_defs[action.to_sym] if ctr.respond_to?(:rfc6570_defs)
|
@@ -322,6 +132,10 @@ module Rails
|
|
322
132
|
nil
|
323
133
|
end
|
324
134
|
|
135
|
+
def build_url_template(ctx, route, **kwargs)
|
136
|
+
route.to_rfc6570(ctx: ctx, **kwargs)
|
137
|
+
end
|
138
|
+
|
325
139
|
extend self # rubocop:disable ModuleFunction
|
326
140
|
end
|
327
141
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module RFC6570
|
5
|
+
class Formatter
|
6
|
+
attr_reader :route
|
7
|
+
|
8
|
+
def initialize(route)
|
9
|
+
@route = route
|
10
|
+
@parts = Visitor.new(factory: method(:symbol)).accept(route.path.spec)
|
11
|
+
end
|
12
|
+
|
13
|
+
def evaluate(ignore: %w[format], ctx:, **kwargs)
|
14
|
+
parts = @parts.reject do |part|
|
15
|
+
part.is_a?(Subst) && ignore.include?(part.name)
|
16
|
+
end
|
17
|
+
|
18
|
+
if kwargs.fetch(:params, true) && route
|
19
|
+
controller = route.defaults[:controller].to_s
|
20
|
+
action = route.defaults[:action].to_s
|
21
|
+
|
22
|
+
if controller.present? && action.present?
|
23
|
+
params = ::Rails::RFC6570.params_for(controller, action)
|
24
|
+
parts << '{?' + params.join(',') + '}' if params&.any?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if kwargs.fetch(:path_only, false)
|
29
|
+
::Addressable::Template.new parts.join
|
30
|
+
else
|
31
|
+
options = ctx.url_options.merge(kwargs)
|
32
|
+
options[:path] = parts.join
|
33
|
+
|
34
|
+
if (osn = options.delete(:original_script_name))
|
35
|
+
options[:script_name] = osn + options[:script_name]
|
36
|
+
end
|
37
|
+
|
38
|
+
::Addressable::Template.new \
|
39
|
+
ActionDispatch::Http::URL.url_for(options)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def symbol(node, prefix: nil, suffix: nil)
|
44
|
+
Subst.new(node.name, "{#{prefix}#{node.name}#{suffix}}")
|
45
|
+
end
|
46
|
+
|
47
|
+
Subst = Struct.new(:name, :to_s)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module RFC6570
|
5
|
+
class Visitor < ::ActionDispatch::Journey::Visitors::Visitor
|
6
|
+
DISPATCH_CACHE = {} # rubocop:disable MutableConstant
|
7
|
+
|
8
|
+
def initialize(factory: nil)
|
9
|
+
super()
|
10
|
+
|
11
|
+
@stack = []
|
12
|
+
@factory = factory || method(:symbolize)
|
13
|
+
end
|
14
|
+
|
15
|
+
def accept(node)
|
16
|
+
Array(visit(node)).flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
def visit(node)
|
20
|
+
@stack.unshift node.type
|
21
|
+
send DISPATCH_CACHE.fetch(node.type), node
|
22
|
+
ensure
|
23
|
+
@stack.shift
|
24
|
+
end
|
25
|
+
|
26
|
+
def terminal(node)
|
27
|
+
node.left
|
28
|
+
end
|
29
|
+
|
30
|
+
# rubocop:disable MethodName
|
31
|
+
def visit_CAT(node)
|
32
|
+
if (mth = DISPATCH_CACHE[:"#{node.left.type}_#{node.right.type}"])
|
33
|
+
send mth, node.left, node.right
|
34
|
+
else
|
35
|
+
[visit(node.left), visit(node.right)]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def visit_LITERAL(node)
|
40
|
+
terminal(node)
|
41
|
+
end
|
42
|
+
|
43
|
+
def visit_SLASH(node)
|
44
|
+
terminal(node)
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit_DOT(node)
|
48
|
+
terminal(node)
|
49
|
+
end
|
50
|
+
|
51
|
+
def visit_SYMBOL(node)
|
52
|
+
symbol(node)
|
53
|
+
end
|
54
|
+
|
55
|
+
def visit_OR(_node)
|
56
|
+
raise 'OR nodes cannot be serialized to URI templates'
|
57
|
+
end
|
58
|
+
|
59
|
+
def visit_GROUP(node)
|
60
|
+
# if @stack.include?(:GROUP) && @stack[1..-1].include?(:GROUP)
|
61
|
+
# raise 'Cannot transform nested groups.'
|
62
|
+
# end
|
63
|
+
|
64
|
+
visit node.left
|
65
|
+
end
|
66
|
+
|
67
|
+
def visit_DOT_SYMBOL(dot, node)
|
68
|
+
if @stack[0..1] == %i[CAT GROUP]
|
69
|
+
symbol(node, prefix: '.')
|
70
|
+
else
|
71
|
+
[visit(dot), visit(node)]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def visit_SLASH_SYMBOL(slash, node)
|
76
|
+
if @stack[0..1] == %i[CAT GROUP]
|
77
|
+
symbol(node, prefix: '/')
|
78
|
+
else
|
79
|
+
[visit(slash), visit(node)]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# rubocop:disable AbcSize
|
84
|
+
def visit_SLASH_CAT(slash, cat)
|
85
|
+
if cat.left.type == :STAR
|
86
|
+
[symbol(cat.left.left, prefix: '/', suffix: '*'), visit(cat.right)]
|
87
|
+
elsif cat.left.type == :SYMBOL && @stack[0..1] == %i[CAT GROUP]
|
88
|
+
[symbol(cat.left, prefix: '/'), visit(cat.right)]
|
89
|
+
else
|
90
|
+
[visit(slash), visit(cat)]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
# rubocop:enable AbcSize
|
94
|
+
|
95
|
+
def visit_SLASH_STAR(_slash, star)
|
96
|
+
symbol(star.left, prefix: '/', suffix: '*')
|
97
|
+
end
|
98
|
+
|
99
|
+
def visit_STAR_CAT(star, cat)
|
100
|
+
[symbol(star.left, prefix: '/', suffix: '*'), visit(cat)]
|
101
|
+
end
|
102
|
+
# rubocop:enable MethodName
|
103
|
+
|
104
|
+
instance_methods(true).each do |meth|
|
105
|
+
next unless meth =~ /^visit_(.*)$/
|
106
|
+
DISPATCH_CACHE[Regexp.last_match(1).to_sym] = meth
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def symbol(node, **kwargs)
|
112
|
+
@factory.call(node, **kwargs)
|
113
|
+
end
|
114
|
+
|
115
|
+
def symbolize(node, prefix: nil, suffix: nil)
|
116
|
+
"{#{prefix}#{node.name}#{suffix}}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|