grape-swagger 0.11.0 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -1
  3. data/.rubocop.yml +3 -0
  4. data/.rubocop_todo.yml +14 -22
  5. data/.travis.yml +7 -4
  6. data/CHANGELOG.md +53 -26
  7. data/Gemfile +1 -1
  8. data/README.md +414 -327
  9. data/RELEASING.md +3 -4
  10. data/example/api/endpoints.rb +132 -0
  11. data/example/api/entities.rb +18 -0
  12. data/example/config.ru +36 -2
  13. data/example/example_requests.postman_collection +146 -0
  14. data/example/swagger-example.png +0 -0
  15. data/grape-swagger.gemspec +9 -6
  16. data/lib/grape-swagger.rb +69 -99
  17. data/lib/grape-swagger/doc_methods.rb +69 -544
  18. data/lib/grape-swagger/doc_methods/data_type.rb +77 -0
  19. data/lib/grape-swagger/doc_methods/extensions.rb +75 -0
  20. data/lib/grape-swagger/doc_methods/move_params.rb +153 -0
  21. data/lib/grape-swagger/doc_methods/operation_id.rb +27 -0
  22. data/lib/grape-swagger/doc_methods/optional_object.rb +15 -0
  23. data/lib/grape-swagger/doc_methods/parse_params.rb +113 -0
  24. data/lib/grape-swagger/doc_methods/path_string.rb +29 -0
  25. data/lib/grape-swagger/doc_methods/produces_consumes.rb +12 -0
  26. data/lib/grape-swagger/doc_methods/status_codes.rb +17 -0
  27. data/lib/grape-swagger/doc_methods/tag_name_description.rb +26 -0
  28. data/lib/grape-swagger/endpoint.rb +317 -0
  29. data/lib/grape-swagger/version.rb +1 -1
  30. data/spec/lib/data_type_spec.rb +57 -0
  31. data/spec/lib/endpoint_spec.rb +6 -0
  32. data/spec/lib/extensions_spec.rb +127 -0
  33. data/spec/lib/move_params_spec.rb +298 -0
  34. data/spec/lib/operation_id_spec.rb +24 -0
  35. data/spec/lib/optional_object_spec.rb +40 -0
  36. data/spec/lib/path_string_spec.rb +38 -0
  37. data/spec/lib/produces_consumes_spec.rb +98 -0
  38. data/spec/markdown/kramdown_adapter_spec.rb +2 -9
  39. data/spec/markdown/redcarpet_adapter_spec.rb +2 -16
  40. data/spec/spec_helper.rb +7 -13
  41. data/spec/support/api_swagger_v2_result.rb +204 -0
  42. data/spec/support/namespace_tags.rb +73 -0
  43. data/spec/support/the_api_entities.rb +52 -0
  44. data/spec/support/the_paths_definitions.rb +94 -0
  45. data/spec/swagger_v2/api_swagger_v2_definitions-models_spec.rb +32 -0
  46. data/spec/swagger_v2/api_swagger_v2_detail_spec.rb +151 -0
  47. data/spec/swagger_v2/api_swagger_v2_extensions_spec.rb +109 -0
  48. data/spec/swagger_v2/api_swagger_v2_format-content_type_spec.rb +124 -0
  49. data/spec/swagger_v2/api_swagger_v2_global_configuration_spec.rb +51 -0
  50. data/spec/swagger_v2/api_swagger_v2_headers_spec.rb +44 -0
  51. data/spec/swagger_v2/api_swagger_v2_hide_documentation_path_spec.rb +56 -0
  52. data/spec/swagger_v2/api_swagger_v2_mounted_spec.rb +146 -0
  53. data/spec/swagger_v2/api_swagger_v2_param_type_body_nested_spec.rb +197 -0
  54. data/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb +151 -0
  55. data/spec/swagger_v2/api_swagger_v2_param_type_spec.rb +217 -0
  56. data/spec/swagger_v2/api_swagger_v2_request_params_fix_spec.rb +64 -0
  57. data/spec/swagger_v2/api_swagger_v2_response_spec.rb +184 -0
  58. data/spec/swagger_v2/api_swagger_v2_spec.rb +207 -0
  59. data/spec/swagger_v2/api_swagger_v2_type-format_spec.rb +121 -0
  60. data/spec/{boolean_params_spec.rb → swagger_v2/boolean_params_spec.rb} +2 -2
  61. data/spec/{default_api_spec.rb → swagger_v2/default_api_spec.rb} +40 -36
  62. data/spec/swagger_v2/description_not_initialized.rb +39 -0
  63. data/spec/{float_api_spec.rb → swagger_v2/float_api_spec.rb} +2 -2
  64. data/spec/{form_params_spec.rb → swagger_v2/form_params_spec.rb} +9 -9
  65. data/spec/{grape-swagger_spec.rb → swagger_v2/grape-swagger_spec.rb} +0 -0
  66. data/spec/swagger_v2/hide_api_spec.rb +131 -0
  67. data/spec/swagger_v2/mounted_target_class_spec.rb +76 -0
  68. data/spec/swagger_v2/namespace_tags_prefix_spec.rb +84 -0
  69. data/spec/swagger_v2/namespace_tags_spec.rb +76 -0
  70. data/spec/{namespaced_api_spec.rb → swagger_v2/namespaced_api_spec.rb} +6 -26
  71. data/spec/{param_type_spec.rb → swagger_v2/param_type_spec.rb} +10 -8
  72. data/spec/{param_values_spec.rb → swagger_v2/param_values_spec.rb} +52 -22
  73. data/spec/swagger_v2/params_array_spec.rb +63 -0
  74. data/spec/swagger_v2/params_hash_spec.rb +65 -0
  75. data/spec/swagger_v2/params_nested_spec.rb +63 -0
  76. data/spec/{reference_entity.rb → swagger_v2/reference_entity.rb} +18 -23
  77. data/spec/swagger_v2/response_model_spec.rb +212 -0
  78. data/spec/swagger_v2/simple_mounted_api_spec.rb +264 -0
  79. metadata +175 -90
  80. data/example/api.rb +0 -66
  81. data/lib/grape-swagger/markdown.rb +0 -23
  82. data/spec/api_description_spec.rb +0 -43
  83. data/spec/api_global_models_spec.rb +0 -77
  84. data/spec/api_models_spec.rb +0 -364
  85. data/spec/api_paths_spec.rb +0 -128
  86. data/spec/api_root_spec.rb +0 -30
  87. data/spec/api_with_nil_types.rb +0 -50
  88. data/spec/api_with_path_versioning_spec.rb +0 -33
  89. data/spec/api_with_prefix_and_namespace_spec.rb +0 -32
  90. data/spec/api_with_standalone_namespace_spec.rb +0 -215
  91. data/spec/array_entity_spec.rb +0 -34
  92. data/spec/array_params_spec.rb +0 -85
  93. data/spec/grape-swagger_helper_spec.rb +0 -152
  94. data/spec/group_params_spec.rb +0 -31
  95. data/spec/hash_params_spec.rb +0 -30
  96. data/spec/hide_api_spec.rb +0 -124
  97. data/spec/i18n_spec.rb +0 -364
  98. data/spec/markdown/markdown_spec.rb +0 -27
  99. data/spec/mounted_target_class_spec.rb +0 -63
  100. data/spec/mutually_exclusive_spec.rb +0 -36
  101. data/spec/non_default_api_spec.rb +0 -733
  102. data/spec/response_model_spec.rb +0 -121
  103. data/spec/simple_mounted_api_spec.rb +0 -213
  104. data/spec/support/i18n_helper.rb +0 -8
@@ -0,0 +1,77 @@
1
+ module GrapeSwagger
2
+ module DocMethods
3
+ class DataType
4
+ class << self
5
+ def call(value)
6
+ raw_data_type = value[:type] if value.is_a?(Hash)
7
+ raw_data_type = value unless value.is_a?(Hash)
8
+ raw_data_type ||= 'string'
9
+ case raw_data_type.to_s
10
+ when 'Boolean', 'Date', 'Integer', 'String', 'Float', 'JSON', 'Array'
11
+ raw_data_type.to_s.downcase
12
+ when 'Hash'
13
+ 'object'
14
+ when 'Rack::Multipart::UploadedFile', 'File'
15
+ 'file'
16
+ when 'Virtus::Attribute::Boolean'
17
+ 'boolean'
18
+ when 'BigDecimal'
19
+ 'double'
20
+ when 'DateTime', 'Time'
21
+ 'dateTime'
22
+ when 'Numeric'
23
+ 'long'
24
+ when 'Symbol'
25
+ 'string'
26
+ else
27
+ parse_entity_name(raw_data_type)
28
+ end
29
+ end
30
+
31
+ def parse_entity_name(model)
32
+ if model.respond_to?(:entity_name)
33
+ model.entity_name
34
+ else
35
+ name = model.to_s
36
+ entity_parts = name.split('::')
37
+ entity_parts.reject! { |p| p == 'Entity' || p == 'Entities' }
38
+ entity_parts.join('::')
39
+ end
40
+ end
41
+
42
+ def request_primitive?(type)
43
+ request_primitives.include?(type.to_s.downcase)
44
+ end
45
+
46
+ def primitive?(type)
47
+ primitives.include?(type.to_s.downcase)
48
+ end
49
+
50
+ def request_primitives
51
+ primitives + %w(object string boolean file json array)
52
+ end
53
+
54
+ def primitives
55
+ PRIMITIVE_MAPPINGS.keys.map(&:downcase)
56
+ end
57
+
58
+ def mapping(value)
59
+ PRIMITIVE_MAPPINGS[value] || 'string'
60
+ end
61
+ end
62
+
63
+ PRIMITIVE_MAPPINGS = {
64
+ 'integer' => %w(integer int32),
65
+ 'long' => %w(integer int64),
66
+ 'float' => %w(number float),
67
+ 'double' => %w(number double),
68
+ 'byte' => %w(string byte),
69
+ 'date' => %w(string date),
70
+ 'dateTime' => %w(string date-time),
71
+ 'binary' => %w(string binary),
72
+ 'password' => %w(string password),
73
+ 'email' => %w(string email)
74
+ }.freeze
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,75 @@
1
+ module GrapeSwagger
2
+ module DocMethods
3
+ class Extensions
4
+ class << self
5
+ def add(path, definitions, route)
6
+ @route = route
7
+ description = route.route_settings[:description]
8
+ add_extension_to(path[method], extension(description)) if description && extended?(description, :x)
9
+
10
+ settings = route.route_settings
11
+ add_extensions_to_path(settings, path) if settings && extended?(settings, :x_path)
12
+ add_extensions_to_definition(settings, path, definitions) if settings && extended?(settings, :x_def)
13
+ end
14
+
15
+ def add_extensions_to_path(settings, path)
16
+ add_extension_to(path, extension(settings, :x_path))
17
+ end
18
+
19
+ def add_extensions_to_definition(settings, path, definitions)
20
+ def_extension = extension(settings, :x_def)
21
+
22
+ if def_extension[:x_def].is_a?(Array)
23
+ def_extension[:x_def].each do |extension|
24
+ next unless extension.key?(:for)
25
+ status = extension.delete(:for)
26
+ definition = find_definition(status, path)
27
+ add_extension_to(definitions[definition], x_def: extension)
28
+ end
29
+ else
30
+ return unless def_extension[:x_def].key?(:for)
31
+ status = def_extension[:x_def].delete(:for)
32
+ definition = find_definition(status, path)
33
+ add_extension_to(definitions[definition], def_extension)
34
+ end
35
+ end
36
+
37
+ def find_definition(status, path)
38
+ response = path[method][:responses][status]
39
+
40
+ response[:schema]['$ref'].split('/').last
41
+ end
42
+
43
+ def add_extension_to(part, extensions)
44
+ concatenate(extensions).each do |key, value|
45
+ part[key] = value
46
+ end
47
+ end
48
+
49
+ def concatenate(extensions)
50
+ result = {}
51
+
52
+ extensions.values.each do |extension|
53
+ extension.each do |key, value|
54
+ result["x-#{key}"] = value
55
+ end
56
+ end
57
+
58
+ result
59
+ end
60
+
61
+ def extended?(part, identifier = :x)
62
+ !extension(part, identifier).empty?
63
+ end
64
+
65
+ def extension(part, identifier = :x)
66
+ part.select { |x| x == identifier }
67
+ end
68
+
69
+ def method
70
+ @route.route_method.downcase.to_sym
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,153 @@
1
+ module GrapeSwagger
2
+ module DocMethods
3
+ class MoveParams
4
+ class << self
5
+ def to_definition(paths, definitions)
6
+ @definitions = definitions
7
+ find_post_put(paths) do |path|
8
+ find_definition_and_params(path)
9
+ end
10
+ end
11
+
12
+ def find_post_put(paths)
13
+ paths.each do |x|
14
+ found = x.last.select { |y| move_methods.include?(y) }
15
+ yield found unless found.empty?
16
+ end
17
+ end
18
+
19
+ def find_definition_and_params(path)
20
+ path.keys.each do |verb|
21
+ params = path[verb][:parameters]
22
+
23
+ next if params.nil?
24
+ next unless should_move?(params)
25
+
26
+ unify!(params)
27
+
28
+ status_code = GrapeSwagger::DocMethods::StatusCodes.get[verb.to_sym][:code]
29
+ response = path[verb][:responses][status_code]
30
+ referenced_definition = parse_model(response[:schema]['$ref'])
31
+
32
+ name = build_definition(referenced_definition, verb)
33
+ move_params_to_new(name, params)
34
+
35
+ @definitions[name][:description] = path[verb][:description]
36
+ path[verb][:parameters] << build_body_parameter(response.dup, name)
37
+ end
38
+ end
39
+
40
+ def move_params_to_new(name, params)
41
+ properties = {}
42
+ definition = @definitions[name]
43
+
44
+ nested_definitions(name, params, properties)
45
+
46
+ params.dup.each do |param|
47
+ next unless movable?(param)
48
+
49
+ name = param[:name].to_sym
50
+ properties[name] = {}
51
+
52
+ properties[name].tap do |x|
53
+ property_keys.each do |attribute|
54
+ x[attribute] = param[attribute] unless param[attribute].nil?
55
+ end
56
+ end
57
+
58
+ properties[name][:readOnly] = true unless deletable?(param)
59
+ params.delete(param) if deletable?(param)
60
+
61
+ definition[:required] << name if deletable?(param) && param[:required]
62
+ end
63
+
64
+ definition.delete(:required) if definition[:required].empty?
65
+ definition[:properties] = properties
66
+ end
67
+
68
+ def nested_definitions(name, params, properties)
69
+ loop do
70
+ nested_name = params.bsearch { |x| x[:name].include?('[') }
71
+ return if nested_name.nil?
72
+
73
+ nested_name = nested_name[:name].split('[').first
74
+
75
+ nested, = params.partition { |x| x[:name].start_with?("#{nested_name}[") }
76
+ nested.each { |x| params.delete(x) }
77
+ nested_def_name = GrapeSwagger::DocMethods::OperationId.manipulate(nested_name)
78
+ def_name = "#{name}#{nested_def_name}"
79
+ properties[nested_name] = { '$ref' => "#/definitions/#{def_name}" }
80
+
81
+ prepare_nested_names(nested)
82
+ build_definition(def_name)
83
+ @definitions[def_name][:description] = "#{name} - #{nested_name}"
84
+ move_params_to_new(def_name, nested)
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def build_body_parameter(response, name = false)
91
+ body_param = {}
92
+ body_param.tap do |x|
93
+ x[:name] = parse_model(response[:schema]['$ref'])
94
+ x[:in] = 'body'
95
+ x[:required] = true
96
+ x[:schema] = { '$ref' => response[:schema]['$ref'] } unless name
97
+ x[:schema] = { '$ref' => "#/definitions/#{name}" } if name
98
+ end
99
+ end
100
+
101
+ def build_definition(name, verb = nil)
102
+ name = "#{verb}Request#{name}" if verb
103
+ @definitions[name] = { type: 'object', properties: {}, required: [] }
104
+
105
+ name
106
+ end
107
+
108
+ def prepare_nested_names(params)
109
+ params.each do |param|
110
+ param.tap do |x|
111
+ name = x[:name].partition('[').last.sub(']', '')
112
+ name = name.partition('[').last.sub(']', '') if name.start_with?('[')
113
+ x[:name] = name
114
+ end
115
+ end
116
+ end
117
+
118
+ def unify!(params)
119
+ params.each do |param|
120
+ param[:in] = param.delete(:param_type) if param.key?(:param_type)
121
+ param[:in] = 'body' if param[:in] == 'formData'
122
+ end
123
+ end
124
+
125
+ def parse_model(ref)
126
+ ref.split('/').last
127
+ end
128
+
129
+ def move_methods
130
+ [:post, :put, :patch]
131
+ end
132
+
133
+ def property_keys
134
+ [:type, :format, :description, :minimum, :maximum, :items]
135
+ end
136
+
137
+ def movable?(param)
138
+ return true if param[:in] == 'body' || param[:in] == 'path'
139
+ false
140
+ end
141
+
142
+ def deletable?(param)
143
+ return true if movable?(param) && param[:in] == 'body'
144
+ false
145
+ end
146
+
147
+ def should_move?(params)
148
+ !params.select { |x| x[:in] == 'body' || x[:param_type] == 'body' }.empty?
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,27 @@
1
+ module GrapeSwagger
2
+ module DocMethods
3
+ class OperationId
4
+ class << self
5
+ def build(method = nil, path = nil)
6
+ verb = method.to_s.downcase
7
+
8
+ operation = manipulate(path) unless path.nil?
9
+
10
+ "#{verb}#{operation}"
11
+ end
12
+
13
+ def manipulate(path)
14
+ operation = path.split('/').map(&:capitalize).join
15
+ operation.gsub!(/\-(\w)/, &:upcase).delete!('-') if operation.include?('-')
16
+ operation.gsub!(/\_(\w)/, &:upcase).delete!('_') if operation.include?('_')
17
+ if path.include?('{')
18
+ operation.gsub!(/\{(\w)/, &:upcase)
19
+ operation.delete!('{').delete!('}')
20
+ end
21
+
22
+ operation
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ module GrapeSwagger
2
+ module DocMethods
3
+ class OptionalObject
4
+ class << self
5
+ def build(key, options, request = nil)
6
+ if options[key]
7
+ options[key].is_a?(Proc) ? options[key].call : options[key]
8
+ else
9
+ request
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,113 @@
1
+ module GrapeSwagger
2
+ module DocMethods
3
+ class ParseParams
4
+ class << self
5
+ def call(param, settings, route)
6
+ path = route.route_path
7
+ method = route.route_method
8
+
9
+ data_type = GrapeSwagger::DocMethods::DataType.call(settings)
10
+ additional_documentation = settings[:documentation]
11
+ if additional_documentation
12
+ settings = additional_documentation.merge(settings)
13
+ end
14
+
15
+ value_type = settings.merge(data_type: data_type, path: path, param_name: param, method: method)
16
+
17
+ @parsed_param = {
18
+ in: param_type(value_type),
19
+ name: settings[:full_name] || param,
20
+ description: settings[:desc] || settings[:description] || nil
21
+ }
22
+
23
+ document_type_and_format(data_type)
24
+ document_array_param(value_type)
25
+ document_default_value(settings)
26
+ document_range_values(settings)
27
+ document_required(settings)
28
+
29
+ @parsed_param
30
+ end
31
+
32
+ private
33
+
34
+ def document_required(settings)
35
+ @parsed_param[:required] = settings[:required] || false
36
+ @parsed_param[:required] = true if @parsed_param[:in] == 'path'
37
+ end
38
+
39
+ def document_range_values(settings)
40
+ values = settings[:values] || nil
41
+ enum_or_range_values = parse_enum_or_range_values(values)
42
+ @parsed_param.merge!(enum_or_range_values) if enum_or_range_values
43
+ end
44
+
45
+ def document_default_value(settings)
46
+ default_value = settings[:default] || nil
47
+ example = settings[:example] || nil
48
+
49
+ @parsed_param[:default] = example if example
50
+ @parsed_param[:default] = default_value if default_value && example.blank?
51
+ end
52
+
53
+ def document_type_and_format(data_type)
54
+ if GrapeSwagger::DocMethods::DataType.primitive?(data_type)
55
+ data = GrapeSwagger::DocMethods::DataType.mapping(data_type)
56
+ @parsed_param[:type], @parsed_param[:format] = data
57
+ else
58
+ @parsed_param[:type] = data_type
59
+ end
60
+ end
61
+
62
+ def document_array_param(value_type)
63
+ if value_type[:is_array]
64
+ if value_type[:documentation].present?
65
+ param_type = value_type[:documentation][:param_type]
66
+ type = GrapeSwagger::DocMethods::DataType.mapping(value_type[:documentation][:type])
67
+ end
68
+ array_items = { 'type' => type || value_type[:data_type] }
69
+
70
+ @parsed_param[:in] = param_type || 'formData'
71
+ @parsed_param[:items] = array_items
72
+ @parsed_param[:type] = 'array'
73
+ @parsed_param.delete(:format)
74
+ end
75
+ end
76
+
77
+ def param_type(value_type)
78
+ param_type = value_type[:param_type] || value_type[:in]
79
+ case
80
+ when value_type[:path].include?("{#{value_type[:param_name]}}")
81
+ 'path'
82
+ when param_type
83
+ param_type
84
+ when %w(POST PUT PATCH).include?(value_type[:method])
85
+ GrapeSwagger::DocMethods::DataType.request_primitive?(value_type[:data_type]) ? 'formData' : 'body'
86
+ else
87
+ 'query'
88
+ end
89
+ end
90
+
91
+ def parse_enum_or_range_values(values)
92
+ case values
93
+ when Range
94
+ parse_range_values(values) if values.first.is_a?(Integer)
95
+ when Proc
96
+ values_result = values.call
97
+ if values_result.is_a?(Range) && values_result.first.is_a?(Integer)
98
+ parse_range_values(values_result)
99
+ else
100
+ { enum: values_result }
101
+ end
102
+ else
103
+ { enum: values } if values
104
+ end
105
+ end
106
+
107
+ def parse_range_values(values)
108
+ { minimum: values.first, maximum: values.last }
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end