Skip to content

Commit ffbebd2

Browse files
committed
Add support for optional path segments
1 parent 1eb9fe6 commit ffbebd2

File tree

5 files changed

+83
-38
lines changed

5 files changed

+83
-38
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#### Features
44

5+
* [#879](https://github.com/ruby-grape/grape-swagger/pull/879): Add support for optional path segments - [@spaceraccoon](https://github.com/spaceraccoon)
56
* Your contribution here.
67

78
#### Fixes

lib/grape-swagger/doc_methods/path_string.rb

+18-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ module GrapeSwagger
44
module DocMethods
55
class PathString
66
class << self
7-
def build(route, options = {})
8-
path = route.path.dup
7+
def build(route, path, options = {})
98
# always removing format
109
path.sub!(/\(\.\w+?\)$/, '')
1110
path.sub!('(.:format)', '')
@@ -29,6 +28,23 @@ def build(route, options = {})
2928

3029
[item, path.start_with?('/') ? path : "/#{path}"]
3130
end
31+
32+
def generate_optional_segments(path)
33+
# always removing format
34+
path.sub!(/\(\.\w+?\)$/, '')
35+
path.sub!('(.:format)', '')
36+
37+
paths = []
38+
if path.match(/\(.+\)/)
39+
# recurse with included optional segment
40+
paths.concat(generate_optional_segments(path.sub(/\([^\)]+\)/, '')))
41+
# recurse with excluded optional segment
42+
paths.concat(generate_optional_segments(path.sub(/\(/, '').sub(/\)/, '')))
43+
else
44+
paths << path
45+
end
46+
paths
47+
end
3248
end
3349
end
3450
end

lib/grape-swagger/endpoint.rb

+10-11
Original file line numberDiff line numberDiff line change
@@ -97,18 +97,17 @@ def path_item(routes, options)
9797
routes.each do |route|
9898
next if hidden?(route, options)
9999

100-
@item, path = GrapeSwagger::DocMethods::PathString.build(route, options)
101-
@entity = route.entity || route.options[:success]
102-
103-
verb, method_object = method_object(route, options, path)
104-
105-
if @paths.key?(path.to_s)
106-
@paths[path.to_s][verb] = method_object
107-
else
108-
@paths[path.to_s] = { verb => method_object }
100+
GrapeSwagger::DocMethods::PathString.generate_optional_segments(route.path.dup).each do |path|
101+
@item, path = GrapeSwagger::DocMethods::PathString.build(route, path, options)
102+
@entity = route.entity || route.options[:success]
103+
verb, method_object = method_object(route, options, path)
104+
if @paths.key?(path.to_s)
105+
@paths[path.to_s][verb] = method_object
106+
else
107+
@paths[path.to_s] = { verb => method_object }
108+
end
109+
GrapeSwagger::DocMethods::Extensions.add(@paths[path.to_s], @definitions, route)
109110
end
110-
111-
GrapeSwagger::DocMethods::Extensions.add(@paths[path.to_s], @definitions, route)
112111
end
113112
end
114113

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe '#878 handle optional path segments' do
6+
let(:app) do
7+
Class.new(Grape::API) do
8+
resource :books do
9+
get 'page(/one)(/:two)/three' do
10+
{ message: 'hello world' }
11+
end
12+
end
13+
14+
add_swagger_documentation
15+
end
16+
end
17+
let(:parameters) { subject['paths']['/books/page/{two}/three']['get']['parameters'] }
18+
19+
subject do
20+
get '/swagger_doc'
21+
JSON.parse(last_response.body)
22+
end
23+
24+
specify do
25+
section_param = parameters.find { |param| param['name'] == 'two' }
26+
expect(section_param['in']).to eq 'path'
27+
expect(subject['paths'].keys).to eq ['/books/page/three', '/books/page/{two}/three', '/books/page/one/three', '/books/page/one/{two}/three']
28+
end
29+
end

spec/lib/path_string_spec.rb

+25-25
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
specify 'The original route path is not mutated' do
1313
route = Struct.new(:version, :path).new
1414
route.path = '/foo/:dynamic/bar'
15-
subject.build(route, add_version: true)
15+
subject.build(route, route.path.dup, add_version: true)
1616
expect(route.path).to eq '/foo/:dynamic/bar'
1717
end
1818

@@ -23,17 +23,17 @@
2323

2424
specify 'The returned path includes version' do
2525
route.path = '/{version}/thing(.json)'
26-
expect(subject.build(route, options)).to eql ['Thing', '/v1/thing']
26+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/v1/thing']
2727
route.path = '/{version}/thing/foo(.json)'
28-
expect(subject.build(route, options)).to eql ['Foo', '/v1/thing/foo']
28+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/v1/thing/foo']
2929
route.path = '/{version}/thing(.:format)'
30-
expect(subject.build(route, options)).to eql ['Thing', '/v1/thing']
30+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/v1/thing']
3131
route.path = '/{version}/thing/foo(.:format)'
32-
expect(subject.build(route, options)).to eql ['Foo', '/v1/thing/foo']
32+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/v1/thing/foo']
3333
route.path = '/{version}/thing/:id'
34-
expect(subject.build(route, options)).to eql ['Thing', '/v1/thing/{id}']
34+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/v1/thing/{id}']
3535
route.path = '/{version}/thing/foo/:id'
36-
expect(subject.build(route, options)).to eql ['Foo', '/v1/thing/foo/{id}']
36+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/v1/thing/foo/{id}']
3737
end
3838
end
3939

@@ -43,17 +43,17 @@
4343

4444
specify 'The returned path does not include version' do
4545
route.path = '/{version}/thing(.json)'
46-
expect(subject.build(route, options)).to eql ['Thing', '/thing']
46+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
4747
route.path = '/{version}/thing/foo(.json)'
48-
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
48+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
4949
route.path = '/{version}/thing(.:format)'
50-
expect(subject.build(route, options)).to eql ['Thing', '/thing']
50+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
5151
route.path = '/{version}/thing/foo(.:format)'
52-
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
52+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
5353
route.path = '/{version}/thing/:id'
54-
expect(subject.build(route, options)).to eql ['Thing', '/thing/{id}']
54+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing/{id}']
5555
route.path = '/{version}/thing/foo/:id'
56-
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo/{id}']
56+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo/{id}']
5757
end
5858
end
5959

@@ -63,17 +63,17 @@
6363

6464
specify 'The returned path does not include version' do
6565
route.path = '/{version}/thing(.json)'
66-
expect(subject.build(route, options)).to eql ['Thing', '/thing']
66+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
6767
route.path = '/{version}/thing/foo(.json)'
68-
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
68+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
6969
route.path = '/{version}/thing(.:format)'
70-
expect(subject.build(route, options)).to eql ['Thing', '/thing']
70+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
7171
route.path = '/{version}/thing/foo(.:format)'
72-
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
72+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
7373
route.path = '/{version}/thing/:id'
74-
expect(subject.build(route, options)).to eql ['Thing', '/thing/{id}']
74+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing/{id}']
7575
route.path = '/{version}/thing/foo/:id'
76-
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo/{id}']
76+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo/{id}']
7777
end
7878
end
7979

@@ -83,17 +83,17 @@
8383

8484
specify 'The returned path does not include version' do
8585
route.path = '/{version}/thing(.json)'
86-
expect(subject.build(route, options)).to eql ['Thing', '/thing']
86+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
8787
route.path = '/{version}/thing/foo(.json)'
88-
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
88+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
8989
route.path = '/{version}/thing(.:format)'
90-
expect(subject.build(route, options)).to eql ['Thing', '/thing']
90+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
9191
route.path = '/{version}/thing/foo(.:format)'
92-
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
92+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
9393
route.path = '/{version}/thing/:id'
94-
expect(subject.build(route, options)).to eql ['Thing', '/thing/{id}']
94+
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing/{id}']
9595
route.path = '/{version}/thing/foo/:id'
96-
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo/{id}']
96+
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo/{id}']
9797
end
9898
end
9999
end

0 commit comments

Comments
 (0)