Skip to content

Commit

Permalink
Add support for optional path segments
Browse files Browse the repository at this point in the history
  • Loading branch information
spaceraccoon committed Nov 10, 2022
1 parent 1eb9fe6 commit 4539dc6
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 38 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#### Features

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

#### Fixes
Expand Down
20 changes: 18 additions & 2 deletions lib/grape-swagger/doc_methods/path_string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ module GrapeSwagger
module DocMethods
class PathString
class << self
def build(route, options = {})
path = route.path.dup
def build(route, path, options = {})
# always removing format
path.sub!(/\(\.\w+?\)$/, '')
path.sub!('(.:format)', '')
Expand All @@ -29,6 +28,23 @@ def build(route, options = {})

[item, path.start_with?('/') ? path : "/#{path}"]
end

def generate_optional_segments(path)
# always removing format
path.sub!(/\(\.\w+?\)$/, '')
path.sub!('(.:format)', '')

paths = []
if path.match(/\(.+\)/)
# recurse with included optional segment
paths.concat(generate_optional_segments(path.sub(/\([^\)]+\)/, '')))
# recurse with excluded optional segment
paths.concat(generate_optional_segments(path.sub(/\(/, '').sub(/\)/, '')))
else
paths << path
end
paths
end
end
end
end
Expand Down
21 changes: 10 additions & 11 deletions lib/grape-swagger/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,17 @@ def path_item(routes, options)
routes.each do |route|
next if hidden?(route, options)

@item, path = GrapeSwagger::DocMethods::PathString.build(route, options)
@entity = route.entity || route.options[:success]

verb, method_object = method_object(route, options, path)

if @paths.key?(path.to_s)
@paths[path.to_s][verb] = method_object
else
@paths[path.to_s] = { verb => method_object }
GrapeSwagger::DocMethods::PathString.generate_optional_segments(route.path.dup).each do |path|
@item, path = GrapeSwagger::DocMethods::PathString.build(route, path, options)
@entity = route.entity || route.options[:success]
verb, method_object = method_object(route, options, path)
if @paths.key?(path.to_s)
@paths[path.to_s][verb] = method_object
else
@paths[path.to_s] = { verb => method_object }
end
GrapeSwagger::DocMethods::Extensions.add(@paths[path.to_s], @definitions, route)
end

GrapeSwagger::DocMethods::Extensions.add(@paths[path.to_s], @definitions, route)
end
end

Expand Down
29 changes: 29 additions & 0 deletions spec/issues/878_optional_path_segments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

require 'spec_helper'

describe '#878 handle optional path segments' do
let(:app) do
Class.new(Grape::API) do
resource :books do
get 'page(/one)(/:two)/three' do
{ message: 'hello world' }
end
end

add_swagger_documentation
end
end
let(:parameters) { subject['paths']['/books/page/{two}/three']['get']['parameters'] }

subject do
get '/swagger_doc'
JSON.parse(last_response.body)
end

specify do
section_param = parameters.find { |param| param['name'] == 'two' }
expect(section_param['in']).to eq 'path'
expect subject['paths'].keys.to eq ['/books/page/three', '/books/page/{two}/three', '/books/page/one/three', '/books/page/one/{two}/three']
end
end
50 changes: 25 additions & 25 deletions spec/lib/path_string_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
specify 'The original route path is not mutated' do
route = Struct.new(:version, :path).new
route.path = '/foo/:dynamic/bar'
subject.build(route, add_version: true)
subject.build(route, route.path.dup, add_version: true)
expect(route.path).to eq '/foo/:dynamic/bar'
end

Expand All @@ -23,17 +23,17 @@

specify 'The returned path includes version' do
route.path = '/{version}/thing(.json)'
expect(subject.build(route, options)).to eql ['Thing', '/v1/thing']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/v1/thing']
route.path = '/{version}/thing/foo(.json)'
expect(subject.build(route, options)).to eql ['Foo', '/v1/thing/foo']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/v1/thing/foo']
route.path = '/{version}/thing(.:format)'
expect(subject.build(route, options)).to eql ['Thing', '/v1/thing']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/v1/thing']
route.path = '/{version}/thing/foo(.:format)'
expect(subject.build(route, options)).to eql ['Foo', '/v1/thing/foo']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/v1/thing/foo']
route.path = '/{version}/thing/:id'
expect(subject.build(route, options)).to eql ['Thing', '/v1/thing/{id}']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/v1/thing/{id}']
route.path = '/{version}/thing/foo/:id'
expect(subject.build(route, options)).to eql ['Foo', '/v1/thing/foo/{id}']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/v1/thing/foo/{id}']
end
end

Expand All @@ -43,17 +43,17 @@

specify 'The returned path does not include version' do
route.path = '/{version}/thing(.json)'
expect(subject.build(route, options)).to eql ['Thing', '/thing']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
route.path = '/{version}/thing/foo(.json)'
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
route.path = '/{version}/thing(.:format)'
expect(subject.build(route, options)).to eql ['Thing', '/thing']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
route.path = '/{version}/thing/foo(.:format)'
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
route.path = '/{version}/thing/:id'
expect(subject.build(route, options)).to eql ['Thing', '/thing/{id}']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing/{id}']
route.path = '/{version}/thing/foo/:id'
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo/{id}']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo/{id}']
end
end

Expand All @@ -63,17 +63,17 @@

specify 'The returned path does not include version' do
route.path = '/{version}/thing(.json)'
expect(subject.build(route, options)).to eql ['Thing', '/thing']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
route.path = '/{version}/thing/foo(.json)'
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
route.path = '/{version}/thing(.:format)'
expect(subject.build(route, options)).to eql ['Thing', '/thing']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
route.path = '/{version}/thing/foo(.:format)'
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
route.path = '/{version}/thing/:id'
expect(subject.build(route, options)).to eql ['Thing', '/thing/{id}']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing/{id}']
route.path = '/{version}/thing/foo/:id'
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo/{id}']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo/{id}']
end
end

Expand All @@ -83,17 +83,17 @@

specify 'The returned path does not include version' do
route.path = '/{version}/thing(.json)'
expect(subject.build(route, options)).to eql ['Thing', '/thing']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
route.path = '/{version}/thing/foo(.json)'
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
route.path = '/{version}/thing(.:format)'
expect(subject.build(route, options)).to eql ['Thing', '/thing']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing']
route.path = '/{version}/thing/foo(.:format)'
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo']
route.path = '/{version}/thing/:id'
expect(subject.build(route, options)).to eql ['Thing', '/thing/{id}']
expect(subject.build(route, route.path.dup, options)).to eql ['Thing', '/thing/{id}']
route.path = '/{version}/thing/foo/:id'
expect(subject.build(route, options)).to eql ['Foo', '/thing/foo/{id}']
expect(subject.build(route, route.path.dup, options)).to eql ['Foo', '/thing/foo/{id}']
end
end
end
Expand Down

0 comments on commit 4539dc6

Please sign in to comment.