grape-path-helpers-1.3.0/0000755000004100000410000000000013641121342015230 5ustar www-datawww-datagrape-path-helpers-1.3.0/Gemfile.lock0000644000004100000410000000503513641121342017455 0ustar www-datawww-dataPATH remote: . specs: grape-path-helpers (1.3.0) activesupport grape (~> 1.3) rake (~> 12) GEM remote: https://rubygems.org/ specs: activesupport (6.0.2.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) zeitwerk (~> 2.2) ast (2.4.0) builder (3.2.4) coderay (1.1.2) concurrent-ruby (1.1.6) diff-lcs (1.3) dry-configurable (0.11.3) concurrent-ruby (~> 1.0) dry-core (~> 0.4, >= 0.4.7) dry-equalizer (~> 0.2) dry-container (0.7.2) concurrent-ruby (~> 1.0) dry-configurable (~> 0.1, >= 0.1.3) dry-core (0.4.9) concurrent-ruby (~> 1.0) dry-equalizer (0.3.0) dry-inflector (0.2.0) dry-logic (1.0.6) concurrent-ruby (~> 1.0) dry-core (~> 0.2) dry-equalizer (~> 0.2) dry-types (1.4.0) concurrent-ruby (~> 1.0) dry-container (~> 0.3) dry-core (~> 0.4, >= 0.4.4) dry-equalizer (~> 0.3) dry-inflector (~> 0.1, >= 0.1.2) dry-logic (~> 1.0, >= 1.0.2) grape (1.3.1) activesupport builder dry-types (>= 1.1) mustermann-grape (~> 1.0.0) rack (>= 1.3.0) rack-accept i18n (1.8.2) concurrent-ruby (~> 1.0) method_source (0.9.0) minitest (5.14.0) mustermann (1.1.1) ruby2_keywords (~> 0.0.1) mustermann-grape (1.0.1) mustermann (>= 1.0.0) parallel (1.12.1) parser (2.5.1.0) ast (~> 2.4.0) powerpack (0.1.1) pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) rack (2.2.2) rack-accept (0.4.5) rack (>= 0.4) rainbow (3.0.0) rake (12.3.3) rspec (3.7.0) rspec-core (~> 3.7.0) rspec-expectations (~> 3.7.0) rspec-mocks (~> 3.7.0) rspec-core (3.7.1) rspec-support (~> 3.7.0) rspec-expectations (3.7.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) rspec-mocks (3.7.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) rspec-support (3.7.1) rubocop (0.56.0) parallel (~> 1.10) parser (>= 2.5) powerpack (~> 0.1) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.9.0) ruby2_keywords (0.0.2) thread_safe (0.3.6) tzinfo (1.2.6) thread_safe (~> 0.1) unicode-display_width (1.3.3) zeitwerk (2.3.0) PLATFORMS ruby DEPENDENCIES grape-path-helpers! pry (~> 0.11) rspec (~> 3.7) rubocop (~> 0.56) BUNDLED WITH 1.17.3 grape-path-helpers-1.3.0/.travis.yml0000644000004100000410000000031313641121342017336 0ustar www-datawww-datalanguage: ruby cache: bundler rvm: - ruby-head - 2.3.0 - 2.2.0 - 2.1.0 - 2.0.0 - 1.9.3 - jruby-19mode - jruby-head matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head grape-path-helpers-1.3.0/README.md0000644000004100000410000001033413641121342016510 0ustar www-datawww-data# grape-path-helpers [![Build status](https://gitlab.com/gitlab-org/grape-path-helpers/badges/master/pipeline.svg)](https://gitlab.com/gitlab-org/grape-path-helpers/commits/master) Provides named route helpers for Grape APIs, similar to [Rails' route helpers](http://edgeguides.rubyonrails.org/routing.html#path-and-url-helpers). **This is a fork and rename of [grape-route-helpers](https://github.com/reprah/grape-route-helpers). It [includes some fixes](CHANGELOG.md) needed for GitLab.** ### Installation #### Compatibility with Grape If you're using grape 0.16.0 or higher, you'll need version 2.0.0 or higher of grape-path-helpers. #### Rails p 1.) Add the gem to your Gemfile. ```bash $ bundle install grape-path-helpers ``` #### Sinatra/Rack 1.) Add the gem to your Gemfile if you're using Bundler. If you're not using Bundler to install/manage dependencies: ```bash $ gem install grape-path-helpers ``` ```ruby # environment setup file require 'grape' require 'grape/path_helpers' ``` 2.) Write a rake task called `:environment` that loads the application's environment first. This gem's tasks are dependent on it. You could put this in the root of your project directory: ```ruby # Rakefile require 'rake' require 'bundler' Bundler.setup require 'grape-path-helpers' require 'grape-path-helpers/tasks' desc 'load the Sinatra environment.' task :environment do require File.expand_path('your_app_file', File.dirname(__FILE__)) end ``` ### Usage #### List All Helper Names To see which methods correspond to which paths, and which options you can pass them: ```bash # In your API root directory, at the command line $ rake grape:path_helpers ``` #### Use Helpers in IRB/Pry You can use helper methods in your REPL session by including a module: ```ruby [1] pry(main)> include GrapePathHelpers::NamedRouteMatcher ``` #### Use Helpers in Your API Use the methods inside your Grape API actions. Given this example API: ```ruby class ExampleAPI < Grape::API::Instance version 'v1' prefix 'api' format 'json' get 'ping' do 'pong' end resource :cats do get '/' do %w(cats cats cats) end route_param :id do get do 'cat' end end end route :any, '*anything' do redirect api_v1_cats_path end end ``` You'd have the following methods available inside your Grape API actions: ```ruby # specifying the version when using Grape's "path" versioning strategy api_v1_ping_path # => '/api/v1/ping.json' # specifying the format api_v1_cats_path(format: '.xml') # => '/api/v1/cats.xml' # adding a query string api_v1_cats_path(params: { sort_by: :age }) # => '/api/v1/cats?sort_by=age' # passing in values required to build a path api_v1_cats_path(id: 1) # => '/api/v1/cats/1.json' # catch-all paths have helpers api_v1_anything_path # => '/api/v1/*anything' ``` #### Custom Helper Names If you want to assign a custom helper name to a route, pass the `:as` option when creating your route in your API: ```ruby class Base < Grape::API::Instance get 'ping', as: 'is_the_server_running' 'pong' end end ``` This results in creating a helper called `is_the_server_running_path`. #### Testing You can use route helpers in your API tests by including the `GrapePathHelpers::NamedRouteMatcher` module inside your specs. Here's an example: ```ruby require 'spec_helper' describe Api::Base do include GrapePathHelpers::NamedRouteMatcher describe 'GET /ping' do it 'returns a 200 OK' do get api_v2_ping_path expect(response.status).to be(200) end end end ``` ### Contributing 1.) Fork it 2.) Create your feature branch `(git checkout -b my-new-feature)` 3.) Write specs for your feature 4.) Commit your changes `(git commit -am 'Add some feature')` 5.) Push to the branch `(git push origin my-new-feature)` 6.) Create a new pull request ### Releasing 1. Update the [CHANGELOG](CHANGELOG.md). 2. Update the version in [lib/grape-path-helpers/version.rb](lib/grape-path-helpers/version.rb). 3. [Tag](https://gitlab.com/gitlab-org/grape-path-helpers/-/tags) the commit with the version number prefixed by 'v'. 4. Run `gem build grape-path-helpers` locally. 5. Run `gem push $new_file.gem` locally, where `$new_file` is the file generated by the step above. ### License See LICENSE grape-path-helpers-1.3.0/spec/0000755000004100000410000000000013641121342016162 5ustar www-datawww-datagrape-path-helpers-1.3.0/spec/grape_path_helpers/0000755000004100000410000000000013641121342022016 5ustar www-datawww-datagrape-path-helpers-1.3.0/spec/grape_path_helpers/named_route_matcher_spec.rb0000644000004100000410000001606613641121342027373 0ustar www-datawww-datarequire 'spec_helper' # rubocop:disable Metrics/BlockLength describe GrapePathHelpers::NamedRouteMatcher do include described_class let(:routes) do Grape::API::Instance.decorated_routes end let(:ping_route) do routes.detect do |route| route.route_path =~ /ping/ && route.route_version == 'v1' end end let(:index_route) do routes.detect do |route| route.route_namespace =~ /cats$/ end end let(:show_route) do routes.detect do |route| route.route_namespace =~ %r{cats/:id} end end let(:helper_class) do fake_class = Class.new do prepend GrapePathHelpers::NamedRouteMatcher def method_missing(_method_id, *_args) 'whatever' || super end def respond_to_missing?(_method_name, _include_private = false) super end end fake_class.new end describe '#method_missing' do it 'returns super method_missing if the method does not end with path' do expect(Grape::API::Instance).not_to receive(:decorated_routes) helper_class.test_method end it 'search for the route if the method ends with path' do expect(Grape::API::Instance).to receive(:decorated_routes).and_call_original # rubocop:disable Metrics/LineLength helper_class.test_method_path end end describe '#route_match?' do context 'when route responds to a method name' do let(:route) { ping_route } let(:method_name) { :api_v1_ping_path } let(:segments) { {} } context 'when segments is not a hash' do it 'raises an ArgumentError' do expect do route_match?(route, method_name, 1234) end.to raise_error(ArgumentError) end end it 'returns true' do is_match = route_match?(route, method_name, segments) expect(is_match).to eq(true) end context 'when requested segments contains expected options' do let(:segments) { { 'format' => 'xml' } } it 'returns true' do is_match = route_match?(route, method_name, segments) expect(is_match).to eq(true) end context 'when no dynamic segments are requested' do context 'when the route requires dynamic segments' do let(:route) { show_route } let(:method_name) { :ap1_v1_cats_path } it 'returns false' do is_match = route_match?(route, method_name, segments) expect(is_match).to eq(false) end end context 'when the route does not require dynamic segments' do it 'returns true' do is_match = route_match?(route, method_name, segments) expect(is_match).to eq(true) end end end context 'when route requires the requested segments' do let(:route) { show_route } let(:method_name) { :api_v1_cats_path } let(:segments) { { id: 1 } } it 'returns true' do is_match = route_match?(route, method_name, segments) expect(is_match).to eq(true) end end context 'when route does not require the requested segments' do let(:segments) { { some_option: 'some value' } } it 'returns false' do is_match = route_match?(route, method_name, segments) expect(is_match).to eq(false) end end end context 'when segments contains unexpected options' do let(:segments) { { some_option: 'some value' } } it 'returns false' do is_match = route_match?(route, method_name, segments) expect(is_match).to eq(false) end end end context 'when route does not respond to a method name' do let(:method_name) { :some_other_path } let(:route) { ping_route } let(:segments) { {} } it 'returns false' do is_match = route_match?(route, method_name, segments) expect(is_match).to eq(false) end end end describe '#respond_to_missing?' do it 'returns super if the method does not end with path' do expect(Grape::API::Instance).not_to receive(:decorated_routes) # rubocop:disable Metrics/LineLength expect(helper_class.send(:respond_to_missing?, :test)).to eq(false) end it 'search for the route if the method ends with path' do expect(Grape::API::Instance).to receive(:decorated_routes).and_call_original # rubocop:disable Metrics/LineLength expect(helper_class.send(:respond_to_missing?, :test_path)).to eq(false) end context 'when method name with segments matches a Grape::Route path' do let(:method_name) { :api_v1_cats_path } it 'returns true' do expect(respond_to_missing?(method_name)).to eq(true) end end context 'when method name matches a Grape::Route path helper name' do let(:method_name) { :api_v1_ping_path } it 'returns true' do expect(respond_to_missing?(method_name)).to eq(true) end end context 'when method name does not match a Grape::Route path helper name' do let(:method_name) { :some_other_path } it 'returns false' do expect(respond_to_missing?(method_name)).to eq(false) end end end describe '#method_missing' do context 'when method name matches a Grape::Route path helper name' do it 'returns the path for that route object' do path = api_v1_ping_path expect(path).to eq('/api/v1/ping.json') end context 'when argument to the helper is not a hash' do it 'raises an ArgumentError' do expect do api_v1_ping_path(1234) end.to raise_error(ArgumentError) end end end context 'when method name does not match a Grape::Route path helper name' do it 'raises a NameError' do expect do some_method_name end.to raise_error(NameError) end end end context 'when Grape::Route objects share the same helper name' do context 'when helpers require different segments to generate their path' do it 'uses arguments to infer which route to use' do show_path = api_v1_cats_path('id' => 1) expect(show_path).to eq('/api/v1/cats/1.json') index_path = api_v1_cats_path expect(index_path).to eq('/api/v1/cats.json') end it 'does not get shadowed by another route with less segments' do show_path = api_v1_cats_owners_path('id' => 1) expect(show_path).to eq('/api/v1/cats/1/owners.json') show_path = api_v1_cats_owners_path('id' => 1, 'owner_id' => 1) expect(show_path).to eq('/api/v1/cats/1/owners/1.json') end end context 'when query params are passed in' do it 'uses arguments to infer which route to use' do show_path = api_v1_cats_path('id' => 1, params: { 'foo' => 'bar' }) expect(show_path).to eq('/api/v1/cats/1.json?foo=bar') index_path = api_v1_cats_path(params: { 'foo' => 'bar' }) expect(index_path).to eq('/api/v1/cats.json?foo=bar') end end end end # rubocop:enable Metrics/BlockLength grape-path-helpers-1.3.0/spec/grape_path_helpers/all_routes_spec.rb0000644000004100000410000000220213641121342025522 0ustar www-datawww-datarequire 'spec_helper' describe GrapePathHelpers::AllRoutes do Grape::API::Instance.extend described_class describe '#all_routes' do context 'when API is mounted within another API' do let(:mounting_api) { Spec::Support::MountedAPI } it 'does not include the same route twice' do mounting_api # A route is unique if no other route shares the same set of options all_route_options = Grape::API::Instance.all_routes.map do |r| r.instance_variable_get(:@options).merge(path: r.path) end duplicates = all_route_options.select do |o| all_route_options.count(o) > 1 end expect(duplicates).to be_empty end end # rubocop:disable Metrics/LineLength context 'when there are multiple POST routes with the same namespace in the same API' do it 'returns all POST routes' do expected_routes = Spec::Support::MultiplePostsAPI.routes.map(&:path) all_routes = Grape::API::Instance.all_routes expect(all_routes.map(&:path)).to include(*expected_routes) end end # rubocop:enable Metrics/LineLength end end grape-path-helpers-1.3.0/spec/grape_path_helpers/decorated_route_spec.rb0000644000004100000410000002677413641121342026545 0ustar www-datawww-datarequire 'spec_helper' # rubocop:disable Metrics/BlockLength describe GrapePathHelpers::DecoratedRoute do let(:api) { Spec::Support::API } let(:routes) do api.routes.map do |route| described_class.new(route) end end let(:index_route) do routes.detect { |route| route.route_namespace == '/cats' } end let(:show_route) do routes.detect { |route| route.route_namespace == '/cats/:id' } end let(:catch_all_route) do routes.detect { |route| route.route_path =~ /\*path/ } end let(:custom_route) do routes.detect { |route| route.route_path =~ /custom_name/ } end let(:ping_route) do routes.detect { |route| route.route_path =~ /ping/ } end let(:optional_route) do routes.detect { |route| route.route_path =~ /optional/ } end let(:wildcard_route) do routes.detect { |route| route.route_path =~ /\*owner_ids/ } end describe '#sanitize_method_name' do it 'removes characters that are illegal in Ruby method names' do illegal_names = ['beta-1', 'name_with_+', 'name_with_('] sanitized = illegal_names.map do |name| described_class.sanitize_method_name(name) end expect(sanitized).to match_array(%w[beta_1 name_with__ name_with__]) end it 'only replaces integers if they appear at the beginning' do illegal_name = '1' legal_name = 'v1' expect(described_class.sanitize_method_name(illegal_name)).to eq('_') expect(described_class.sanitize_method_name(legal_name)).to eq('v1') end end describe '#define_path_helper' do context 'with only static segments' do let(:route) { ping_route } subject { route.api_v1_ping_path } it { is_expected.to eq '/api/v1/ping.json' } end context 'with optional segments' do let(:route) { optional_route } subject { route.api_v1_cats______optional_path } it { is_expected.to eq '/api/v1/cats/(-/)/optional.json' } end context 'with dynamic segments' do let(:route) { show_route } subject { route.api_v1_cats_path(id: 1) } it { is_expected.to eq '/api/v1/cats/1.json' } end context 'with wildcard segments' do let(:route) { wildcard_route } context 'including them' do subject do route.api_v1_cats_owners_owner_ids_cats_path( { owner_ids: 'foobar' }, true ) end it { is_expected.to eq '/api/v1/cats/owners/foobar/cats.json' } end context 'excluding them' do subject { route.api_v1_cats_owners_owner_ids_cats_path } it { is_expected.to eq '/api/v1/cats/owners/owner_ids/cats.json' } end end end describe '#helper_names' do context 'when a route is given a custom helper name' do it 'uses the custom name instead of the dynamically generated one' do expect(custom_route.helper_names.first) .to eq('my_custom_route_name_path') end it 'returns the correct path' do expect( custom_route.my_custom_route_name_path ).to eq('/api/v1/custom_name.json') end end context 'when an API has multiple POST routes in a resource' do let(:api) { Spec::Support::MultiplePostsAPI } it 'it creates a helper for each POST route' do expect(routes.size).to eq(2) end end context 'when an API has multiple versions' do let(:api) { Spec::Support::APIWithMultipleVersions } it "returns the route's helper name for each version" do helper_names = ping_route.helper_names expect(helper_names.size).to eq(api.versions.size) end it 'returns an array of the routes versions' do expect(ping_route.route_version).to eq(%w[beta alpha v1]) end end context 'when an API has one version' do it "returns the route's helper name for that version" do helper_name = show_route.helper_names.first expect(helper_name).to eq('api_v1_cats_path') end end end describe '#helper_arguments' do context 'when no user input is needed to generate the correct path' do it 'returns an empty array' do expect(index_route.helper_arguments).to eq([]) end end context 'when user input is needed to generate the correct path' do it 'returns an array of required segments' do expect(show_route.helper_arguments).to eq(['id']) end end end describe '#path_segments' do context 'when path has optional segments' do it 'leaves them intact' do result = optional_route.path_segments expect(result).to eq(%w[api :version cats :id (-/) optional]) end end end describe '#path_segments_with_values' do context 'when path has dynamic segments' do it 'replaces segments with corresponding values found in options' do opts = { id: 1 } result = show_route.path_segments_with_values(opts) expect(result).to include(1) end context 'when options contains string keys' do it 'replaces segments with corresponding values found in the options' do opts = { 'id' => 1 } result = show_route.path_segments_with_values(opts) expect(result).to include(1) end end end context 'when path has wildcard segments' do context 'with regex excluding them' do it "doesn't replaces wildcard segments" do opts = { version: 4, id: 34, owner_ids: 'foobar' } result = wildcard_route.path_segments_with_values(opts) expect(result).to include(4) expect(result).to include(34) expect(result).to include('owner_ids') expect(result).not_to include('foobar') end it "doesn't blank wildcard segments" do opts = { version: 4, id: 34, owner_ids: '' } result = wildcard_route.path_segments_with_values(opts) expect(result).to include(4) expect(result).to include(34) expect(result).to include('owner_ids') end end context 'with regex including them' do context 'replaces segments' do it 'with static text' do opts = { version: 4, id: 34, owner_ids: 'foobar' } result = wildcard_route.path_segments_with_values(opts, true) expect(result).to include(4) expect(result).to include(34) expect(result).to include('foobar') expect(result).not_to include('owner_ids') end it 'with additional segments' do opts = { version: 4, id: 34, owner_ids: 'foo/bar/baz' } result = wildcard_route.path_segments_with_values(opts, true) expect(result).to include(4) expect(result).to include(34) expect(result).to include('foo/bar/baz') expect(result).not_to include('owner_ids') end end it 'blanks wildcard segments' do opts = { version: 4, id: 34, owner_ids: '' } result = wildcard_route.path_segments_with_values(opts, true) expect(result).to include(4) expect(result).to include(34) expect(result).not_to include('owner_ids') end context 'when options contains string keys' do it 'replaces segments' do opts = { 'version' => 4, 'id' => 34, 'owner_ids' => 'foobar' } result = wildcard_route.path_segments_with_values(opts, true) expect(result).to include(4) expect(result).to include(34) expect(result).to include('foobar') expect(result).not_to include('owner_ids') end end end end end describe '#path_helper_name' do it "returns the name of a route's helper method" do expect(index_route.path_helper_name).to eq('api_v1_cats_path') end context 'when the path is the root path' do let(:api_with_root) do Class.new(Grape::API) do get '/' do end end end let(:root_route) do grape_route = api_with_root.routes.first described_class.new(grape_route) end it 'returns "root_path"' do result = root_route.path_helper_name expect(result).to eq('root_path') end end context 'when the path is a catch-all path' do it 'returns a name without the glob star' do result = catch_all_route.path_helper_name expect(result).to eq('api_v1_path_path') end end context 'when the path has a wildcard segment' do it 'returns a name without the glob star' do result = wildcard_route.path_helper_name expect(result).to eq('api_v1_cats_owners_owner_ids_cats_path') end end end describe '#segment_to_value' do context 'when segment is dynamic' do it 'returns the value the segment corresponds to' do result = index_route.segment_to_value(':version') expect(result).to eq('v1') end context 'when segment is found in options' do it 'returns the value found in options' do options = { id: 1 } result = show_route.segment_to_value(':id', options) expect(result).to eq(1) end end end context 'when segment is static' do it 'returns the segment' do result = index_route.segment_to_value('api') expect(result).to eq('api') end end end describe 'path helper method' do context 'when given a "params" key' do context 'when value under "params" key is a hash' do it 'creates a query string' do query = { foo: :bar, baz: :zot } path = index_route.api_v1_cats_path(params: query) expect(path).to eq('/api/v1/cats.json?' + query.to_param) end end context 'when value under "params" is not a hash' do it 'coerces the value into a string' do path = index_route.api_v1_cats_path(params: 1) expect(path).to eq('/api/v1/cats.json?1') end end end # handle different Grape::Route#route_path formats in Grape 0.12.0 context 'when route_path contains a specific format' do it 'returns the correct path with the correct format' do path = index_route.api_v1_cats_path expect(path).to eq('/api/v1/cats.json') end end context 'when helper does not require arguments' do it 'returns the correct path' do path = index_route.api_v1_cats_path expect(path).to eq('/api/v1/cats.json') end end context 'when arguments are needed required to construct the right path' do context 'when not missing arguments' do it 'returns the correct path' do path = show_route.api_v1_cats_path(id: 1) expect(path).to eq('/api/v1/cats/1.json') end end end context "when a route's API has multiple versions" do let(:api) { Spec::Support::APIWithMultipleVersions } it 'returns a path for each version' do expect(ping_route.alpha_ping_path).to eq('/alpha/ping') expect(ping_route.beta_ping_path).to eq('/beta/ping') expect(ping_route.v1_ping_path).to eq('/v1/ping') end end context 'when a format is given' do it 'returns the path with a correct extension' do path = show_route.api_v1_cats_path(id: 1, format: '.xml') expect(path).to eq('/api/v1/cats/1.xml') end end end describe '#optional_segment?' do it { expect(index_route.optional_segment?('mandatory')).to eq(false) } it { expect(index_route.optional_segment?('(optional/)')).to eq(true) } end end # rubocop:enable Metrics/BlockLength grape-path-helpers-1.3.0/spec/spec_helper.rb0000644000004100000410000000027213641121342021001 0ustar www-datawww-data$LOAD_PATH.unshift File.expand_path('lib') require 'pry' require 'grape/path_helpers' support_files = Dir.glob('spec/support/*') support_files.each { |f| require File.expand_path(f) } grape-path-helpers-1.3.0/spec/support/0000755000004100000410000000000013641121342017676 5ustar www-datawww-datagrape-path-helpers-1.3.0/spec/support/api.rb0000644000004100000410000000310313641121342020771 0ustar www-datawww-datamodule Spec module Support # Test API class API < Grape::API::Instance version 'v1' prefix 'api' format 'json' get 'custom_name', as: :my_custom_route_name do 'hello' end get 'ping' do 'pong' end resource :cats do get '/' do %w[cats cats cats] end route_param :id do get do 'cat' end end get ':id/(-/)optional' do 'optional content' end get ':id/owners' do %w[owner1 owner2] end get ':id/owners/:owner_id' do 'owner' end get ':id/owners/*owner_ids/cats' do %w[cats cats cats] end end route :any, '*path' do 'catch-all route' end end # API with more than one version class APIWithMultipleVersions < Grape::API::Instance version %w[beta alpha v1] get 'ping' do 'pong' end end # API with another API mounted inside it class MountedAPI < Grape::API::Instance mount Spec::Support::API mount Spec::Support::APIWithMultipleVersions end # API with a version that would be illegal as a method name class APIWithIllegalVersion < Grape::API::Instance version 'beta-1' get 'ping' do 'pong' end end # API with multiple POST routes class MultiplePostsAPI < Grape::API::Instance resource :hamlet do post 'to_be' do end post 'or_not_to_be' do end end end end end grape-path-helpers-1.3.0/CHANGELOG.md0000644000004100000410000000475213641121342017051 0ustar www-datawww-data# Changelog ## 1.3.0 * [Upgrade to Grape 1.3.1](https://gitlab.com/gitlab-org/grape-path-helpers/-/merge_requests/21) ## 1.2.0 * [Add wildcard segments support](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/16) ## 1.1.0 * [Relax dependency on ActiveSupport](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/12) ## 1.0.6 * [Fix segments parsing for optional segments](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/10) ## 1.0.5 * [Relax dependencies](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/9) ## 1.0.4 * [Fix respond_to_missing? for included modules](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/8) ## 1.0.3 * [Fix return value in method_missing? implementation](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/7) * [Fix broken respond_to_missing? implementation](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/6) ## 1.0.2 * [Rename rake task from `grape:route_helpers` to `grape:path_helpers`](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/5) ## 1.0.1 * [Do not shadow helpers with the same name but more params](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/3) * [Reduce the number of calls to HashWithIndifferentAccess](https://gitlab.com/gitlab-org/grape-path-helpers/merge_requests/4) * Update to Grape 1.0 # Changelog for grape-route-helpers ## December 17 2016 * Bump to 2.1.0 * Fix bug that caused POST routes to be ignored if more than one was defined * Many thanks to @njd5475 for the bug report and pull request that helped me write this ## April 28 2016 * Release 2.0.0 * Fix incompatibility between grape-route-helpers and grape 0.16.0 ## April 11 2016 * Release 1.2.2 * Fixed incompatibility between grape-route-helpers and Ruby 2.3 by merging PR #8 from phallguy ## October 11 2015 * Release 1.2.1 * Fixed issue #4 ## September 27 2015 * Release 1.2.0 * You can now assign custom helper names to Grape routes * Fixed a bug where routes would be listed more than once in the rake task if they are mounted to another API * Added the HTTP verb to rake task output ## July 5 2015 * You can now pass query parameters to helper functions in order to generate your own query string (Issue #1) ## June 28 2015 Release 1.0.1 * Rename rake task from `grape:routes` to `grape:route_helpers` * If a Grape::Route has a specific format (json, etc.) in its route_path attribute, the helper will return a path with this extension at the end grape-path-helpers-1.3.0/.rubocop.yml0000644000004100000410000000020413641121342017476 0ustar www-datawww-dataMetrics/ClassLength: Max: 104 Enabled: false Metrics/MethodLength: Max: 21 Enabled: false Style/FileName: Enabled: false grape-path-helpers-1.3.0/grape-path-helpers.gemspec0000644000004100000410000000147413641121342022273 0ustar www-datawww-datarequire File.join(Dir.pwd, 'lib', 'grape-path-helpers', 'version') Gem::Specification.new do |gem| gem.name = 'grape-path-helpers' gem.version = GrapePathHelpers::VERSION gem.licenses = ['MIT'] gem.summary = 'Route path helpers for Grape' gem.description = 'Route path helpers for Grape' gem.authors = ['Drew Blessing', 'Harper Henn'] gem.email = '' gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) gem.homepage = 'https://gitlab.com/gitlab-org/grape-path-helpers' gem.add_runtime_dependency 'activesupport' gem.add_runtime_dependency 'grape', '~> 1.3' gem.add_runtime_dependency 'rake', '~> 12' gem.add_development_dependency 'pry', '~> 0.11' gem.add_development_dependency 'rspec', '~> 3.7' gem.add_development_dependency 'rubocop', '~> 0.56' end grape-path-helpers-1.3.0/.gitignore0000644000004100000410000000003213641121342017213 0ustar www-datawww-data*.swp *.gem .ruby-version grape-path-helpers-1.3.0/Rakefile0000644000004100000410000000051313641121342016674 0ustar www-datawww-datarequire 'rubygems' require 'bundler' Bundler.setup :default, :test, :development Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = 'spec/**/*_spec.rb' end task :spec require 'rainbow/ext/string' unless String.respond_to?(:color) task default: [:spec] grape-path-helpers-1.3.0/lib/0000755000004100000410000000000013641121342015776 5ustar www-datawww-datagrape-path-helpers-1.3.0/lib/grape/0000755000004100000410000000000013641121342017074 5ustar www-datawww-datagrape-path-helpers-1.3.0/lib/grape/path_helpers.rb0000644000004100000410000000003513641121342022075 0ustar www-datawww-datarequire 'grape-path-helpers' grape-path-helpers-1.3.0/lib/tasks/0000755000004100000410000000000013641121342017123 5ustar www-datawww-datagrape-path-helpers-1.3.0/lib/tasks/grape_path_helpers.rake0000644000004100000410000000023113641121342023617 0ustar www-datawww-datanamespace :grape do desc 'Print route helper methods.' task path_helpers: :environment do GrapePathHelpers::RouteDisplayer.new.display end end grape-path-helpers-1.3.0/lib/grape-path-helpers/0000755000004100000410000000000013641121342021466 5ustar www-datawww-datagrape-path-helpers-1.3.0/lib/grape-path-helpers/version.rb0000644000004100000410000000010513641121342023474 0ustar www-datawww-data# Gem version module GrapePathHelpers VERSION = '1.3.0'.freeze end grape-path-helpers-1.3.0/lib/grape-path-helpers/decorated_route.rb0000644000004100000410000001073513641121342025171 0ustar www-datawww-datamodule GrapePathHelpers # wrapper around Grape::Route that adds a helper method class DecoratedRoute attr_reader :route, :helper_names, :helper_arguments, :extension, :route_options PATH_SEGMENTS_REGEXP = %r{\(/?\.:?\w+\)|/(?!\))|(?<=\))|\??\*} PATH_SEGMENTS_WITH_WILDCARDS_REGEXP = %r{\(/?\.:?\w+\)|/(?!\))|(?<=\))|\?} def self.sanitize_method_name(string) string.gsub(/\W|^[0-9]/, '_') end def initialize(route) @route = route @route_options = route.options @helper_names = [] @helper_arguments = required_helper_segments @extension = default_extension define_path_helpers end def default_extension pattern = /\((\.\:?\w+)\)$/ match = route_path.match(pattern) return '' unless match ext = match.captures.first if ext == '.:format' '' else ext end end def define_path_helpers route_versions.each do |version| route_attributes = { version: version, format: extension } method_name = path_helper_name(route_attributes) @helper_names << method_name define_path_helper(method_name, route_attributes) end end def define_path_helper(method_name, route_attributes) method_body = <<-RUBY def #{method_name}(attributes = {}, include_wildcard_segments = false) attrs = #{route_attributes}.merge(attributes) query_params = attrs.delete(:params) content_type = attrs.delete(:format) path = '/' + path_segments_with_values(attrs, include_wildcard_segments).join('/') path + content_type + query_string(query_params) end RUBY instance_eval method_body end def query_string(params) if params.nil? '' else '?' + params.to_param end end def route_versions return [nil] if route_version.nil? || route_version.empty? if route_version.is_a?(String) version_pattern = /[^\[",\]\s]+/ route_version.scan(version_pattern) else route_version end end def path_helper_name(opts = {}) if route_options[:as] name = route_options[:as].to_s else segments = path_segments_with_values(opts) name = if segments.empty? 'root' else segments.join('_') end end sanitized_name = self.class.sanitize_method_name(name) sanitized_name + '_path' end def segment_to_value(segment, opts = {}) if dynamic_segment?(segment) options = route.options.merge(stringify_keys(opts)) key = segment.slice(1..-1).to_sym options[key] else segment end end def path_segments_with_values(opts, include_wildcard_segments = false) segments = path_segments(include_wildcard_segments).map do |s| segment_to_value(s, opts) end segments.reject(&:blank?) end def path_segments(include_wildcard_segments = false) pattern = if include_wildcard_segments PATH_SEGMENTS_WITH_WILDCARDS_REGEXP else PATH_SEGMENTS_REGEXP end route_path.split(pattern).reject(&:blank?) end def dynamic_path_segments segments = path_segments.select do |segment| dynamic_segment?(segment) end segments.map { |s| s.slice(1..-1) } end def dynamic_segment?(segment) segment.start_with?(':', '*') end def optional_segment?(segment) segment.start_with?('(') end def required_helper_segments segments_in_options = dynamic_path_segments.select do |segment| route.options[segment.to_sym] end dynamic_path_segments - segments_in_options end def special_keys %w[format params] end def uses_segments_in_path_helper?(segments) segments = segments.reject { |x| special_keys.include?(x) } if required_helper_segments.empty? && segments.any? false else required_helper_segments.all? { |x| segments.include?(x) } end end def route_path route.path end def route_version route.version end def route_namespace route.namespace end def route_method route.request_method end private def stringify_keys(original) original.each_with_object({}) do |(key, value), hash| hash[key.to_sym] = value end end end end grape-path-helpers-1.3.0/lib/grape-path-helpers/named_route_matcher.rb0000644000004100000410000000231713641121342026023 0ustar www-datawww-datamodule GrapePathHelpers # methods to extend Grape::Endpoint so that calls # to unknown methods will look for a route with a matching # helper function name module NamedRouteMatcher def method_missing(method_id, *arguments) return super unless method_id.to_s =~ /_path$/ segments = arguments.first || {} route = Grape::API::Instance.decorated_routes.detect do |r| route_match?(r, method_id, segments) end if route route.send(method_id, *arguments) else super end end def respond_to_missing?(method_name, _include_private = false) return super unless method_name =~ /_path$/ Grape::API::Instance.decorated_routes.detect do |route| return true if route.respond_to?(method_name) end super end def route_match?(route, method_name, segments) return false unless route.respond_to?(method_name) # rubocop:disable Metrics/LineLength raise ArgumentError, 'Helper options must be a hash' unless segments.is_a?(Hash) # rubocop:enable Metrics/LineLength requested_segments = segments.keys.map(&:to_s) route.uses_segments_in_path_helper?(requested_segments) end end end grape-path-helpers-1.3.0/lib/grape-path-helpers/railtie.rb0000644000004100000410000000040213641121342023440 0ustar www-datawww-datamodule GrapePathHelpers # Pulls in Rake helper for displaying route helper methods class Railtie < Rails::Railtie rake_tasks do files = File.join(File.dirname(__FILE__), '../tasks/*.rake') Dir[files].each { |f| load f } end end end grape-path-helpers-1.3.0/lib/grape-path-helpers/all_routes.rb0000644000004100000410000000116613641121342024170 0ustar www-datawww-datamodule GrapePathHelpers # methods to extend Grape::API's behavior so it can get a # list of routes from all APIs and decorate them with # the DecoratedRoute class module AllRoutes def decorated_routes # memoize so that construction of decorated routes happens once @decorated_routes ||= all_routes .map { |r| DecoratedRoute.new(r) } .sort_by { |r| -r.dynamic_path_segments.count } end def all_routes routes = subclasses.flat_map { |s| s.send(:prepare_routes) } routes.uniq { |r| r.options.merge(path: r.path) } end end end grape-path-helpers-1.3.0/lib/grape-path-helpers/route_displayer.rb0000644000004100000410000000167013641121342025231 0ustar www-datawww-datamodule GrapePathHelpers # class for displaying the path, helper method name, # and required arguments for every Grape::Route. class RouteDisplayer def route_attributes Grape::API::Instance.decorated_routes.map do |route| { route_path: route.route_path, route_method: route.route_method, helper_names: route.helper_names, helper_arguments: route.helper_arguments } end end def display puts("== GRAPE ROUTE HELPERS ==\n\n") route_attributes.each do |attributes| printf("%s: %s\n", 'Verb', attributes[:route_method]) printf("%s: %s\n", 'Path', attributes[:route_path]) printf("%s: %s\n", 'Helper Method', attributes[:helper_names].join(', ')) printf("%s: %s\n", 'Arguments', attributes[:helper_arguments].join(', ')) puts("\n") end end end end grape-path-helpers-1.3.0/lib/grape-path-helpers/tasks.rb0000644000004100000410000000011613641121342023136 0ustar www-datawww-dataDir[File.join(File.dirname(__FILE__), '../tasks/*.rake')].each { |f| load f } grape-path-helpers-1.3.0/lib/grape-path-helpers.rb0000644000004100000410000000100113641121342022003 0ustar www-datawww-datarequire 'grape' require 'active_support' require 'active_support/core_ext/class' require 'grape-path-helpers/decorated_route' require 'grape-path-helpers/named_route_matcher' require 'grape-path-helpers/all_routes' require 'grape-path-helpers/route_displayer' # Load the Grape route helper for Rails module GrapePathHelpers require 'grape-path-helpers/railtie' if defined?(Rails) end Grape::API::Instance.extend GrapePathHelpers::AllRoutes Grape::Endpoint.send(:include, GrapePathHelpers::NamedRouteMatcher) grape-path-helpers-1.3.0/Gemfile0000644000004100000410000000004713641121342016524 0ustar www-datawww-datasource 'https://rubygems.org' gemspec grape-path-helpers-1.3.0/LICENSE.txt0000644000004100000410000000213413641121342017053 0ustar www-datawww-dataMIT License (MIT) Copyright (c) 2015,2016 Harper Henn Copyright (c) 2018-2020 GitLab B.V. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. grape-path-helpers-1.3.0/.gitlab-ci.yml0000644000004100000410000000040013641121342017656 0ustar www-datawww-dataimage: "ruby:2.6-alpine" cache: paths: - vendor/ruby before_script: - apk add --update git - gem install bundler - bundle install --path vendor/ruby rubocop: script: - bundle exec rubocop rspec: script: - bundle exec rspec spec