Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add default_route_visibility to Defaults #888

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#### Features

* Your contribution here.
* [#888](https://github.com/ruby-grape/grape-swagger/pull/888): Add default_route_visibility to easily hide all endpoints - [@dmoss18](https://github.com/dmoss18)

#### Fixes

Expand Down
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ end
* [array_use_braces](#array_use_braces)
* [api_documentation](#api_documentation)
* [specific_api_documentation](#specific_api_documentation)
* [default_route_visibility](#default_route_visibility)
* [consumes](#consumes)
* [produces](#produces)

Expand Down Expand Up @@ -434,6 +435,23 @@ add_swagger_documentation \
specific_api_documentation: { desc: 'Reticulated splines API swagger-compatible endpoint documentation.' }
```

#### default_route_visibility
By default, grape-swagger will include all routes in the genrated documentation. You can configure this via `default_route_visibility`:
```rb
add_swagger_documentation \
default_route_visibility: :hidden
```
Grape-swagger will now *exclude* all routes in the generated documentation. You can then explicitly mark specific routes public like this:
```rb
desc 'Get all accounts', public: true
get 'accounts' do
['account1', 'account2']
end
```

**Please note**: Marking a route `public: false` or `hidden: false` will simply fall back to the overall `default_route_visibility`. You should consider `public` and `hidden` to be flags that only take effect when their value is truthy.


#### consumes

Customize the Swagger API default global `consumes` field value.
Expand All @@ -452,10 +470,12 @@ add_swagger_documentation \
produces: ['text/plain']
```


## Routes Configuration <a name="routes"></a>

* [Swagger Header Parameters](#headers)
* [Hiding an Endpoint](#hiding)
* [Hiding all Endpoints](#hiding-all-endpoints)
* [Overriding Auto-Generated Nicknames](#overriding-auto-generated-nicknames)
* [Specify endpoint details](#details)
* [Overriding the route summary](#summary)
Expand Down Expand Up @@ -530,6 +550,34 @@ state:
desc 'Conditionally hide this endpoint', hidden: lambda { ENV['EXPERIMENTAL'] != 'true' }
```

#### Hiding all endpoints <a name="hiding-all-endpoints"></a>
You can hide all endpoints by default via `default_endpoint_visibility: :hidden`. You'll need to explicitly add `public: true` to each endpoint. This is functionally the inverse of the above [Hiding an Endpoint](#hiding) section.

You can show an endpoint by adding ```public: true``` in the description of the endpoint:
```ruby
desc 'Show this endpoint', public: true
```

Or by adding ```public: true``` on the verb method of the endpoint, such as `get`, `post` and `put`:

```ruby
get '/kittens', public: true do
```

Or by using a route setting:

```ruby
route_setting :swagger, { public: true }
get '/kittens' do
```

Endpoints can be conditionally shown by providing a callable object such as a lambda which evaluates to the desired
state:

```ruby
desc 'Conditionally hide this endpoint', public: lambda { ENV['EXPERIMENTAL'] != 'true' }
```


#### Overriding Auto-Generated Nicknames <a name="overriding-auto-generated-nicknames"></a>

Expand Down
3 changes: 2 additions & 1 deletion lib/grape-swagger/doc_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ module DocMethods
specific_api_documentation: { desc: 'Swagger compatible API description for specific API' },
endpoint_auth_wrapper: nil,
swagger_endpoint_guard: nil,
token_owner: nil
token_owner: nil,
default_route_visibility: :public
}.freeze

FORMATTER_METHOD = %i[format default_format default_error_formatter].freeze
Expand Down
23 changes: 3 additions & 20 deletions lib/grape-swagger/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'active_support'
require 'active_support/core_ext/string/inflections'
require 'grape-swagger/endpoint/params_parser'
require 'grape-swagger/endpoint/visibility'

module Grape
class Endpoint # rubocop:disable Metrics/ClassLength
Expand Down Expand Up @@ -96,7 +97,7 @@ def add_definitions_from(models)
# path object
def path_item(routes, options)
routes.each do |route|
next if hidden?(route, options)
next if GrapeSwagger::Endpoint::Visibility.hidden_route?(route, options)

@item, path = GrapeSwagger::DocMethods::PathString.build(route, options)
@entity = route.entity || route.options[:success]
Expand Down Expand Up @@ -177,7 +178,7 @@ def consumes_object(route, format)

def params_object(route, options, path)
parameters = build_request_params(route, options).each_with_object([]) do |(param, value), memo|
next if hidden_parameter?(value)
next if GrapeSwagger::Endpoint::Visibility.hidden_parameter?(value)

value = { required: false }.merge(value) if value.is_a?(Hash)
_, value = default_type([[param, value]]).first if value == ''
Expand Down Expand Up @@ -435,24 +436,6 @@ def model_name(name)
GrapeSwagger::DocMethods::DataType.parse_entity_name(name)
end

def hidden?(route, options)
route_hidden = route.settings.try(:[], :swagger).try(:[], :hidden)
route_hidden = route.options[:hidden] if route.options.key?(:hidden)
return route_hidden unless route_hidden.is_a?(Proc)

options[:token_owner] ? route_hidden.call(send(options[:token_owner].to_sym)) : route_hidden.call
end

def hidden_parameter?(value)
return false if value[:required]

if value.dig(:documentation, :hidden).is_a?(Proc)
value.dig(:documentation, :hidden).call
else
value.dig(:documentation, :hidden)
end
end

def success_code_from_entity(route, entity)
default_code = GrapeSwagger::DocMethods::StatusCodes.get[route.request_method.downcase.to_sym]
if entity.is_a?(Hash)
Expand Down
40 changes: 40 additions & 0 deletions lib/grape-swagger/endpoint/visibility.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

module GrapeSwagger
module Endpoint
class Visibility
class << self
def hidden_route?(route, options)
return !public_route?(route, options) if options[:default_route_visibility] == :hidden

scan_route_for_value(:hidden, route, options)
end

def public_route?(route, options)
scan_route_for_value(:public, route, options)
end

def hidden_parameter?(value)
return false if value[:required]

if value.dig(:documentation, :hidden).is_a?(Proc)
value.dig(:documentation, :hidden).call
else
value.dig(:documentation, :hidden)
end
end

private

def scan_route_for_value(key, route, options)
key = key.to_sym
route_value = route.settings.try(:[], :swagger).try(:[], key)
route_value = route.options[key] if route.options.key?(key)
return route_value unless route_value.is_a?(Proc)

options[:token_owner] ? route_value.call(send(options[:token_owner].to_sym)) : route_value.call
end
end
end
end
end
177 changes: 177 additions & 0 deletions spec/issues/888_default_route_visbility_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'default endpoint visibility' do
let(:documentation_options) do
{ default_route_visibility: default_visibility }
end
let(:app) do
swagger_options = documentation_options
options = route_options

Class.new(Grape::API) do
desc 'Get all accounts', options
resource :accounts do
get do
[{ message: 'hello world' }]
end
end

add_swagger_documentation(swagger_options)
end
end

shared_examples 'public endpoint' do
it 'exposes endpoint' do
get_route = subject.dig('paths', '/accounts', 'get')
expect(get_route).to be_present
expect(get_route['description']).to eq 'Get all accounts'
end
end

shared_examples 'hidden endpoint' do
it 'hides endpoint' do
expect(subject.dig('paths', '/accounts')).to be_nil
end
end

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

context 'with :public default visibility' do
let(:default_visibility) { :public }

context 'with endpoint marked hidden: true' do
let(:route_options) do
{ hidden: true }
end

it_behaves_like 'hidden endpoint'
end

context 'with endpoint marked public: true' do
let(:route_options) do
{ public: true }
end

it_behaves_like 'public endpoint'
end

context 'with blank endpoint options' do
let(:route_options) do
{}
end

it_behaves_like 'public endpoint'
end

context 'with endpoint marked hidden: false' do
let(:route_options) do
{ hidden: false }
end

it_behaves_like 'public endpoint'
end

context 'with endpoint marked public: false' do
let(:route_options) do
{ public: false }
end

it_behaves_like 'public endpoint'
end
end

context 'with :hidden default visibility' do
let(:default_visibility) { :hidden }

context 'with endpoint marked public: true' do
let(:route_options) do
{ public: true }
end

it_behaves_like 'public endpoint'
end

context 'with endpoint marked hidden: true' do
let(:route_options) do
{ hidden: true }
end

it_behaves_like 'hidden endpoint'
end

context 'with blank endpoint options' do
let(:route_options) do
{}
end

it_behaves_like 'hidden endpoint'
end

context 'with endpoint marked public: false' do
let(:route_options) do
{ public: false }
end

it_behaves_like 'hidden endpoint'
end

context 'with endpoint marked hidden: false' do
let(:route_options) do
{ hidden: false }
end

it_behaves_like 'hidden endpoint'
end
end

context 'with no visibility specified' do
let(:documentation_options) do
{}
end

context 'with endpoint marked public: true' do
let(:route_options) do
{ public: true }
end

it_behaves_like 'public endpoint'
end

context 'with endpoint marked hidden: true' do
let(:route_options) do
{ hidden: true }
end

it_behaves_like 'hidden endpoint'
end

context 'with blank endpoint options' do
let(:route_options) do
{}
end

it_behaves_like 'public endpoint'
end

context 'with endpoint marked public: false' do
let(:route_options) do
{ public: false }
end

it_behaves_like 'public endpoint'
end

context 'with endpoint marked hidden: false' do
let(:route_options) do
{ hidden: false }
end

it_behaves_like 'public endpoint'
end
end
end
2 changes: 1 addition & 1 deletion spec/lib/oapi_tasks_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Base < Grape::API
end

it 'accepts class name as a string' do
expect(described_class.new('::Api::Base').send(:api_class)).to eq(Api::Base)
expect(described_class.new('Api::Base').send(:api_class)).to eq(Api::Base)
end
end

Expand Down