grape-swagger 0.34.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
  s.license = 'MIT'
15
15
 
16
16
  s.required_ruby_version = '>= 2.4'
17
- s.add_runtime_dependency 'grape', '>= 0.16.2', '<= 1.2.5'
17
+ s.add_runtime_dependency 'grape', '~> 1.3'
18
18
 
19
19
  s.files = `git ls-files`.split("\n")
20
20
  s.test_files = `git ls-files -- {test,spec}/*`.split("\n")
@@ -109,6 +109,7 @@ end
109
109
  module SwaggerDocumentationAdder
110
110
  attr_accessor :combined_namespaces, :combined_namespace_identifiers
111
111
  attr_accessor :combined_routes, :combined_namespace_routes
112
+
112
113
  include SwaggerRouting
113
114
 
114
115
  def add_swagger_documentation(options = {})
@@ -4,8 +4,8 @@ module GrapeSwagger
4
4
  module DocMethods
5
5
  class BuildModelDefinition
6
6
  class << self
7
- def build(model, properties, required)
8
- definition = { type: 'object', properties: properties }
7
+ def build(model, properties, required, other_def_properties = {})
8
+ definition = { type: 'object', properties: properties }.merge(other_def_properties)
9
9
 
10
10
  if required.nil?
11
11
  required_attrs = required_attributes(model)
@@ -17,6 +17,57 @@ module GrapeSwagger
17
17
  definition
18
18
  end
19
19
 
20
+ def parse_params_from_model(parsed_response, model, model_name)
21
+ if parsed_response.is_a?(Hash) && parsed_response.keys.first == :allOf
22
+ refs_or_models = parsed_response[:allOf]
23
+ parsed = parse_refs_and_models(refs_or_models, model)
24
+
25
+ {
26
+ allOf: parsed
27
+ }
28
+ else
29
+ properties, required = parsed_response
30
+ unless properties&.any?
31
+ raise GrapeSwagger::Errors::SwaggerSpec,
32
+ "Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
33
+ end
34
+ properties, other_def_properties = parse_properties(properties)
35
+
36
+ build(
37
+ model, properties, required, other_def_properties
38
+ )
39
+ end
40
+ end
41
+
42
+ def parse_properties(properties)
43
+ other_properties = {}
44
+
45
+ discriminator_key, discriminator_value =
46
+ properties.find do |_key, value|
47
+ value[:documentation].try(:[], :is_discriminator)
48
+ end
49
+
50
+ if discriminator_key
51
+ discriminator_value.delete(:documentation)
52
+ properties[discriminator_key] = discriminator_value
53
+
54
+ other_properties[:discriminator] = discriminator_key
55
+ end
56
+
57
+ [properties, other_properties]
58
+ end
59
+
60
+ def parse_refs_and_models(refs_or_models, model)
61
+ refs_or_models.map do |ref_or_models|
62
+ if ref_or_models.is_a?(Hash) && ref_or_models.keys.first == '$ref'
63
+ ref_or_models
64
+ else
65
+ properties, required = ref_or_models
66
+ GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
67
+ end
68
+ end
69
+ end
70
+
20
71
  private
21
72
 
22
73
  def required_attributes(model)
@@ -16,7 +16,7 @@ module GrapeSwagger
16
16
  'object'
17
17
  when 'Rack::Multipart::UploadedFile', 'File'
18
18
  'file'
19
- when 'Virtus::Attribute::Boolean'
19
+ when 'Grape::API::Boolean'
20
20
  'boolean'
21
21
  when 'BigDecimal'
22
22
  'double'
@@ -48,14 +48,14 @@ module GrapeSwagger
48
48
  end
49
49
 
50
50
  def parse_entity_name(model)
51
- if model.methods(false).include?(:entity_name)
51
+ if model.respond_to?(:entity_name)
52
52
  model.entity_name
53
53
  elsif model.to_s.end_with?('::Entity', '::Entities')
54
- model.to_s.split('::')[-2]
55
- elsif model.respond_to?(:name)
56
- model.name.demodulize.camelize
54
+ model.to_s.split('::')[0..-2].join('_')
55
+ elsif model.to_s.start_with?('Entity::', 'Entities::', 'Representable::')
56
+ model.to_s.split('::')[1..-1].join('_')
57
57
  else
58
- model.to_s.split('::').last
58
+ model.to_s.split('::').join('_')
59
59
  end
60
60
  end
61
61
 
@@ -16,8 +16,8 @@ module GrapeSwagger
16
16
 
17
17
  def manipulate(path)
18
18
  operation = path.split('/').map(&:capitalize).join
19
- operation.gsub!(/\-(\w)/, &:upcase).delete!('-') if operation[/\-(\w)/]
20
- operation.gsub!(/\_(\w)/, &:upcase).delete!('_') if operation.include?('_')
19
+ operation.gsub!(/-(\w)/, &:upcase).delete!('-') if operation[/-(\w)/]
20
+ operation.gsub!(/_(\w)/, &:upcase).delete!('_') if operation.include?('_')
21
21
  operation.gsub!(/\.(\w)/, &:upcase).delete!('.') if operation[/\.(\w)/]
22
22
  if path.include?('{')
23
23
  operation.gsub!(/\{(\w)/, &:upcase)
@@ -26,6 +26,7 @@ module GrapeSwagger
26
26
  document_range_values(settings) unless value_type[:is_array]
27
27
  document_required(settings)
28
28
  document_additional_properties(settings)
29
+ document_add_extensions(settings)
29
30
 
30
31
  @parsed_param
31
32
  end
@@ -62,6 +63,10 @@ module GrapeSwagger
62
63
  @parsed_param[:format] = settings[:format] if settings[:format].present?
63
64
  end
64
65
 
66
+ def document_add_extensions(settings)
67
+ GrapeSwagger::DocMethods::Extensions.add_extensions_to_root(settings, @parsed_param)
68
+ end
69
+
65
70
  def document_array_param(value_type, definitions)
66
71
  if value_type[:documentation].present?
67
72
  param_type = value_type[:documentation][:param_type]
@@ -72,6 +77,19 @@ module GrapeSwagger
72
77
 
73
78
  param_type ||= value_type[:param_type]
74
79
 
80
+ array_items = parse_array_item(
81
+ definitions,
82
+ type,
83
+ value_type
84
+ )
85
+
86
+ @parsed_param[:in] = param_type || 'formData'
87
+ @parsed_param[:items] = array_items
88
+ @parsed_param[:type] = 'array'
89
+ @parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
90
+ end
91
+
92
+ def parse_array_item(definitions, type, value_type)
75
93
  array_items = {}
76
94
  if definitions[value_type[:data_type]]
77
95
  array_items['$ref'] = "#/definitions/#{@parsed_param[:type]}"
@@ -86,10 +104,7 @@ module GrapeSwagger
86
104
 
87
105
  array_items[:default] = value_type[:default] if value_type[:default].present?
88
106
 
89
- @parsed_param[:in] = param_type || 'formData'
90
- @parsed_param[:items] = array_items
91
- @parsed_param[:type] = 'array'
92
- @parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
107
+ array_items
93
108
  end
94
109
 
95
110
  def document_additional_properties(settings)
@@ -196,10 +196,7 @@ module Grape
196
196
  end
197
197
 
198
198
  def response_object(route, options)
199
- codes = http_codes_from_route(route)
200
- codes.map! { |x| x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x }
201
-
202
- codes.each_with_object({}) do |value, memo|
199
+ codes(route).each_with_object({}) do |value, memo|
203
200
  value[:message] ||= ''
204
201
  memo[value[:code]] = { description: value[:message] }
205
202
 
@@ -225,6 +222,12 @@ module Grape
225
222
  end
226
223
  end
227
224
 
225
+ def codes(route)
226
+ http_codes_from_route(route).map do |x|
227
+ x.is_a?(Array) ? { code: x[0], message: x[1], model: x[2], examples: x[3], headers: x[4] } : x
228
+ end
229
+ end
230
+
228
231
  def success_code?(code)
229
232
  status = code.is_a?(Array) ? code.first : code[:code]
230
233
  status.between?(200, 299)
@@ -301,7 +304,7 @@ module Grape
301
304
  default_type(required)
302
305
 
303
306
  request_params = unless declared_params.nil? && route.headers.nil?
304
- GrapeSwagger::Endpoint::ParamsParser.parse_request_params(required, settings)
307
+ GrapeSwagger::Endpoint::ParamsParser.parse_request_params(required, settings, self)
305
308
  end || {}
306
309
 
307
310
  request_params.empty? ? required : request_params
@@ -340,13 +343,10 @@ module Grape
340
343
  parser = GrapeSwagger.model_parsers.find(model)
341
344
  raise GrapeSwagger::Errors::UnregisteredParser, "No parser registered for #{model_name}." unless parser
342
345
 
343
- properties, required = parser.new(model, self).call
344
- unless properties&.any?
345
- raise GrapeSwagger::Errors::SwaggerSpec,
346
- "Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
347
- end
346
+ parsed_response = parser.new(model, self).call
348
347
 
349
- @definitions[model_name] = GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
348
+ @definitions[model_name] =
349
+ GrapeSwagger::DocMethods::BuildModelDefinition.parse_params_from_model(parsed_response, model, model_name)
350
350
 
351
351
  model_name
352
352
  end
@@ -3,15 +3,16 @@
3
3
  module GrapeSwagger
4
4
  module Endpoint
5
5
  class ParamsParser
6
- attr_reader :params, :settings
6
+ attr_reader :params, :settings, :endpoint
7
7
 
8
- def self.parse_request_params(params, settings)
9
- new(params, settings).parse_request_params
8
+ def self.parse_request_params(params, settings, endpoint)
9
+ new(params, settings, endpoint).parse_request_params
10
10
  end
11
11
 
12
- def initialize(params, settings)
12
+ def initialize(params, settings, endpoint)
13
13
  @params = params
14
14
  @settings = settings
15
+ @endpoint = endpoint
15
16
  end
16
17
 
17
18
  def parse_request_params
@@ -55,7 +56,13 @@ module GrapeSwagger
55
56
  return true unless param_options.key?(:documentation) && !param_options[:required]
56
57
 
57
58
  param_hidden = param_options[:documentation].fetch(:hidden, false)
58
- param_hidden = param_hidden.call if param_hidden.is_a?(Proc)
59
+ if param_hidden.is_a?(Proc)
60
+ param_hidden = if settings[:token_owner]
61
+ param_hidden.call(endpoint.send(settings[:token_owner].to_sym))
62
+ else
63
+ param_hidden.call
64
+ end
65
+ end
59
66
  !param_hidden
60
67
  end
61
68
 
@@ -10,17 +10,25 @@ module GrapeSwagger
10
10
  include Rack::Test::Methods
11
11
 
12
12
  attr_reader :oapi
13
- attr_reader :api_class
14
13
 
15
14
  def initialize(api_class)
16
15
  super()
17
16
 
18
- @api_class = api_class
17
+ if api_class.is_a? String
18
+ @api_class_name = api_class
19
+ else
20
+ @api_class = api_class
21
+ end
22
+
19
23
  define_tasks
20
24
  end
21
25
 
22
26
  private
23
27
 
28
+ def api_class
29
+ @api_class ||= @api_class_name.constantize
30
+ end
31
+
24
32
  def define_tasks
25
33
  namespace :oapi do
26
34
  fetch
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GrapeSwagger
4
- VERSION = '0.34.0'
4
+ VERSION = '1.2.0'
5
5
  end
@@ -35,5 +35,5 @@ describe '#427 nested entity given as string' do
35
35
  JSON.parse(last_response.body)['definitions']
36
36
  end
37
37
 
38
- specify { expect(subject.keys).to include 'RoleEntity', 'WithoutRole' }
38
+ specify { expect(subject.keys).to include 'RoleEntity', 'Permission_WithoutRole' }
39
39
  end
@@ -55,6 +55,8 @@ describe 'definition names' do
55
55
  class Class7
56
56
  class SeventhEntity < Class6::SixthEntity
57
57
  expose :seventh_thing
58
+
59
+ private_class_method :entity_name
58
60
  end
59
61
  end
60
62
  end
@@ -82,11 +84,11 @@ describe 'definition names' do
82
84
  JSON.parse(last_response.body)['definitions']
83
85
  end
84
86
 
85
- specify { expect(subject).to include 'Class1' }
86
- specify { expect(subject).to include 'Class2' }
87
+ specify { expect(subject).to include 'TestDefinition_DummyEntities_WithVeryLongName_AnotherGroupingModule_Class1' }
88
+ specify { expect(subject).to include 'TestDefinition_DummyEntities_WithVeryLongName_AnotherGroupingModule_Class2' }
87
89
  specify { expect(subject).to include 'FooKlass' }
88
- specify { expect(subject).to include 'FourthEntity' }
89
- specify { expect(subject).to include 'FithEntity' }
90
+ specify { expect(subject).to include 'TestDefinition_DummyEntities_WithVeryLongName_AnotherGroupingModule_Class4_FourthEntity' }
91
+ specify { expect(subject).to include 'TestDefinition_DummyEntities_WithVeryLongName_AnotherGroupingModule_Class5_FithEntity' }
90
92
  specify { expect(subject).to include 'BarKlass' }
91
- specify { expect(subject).to include 'SeventhEntity' }
93
+ specify { expect(subject).to include 'TestDefinition_DummyEntities_WithVeryLongName_AnotherGroupingModule_Class7_SeventhEntity' }
92
94
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe '#532 allow custom format' do
6
+ let(:app) do
7
+ Class.new(Grape::API) do
8
+ namespace :issue_784 do
9
+ params do
10
+ requires :logs, type: String, documentation: { format: 'log', x: { name: 'Log' } }
11
+ optional :phone_number, type: Integer, documentation: { format: 'phone_number', x: { name: 'PhoneNumber' } }
12
+ end
13
+
14
+ post do
15
+ present params
16
+ end
17
+ end
18
+
19
+ add_swagger_documentation format: :json
20
+ end
21
+ end
22
+
23
+ subject do
24
+ get '/swagger_doc'
25
+ JSON.parse(last_response.body)
26
+ end
27
+
28
+ let(:parameters) { subject['paths']['/issue_784']['post']['parameters'] }
29
+
30
+ specify do
31
+ expect(parameters).to eql(
32
+ [
33
+ { 'in' => 'formData', 'name' => 'logs', 'type' => 'string', 'format' => 'log', 'required' => true, 'x-name' => 'Log' },
34
+ { 'in' => 'formData', 'name' => 'phone_number', 'type' => 'integer', 'format' => 'phone_number', 'required' => false, 'x-name' => 'PhoneNumber' }
35
+ ]
36
+ )
37
+ end
38
+ end
@@ -49,14 +49,26 @@ describe GrapeSwagger::DocMethods::DataType do
49
49
  it { is_expected.to eq 'MyInteger' }
50
50
  end
51
51
 
52
+ describe 'Types in array with inherited entity_name' do
53
+ before do
54
+ stub_const 'EntityBase', Class.new
55
+ allow(EntityBase).to receive(:entity_name).and_return 'MyInteger'
56
+ stub_const 'MyEntity', Class.new(EntityBase)
57
+ end
58
+
59
+ let(:value) { { type: '[MyEntity]' } }
60
+
61
+ it { is_expected.to eq 'MyInteger' }
62
+ end
63
+
52
64
  describe 'Rack::Multipart::UploadedFile' do
53
65
  let(:value) { { type: Rack::Multipart::UploadedFile } }
54
66
 
55
67
  it { is_expected.to eq 'file' }
56
68
  end
57
69
 
58
- describe 'Virtus::Attribute::Boolean' do
59
- let(:value) { { type: Virtus::Attribute::Boolean } }
70
+ describe 'Grape::API::Boolean' do
71
+ let(:value) { { type: Grape::API::Boolean } }
60
72
 
61
73
  it { is_expected.to eq 'boolean' }
62
74
  end
@@ -5,8 +5,9 @@ require 'spec_helper'
5
5
  describe GrapeSwagger::Endpoint::ParamsParser do
6
6
  let(:settings) { {} }
7
7
  let(:params) { [] }
8
+ let(:endpoint) { nil }
8
9
 
9
- let(:parser) { described_class.new(params, settings) }
10
+ let(:parser) { described_class.new(params, settings, endpoint) }
10
11
 
11
12
  describe '#parse_request_params' do
12
13
  subject(:parse_request_params) { parser.parse_request_params }
@@ -49,7 +49,7 @@ describe Grape::Endpoint do
49
49
  describe 'parse_request_params' do
50
50
  let(:subject) { GrapeSwagger::Endpoint::ParamsParser }
51
51
  before do
52
- subject.send(:parse_request_params, params, {})
52
+ subject.send(:parse_request_params, params, {}, nil)
53
53
  end
54
54
 
55
55
  context 'when params do not contain an array' do
@@ -3,8 +3,8 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  RSpec.describe GrapeSwagger::Rake::OapiTasks do
6
- let(:api) do
7
- item = Class.new(Grape::API) do
6
+ module Api
7
+ class Item < Grape::API
8
8
  version 'v1', using: :path
9
9
 
10
10
  namespace :item do
@@ -16,14 +16,24 @@ RSpec.describe GrapeSwagger::Rake::OapiTasks do
16
16
  end
17
17
  end
18
18
 
19
- Class.new(Grape::API) do
19
+ class Base < Grape::API
20
20
  prefix :api
21
- mount item
21
+ mount Api::Item
22
22
  add_swagger_documentation add_version: true
23
23
  end
24
24
  end
25
25
 
26
- subject { described_class.new(api) }
26
+ subject { described_class.new(Api::Base) }
27
+
28
+ describe '.new' do
29
+ it 'accepts class name as a constant' do
30
+ expect(described_class.new(::Api::Base).send(:api_class)).to eq(Api::Base)
31
+ end
32
+
33
+ it 'accepts class name as a string' do
34
+ expect(described_class.new('::Api::Base').send(:api_class)).to eq(Api::Base)
35
+ end
36
+ end
27
37
 
28
38
  describe '#make_request' do
29
39
  describe 'complete documentation' do