gitlab-grape-swagger 1.5.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.
Files changed (166) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.github/dependabot.yml +20 -0
  4. data/.github/workflows/ci.yml +45 -0
  5. data/.gitignore +44 -0
  6. data/.gitlab-ci.yml +19 -0
  7. data/.rspec +3 -0
  8. data/.rubocop.yml +136 -0
  9. data/.rubocop_todo.yml +60 -0
  10. data/.ruby-gemset +1 -0
  11. data/CHANGELOG.md +671 -0
  12. data/CONTRIBUTING.md +126 -0
  13. data/Dangerfile +3 -0
  14. data/Gemfile +45 -0
  15. data/Gemfile.lock +249 -0
  16. data/LICENSE.txt +20 -0
  17. data/README.md +1772 -0
  18. data/RELEASING.md +82 -0
  19. data/Rakefile +20 -0
  20. data/UPGRADING.md +201 -0
  21. data/example/api/endpoints.rb +131 -0
  22. data/example/api/entities.rb +18 -0
  23. data/example/config.ru +42 -0
  24. data/example/example_requests.postman_collection +146 -0
  25. data/example/splines.png +0 -0
  26. data/example/swagger-example.png +0 -0
  27. data/grape-swagger.gemspec +23 -0
  28. data/lib/grape-swagger/doc_methods/build_model_definition.rb +68 -0
  29. data/lib/grape-swagger/doc_methods/data_type.rb +110 -0
  30. data/lib/grape-swagger/doc_methods/extensions.rb +101 -0
  31. data/lib/grape-swagger/doc_methods/file_params.rb +17 -0
  32. data/lib/grape-swagger/doc_methods/format_data.rb +53 -0
  33. data/lib/grape-swagger/doc_methods/headers.rb +20 -0
  34. data/lib/grape-swagger/doc_methods/move_params.rb +209 -0
  35. data/lib/grape-swagger/doc_methods/operation_id.rb +32 -0
  36. data/lib/grape-swagger/doc_methods/optional_object.rb +30 -0
  37. data/lib/grape-swagger/doc_methods/parse_params.rb +190 -0
  38. data/lib/grape-swagger/doc_methods/path_string.rb +52 -0
  39. data/lib/grape-swagger/doc_methods/produces_consumes.rb +15 -0
  40. data/lib/grape-swagger/doc_methods/status_codes.rb +21 -0
  41. data/lib/grape-swagger/doc_methods/tag_name_description.rb +34 -0
  42. data/lib/grape-swagger/doc_methods/version.rb +20 -0
  43. data/lib/grape-swagger/doc_methods.rb +142 -0
  44. data/lib/grape-swagger/endpoint/params_parser.rb +76 -0
  45. data/lib/grape-swagger/endpoint.rb +476 -0
  46. data/lib/grape-swagger/errors.rb +17 -0
  47. data/lib/grape-swagger/instance.rb +7 -0
  48. data/lib/grape-swagger/model_parsers.rb +42 -0
  49. data/lib/grape-swagger/rake/oapi_tasks.rb +135 -0
  50. data/lib/grape-swagger/version.rb +5 -0
  51. data/lib/grape-swagger.rb +174 -0
  52. data/spec/issues/267_nested_namespaces.rb +55 -0
  53. data/spec/issues/403_versions_spec.rb +124 -0
  54. data/spec/issues/427_entity_as_string_spec.rb +39 -0
  55. data/spec/issues/430_entity_definitions_spec.rb +94 -0
  56. data/spec/issues/532_allow_custom_format_spec.rb +42 -0
  57. data/spec/issues/533_specify_status_code_spec.rb +78 -0
  58. data/spec/issues/537_enum_values_spec.rb +50 -0
  59. data/spec/issues/539_array_post_body_spec.rb +65 -0
  60. data/spec/issues/542_array_of_type_in_post_body_spec.rb +46 -0
  61. data/spec/issues/553_align_array_put_post_params_spec.rb +152 -0
  62. data/spec/issues/572_array_post_body_spec.rb +51 -0
  63. data/spec/issues/579_align_put_post_parameters_spec.rb +185 -0
  64. data/spec/issues/582_file_response_spec.rb +55 -0
  65. data/spec/issues/587_range_parameter_delimited_by_dash_spec.rb +26 -0
  66. data/spec/issues/605_root_route_documentation_spec.rb +23 -0
  67. data/spec/issues/650_params_array_spec.rb +65 -0
  68. data/spec/issues/677_consumes_produces_add_swagger_documentation_options_spec.rb +100 -0
  69. data/spec/issues/680_keep_204_error_schemas_spec.rb +55 -0
  70. data/spec/issues/721_set_default_parameter_location_based_on_consumes_spec.rb +62 -0
  71. data/spec/issues/751_deeply_nested_objects_spec.rb +190 -0
  72. data/spec/issues/776_multiple_presents_spec.rb +59 -0
  73. data/spec/issues/784_extensions_on_params_spec.rb +42 -0
  74. data/spec/issues/809_utf8_routes_spec.rb +55 -0
  75. data/spec/issues/832_array_hash_float_decimal_spec.rb +114 -0
  76. data/spec/issues/847_route_param_options_spec.rb +37 -0
  77. data/spec/issues/873_wildcard_segments_path_parameters_spec.rb +28 -0
  78. data/spec/issues/878_optional_path_segments_spec.rb +29 -0
  79. data/spec/issues/881_handle_file_params_spec.rb +38 -0
  80. data/spec/issues/883_query_array_parameter_spec.rb +46 -0
  81. data/spec/issues/884_dont_document_non_schema_examples_spec.rb +49 -0
  82. data/spec/issues/887_prevent_duplicate_operation_ids_spec.rb +35 -0
  83. data/spec/lib/data_type_spec.rb +111 -0
  84. data/spec/lib/endpoint/params_parser_spec.rb +124 -0
  85. data/spec/lib/endpoint_spec.rb +153 -0
  86. data/spec/lib/extensions_spec.rb +185 -0
  87. data/spec/lib/format_data_spec.rb +115 -0
  88. data/spec/lib/model_parsers_spec.rb +104 -0
  89. data/spec/lib/move_params_spec.rb +444 -0
  90. data/spec/lib/oapi_tasks_spec.rb +163 -0
  91. data/spec/lib/operation_id_spec.rb +55 -0
  92. data/spec/lib/optional_object_spec.rb +47 -0
  93. data/spec/lib/parse_params_spec.rb +68 -0
  94. data/spec/lib/path_string_spec.rb +101 -0
  95. data/spec/lib/produces_consumes_spec.rb +116 -0
  96. data/spec/lib/tag_name_description_spec.rb +80 -0
  97. data/spec/lib/version_spec.rb +28 -0
  98. data/spec/spec_helper.rb +39 -0
  99. data/spec/support/empty_model_parser.rb +23 -0
  100. data/spec/support/grape_version.rb +13 -0
  101. data/spec/support/mock_parser.rb +23 -0
  102. data/spec/support/model_parsers/entity_parser.rb +334 -0
  103. data/spec/support/model_parsers/mock_parser.rb +346 -0
  104. data/spec/support/model_parsers/representable_parser.rb +406 -0
  105. data/spec/support/namespace_tags.rb +93 -0
  106. data/spec/support/the_paths_definitions.rb +109 -0
  107. data/spec/swagger_v2/api_documentation_spec.rb +42 -0
  108. data/spec/swagger_v2/api_swagger_v2_additional_properties_spec.rb +83 -0
  109. data/spec/swagger_v2/api_swagger_v2_body_definitions_spec.rb +48 -0
  110. data/spec/swagger_v2/api_swagger_v2_definitions-models_spec.rb +36 -0
  111. data/spec/swagger_v2/api_swagger_v2_detail_spec.rb +79 -0
  112. data/spec/swagger_v2/api_swagger_v2_extensions_spec.rb +145 -0
  113. data/spec/swagger_v2/api_swagger_v2_format-content_type_spec.rb +137 -0
  114. data/spec/swagger_v2/api_swagger_v2_global_configuration_spec.rb +56 -0
  115. data/spec/swagger_v2/api_swagger_v2_hash_and_array_spec.rb +64 -0
  116. data/spec/swagger_v2/api_swagger_v2_headers_spec.rb +58 -0
  117. data/spec/swagger_v2/api_swagger_v2_hide_documentation_path_spec.rb +57 -0
  118. data/spec/swagger_v2/api_swagger_v2_hide_param_spec.rb +109 -0
  119. data/spec/swagger_v2/api_swagger_v2_ignore_defaults_spec.rb +48 -0
  120. data/spec/swagger_v2/api_swagger_v2_mounted_spec.rb +153 -0
  121. data/spec/swagger_v2/api_swagger_v2_param_type_body_nested_spec.rb +355 -0
  122. data/spec/swagger_v2/api_swagger_v2_param_type_body_spec.rb +217 -0
  123. data/spec/swagger_v2/api_swagger_v2_param_type_spec.rb +247 -0
  124. data/spec/swagger_v2/api_swagger_v2_request_params_fix_spec.rb +80 -0
  125. data/spec/swagger_v2/api_swagger_v2_response_spec.rb +147 -0
  126. data/spec/swagger_v2/api_swagger_v2_response_with_examples_spec.rb +135 -0
  127. data/spec/swagger_v2/api_swagger_v2_response_with_headers_spec.rb +216 -0
  128. data/spec/swagger_v2/api_swagger_v2_response_with_models_spec.rb +53 -0
  129. data/spec/swagger_v2/api_swagger_v2_response_with_root_spec.rb +153 -0
  130. data/spec/swagger_v2/api_swagger_v2_spec.rb +245 -0
  131. data/spec/swagger_v2/api_swagger_v2_status_codes_spec.rb +93 -0
  132. data/spec/swagger_v2/api_swagger_v2_type-format_spec.rb +90 -0
  133. data/spec/swagger_v2/boolean_params_spec.rb +38 -0
  134. data/spec/swagger_v2/default_api_spec.rb +175 -0
  135. data/spec/swagger_v2/deprecated_field_spec.rb +25 -0
  136. data/spec/swagger_v2/description_not_initialized_spec.rb +39 -0
  137. data/spec/swagger_v2/endpoint_versioned_path_spec.rb +130 -0
  138. data/spec/swagger_v2/errors_spec.rb +77 -0
  139. data/spec/swagger_v2/float_api_spec.rb +36 -0
  140. data/spec/swagger_v2/form_params_spec.rb +76 -0
  141. data/spec/swagger_v2/grape-swagger_spec.rb +17 -0
  142. data/spec/swagger_v2/guarded_endpoint_spec.rb +162 -0
  143. data/spec/swagger_v2/hide_api_spec.rb +147 -0
  144. data/spec/swagger_v2/host_spec.rb +43 -0
  145. data/spec/swagger_v2/inheritance_and_discriminator_spec.rb +57 -0
  146. data/spec/swagger_v2/mount_override_api_spec.rb +58 -0
  147. data/spec/swagger_v2/mounted_target_class_spec.rb +76 -0
  148. data/spec/swagger_v2/namespace_tags_prefix_spec.rb +122 -0
  149. data/spec/swagger_v2/namespace_tags_spec.rb +78 -0
  150. data/spec/swagger_v2/namespaced_api_spec.rb +121 -0
  151. data/spec/swagger_v2/nicknamed_api_spec.rb +25 -0
  152. data/spec/swagger_v2/operation_id_api_spec.rb +27 -0
  153. data/spec/swagger_v2/param_multi_type_spec.rb +82 -0
  154. data/spec/swagger_v2/param_type_spec.rb +95 -0
  155. data/spec/swagger_v2/param_values_spec.rb +180 -0
  156. data/spec/swagger_v2/params_array_collection_format_spec.rb +105 -0
  157. data/spec/swagger_v2/params_array_spec.rb +225 -0
  158. data/spec/swagger_v2/params_example_spec.rb +38 -0
  159. data/spec/swagger_v2/params_hash_spec.rb +77 -0
  160. data/spec/swagger_v2/params_nested_spec.rb +92 -0
  161. data/spec/swagger_v2/parent_less_namespace_spec.rb +32 -0
  162. data/spec/swagger_v2/reference_entity_spec.rb +129 -0
  163. data/spec/swagger_v2/security_requirement_spec.rb +46 -0
  164. data/spec/swagger_v2/simple_mounted_api_spec.rb +332 -0
  165. data/spec/version_spec.rb +10 -0
  166. metadata +225 -0
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrapeSwagger
4
+ module DocMethods
5
+ class BuildModelDefinition
6
+ class << self
7
+ def build(_model, properties, required, other_def_properties = {})
8
+ definition = { type: 'object', properties: properties }.merge(other_def_properties)
9
+
10
+ definition[:required] = required if required.is_a?(Array) && required.any?
11
+
12
+ definition
13
+ end
14
+
15
+ def parse_params_from_model(parsed_response, model, model_name)
16
+ if parsed_response.is_a?(Hash) && parsed_response.keys.first == :allOf
17
+ refs_or_models = parsed_response[:allOf]
18
+ parsed = parse_refs_and_models(refs_or_models, model)
19
+
20
+ {
21
+ allOf: parsed
22
+ }
23
+ else
24
+ properties, required = parsed_response
25
+ unless properties&.any?
26
+ raise GrapeSwagger::Errors::SwaggerSpec,
27
+ "Empty model #{model_name}, swagger 2.0 doesn't support empty definitions."
28
+ end
29
+ properties, other_def_properties = parse_properties(properties)
30
+
31
+ build(
32
+ model, properties, required, other_def_properties
33
+ )
34
+ end
35
+ end
36
+
37
+ def parse_properties(properties)
38
+ other_properties = {}
39
+
40
+ discriminator_key, discriminator_value =
41
+ properties.find do |_key, value|
42
+ value[:documentation].try(:[], :is_discriminator)
43
+ end
44
+
45
+ if discriminator_key
46
+ discriminator_value.delete(:documentation)
47
+ properties[discriminator_key] = discriminator_value
48
+
49
+ other_properties[:discriminator] = discriminator_key
50
+ end
51
+
52
+ [properties, other_properties]
53
+ end
54
+
55
+ def parse_refs_and_models(refs_or_models, model)
56
+ refs_or_models.map do |ref_or_models|
57
+ if ref_or_models.is_a?(Hash) && ref_or_models.keys.first == '$ref'
58
+ ref_or_models
59
+ else
60
+ properties, required = ref_or_models
61
+ GrapeSwagger::DocMethods::BuildModelDefinition.build(model, properties, required)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrapeSwagger
4
+ module DocMethods
5
+ class DataType
6
+ class << self
7
+ def call(value)
8
+ raw_data_type = value.is_a?(Hash) ? value[:type] : value
9
+ raw_data_type ||= 'String'
10
+ raw_data_type = parse_multi_type(raw_data_type)
11
+
12
+ case raw_data_type.to_s
13
+ when 'Boolean', 'Date', 'Integer', 'String', 'Float', 'JSON', 'Array'
14
+ raw_data_type.to_s.downcase
15
+ when 'Hash'
16
+ 'object'
17
+ when 'Rack::Multipart::UploadedFile', 'File'
18
+ 'file'
19
+ when 'Grape::API::Boolean'
20
+ 'boolean'
21
+ when 'BigDecimal'
22
+ 'double'
23
+ when 'DateTime', 'Time'
24
+ 'dateTime'
25
+ when 'Numeric'
26
+ 'long'
27
+ when 'Symbol'
28
+ 'string'
29
+ else
30
+ parse_entity_name(raw_data_type)
31
+ end
32
+ end
33
+
34
+ def parse_multi_type(raw_data_type)
35
+ case raw_data_type
36
+ when /\A\[.*\]\z/
37
+ type_as_string = raw_data_type.gsub(/[\[\s+\]]/, '').split(',').first
38
+ begin
39
+ Object.const_get(type_as_string)
40
+ rescue NameError
41
+ type_as_string
42
+ end
43
+ when Array
44
+ raw_data_type.first
45
+ else
46
+ raw_data_type
47
+ end
48
+ end
49
+
50
+ def parse_entity_name(model)
51
+ if model.respond_to?(:entity_name)
52
+ model.entity_name
53
+ elsif model.to_s.end_with?('::Entity', '::Entities')
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
+ else
58
+ model.to_s.split('::').join('_')
59
+ end
60
+ end
61
+
62
+ def request_primitive?(type)
63
+ request_primitives.include?(type.to_s.downcase)
64
+ end
65
+
66
+ def primitive?(type)
67
+ primitives.include?(type.to_s.downcase)
68
+ end
69
+
70
+ def request_primitives
71
+ primitives + %w[object string boolean file json array]
72
+ end
73
+
74
+ def primitives
75
+ PRIMITIVE_MAPPINGS.keys.map(&:downcase)
76
+ end
77
+
78
+ def query_array_primitive?(type)
79
+ query_array_primitives.include?(type.to_s.downcase)
80
+ end
81
+
82
+ def query_array_primitives
83
+ primitives << 'string'
84
+ end
85
+
86
+ def mapping(value)
87
+ PRIMITIVE_MAPPINGS[value] || 'string'
88
+ end
89
+
90
+ def collections
91
+ %w[csv ssv tsv pipes multi brackets]
92
+ end
93
+ end
94
+
95
+ PRIMITIVE_MAPPINGS = {
96
+ 'integer' => %w[integer int32],
97
+ 'long' => %w[integer int64],
98
+ 'float' => %w[number float],
99
+ 'double' => %w[number double],
100
+ 'byte' => %w[string byte],
101
+ 'date' => %w[string date],
102
+ 'dateTime' => %w[string date-time],
103
+ 'binary' => %w[string binary],
104
+ 'password' => %w[string password],
105
+ 'email' => %w[string email],
106
+ 'uuid' => %w[string uuid]
107
+ }.freeze
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrapeSwagger
4
+ module DocMethods
5
+ class Extensions
6
+ class << self
7
+ def add(path, definitions, route)
8
+ @route = route
9
+
10
+ description = route.settings[:description]
11
+ add_extension_to(path[method], extension(description)) if description && extended?(description, :x)
12
+
13
+ settings = route.settings
14
+ add_extensions_to_operation(settings, path, route) if settings && extended?(settings, :x_operation)
15
+ add_extensions_to_path(settings, path) if settings && extended?(settings, :x_path)
16
+ add_extensions_to_definition(settings, path, definitions) if settings && extended?(settings, :x_def)
17
+ end
18
+
19
+ def add_extensions_to_root(settings, object)
20
+ add_extension_to(object, extension(settings)) if extended?(settings, :x)
21
+ end
22
+
23
+ def add_extensions_to_info(settings, info)
24
+ add_extension_to(info, extension(settings)) if extended?(settings, :x)
25
+ end
26
+
27
+ def add_extensions_to_operation(settings, path, route)
28
+ add_extension_to(path[route.request_method.downcase.to_sym], extension(settings, :x_operation))
29
+ end
30
+
31
+ def add_extensions_to_path(settings, path)
32
+ add_extension_to(path, extension(settings, :x_path))
33
+ end
34
+
35
+ def add_extensions_to_definition(settings, path, definitions)
36
+ def_extension = extension(settings, :x_def)
37
+
38
+ if def_extension[:x_def].is_a?(Array)
39
+ def_extension[:x_def].each { |extension| setup_definition(extension, path, definitions) }
40
+ else
41
+ setup_definition(def_extension[:x_def], path, definitions)
42
+ end
43
+ end
44
+
45
+ def setup_definition(def_extension, path, definitions)
46
+ return unless def_extension.key?(:for)
47
+
48
+ status = def_extension[:for]
49
+
50
+ definition = find_definition(status, path)
51
+ add_extension_to(definitions[definition], x_def: def_extension)
52
+ end
53
+
54
+ def find_definition(status, path)
55
+ response = path[method][:responses][status]
56
+ return if response.nil?
57
+
58
+ return response[:schema]['$ref'].split('/').last if response[:schema].key?('$ref')
59
+ return response[:schema]['items']['$ref'].split('/').last if response[:schema].key?('items')
60
+ end
61
+
62
+ def add_extension_to(part, extensions)
63
+ return if part.nil?
64
+
65
+ concatenate(extensions).each do |key, value|
66
+ part[key] = value unless key.start_with?('x-for')
67
+ end
68
+ end
69
+
70
+ def concatenate(extensions)
71
+ result = {}
72
+
73
+ extensions.each_value do |extension|
74
+ extension.each do |key, value|
75
+ result["x-#{key}"] = value
76
+ end
77
+ end
78
+
79
+ result
80
+ end
81
+
82
+ def extended?(part, identifier = :x)
83
+ !extension(part, identifier).empty?
84
+ end
85
+
86
+ def extension(part, identifier = :x)
87
+ part.select { |x| x == identifier }
88
+ end
89
+
90
+ def method(*args)
91
+ # We're shadowing Object.method(:symbol) here so we provide
92
+ # a compatibility layer for code that introspects the methods
93
+ # of this class
94
+ return super if args.size.positive?
95
+
96
+ @route.request_method.downcase.to_sym
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrapeSwagger
4
+ module DocMethods
5
+ class FileParams
6
+ class << self
7
+ def includes_file_param?(params)
8
+ return params.any? { |x| x[:type] == 'file' }
9
+ end
10
+
11
+ def to_formdata(params)
12
+ params.each { |x| x[:in] = 'formData' if x[:in] == 'body' }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrapeSwagger
4
+ module DocMethods
5
+ class FormatData
6
+ class << self
7
+ def to_format(parameters)
8
+ parameters.reject { |parameter| parameter[:in] == 'body' }.each do |b|
9
+ related_parameters = parameters.select do |p|
10
+ p[:name] != b[:name] && p[:name].to_s.start_with?("#{b[:name].to_s.gsub(/\[\]\z/, '')}[")
11
+ end
12
+ parameters.reject! { |p| p[:name] == b[:name] } if move_down(b, related_parameters)
13
+ end
14
+ parameters
15
+ end
16
+
17
+ def move_down(parameter, related_parameters)
18
+ case parameter[:type]
19
+ when 'array'
20
+ add_array(parameter, related_parameters)
21
+ unless related_parameters.blank?
22
+ add_braces(parameter, related_parameters) if parameter[:name].match?(/\A.*\[\]\z/)
23
+ return true
24
+ end
25
+ when 'object'
26
+ return true
27
+ end
28
+ false
29
+ end
30
+
31
+ def add_braces(parameter, related_parameters)
32
+ param_name = parameter[:name].gsub(/\A(.*)\[\]\z/, '\1')
33
+ related_parameters.each { |p| p[:name] = p[:name].gsub(param_name, "#{param_name}[]") }
34
+ end
35
+
36
+ def add_array(parameter, related_parameters)
37
+ related_parameters.each do |p|
38
+ next if p.key?(:items)
39
+
40
+ p_type = p[:type] == 'array' ? 'string' : p[:type]
41
+ p[:items] = { type: p_type, format: p[:format], enum: p[:enum], is_array: p[:is_array] }
42
+ p[:items].delete_if { |_k, v| v.nil? }
43
+ p[:type] = 'array'
44
+ p[:is_array] = parameter[:is_array]
45
+ p.delete(:format)
46
+ p.delete(:enum)
47
+ p.delete_if { |_k, v| v.nil? }
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrapeSwagger
4
+ module DocMethods
5
+ class Headers
6
+ class << self
7
+ def parse(route)
8
+ route.headers.to_a.map do |route_header|
9
+ route_header.tap do |header|
10
+ hash = header[1]
11
+ description = hash.delete('description')
12
+ hash[:documentation] = { desc: description, in: 'header' }
13
+ hash[:type] = hash['type'].titleize if hash['type']
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/hash/deep_merge'
4
+
5
+ module GrapeSwagger
6
+ module DocMethods
7
+ class MoveParams
8
+ class << self
9
+ attr_accessor :definitions
10
+
11
+ def can_be_moved?(http_verb, params)
12
+ move_methods.include?(http_verb) && includes_body_param?(params)
13
+ end
14
+
15
+ def to_definition(path, params, route, definitions)
16
+ @definitions = definitions
17
+ unify!(params)
18
+
19
+ params_to_move = movable_params(params)
20
+
21
+ return (params + correct_array_param(params_to_move)) if should_correct_array?(params_to_move)
22
+
23
+ params << parent_definition_of_params(params_to_move, path, route)
24
+
25
+ params
26
+ end
27
+
28
+ private
29
+
30
+ def should_correct_array?(param)
31
+ param.length == 1 && param.first[:in] == 'body' && param.first[:type] == 'array'
32
+ end
33
+
34
+ def correct_array_param(param)
35
+ param.first[:schema] = { type: param.first.delete(:type), items: param.first.delete(:items) }
36
+
37
+ param
38
+ end
39
+
40
+ def parent_definition_of_params(params, path, route)
41
+ definition_name = OperationId.build(route, path)
42
+ build_definition(definition_name, params)
43
+ definition = @definitions[definition_name]
44
+
45
+ move_params_to_new(definition, params)
46
+
47
+ definition[:description] = route.description if route.try(:description)
48
+
49
+ build_body_parameter(definition_name, route.options)
50
+ end
51
+
52
+ def move_params_to_new(definition, params)
53
+ params, nested_params = params.partition { |x| !x[:name].to_s.include?('[') }
54
+ params.each do |param|
55
+ property = param[:name]
56
+ param_properties, param_required = build_properties([param])
57
+ add_properties_to_definition(definition, param_properties, param_required)
58
+ related_nested_params, nested_params = nested_params.partition { |x| x[:name].start_with?("#{property}[") }
59
+ prepare_nested_names(property, related_nested_params)
60
+
61
+ next if related_nested_params.blank?
62
+
63
+ nested_definition = if should_expose_as_array?([param])
64
+ move_params_to_new(array_type, related_nested_params)
65
+ else
66
+ move_params_to_new(object_type, related_nested_params)
67
+ end
68
+ if definition.key?(:items)
69
+ definition[:items][:properties][property.to_sym].deep_merge!(nested_definition)
70
+ else
71
+ definition[:properties][property.to_sym].deep_merge!(nested_definition)
72
+ end
73
+ end
74
+ definition
75
+ end
76
+
77
+ def build_properties(params)
78
+ properties = {}
79
+ required = []
80
+
81
+ params.each do |param|
82
+ name = param[:name].to_sym
83
+
84
+ properties[name] = if should_expose_as_array?([param])
85
+ document_as_array(param)
86
+ else
87
+ document_as_property(param)
88
+ end
89
+
90
+ required << name if deletable?(param) && param[:required]
91
+ end
92
+
93
+ [properties, required]
94
+ end
95
+
96
+ def document_as_array(param)
97
+ {}.tap do |property|
98
+ property[:type] = 'array'
99
+ property[:description] = param.delete(:description) unless param[:description].nil?
100
+ property[:items] = document_as_property(param)[:items]
101
+ end
102
+ end
103
+
104
+ def document_as_property(param)
105
+ property_keys.each_with_object({}) do |x, memo|
106
+ next unless param.key?(x)
107
+
108
+ value = param[x]
109
+ if x == :type && @definitions[value].present?
110
+ memo['$ref'] = "#/definitions/#{value}"
111
+ else
112
+ memo[x] = value
113
+ end
114
+ end
115
+ end
116
+
117
+ def movable_params(params)
118
+ to_delete = params.each_with_object([]) { |x, memo| memo << x if deletable?(x) }
119
+ delete_from(params, to_delete)
120
+
121
+ to_delete
122
+ end
123
+
124
+ def delete_from(params, to_delete)
125
+ to_delete.each { |x| params.delete(x) }
126
+ end
127
+
128
+ def add_properties_to_definition(definition, properties, required)
129
+ if definition.key?(:items)
130
+ definition[:items][:properties].deep_merge!(properties)
131
+ add_to_required(definition[:items], required)
132
+ else
133
+ definition[:properties].deep_merge!(properties)
134
+ add_to_required(definition, required)
135
+ end
136
+ end
137
+
138
+ def add_to_required(definition, value)
139
+ return if value.blank?
140
+
141
+ definition[:required] ||= []
142
+ definition[:required].push(*value)
143
+ end
144
+
145
+ def build_body_parameter(name, options)
146
+ {}.tap do |x|
147
+ x[:name] = options[:body_name] || name
148
+ x[:in] = 'body'
149
+ x[:required] = true
150
+ x[:schema] = { '$ref' => "#/definitions/#{name}" }
151
+ end
152
+ end
153
+
154
+ def build_definition(name, params)
155
+ @definitions[name] = should_expose_as_array?(params) ? array_type : object_type
156
+
157
+ name
158
+ end
159
+
160
+ def array_type
161
+ { type: 'array', items: { type: 'object', properties: {} } }
162
+ end
163
+
164
+ def object_type
165
+ { type: 'object', properties: {} }
166
+ end
167
+
168
+ def prepare_nested_names(property, params)
169
+ params.each { |x| x[:name] = x[:name].sub(property, '').sub('[', '').sub(']', '') }
170
+ end
171
+
172
+ def unify!(params)
173
+ params.each { |x| x[:in] = x.delete(:param_type) if x[:param_type] }
174
+ params.each { |x| x[:in] = 'body' if x[:in] == 'formData' } if includes_body_param?(params)
175
+ end
176
+
177
+ def parse_model(ref)
178
+ parts = ref.split('/')
179
+ parts.last.include?('{') ? parts[0..-2].join('/') : parts[0..-1].join('/')
180
+ end
181
+
182
+ def property_keys
183
+ %i[type format description minimum maximum items enum default additional_properties additionalProperties
184
+ example]
185
+ end
186
+
187
+ def deletable?(param)
188
+ param[:in] == 'body'
189
+ end
190
+
191
+ def move_methods
192
+ [:post, :put, :patch, 'POST', 'PUT', 'PATCH']
193
+ end
194
+
195
+ def includes_body_param?(params)
196
+ params.any? { |x| x[:in] == 'body' || x[:param_type] == 'body' }
197
+ end
198
+
199
+ def should_expose_as_array?(params)
200
+ should_exposed_as(params) == 'array'
201
+ end
202
+
203
+ def should_exposed_as(params)
204
+ params.any? { |x| x[:type] && x[:type] != 'array' } ? 'object' : 'array'
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrapeSwagger
4
+ module DocMethods
5
+ class OperationId
6
+ class << self
7
+ def build(route, path = nil)
8
+ if route.options[:nickname]
9
+ route.options[:nickname]
10
+ else
11
+ verb = route.request_method.to_s.downcase
12
+ operation = manipulate(path) unless path.nil?
13
+ "#{verb}#{operation}"
14
+ end
15
+ end
16
+
17
+ def manipulate(path)
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?('_')
21
+ operation.gsub!(/\.(\w)/, &:upcase).delete!('.') if operation[/\.(\w)/]
22
+ if path.include?('{')
23
+ operation.gsub!(/\{(\w)/, &:upcase)
24
+ operation.delete!('{').delete!('}')
25
+ end
26
+
27
+ operation
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrapeSwagger
4
+ module DocMethods
5
+ class OptionalObject
6
+ class << self
7
+ def build(key, options, request = nil)
8
+ if options[key]
9
+ return evaluate(key, options, request) if options[key].is_a?(Proc)
10
+
11
+ options[key]
12
+ else
13
+ request.send(default_values[key])
14
+ end
15
+ end
16
+
17
+ def evaluate(key, options, request)
18
+ options[key].arity.zero? ? options[key].call : options[key].call(request)
19
+ end
20
+
21
+ def default_values
22
+ {
23
+ host: 'host_with_port',
24
+ base_path: 'script_name'
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end