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.
- checksums.yaml +7 -0
- data/.github/workflows/build.yml +32 -0
- data/.github/workflows/rubocop-challenger.yml +26 -0
- data/.github/workflows/rubocop.yml +18 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.rubocop.yml +132 -0
- data/.rubocop_todo.yml +1967 -0
- data/.vscode/settings.json +3 -0
- data/APACHE-LICENSE-2.0 +202 -0
- data/CHANGELOG.md +693 -0
- data/Gemfile +19 -0
- data/MIT-LICENSE +20 -0
- data/NOTICE +4 -0
- data/PROPOSAL_FOR_RESPONSE_DESCRIPTIONS.md +244 -0
- data/README.md +2088 -0
- data/Rakefile +8 -0
- data/apipie-rails.gemspec +44 -0
- data/app/controllers/apipie/apipies_controller.rb +184 -0
- data/app/helpers/apipie_helper.rb +10 -0
- data/app/public/apipie/javascripts/apipie.js +6 -0
- data/app/public/apipie/javascripts/bundled/bootstrap-collapse.js +167 -0
- data/app/public/apipie/javascripts/bundled/bootstrap.js +2280 -0
- data/app/public/apipie/javascripts/bundled/jquery.js +2 -0
- data/app/public/apipie/javascripts/bundled/prettify.js +28 -0
- data/app/public/apipie/stylesheets/application.css +7 -0
- data/app/public/apipie/stylesheets/bundled/bootstrap-responsive.min.css +9 -0
- data/app/public/apipie/stylesheets/bundled/bootstrap.min.css +9 -0
- data/app/public/apipie/stylesheets/bundled/prettify.css +30 -0
- data/app/views/apipie/apipies/_deprecation.html.erb +16 -0
- data/app/views/apipie/apipies/_disqus.html.erb +13 -0
- data/app/views/apipie/apipies/_errors.html.erb +23 -0
- data/app/views/apipie/apipies/_headers.html.erb +26 -0
- data/app/views/apipie/apipies/_languages.erb +6 -0
- data/app/views/apipie/apipies/_metadata.erb +1 -0
- data/app/views/apipie/apipies/_method_detail.erb +63 -0
- data/app/views/apipie/apipies/_params.html.erb +49 -0
- data/app/views/apipie/apipies/_params_plain.html.erb +21 -0
- data/app/views/apipie/apipies/apipie_404.html.erb +17 -0
- data/app/views/apipie/apipies/apipie_checksum.json.erb +1 -0
- data/app/views/apipie/apipies/getting_started.html.erb +6 -0
- data/app/views/apipie/apipies/index.html.erb +56 -0
- data/app/views/apipie/apipies/method.html.erb +41 -0
- data/app/views/apipie/apipies/plain.html.erb +77 -0
- data/app/views/apipie/apipies/resource.html.erb +80 -0
- data/app/views/apipie/apipies/static.html.erb +103 -0
- data/app/views/layouts/apipie/apipie.html.erb +27 -0
- data/config/locales/de.yml +28 -0
- data/config/locales/en.yml +41 -0
- data/config/locales/es.yml +28 -0
- data/config/locales/fr.yml +31 -0
- data/config/locales/it.yml +41 -0
- data/config/locales/ja.yml +31 -0
- data/config/locales/ko.yml +32 -0
- data/config/locales/pl.yml +28 -0
- data/config/locales/pt-BR.yml +28 -0
- data/config/locales/ru.yml +28 -0
- data/config/locales/tr.yml +28 -0
- data/config/locales/zh-CN.yml +28 -0
- data/config/locales/zh-TW.yml +28 -0
- data/gemfiles/Gemfile.tools +9 -0
- data/images/screenshot-1.png +0 -0
- data/images/screenshot-2.png +0 -0
- data/lib/apipie/apipie_module.rb +83 -0
- data/lib/apipie/application.rb +499 -0
- data/lib/apipie/configuration.rb +196 -0
- data/lib/apipie/core_ext/route.rb +9 -0
- data/lib/apipie/dsl_definition.rb +630 -0
- data/lib/apipie/error_description.rb +46 -0
- data/lib/apipie/errors.rb +86 -0
- data/lib/apipie/extractor/collector.rb +116 -0
- data/lib/apipie/extractor/recorder.rb +193 -0
- data/lib/apipie/extractor/writer.rb +454 -0
- data/lib/apipie/extractor.rb +181 -0
- data/lib/apipie/generator/config.rb +12 -0
- data/lib/apipie/generator/generator.rb +2 -0
- data/lib/apipie/generator/swagger/computed_interface_id.rb +23 -0
- data/lib/apipie/generator/swagger/config.rb +80 -0
- data/lib/apipie/generator/swagger/context.rb +38 -0
- data/lib/apipie/generator/swagger/method_description/api_decorator.rb +20 -0
- data/lib/apipie/generator/swagger/method_description/api_schema_service.rb +89 -0
- data/lib/apipie/generator/swagger/method_description/decorator.rb +22 -0
- data/lib/apipie/generator/swagger/method_description/parameters_service.rb +139 -0
- data/lib/apipie/generator/swagger/method_description/response_schema_service.rb +46 -0
- data/lib/apipie/generator/swagger/method_description/response_service.rb +71 -0
- data/lib/apipie/generator/swagger/method_description.rb +2 -0
- data/lib/apipie/generator/swagger/operation_id.rb +51 -0
- data/lib/apipie/generator/swagger/param_description/builder.rb +114 -0
- data/lib/apipie/generator/swagger/param_description/composite.rb +119 -0
- data/lib/apipie/generator/swagger/param_description/description.rb +15 -0
- data/lib/apipie/generator/swagger/param_description/in.rb +37 -0
- data/lib/apipie/generator/swagger/param_description/name.rb +18 -0
- data/lib/apipie/generator/swagger/param_description/path_params_composite.rb +61 -0
- data/lib/apipie/generator/swagger/param_description/referenced_composite.rb +36 -0
- data/lib/apipie/generator/swagger/param_description/type.rb +132 -0
- data/lib/apipie/generator/swagger/param_description.rb +18 -0
- data/lib/apipie/generator/swagger/path_decorator.rb +36 -0
- data/lib/apipie/generator/swagger/referenced_definitions.rb +17 -0
- data/lib/apipie/generator/swagger/resource_description_collection.rb +30 -0
- data/lib/apipie/generator/swagger/resource_description_composite.rb +56 -0
- data/lib/apipie/generator/swagger/schema.rb +63 -0
- data/lib/apipie/generator/swagger/swagger.rb +2 -0
- data/lib/apipie/generator/swagger/type.rb +16 -0
- data/lib/apipie/generator/swagger/type_extractor.rb +51 -0
- data/lib/apipie/generator/swagger/warning.rb +74 -0
- data/lib/apipie/generator/swagger/warning_writer.rb +54 -0
- data/lib/apipie/helpers.rb +73 -0
- data/lib/apipie/markup.rb +52 -0
- data/lib/apipie/method_description/api.rb +12 -0
- data/lib/apipie/method_description/apis_service.rb +82 -0
- data/lib/apipie/method_description.rb +230 -0
- data/lib/apipie/middleware/checksum_in_headers.rb +35 -0
- data/lib/apipie/param_description/deprecation.rb +24 -0
- data/lib/apipie/param_description.rb +313 -0
- data/lib/apipie/railtie.rb +9 -0
- data/lib/apipie/resource_description.rb +152 -0
- data/lib/apipie/response_description.rb +157 -0
- data/lib/apipie/response_description_adapter.rb +202 -0
- data/lib/apipie/routes_formatter.rb +33 -0
- data/lib/apipie/routing.rb +16 -0
- data/lib/apipie/rspec/response_validation_helper.rb +194 -0
- data/lib/apipie/see_description.rb +39 -0
- data/lib/apipie/static_dispatcher.rb +75 -0
- data/lib/apipie/swagger_generator.rb +45 -0
- data/lib/apipie/tag_list_description.rb +11 -0
- data/lib/apipie/validator.rb +552 -0
- data/lib/apipie/version.rb +3 -0
- data/lib/apipie-rails.rb +60 -0
- data/lib/generators/apipie/install/README +6 -0
- data/lib/generators/apipie/install/install_generator.rb +25 -0
- data/lib/generators/apipie/install/templates/initializer.rb.erb +7 -0
- data/lib/generators/apipie/views_generator.rb +11 -0
- data/lib/tasks/apipie.rake +355 -0
- data/rel-eng/gem_release.ipynb +398 -0
- data/rel-eng/packages/.readme +3 -0
- data/rel-eng/packages/rubygem-apipie-rails +1 -0
- data/rel-eng/tito.props +5 -0
- data/spec/controllers/api/v1/architectures_controller_spec.rb +29 -0
- data/spec/controllers/api/v2/architectures_controller_spec.rb +19 -0
- data/spec/controllers/api/v2/empty_middle_controller_spec.rb +23 -0
- data/spec/controllers/api/v2/nested/resources_controller_spec.rb +27 -0
- data/spec/controllers/api/v2/sub/footguns_controller_spec.rb +19 -0
- data/spec/controllers/concerns_controller_spec.rb +42 -0
- data/spec/controllers/extended_controller_spec.rb +14 -0
- data/spec/controllers/included_param_group_controller_spec.rb +13 -0
- data/spec/controllers/pets_controller_spec.rb +98 -0
- data/spec/controllers/users_controller_spec.rb +794 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/api/base_controller.rb +4 -0
- data/spec/dummy/app/controllers/api/v1/architectures_controller.rb +43 -0
- data/spec/dummy/app/controllers/api/v1/base_controller.rb +11 -0
- data/spec/dummy/app/controllers/api/v2/architectures_controller.rb +31 -0
- data/spec/dummy/app/controllers/api/v2/base_controller.rb +17 -0
- data/spec/dummy/app/controllers/api/v2/empty_middle_controller.rb +14 -0
- data/spec/dummy/app/controllers/api/v2/nested/architectures_controller.rb +32 -0
- data/spec/dummy/app/controllers/api/v2/nested/resources_controller.rb +33 -0
- data/spec/dummy/app/controllers/api/v2/sub/footguns_controller.rb +30 -0
- data/spec/dummy/app/controllers/application_controller.rb +18 -0
- data/spec/dummy/app/controllers/concerns_controller.rb +8 -0
- data/spec/dummy/app/controllers/extended_controller.rb +14 -0
- data/spec/dummy/app/controllers/extending_concern.rb +10 -0
- data/spec/dummy/app/controllers/files_controller.rb +5 -0
- data/spec/dummy/app/controllers/included_param_group_controller.rb +19 -0
- data/spec/dummy/app/controllers/overridden_concerns_controller.rb +31 -0
- data/spec/dummy/app/controllers/pets_controller.rb +408 -0
- data/spec/dummy/app/controllers/pets_using_auto_views_controller.rb +73 -0
- data/spec/dummy/app/controllers/pets_using_self_describing_classes_controller.rb +95 -0
- data/spec/dummy/app/controllers/sample_controller.rb +39 -0
- data/spec/dummy/app/controllers/tagged_cats_controller.rb +32 -0
- data/spec/dummy/app/controllers/tagged_dogs_controller.rb +15 -0
- data/spec/dummy/app/controllers/twitter_example_controller.rb +307 -0
- data/spec/dummy/app/controllers/users_controller.rb +310 -0
- data/spec/dummy/app/helpers/random_param_group.rb +8 -0
- data/spec/dummy/app/views/layouts/application.html.erb +21 -0
- data/spec/dummy/components/test_engine/Gemfile +6 -0
- data/spec/dummy/components/test_engine/app/controllers/test_engine/application_controller.rb +4 -0
- data/spec/dummy/components/test_engine/app/controllers/test_engine/memes_controller.rb +37 -0
- data/spec/dummy/components/test_engine/config/routes.rb +3 -0
- data/spec/dummy/components/test_engine/db/.gitkeep +0 -0
- data/spec/dummy/components/test_engine/lib/test_engine.rb +7 -0
- data/spec/dummy/components/test_engine/test_engine.gemspec +11 -0
- data/spec/dummy/config/application.rb +47 -0
- data/spec/dummy/config/boot.rb +12 -0
- data/spec/dummy/config/database.yml +21 -0
- data/spec/dummy/config/environment.rb +8 -0
- data/spec/dummy/config/environments/development.rb +25 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +33 -0
- data/spec/dummy/config/initializers/apipie.rb +110 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +8 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +61 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/.gitkeep +0 -0
- data/spec/dummy/doc/apipie_examples.json +1 -0
- data/spec/dummy/doc/users/desc_from_file.md +1 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/lib/apipie/apipies_controller_spec.rb +345 -0
- data/spec/lib/apipie/application_spec.rb +62 -0
- data/spec/lib/apipie/configuration_spec.rb +38 -0
- data/spec/lib/apipie/extractor/collector_spec.rb +57 -0
- data/spec/lib/apipie/extractor/recorder/middleware_spec.rb +44 -0
- data/spec/lib/apipie/extractor/recorder_spec.rb +77 -0
- data/spec/lib/apipie/extractor/writer_spec.rb +112 -0
- data/spec/lib/apipie/extractor_spec.rb +9 -0
- data/spec/lib/apipie/file_handler_spec.rb +25 -0
- data/spec/lib/apipie/generator/swagger/config_spec.rb +19 -0
- data/spec/lib/apipie/generator/swagger/context_spec.rb +56 -0
- data/spec/lib/apipie/generator/swagger/method_description/api_schema_service_spec.rb +119 -0
- data/spec/lib/apipie/generator/swagger/method_description/response_schema_service_spec.rb +105 -0
- data/spec/lib/apipie/generator/swagger/method_description/response_service_spec.rb +62 -0
- data/spec/lib/apipie/generator/swagger/operation_id_spec.rb +63 -0
- data/spec/lib/apipie/generator/swagger/param_description/builder_spec.rb +245 -0
- data/spec/lib/apipie/generator/swagger/param_description/composite_spec.rb +95 -0
- data/spec/lib/apipie/generator/swagger/param_description/description_spec.rb +79 -0
- data/spec/lib/apipie/generator/swagger/param_description/in_spec.rb +86 -0
- data/spec/lib/apipie/generator/swagger/param_description/name_spec.rb +81 -0
- data/spec/lib/apipie/generator/swagger/param_description/type_spec.rb +210 -0
- data/spec/lib/apipie/generator/swagger/param_description_spec.rb +28 -0
- data/spec/lib/apipie/generator/swagger/path_decorator_spec.rb +57 -0
- data/spec/lib/apipie/generator/swagger/referenced_definitions_spec.rb +35 -0
- data/spec/lib/apipie/generator/swagger/resource_description_composite_spec.rb +37 -0
- data/spec/lib/apipie/generator/swagger/resource_descriptions_collection_spec.rb +57 -0
- data/spec/lib/apipie/generator/swagger/schema_spec.rb +89 -0
- data/spec/lib/apipie/generator/swagger/type_extractor_spec.rb +38 -0
- data/spec/lib/apipie/generator/swagger/warning_spec.rb +51 -0
- data/spec/lib/apipie/generator/swagger/warning_writer_spec.rb +71 -0
- data/spec/lib/apipie/method_description/apis_service_spec.rb +60 -0
- data/spec/lib/apipie/method_description_spec.rb +133 -0
- data/spec/lib/apipie/no_documented_method_spec.rb +17 -0
- data/spec/lib/apipie/param_description/deprecation_spec.rb +31 -0
- data/spec/lib/apipie/param_description_spec.rb +671 -0
- data/spec/lib/apipie/param_group_spec.rb +61 -0
- data/spec/lib/apipie/resource_description_spec.rb +91 -0
- data/spec/lib/apipie/response_description/response_object_spec.rb +22 -0
- data/spec/lib/apipie/response_description_spec.rb +56 -0
- data/spec/lib/apipie/response_does_not_match_swagger_schema_spec.rb +35 -0
- data/spec/lib/apipie/swagger_generator_spec.rb +94 -0
- data/spec/lib/apipie/validator_spec.rb +149 -0
- data/spec/lib/rake_spec.rb +69 -0
- data/spec/lib/swagger/openapi_2_0_schema.json +1614 -0
- data/spec/lib/swagger/rake_swagger_spec.rb +159 -0
- data/spec/lib/swagger/swagger_dsl_spec.rb +664 -0
- data/spec/lib/validators/array_validator_spec.rb +85 -0
- data/spec/spec_helper.rb +92 -0
- data/spec/support/custom_bool_validator.rb +17 -0
- data/spec/support/rake.rb +21 -0
- data/spec/test_engine/memes_controller_spec.rb +10 -0
- 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,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
|