apipie-rails-jq 1.4.3.pre.beta.pre.jq.1

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 (258) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build.yml +32 -0
  3. data/.github/workflows/rubocop-challenger.yml +26 -0
  4. data/.github/workflows/rubocop.yml +18 -0
  5. data/.gitignore +16 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +132 -0
  8. data/.rubocop_todo.yml +1967 -0
  9. data/.vscode/settings.json +3 -0
  10. data/APACHE-LICENSE-2.0 +202 -0
  11. data/CHANGELOG.md +693 -0
  12. data/Gemfile +19 -0
  13. data/MIT-LICENSE +20 -0
  14. data/NOTICE +4 -0
  15. data/PROPOSAL_FOR_RESPONSE_DESCRIPTIONS.md +244 -0
  16. data/README.md +2088 -0
  17. data/Rakefile +8 -0
  18. data/apipie-rails.gemspec +44 -0
  19. data/app/controllers/apipie/apipies_controller.rb +184 -0
  20. data/app/helpers/apipie_helper.rb +10 -0
  21. data/app/public/apipie/javascripts/apipie.js +6 -0
  22. data/app/public/apipie/javascripts/bundled/bootstrap-collapse.js +167 -0
  23. data/app/public/apipie/javascripts/bundled/bootstrap.js +2280 -0
  24. data/app/public/apipie/javascripts/bundled/jquery.js +2 -0
  25. data/app/public/apipie/javascripts/bundled/prettify.js +28 -0
  26. data/app/public/apipie/stylesheets/application.css +7 -0
  27. data/app/public/apipie/stylesheets/bundled/bootstrap-responsive.min.css +9 -0
  28. data/app/public/apipie/stylesheets/bundled/bootstrap.min.css +9 -0
  29. data/app/public/apipie/stylesheets/bundled/prettify.css +30 -0
  30. data/app/views/apipie/apipies/_deprecation.html.erb +16 -0
  31. data/app/views/apipie/apipies/_disqus.html.erb +13 -0
  32. data/app/views/apipie/apipies/_errors.html.erb +23 -0
  33. data/app/views/apipie/apipies/_headers.html.erb +26 -0
  34. data/app/views/apipie/apipies/_languages.erb +6 -0
  35. data/app/views/apipie/apipies/_metadata.erb +1 -0
  36. data/app/views/apipie/apipies/_method_detail.erb +63 -0
  37. data/app/views/apipie/apipies/_params.html.erb +49 -0
  38. data/app/views/apipie/apipies/_params_plain.html.erb +21 -0
  39. data/app/views/apipie/apipies/apipie_404.html.erb +17 -0
  40. data/app/views/apipie/apipies/apipie_checksum.json.erb +1 -0
  41. data/app/views/apipie/apipies/getting_started.html.erb +6 -0
  42. data/app/views/apipie/apipies/index.html.erb +56 -0
  43. data/app/views/apipie/apipies/method.html.erb +41 -0
  44. data/app/views/apipie/apipies/plain.html.erb +77 -0
  45. data/app/views/apipie/apipies/resource.html.erb +80 -0
  46. data/app/views/apipie/apipies/static.html.erb +103 -0
  47. data/app/views/layouts/apipie/apipie.html.erb +27 -0
  48. data/config/locales/de.yml +28 -0
  49. data/config/locales/en.yml +41 -0
  50. data/config/locales/es.yml +28 -0
  51. data/config/locales/fr.yml +31 -0
  52. data/config/locales/it.yml +41 -0
  53. data/config/locales/ja.yml +31 -0
  54. data/config/locales/ko.yml +32 -0
  55. data/config/locales/pl.yml +28 -0
  56. data/config/locales/pt-BR.yml +28 -0
  57. data/config/locales/ru.yml +28 -0
  58. data/config/locales/tr.yml +28 -0
  59. data/config/locales/zh-CN.yml +28 -0
  60. data/config/locales/zh-TW.yml +28 -0
  61. data/gemfiles/Gemfile.tools +9 -0
  62. data/images/screenshot-1.png +0 -0
  63. data/images/screenshot-2.png +0 -0
  64. data/lib/apipie/apipie_module.rb +83 -0
  65. data/lib/apipie/application.rb +499 -0
  66. data/lib/apipie/configuration.rb +196 -0
  67. data/lib/apipie/core_ext/route.rb +9 -0
  68. data/lib/apipie/dsl_definition.rb +630 -0
  69. data/lib/apipie/error_description.rb +46 -0
  70. data/lib/apipie/errors.rb +86 -0
  71. data/lib/apipie/extractor/collector.rb +116 -0
  72. data/lib/apipie/extractor/recorder.rb +193 -0
  73. data/lib/apipie/extractor/writer.rb +454 -0
  74. data/lib/apipie/extractor.rb +181 -0
  75. data/lib/apipie/generator/config.rb +12 -0
  76. data/lib/apipie/generator/generator.rb +2 -0
  77. data/lib/apipie/generator/swagger/computed_interface_id.rb +23 -0
  78. data/lib/apipie/generator/swagger/config.rb +80 -0
  79. data/lib/apipie/generator/swagger/context.rb +38 -0
  80. data/lib/apipie/generator/swagger/method_description/api_decorator.rb +20 -0
  81. data/lib/apipie/generator/swagger/method_description/api_schema_service.rb +89 -0
  82. data/lib/apipie/generator/swagger/method_description/decorator.rb +22 -0
  83. data/lib/apipie/generator/swagger/method_description/parameters_service.rb +139 -0
  84. data/lib/apipie/generator/swagger/method_description/response_schema_service.rb +46 -0
  85. data/lib/apipie/generator/swagger/method_description/response_service.rb +71 -0
  86. data/lib/apipie/generator/swagger/method_description.rb +2 -0
  87. data/lib/apipie/generator/swagger/operation_id.rb +51 -0
  88. data/lib/apipie/generator/swagger/param_description/builder.rb +114 -0
  89. data/lib/apipie/generator/swagger/param_description/composite.rb +119 -0
  90. data/lib/apipie/generator/swagger/param_description/description.rb +15 -0
  91. data/lib/apipie/generator/swagger/param_description/in.rb +37 -0
  92. data/lib/apipie/generator/swagger/param_description/name.rb +18 -0
  93. data/lib/apipie/generator/swagger/param_description/path_params_composite.rb +61 -0
  94. data/lib/apipie/generator/swagger/param_description/referenced_composite.rb +36 -0
  95. data/lib/apipie/generator/swagger/param_description/type.rb +132 -0
  96. data/lib/apipie/generator/swagger/param_description.rb +18 -0
  97. data/lib/apipie/generator/swagger/path_decorator.rb +36 -0
  98. data/lib/apipie/generator/swagger/referenced_definitions.rb +17 -0
  99. data/lib/apipie/generator/swagger/resource_description_collection.rb +30 -0
  100. data/lib/apipie/generator/swagger/resource_description_composite.rb +56 -0
  101. data/lib/apipie/generator/swagger/schema.rb +63 -0
  102. data/lib/apipie/generator/swagger/swagger.rb +2 -0
  103. data/lib/apipie/generator/swagger/type.rb +16 -0
  104. data/lib/apipie/generator/swagger/type_extractor.rb +51 -0
  105. data/lib/apipie/generator/swagger/warning.rb +74 -0
  106. data/lib/apipie/generator/swagger/warning_writer.rb +54 -0
  107. data/lib/apipie/helpers.rb +73 -0
  108. data/lib/apipie/markup.rb +52 -0
  109. data/lib/apipie/method_description/api.rb +12 -0
  110. data/lib/apipie/method_description/apis_service.rb +82 -0
  111. data/lib/apipie/method_description.rb +230 -0
  112. data/lib/apipie/middleware/checksum_in_headers.rb +35 -0
  113. data/lib/apipie/param_description/deprecation.rb +24 -0
  114. data/lib/apipie/param_description.rb +313 -0
  115. data/lib/apipie/railtie.rb +9 -0
  116. data/lib/apipie/resource_description.rb +152 -0
  117. data/lib/apipie/response_description.rb +157 -0
  118. data/lib/apipie/response_description_adapter.rb +202 -0
  119. data/lib/apipie/routes_formatter.rb +33 -0
  120. data/lib/apipie/routing.rb +16 -0
  121. data/lib/apipie/rspec/response_validation_helper.rb +194 -0
  122. data/lib/apipie/see_description.rb +39 -0
  123. data/lib/apipie/static_dispatcher.rb +75 -0
  124. data/lib/apipie/swagger_generator.rb +45 -0
  125. data/lib/apipie/tag_list_description.rb +11 -0
  126. data/lib/apipie/validator.rb +552 -0
  127. data/lib/apipie/version.rb +3 -0
  128. data/lib/apipie-rails.rb +60 -0
  129. data/lib/generators/apipie/install/README +6 -0
  130. data/lib/generators/apipie/install/install_generator.rb +25 -0
  131. data/lib/generators/apipie/install/templates/initializer.rb.erb +7 -0
  132. data/lib/generators/apipie/views_generator.rb +11 -0
  133. data/lib/tasks/apipie.rake +355 -0
  134. data/rel-eng/gem_release.ipynb +398 -0
  135. data/rel-eng/packages/.readme +3 -0
  136. data/rel-eng/packages/rubygem-apipie-rails +1 -0
  137. data/rel-eng/tito.props +5 -0
  138. data/spec/controllers/api/v1/architectures_controller_spec.rb +29 -0
  139. data/spec/controllers/api/v2/architectures_controller_spec.rb +19 -0
  140. data/spec/controllers/api/v2/empty_middle_controller_spec.rb +23 -0
  141. data/spec/controllers/api/v2/nested/resources_controller_spec.rb +27 -0
  142. data/spec/controllers/api/v2/sub/footguns_controller_spec.rb +19 -0
  143. data/spec/controllers/concerns_controller_spec.rb +42 -0
  144. data/spec/controllers/extended_controller_spec.rb +14 -0
  145. data/spec/controllers/included_param_group_controller_spec.rb +13 -0
  146. data/spec/controllers/pets_controller_spec.rb +98 -0
  147. data/spec/controllers/users_controller_spec.rb +794 -0
  148. data/spec/dummy/Rakefile +7 -0
  149. data/spec/dummy/app/controllers/api/base_controller.rb +4 -0
  150. data/spec/dummy/app/controllers/api/v1/architectures_controller.rb +43 -0
  151. data/spec/dummy/app/controllers/api/v1/base_controller.rb +11 -0
  152. data/spec/dummy/app/controllers/api/v2/architectures_controller.rb +31 -0
  153. data/spec/dummy/app/controllers/api/v2/base_controller.rb +17 -0
  154. data/spec/dummy/app/controllers/api/v2/empty_middle_controller.rb +14 -0
  155. data/spec/dummy/app/controllers/api/v2/nested/architectures_controller.rb +32 -0
  156. data/spec/dummy/app/controllers/api/v2/nested/resources_controller.rb +33 -0
  157. data/spec/dummy/app/controllers/api/v2/sub/footguns_controller.rb +30 -0
  158. data/spec/dummy/app/controllers/application_controller.rb +18 -0
  159. data/spec/dummy/app/controllers/concerns_controller.rb +8 -0
  160. data/spec/dummy/app/controllers/extended_controller.rb +14 -0
  161. data/spec/dummy/app/controllers/extending_concern.rb +10 -0
  162. data/spec/dummy/app/controllers/files_controller.rb +5 -0
  163. data/spec/dummy/app/controllers/included_param_group_controller.rb +19 -0
  164. data/spec/dummy/app/controllers/overridden_concerns_controller.rb +31 -0
  165. data/spec/dummy/app/controllers/pets_controller.rb +408 -0
  166. data/spec/dummy/app/controllers/pets_using_auto_views_controller.rb +73 -0
  167. data/spec/dummy/app/controllers/pets_using_self_describing_classes_controller.rb +95 -0
  168. data/spec/dummy/app/controllers/sample_controller.rb +39 -0
  169. data/spec/dummy/app/controllers/tagged_cats_controller.rb +32 -0
  170. data/spec/dummy/app/controllers/tagged_dogs_controller.rb +15 -0
  171. data/spec/dummy/app/controllers/twitter_example_controller.rb +307 -0
  172. data/spec/dummy/app/controllers/users_controller.rb +310 -0
  173. data/spec/dummy/app/helpers/random_param_group.rb +8 -0
  174. data/spec/dummy/app/views/layouts/application.html.erb +21 -0
  175. data/spec/dummy/components/test_engine/Gemfile +6 -0
  176. data/spec/dummy/components/test_engine/app/controllers/test_engine/application_controller.rb +4 -0
  177. data/spec/dummy/components/test_engine/app/controllers/test_engine/memes_controller.rb +37 -0
  178. data/spec/dummy/components/test_engine/config/routes.rb +3 -0
  179. data/spec/dummy/components/test_engine/db/.gitkeep +0 -0
  180. data/spec/dummy/components/test_engine/lib/test_engine.rb +7 -0
  181. data/spec/dummy/components/test_engine/test_engine.gemspec +11 -0
  182. data/spec/dummy/config/application.rb +47 -0
  183. data/spec/dummy/config/boot.rb +12 -0
  184. data/spec/dummy/config/database.yml +21 -0
  185. data/spec/dummy/config/environment.rb +8 -0
  186. data/spec/dummy/config/environments/development.rb +25 -0
  187. data/spec/dummy/config/environments/production.rb +49 -0
  188. data/spec/dummy/config/environments/test.rb +33 -0
  189. data/spec/dummy/config/initializers/apipie.rb +110 -0
  190. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  191. data/spec/dummy/config/initializers/inflections.rb +10 -0
  192. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  193. data/spec/dummy/config/initializers/secret_token.rb +8 -0
  194. data/spec/dummy/config/initializers/session_store.rb +8 -0
  195. data/spec/dummy/config/locales/en.yml +5 -0
  196. data/spec/dummy/config/routes.rb +61 -0
  197. data/spec/dummy/config.ru +4 -0
  198. data/spec/dummy/db/.gitkeep +0 -0
  199. data/spec/dummy/doc/apipie_examples.json +1 -0
  200. data/spec/dummy/doc/users/desc_from_file.md +1 -0
  201. data/spec/dummy/public/404.html +26 -0
  202. data/spec/dummy/public/422.html +26 -0
  203. data/spec/dummy/public/500.html +26 -0
  204. data/spec/dummy/public/favicon.ico +0 -0
  205. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  206. data/spec/dummy/script/rails +6 -0
  207. data/spec/lib/apipie/apipies_controller_spec.rb +345 -0
  208. data/spec/lib/apipie/application_spec.rb +62 -0
  209. data/spec/lib/apipie/configuration_spec.rb +38 -0
  210. data/spec/lib/apipie/extractor/collector_spec.rb +57 -0
  211. data/spec/lib/apipie/extractor/recorder/middleware_spec.rb +44 -0
  212. data/spec/lib/apipie/extractor/recorder_spec.rb +77 -0
  213. data/spec/lib/apipie/extractor/writer_spec.rb +112 -0
  214. data/spec/lib/apipie/extractor_spec.rb +9 -0
  215. data/spec/lib/apipie/file_handler_spec.rb +25 -0
  216. data/spec/lib/apipie/generator/swagger/config_spec.rb +19 -0
  217. data/spec/lib/apipie/generator/swagger/context_spec.rb +56 -0
  218. data/spec/lib/apipie/generator/swagger/method_description/api_schema_service_spec.rb +119 -0
  219. data/spec/lib/apipie/generator/swagger/method_description/response_schema_service_spec.rb +105 -0
  220. data/spec/lib/apipie/generator/swagger/method_description/response_service_spec.rb +62 -0
  221. data/spec/lib/apipie/generator/swagger/operation_id_spec.rb +63 -0
  222. data/spec/lib/apipie/generator/swagger/param_description/builder_spec.rb +245 -0
  223. data/spec/lib/apipie/generator/swagger/param_description/composite_spec.rb +95 -0
  224. data/spec/lib/apipie/generator/swagger/param_description/description_spec.rb +79 -0
  225. data/spec/lib/apipie/generator/swagger/param_description/in_spec.rb +86 -0
  226. data/spec/lib/apipie/generator/swagger/param_description/name_spec.rb +81 -0
  227. data/spec/lib/apipie/generator/swagger/param_description/type_spec.rb +210 -0
  228. data/spec/lib/apipie/generator/swagger/param_description_spec.rb +28 -0
  229. data/spec/lib/apipie/generator/swagger/path_decorator_spec.rb +57 -0
  230. data/spec/lib/apipie/generator/swagger/referenced_definitions_spec.rb +35 -0
  231. data/spec/lib/apipie/generator/swagger/resource_description_composite_spec.rb +37 -0
  232. data/spec/lib/apipie/generator/swagger/resource_descriptions_collection_spec.rb +57 -0
  233. data/spec/lib/apipie/generator/swagger/schema_spec.rb +89 -0
  234. data/spec/lib/apipie/generator/swagger/type_extractor_spec.rb +38 -0
  235. data/spec/lib/apipie/generator/swagger/warning_spec.rb +51 -0
  236. data/spec/lib/apipie/generator/swagger/warning_writer_spec.rb +71 -0
  237. data/spec/lib/apipie/method_description/apis_service_spec.rb +60 -0
  238. data/spec/lib/apipie/method_description_spec.rb +133 -0
  239. data/spec/lib/apipie/no_documented_method_spec.rb +17 -0
  240. data/spec/lib/apipie/param_description/deprecation_spec.rb +31 -0
  241. data/spec/lib/apipie/param_description_spec.rb +671 -0
  242. data/spec/lib/apipie/param_group_spec.rb +61 -0
  243. data/spec/lib/apipie/resource_description_spec.rb +91 -0
  244. data/spec/lib/apipie/response_description/response_object_spec.rb +22 -0
  245. data/spec/lib/apipie/response_description_spec.rb +56 -0
  246. data/spec/lib/apipie/response_does_not_match_swagger_schema_spec.rb +35 -0
  247. data/spec/lib/apipie/swagger_generator_spec.rb +94 -0
  248. data/spec/lib/apipie/validator_spec.rb +149 -0
  249. data/spec/lib/rake_spec.rb +69 -0
  250. data/spec/lib/swagger/openapi_2_0_schema.json +1614 -0
  251. data/spec/lib/swagger/rake_swagger_spec.rb +159 -0
  252. data/spec/lib/swagger/swagger_dsl_spec.rb +664 -0
  253. data/spec/lib/validators/array_validator_spec.rb +85 -0
  254. data/spec/spec_helper.rb +92 -0
  255. data/spec/support/custom_bool_validator.rb +17 -0
  256. data/spec/support/rake.rb +21 -0
  257. data/spec/test_engine/memes_controller_spec.rb +10 -0
  258. metadata +499 -0
@@ -0,0 +1,454 @@
1
+ require 'set'
2
+
3
+ module Apipie
4
+ module Extractor
5
+ class Writer
6
+ class << self
7
+ def compressed
8
+ Apipie.configuration.compress_examples
9
+ end
10
+
11
+ def update_action_description(controller, action)
12
+ updater = ActionDescriptionUpdater.new(controller, action)
13
+ yield updater
14
+ updater.write!
15
+ rescue ActionDescriptionUpdater::ControllerNotFound
16
+ logger.warn("REST_API: Couldn't find controller file for #{controller}")
17
+ rescue ActionDescriptionUpdater::ActionNotFound
18
+ logger.warn("REST_API: Couldn't find action #{action} in #{controller}")
19
+ end
20
+
21
+ def write_recorded_examples(examples)
22
+ FileUtils.mkdir_p(File.dirname(examples_file))
23
+ content = serialize_examples(examples)
24
+ content = Zlib::Deflate.deflate(content).force_encoding('utf-8') if compressed
25
+ File.open(examples_file, 'w') { |f| f << content }
26
+ end
27
+
28
+ def load_recorded_examples
29
+ return {} unless File.exist?(examples_file)
30
+ load_json_examples
31
+ end
32
+
33
+ def examples_file
34
+ pure_path = Rails.root.join(
35
+ Apipie.configuration.doc_path, 'apipie_examples.json'
36
+ )
37
+ zipped_path = pure_path.to_s + '.gz'
38
+ return zipped_path if compressed
39
+ pure_path.to_s
40
+ end
41
+
42
+ protected
43
+
44
+ def serialize_examples(examples)
45
+ JSON.pretty_generate(
46
+ OrderedHash[*examples.sort_by(&:first).flatten(1)]
47
+ )
48
+ end
49
+
50
+ def deserialize_examples(examples_string)
51
+ examples = JSON.parse(examples_string)
52
+ return {} if examples.nil?
53
+ examples.each_value do |records|
54
+ records.each do |record|
55
+ record['verb'] = record['verb'].to_sym if record['verb']
56
+ end
57
+ end
58
+ end
59
+
60
+ def load_json_examples
61
+ raw = IO.read(examples_file)
62
+ raw = Zlib::Inflate.inflate(raw).force_encoding('utf-8') if compressed
63
+ deserialize_examples(raw)
64
+ end
65
+
66
+ def logger
67
+ Extractor.logger
68
+ end
69
+ end
70
+
71
+ def initialize(collector)
72
+ @collector = collector
73
+ end
74
+
75
+
76
+ def write_examples
77
+ merged_examples = merge_old_new_examples
78
+ self.class.write_recorded_examples(merged_examples)
79
+ end
80
+
81
+ def write_docs
82
+ descriptions = @collector.finalize_descriptions
83
+ descriptions.each_value do |desc|
84
+ if desc[:api].empty?
85
+ logger.warn("REST_API: Couldn't find any path for #{desc_to_s(desc)}")
86
+ next
87
+ end
88
+ self.class.update_action_description(desc[:controller], desc[:action]) do |u|
89
+ u.update_generated_description desc
90
+ end
91
+ end
92
+ end
93
+
94
+
95
+ protected
96
+
97
+
98
+ def desc_to_s(description)
99
+ "#{description[:controller].name}##{description[:action]}"
100
+ end
101
+
102
+ def ordered_call(call)
103
+ call = call.stringify_keys
104
+ ordered_call = OrderedHash.new
105
+ %w[title verb path versions query request_data response_data code show_in_doc recorded].each do |k|
106
+ next unless call.key?(k)
107
+ ordered_call[k] = case call[k]
108
+ when ActiveSupport::HashWithIndifferentAccess
109
+ convert_file_value(call[k]).to_hash
110
+ else
111
+ call[k]
112
+ end
113
+ end
114
+ return ordered_call
115
+ end
116
+
117
+ def convert_file_value hash
118
+ hash.each do |k, v|
119
+ case v
120
+ when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile
121
+ hash[k] = "<FILE CONTENT '#{v.original_filename}'>"
122
+ when Hash
123
+ hash[k] = convert_file_value(v)
124
+ end
125
+ end
126
+ hash
127
+ end
128
+
129
+ def load_recorded_examples
130
+ self.class.load_recorded_examples
131
+ end
132
+
133
+ def merge_old_new_examples
134
+ new_examples = self.load_new_examples
135
+ old_examples = self.load_recorded_examples
136
+ merged_examples = []
137
+ (new_examples.keys + old_examples.keys).uniq.each do |key|
138
+ if new_examples.key?(key)
139
+ if old_examples.key?(key)
140
+ records = deep_merge_examples(new_examples[key], old_examples[key])
141
+ else
142
+ records = new_examples[key]
143
+ end
144
+ else
145
+ records = old_examples[key]
146
+ end
147
+ merged_examples << [key, records.map { |r| ordered_call(r) } ]
148
+ end
149
+ return merged_examples
150
+ end
151
+
152
+ def deep_merge_examples(new_examples, old_examples)
153
+ new_examples.map do |new_example|
154
+ # Use ordered to get compareble output (mainly for the :query)
155
+ new_example_ordered = ordered_call(new_example.dup)
156
+
157
+ # Comparing verb, versions and query
158
+ if old_example = old_examples.find{ |old_example| old_example["verb"] == new_example_ordered["verb"] && old_example["versions"] == new_example_ordered["versions"] && old_example["query"] == new_example_ordered["query"]}
159
+
160
+ # Take the 'show in doc' attribute from the old example if it is present and the configuration requests the value to be persisted.
161
+ new_example[:show_in_doc] = old_example["show_in_doc"] if Apipie.configuration.persist_show_in_doc && old_example["show_in_doc"].to_i > 0
162
+
163
+ # Always take the title from the old example if it exists.
164
+ new_example[:title] ||= old_example["title"] if old_example["title"].present?
165
+ end
166
+ new_example
167
+ end
168
+ end
169
+
170
+ def load_new_examples
171
+ @collector.records.reduce({}) do |h, (method, calls)|
172
+ showed_in_versions = Set.new
173
+ # we have already shown some example
174
+ recorded_examples = calls.map do |call|
175
+ method_descriptions = Apipie.get_method_descriptions(call[:controller], call[:action])
176
+ call[:versions] = method_descriptions.map(&:version)
177
+
178
+ if Apipie.configuration.show_all_examples
179
+ show_in_doc = 1
180
+ elsif call[:versions].any? { |v| ! showed_in_versions.include?(v) }
181
+ call[:versions].each { |v| showed_in_versions << v }
182
+ show_in_doc = 1
183
+ else
184
+ show_in_doc = 0
185
+ end
186
+ example = call.merge(:show_in_doc => show_in_doc.to_i, :recorded => true)
187
+ example
188
+ end
189
+ h.update(method => recorded_examples)
190
+ end
191
+ end
192
+
193
+ def load_old_examples
194
+ if File.exist?(@examples_file)
195
+ if defined? SafeYAML
196
+ return YAML.load_file(@examples_file, :safe=>false)
197
+ else
198
+ return YAML.load_file(@examples_file)
199
+ end
200
+ end
201
+ return {}
202
+ end
203
+
204
+ def logger
205
+ self.class.logger
206
+ end
207
+
208
+ def showable_in_doc?(call)
209
+ # we don't want to mess documentation with too large examples
210
+ if hash_nodes_count(call["request_data"]) + hash_nodes_count(call["response_data"]) > 100
211
+ return false
212
+ else
213
+ return 1
214
+ end
215
+ end
216
+
217
+ def hash_nodes_count(node)
218
+ case node
219
+ when Hash
220
+ 1 + (node.values.map { |v| hash_nodes_count(v) }.reduce(:+) || 0)
221
+ when Array
222
+ node.map { |v| hash_nodes_count(v) }.reduce(:+) || 1
223
+ else
224
+ 1
225
+ end
226
+ end
227
+
228
+ end
229
+
230
+ class ActionDescriptionUpdater
231
+
232
+ class ControllerNotFound < StandardError; end
233
+
234
+ class ActionNotFound < StandardError; end
235
+
236
+ def initialize(controller, action)
237
+ @controller = controller
238
+ @action = action
239
+ end
240
+
241
+ def generated?
242
+ old_header.include?(Apipie.configuration.generated_doc_disclaimer)
243
+ end
244
+
245
+ def update_apis(apis)
246
+ new_header = ""
247
+ new_header << Apipie.configuration.generated_doc_disclaimer << "\n" if generated?
248
+ new_header << generate_apis_code(apis)
249
+ new_header << ensure_line_breaks(old_header.lines).reject do |line|
250
+ line.include?(Apipie.configuration.generated_doc_disclaimer) ||
251
+ line =~ /^api/
252
+ end.join
253
+ overwrite_header(new_header)
254
+ end
255
+
256
+ def update_generated_description(desc)
257
+ if generated? || old_header.empty?
258
+ new_header = generate_code(desc)
259
+ overwrite_header(new_header)
260
+ end
261
+ end
262
+
263
+ def update(new_header)
264
+ overwrite_header(new_header)
265
+ end
266
+
267
+ def old_header
268
+ return @old_header if defined? @old_header
269
+ @old_header = lines_above_method[/^\s*?#{Regexp.escape(Apipie.configuration.generated_doc_disclaimer)}.*/m]
270
+ @old_header ||= lines_above_method[/^\s*?\b(api|params|error|example)\b.*/m]
271
+ @old_header ||= ""
272
+ @old_header.sub!(/\A\s*\n/,"")
273
+ @old_header = align_indented(@old_header)
274
+ end
275
+
276
+ def write!
277
+ File.open(controller_path, "w") { |f| f << @controller_content }
278
+ @changed=false
279
+ end
280
+
281
+ protected
282
+
283
+ def logger
284
+ Extractor.logger
285
+ end
286
+
287
+ def action_line
288
+ return @action_line if defined? @action_line
289
+ @action_line = ensure_line_breaks(controller_content.lines).find_index { |line| line =~ /def \b#{@action}\b/ }
290
+ raise ActionNotFound unless @action_line
291
+ @action_line
292
+ end
293
+
294
+ def controller_path
295
+ @controller_path ||= Apipie::Extractor.controller_path(@controller.controller_path)
296
+ end
297
+
298
+ def controller_content
299
+ raise ControllerNotFound.new unless controller_path && File.exist?(controller_path)
300
+ @controller_content ||= File.read(controller_path)
301
+ end
302
+
303
+ def controller_content=(new_content)
304
+ return if @controller_name == new_content
305
+ remove_instance_variable("@action_line")
306
+ remove_instance_variable("@old_header")
307
+ @controller_content=new_content
308
+ @changed = true
309
+ end
310
+
311
+ def generate_code(desc)
312
+ code = "#{Apipie.configuration.generated_doc_disclaimer}\n"
313
+ code << generate_apis_code(desc[:api])
314
+ code << generate_params_code(desc[:params])
315
+ code << generate_errors_code(desc[:errors])
316
+ return code
317
+ end
318
+
319
+ def generate_apis_code(apis)
320
+ code = ""
321
+ apis.sort_by {|a| a[:path] }.each do |api|
322
+ desc = api[:desc]
323
+ name = @controller.controller_name.gsub("_", " ")
324
+ desc ||= case @action.to_s
325
+ when "show", "create", "update", "destroy"
326
+ name = name.singularize
327
+ "#{@action.capitalize} #{/^[aeiou]/.match?(name) ? 'an' : 'a'} #{name}"
328
+ when "index"
329
+ "List #{name}"
330
+ end
331
+
332
+ code << "api :#{api[:method]}, '#{api[:path]}'"
333
+ code << ", '#{desc}'" if desc
334
+ code << "\n"
335
+ end
336
+ return code
337
+ end
338
+
339
+ def generate_params_code(params, indent = "")
340
+ code = ""
341
+ params.sort_by {|n,_| n }.each do |(name, desc)|
342
+ desc[:type] = (desc[:type] && desc[:type].first) || Object
343
+ code << "#{indent}param"
344
+ if /\W/.match?(name)
345
+ code << " :'#{name}'"
346
+ else
347
+ code << " :#{name}"
348
+ end
349
+ code << ", #{desc[:type].inspect}"
350
+ if desc[:allow_nil]
351
+ code << ", allow_nil: true"
352
+ end
353
+ if desc[:required]
354
+ code << ", required: true"
355
+ end
356
+ if desc[:nested]
357
+ code << " do\n"
358
+ code << generate_params_code(desc[:nested], indent + " ")
359
+ code << "#{indent}end"
360
+ else
361
+ end
362
+ code << "\n"
363
+ end
364
+ code
365
+ end
366
+
367
+ def generate_errors_code(errors)
368
+ code = ""
369
+ errors.sort_by {|e| e[:code] }.each do |error|
370
+ code << "error code: #{error[:code]}\n"
371
+ end
372
+ code
373
+ end
374
+
375
+ def align_indented(text)
376
+ shift_left = ensure_line_breaks(text.lines).map { |l| l[/^\s*/].size }.min
377
+ ensure_line_breaks(text.lines).map { |l| l[shift_left..-1] }.join
378
+ end
379
+
380
+ def overwrite_header(new_header)
381
+ overwrite_line_from = action_line
382
+ overwrite_line_to = action_line
383
+ unless old_header.empty?
384
+ overwrite_line_from -= ensure_line_breaks(old_header.lines).count
385
+ end
386
+ lines = ensure_line_breaks(controller_content.lines).to_a
387
+ indentation = lines[action_line][/^\s*/]
388
+ self.controller_content= (lines[0...overwrite_line_from] +
389
+ [new_header.gsub(/^/,indentation)] +
390
+ lines[overwrite_line_to..-1]).join
391
+ end
392
+
393
+ # returns all the lines before the method that might contain the restpi descriptions
394
+ # not bulletproof but working for conventional Ruby code
395
+ def lines_above_method
396
+ added_lines = []
397
+ lines_to_add = []
398
+ block_level = 0
399
+ ensure_line_breaks(controller_content.lines).first(action_line).reverse_each do |line|
400
+ if /\s*\bend\b\s*/.match?(line)
401
+ block_level += 1
402
+ end
403
+ if block_level > 0
404
+ lines_to_add << line
405
+ else
406
+ added_lines << line
407
+ end
408
+ if /\s*\b(module|class|def)\b /.match?(line)
409
+ break
410
+ end
411
+ next unless /do\s*(\|.*?\|)?\s*$/.match?(line)
412
+ block_level -= 1
413
+ if block_level == 0
414
+ added_lines.concat(lines_to_add)
415
+ lines_to_add = []
416
+ end
417
+ end
418
+ return added_lines.reverse.join
419
+ end
420
+
421
+ # this method would be totally useless unless some clever guy
422
+ # decided that it would be great idea to change a behavior of
423
+ # "".lines method to not include end of lines.
424
+ #
425
+ # For more details:
426
+ # https://github.com/puppetlabs/puppet/blob/0dc44695/lib/puppet/util/monkey_patches.rb
427
+ def ensure_line_breaks(lines)
428
+ if lines.to_a.size > 1 && lines.first !~ /\n\Z/
429
+ lines.map { |l| !/\n\Z/.match?(l) ? (l << "\n") : l }.to_enum
430
+ else
431
+ lines
432
+ end
433
+ end
434
+ end
435
+
436
+ # Used to keep apipie_examples.yml params in order
437
+ class OrderedHash < ActiveSupport::OrderedHash
438
+
439
+ def to_yaml_type
440
+ "!tag:yaml.org,2002:map"
441
+ end
442
+
443
+ def to_yaml(opts = {})
444
+ YAML.quick_emit(self, opts) do |out|
445
+ out.map(taguri) do |map|
446
+ each do |k, v|
447
+ map.add(k, v)
448
+ end
449
+ end
450
+ end
451
+ end
452
+ end
453
+ end
454
+ end
@@ -0,0 +1,181 @@
1
+ require 'singleton'
2
+ require 'fileutils'
3
+ require 'set'
4
+ require 'yaml'
5
+ require 'apipie/extractor/recorder'
6
+ require 'apipie/extractor/writer'
7
+ require 'apipie/extractor/collector'
8
+
9
+ class Apipie::Railtie
10
+ initializer 'apipie.extractor' do |app|
11
+ ActiveSupport.on_load :action_controller do
12
+ before_action do |controller|
13
+ if Apipie.configuration.record
14
+ Apipie::Extractor.call_recorder.analyse_controller(controller)
15
+ end
16
+ end
17
+ end
18
+
19
+ if Apipie.configuration.record
20
+ app.middleware.use ::Apipie::Extractor::Recorder::Middleware
21
+
22
+ ActionController::TestCase.send(:prepend, Apipie::Extractor::Recorder::FunctionalTestRecording)
23
+ ActionController::TestCase::Behavior.send(:prepend, Apipie::Extractor::Recorder::FunctionalTestRecording)
24
+ end
25
+ end
26
+ end
27
+
28
+ module Apipie
29
+
30
+ module Extractor
31
+
32
+ class << self
33
+
34
+ def start(record)
35
+ Apipie.configuration.record = record
36
+ Apipie.configuration.force_dsl = true
37
+ end
38
+
39
+ def finish
40
+ record_params, record_examples = false, false
41
+ case Apipie.configuration.record
42
+ when "params" then record_params = true
43
+ when "examples" then record_examples = true
44
+ when "all" then record_params = true, record_examples = true
45
+ end
46
+
47
+ if record_examples
48
+ puts "Writing examples to a file"
49
+ write_examples
50
+ end
51
+ if record_params
52
+ puts "Updating auto-generated documentation"
53
+ write_docs
54
+ end
55
+ end
56
+
57
+ delegate :logger, to: :Rails
58
+
59
+ def call_recorder
60
+ Thread.current[:apipie_call_recorder] ||= Recorder.new
61
+ end
62
+
63
+ def call_finished
64
+ @collector ||= Collector.new
65
+ if record = call_recorder.record
66
+ @collector.handle_record(record)
67
+ end
68
+ end
69
+
70
+ def clean_call_recorder
71
+ Thread.current[:apipie_call_recorder] = nil
72
+ end
73
+
74
+ def write_docs
75
+ Writer.new(@collector).write_docs if @collector
76
+ end
77
+
78
+ def write_examples
79
+ Writer.new(@collector).write_examples if @collector
80
+ end
81
+
82
+ def apis_from_routes
83
+ return @apis_from_routes if @apis_from_routes
84
+
85
+ @api_prefix = Apipie.api_base_url.sub(%r{/$},"")
86
+ populate_api_routes
87
+ update_api_descriptions
88
+
89
+ @apis_from_routes
90
+ end
91
+
92
+ def controller_path(name)
93
+ Apipie.api_controllers_paths.detect { |p| p.include?("#{name}_controller.rb") }
94
+ end
95
+
96
+ private
97
+
98
+ def all_api_routes
99
+ all_routes = Apipie.configuration.api_routes.routes.map do |r|
100
+ {
101
+ :verb => case r.verb
102
+ when Regexp then r.verb.source[/\w+/]
103
+ else r.verb.to_s
104
+ end,
105
+ :path => case
106
+ when r.path.respond_to?(:spec) then r.path.spec.to_s
107
+ else r.path.to_s
108
+ end,
109
+ :controller => r.requirements[:controller],
110
+ :action => r.requirements[:action]
111
+ }
112
+ end
113
+
114
+ return all_routes.find_all do |r|
115
+ r[:path].starts_with?(Apipie.api_base_url)
116
+ end
117
+ end
118
+
119
+ def populate_api_routes
120
+ @apis_from_routes = Hash.new { |h, k| h[k] = [] }
121
+
122
+ all_api_routes.each do |route|
123
+ controller_path, action = route[:controller], route[:action]
124
+ next unless controller_path && action
125
+
126
+ controller_path = controller_path.split('::').map(&:camelize).join('::')
127
+ controller = "#{controller_path}Controller"
128
+
129
+ path = if /^#{Regexp.escape(@api_prefix)}(.*)$/ =~ route[:path]
130
+ $1.sub(/\(\.:format\)$/,'')
131
+ else
132
+ nil
133
+ end
134
+
135
+ if route[:verb].present?
136
+ @apis_from_routes[[controller, action]] << {:method => route[:verb], :path => path}
137
+ end
138
+ end
139
+ end
140
+
141
+ def all_apis_from_docs
142
+ resource_descriptions = Apipie.resource_descriptions.values.map(&:values).flatten
143
+ method_descriptions = resource_descriptions.map(&:method_descriptions).flatten
144
+
145
+ return method_descriptions.reduce({}) do |h, desc|
146
+ apis = desc.method_apis_to_json.map do |api|
147
+ { :method => api[:http_method],
148
+ :path => api[:api_url],
149
+ :desc => api[:short_description] }
150
+ end
151
+ h.update(desc.id => apis)
152
+ end
153
+ end
154
+
155
+ def update_api_descriptions
156
+ apis_from_docs = all_apis_from_docs
157
+ @apis_from_routes.each do |(controller, action), new_apis|
158
+ method_key = "#{Apipie.get_resource_id(controller.safe_constantize || next)}##{action}"
159
+ old_apis = apis_from_docs[method_key] || []
160
+ new_apis.each do |new_api|
161
+ new_api[:path]&.sub!(/\(\.:format\)$/,"")
162
+ old_api = old_apis.find do |api|
163
+ api[:path] == "#{@api_prefix}#{new_api[:path]}"
164
+ end
165
+ if old_api
166
+ new_api[:desc] = old_api[:desc]
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ if ENV["APIPIE_RECORD"]
176
+ Apipie::Extractor.start ENV["APIPIE_RECORD"]
177
+ end
178
+
179
+ at_exit do
180
+ Apipie::Extractor.finish
181
+ end
@@ -0,0 +1,12 @@
1
+ module Apipie
2
+ module Generator
3
+ # Configuration interface for generators
4
+ class Config
5
+ include Singleton
6
+
7
+ def swagger
8
+ Apipie::Generator::Swagger::Config.instance
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,2 @@
1
+ module Apipie::Generator
2
+ end
@@ -0,0 +1,23 @@
1
+ # The {Apipie::Generator::Swagger::ComputedInterfaceId.id} is a number that is
2
+ # uniquely derived from the list of operations added to the swagger definition (in an order-dependent way).
3
+ # it can be used for regression testing, allowing some differentiation between changes that
4
+ # result from changes to the input and those that result from changes to the generation
5
+ # algorithms.
6
+ #
7
+ # @note At the moment, this only takes operation ids into account, and ignores
8
+ # parameter definitions, so it's only partially useful.
9
+ class Apipie::Generator::Swagger::ComputedInterfaceId
10
+ include Singleton
11
+
12
+ def initialize
13
+ @computed_interface_id = 0
14
+ end
15
+
16
+ def add!(operation_id)
17
+ @computed_interface_id = Zlib.crc32("#{@computed_interface_id} #{operation_id}")
18
+ end
19
+
20
+ def id
21
+ @computed_interface_id
22
+ end
23
+ end