brainstem 2.0.0 → 2.1.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +147 -0
  3. data/Gemfile.lock +68 -39
  4. data/lib/brainstem/api_docs.rb +9 -4
  5. data/lib/brainstem/api_docs/atlas.rb +3 -3
  6. data/lib/brainstem/api_docs/controller.rb +12 -4
  7. data/lib/brainstem/api_docs/controller_collection.rb +11 -2
  8. data/lib/brainstem/api_docs/endpoint.rb +17 -7
  9. data/lib/brainstem/api_docs/endpoint_collection.rb +9 -1
  10. data/lib/brainstem/api_docs/formatters/open_api_specification/helper.rb +19 -16
  11. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter.rb +52 -80
  12. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter.rb +64 -84
  13. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter.rb +1 -1
  14. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/endpoint_param_formatter.rb +39 -0
  15. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/presenter_field_formatter.rb +147 -0
  16. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/response_field_formatter.rb +146 -0
  17. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter.rb +53 -55
  18. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter.rb +1 -1
  19. data/lib/brainstem/api_docs/presenter.rb +16 -8
  20. data/lib/brainstem/api_docs/presenter_collection.rb +8 -5
  21. data/lib/brainstem/api_docs/sinks/open_api_specification_sink.rb +3 -1
  22. data/lib/brainstem/cli/generate_api_docs_command.rb +4 -0
  23. data/lib/brainstem/concerns/controller_dsl.rb +90 -20
  24. data/lib/brainstem/concerns/presenter_dsl.rb +16 -8
  25. data/lib/brainstem/dsl/association.rb +12 -0
  26. data/lib/brainstem/dsl/fields_block.rb +1 -1
  27. data/lib/brainstem/version.rb +1 -1
  28. data/spec/brainstem/api_docs/controller_spec.rb +127 -5
  29. data/spec/brainstem/api_docs/endpoint_spec.rb +489 -57
  30. data/spec/brainstem/api_docs/formatters/open_api_specification/helper_spec.rb +15 -4
  31. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter_spec.rb +112 -66
  32. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter_spec.rb +404 -32
  33. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/endpoint_param_formatter_spec.rb +335 -0
  34. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/presenter_field_formatter_spec.rb +237 -0
  35. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/field_definitions/response_field_formatter_spec.rb +413 -0
  36. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter_spec.rb +116 -4
  37. data/spec/brainstem/api_docs/presenter_spec.rb +406 -24
  38. data/spec/brainstem/cli/generate_api_docs_command_spec.rb +8 -0
  39. data/spec/brainstem/concerns/controller_dsl_spec.rb +606 -45
  40. data/spec/brainstem/concerns/presenter_dsl_spec.rb +34 -2
  41. data/spec/brainstem/dsl/association_spec.rb +54 -3
  42. metadata +11 -2
@@ -109,7 +109,7 @@ module Brainstem
109
109
  def format_tag_data(controller)
110
110
  {
111
111
  name: tag_name(controller),
112
- description: format_description(controller.description),
112
+ description: format_sentence(controller.description),
113
113
  }.reject { |_, v| v.blank? }
114
114
  end
115
115
  end
@@ -21,14 +21,16 @@ module Brainstem
21
21
  :filename_pattern,
22
22
  :filename_link_pattern,
23
23
  :document_empty_associations,
24
- :document_empty_filters
24
+ :document_empty_filters,
25
+ :include_internal
25
26
  ]
26
27
  end
27
28
 
28
29
  attr_accessor :const,
29
30
  :target_class,
30
31
  :document_empty_associations,
31
- :document_empty_filters
32
+ :document_empty_filters,
33
+ :include_internal
32
34
 
33
35
  attr_writer :filename_pattern,
34
36
  :filename_link_pattern
@@ -75,7 +77,7 @@ module Brainstem
75
77
  delegate :find_by_class => :atlas
76
78
 
77
79
  def nodoc?
78
- configuration[:nodoc]
80
+ nodoc_for?(configuration)
79
81
  end
80
82
 
81
83
  def title
@@ -98,7 +100,7 @@ module Brainstem
98
100
  alias_method :valid_fields_in, :valid_fields
99
101
 
100
102
  def invalid_field?(field)
101
- field.options[:nodoc]
103
+ nodoc_for?(field.options)
102
104
  end
103
105
 
104
106
  def nested_field?(field)
@@ -131,7 +133,7 @@ module Brainstem
131
133
  end
132
134
 
133
135
  def documentable_filter?(_, filter)
134
- !filter[:nodoc] &&
136
+ !nodoc_for?(filter) &&
135
137
  (
136
138
  document_empty_filters? || # document empty filters or
137
139
  !(filter[:info] || "").empty? # has info string
@@ -143,7 +145,7 @@ module Brainstem
143
145
  end
144
146
 
145
147
  def valid_sort_orders
146
- configuration[:sort_orders].to_h.reject {|k, v| v[:nodoc] }
148
+ configuration[:sort_orders].to_h.reject {|_k, v| nodoc_for?(v) }
147
149
  end
148
150
 
149
151
  def valid_associations
@@ -168,7 +170,7 @@ module Brainstem
168
170
  # @return [Bool] document this association?
169
171
  #
170
172
  def documentable_association?(_, association)
171
- !association.options[:nodoc] && # not marked nodoc and
173
+ !nodoc_for?(association.options) && # not marked nodoc and
172
174
  (
173
175
  document_empty_associations? || # document empty associations or
174
176
  !(association.description.nil? || association.description.empty?) # has description
@@ -196,7 +198,7 @@ module Brainstem
196
198
  #
197
199
  def contextual_documentation(key)
198
200
  configuration.has_key?(key) &&
199
- !configuration[key][:nodoc] &&
201
+ !nodoc_for?(configuration[key]) &&
200
202
  configuration[key][:info]
201
203
  end
202
204
 
@@ -210,6 +212,12 @@ module Brainstem
210
212
 
211
213
  presenter_path.relative_path_from(my_path).to_s
212
214
  end
215
+
216
+ private
217
+
218
+ def nodoc_for?(config)
219
+ !!(config[:nodoc] || (config[:internal] && !include_internal))
220
+ end
213
221
  end
214
222
  end
215
223
  end
@@ -9,10 +9,11 @@ module Brainstem
9
9
  include Concerns::Formattable
10
10
 
11
11
  def valid_options
12
- super | [ :presenter_constant_lookup_method ]
12
+ super | [ :presenter_constant_lookup_method, :include_internal ]
13
13
  end
14
14
 
15
15
  attr_writer :presenter_constant_lookup_method
16
+ attr_accessor :include_internal
16
17
 
17
18
  #
18
19
  # Finds or creates a presenter with the given target class and appends it to the
@@ -38,8 +39,9 @@ module Brainstem
38
39
  #
39
40
  def create_from_target_class(target_class)
40
41
  ::Brainstem::ApiDocs::Presenter.new(atlas,
41
- target_class: target_class,
42
- const: target_class_to_const(target_class)
42
+ target_class: target_class,
43
+ const: target_class_to_const(target_class),
44
+ include_internal: include_internal
43
45
  ).tap { |p| self.<< p }
44
46
  rescue KeyError
45
47
  nil
@@ -54,8 +56,9 @@ module Brainstem
54
56
 
55
57
  def create_from_presenter_collection(target_class, const)
56
58
  ::Brainstem::ApiDocs::Presenter.new(atlas,
57
- target_class: target_class,
58
- const: const
59
+ target_class: target_class,
60
+ const: const,
61
+ include_internal: include_internal
59
62
  ).tap { |p| self.<< p }
60
63
  end
61
64
 
@@ -20,6 +20,7 @@ module Brainstem
20
20
  :write_path,
21
21
  :oas_filename_pattern,
22
22
  :output_extension,
23
+ :include_internal
23
24
  ]
24
25
  end
25
26
 
@@ -32,7 +33,8 @@ module Brainstem
32
33
  :ignore_tagging,
33
34
  :oas_filename_pattern,
34
35
  :output_extension,
35
- :output
36
+ :output,
37
+ :include_internal
36
38
 
37
39
  delegate [:controllers, :presenters] => :atlas
38
40
 
@@ -158,6 +158,10 @@ module Brainstem
158
158
  options[:sink][:options][:output_extension] = extension
159
159
  end
160
160
 
161
+ opts.on('--include-internal', 'Generate docs for all `internal!` flagged presenters/controllers') do |_|
162
+ options[:builder][:args_for_atlas][:include_internal] = true
163
+ end
164
+
161
165
  #########################################################
162
166
  # #
163
167
  # Open API Specification generation specific commands: #
@@ -8,6 +8,7 @@ module Brainstem
8
8
  include Brainstem::Concerns::InheritableConfiguration
9
9
 
10
10
  DEFAULT_BRAINSTEM_PARAMS_CONTEXT = :_default
11
+ DYNAMIC_KEY = :_dynamic_key
11
12
 
12
13
  included do
13
14
  reset_configuration!
@@ -64,8 +65,18 @@ module Brainstem
64
65
  # whereas setting it within an action context will force that action to
65
66
  # be undocumented.
66
67
  #
67
- def nodoc!
68
- configuration[brainstem_params_context][:nodoc] = true
68
+ def nodoc!(description = true)
69
+ configuration[brainstem_params_context][:nodoc] = description
70
+ end
71
+
72
+ #
73
+ # Specifies that the scope should not be documented unless the `--include-internal`
74
+ # flag is passed in the CLI. Setting this on the default context
75
+ # will force the controller to be undocumented, whereas setting it
76
+ # within an action context will force that action to be undocumented.
77
+ #
78
+ def internal!(description = true)
79
+ configuration[brainstem_params_context][:internal] = description
69
80
  end
70
81
 
71
82
  #
@@ -77,11 +88,15 @@ module Brainstem
77
88
  # Setting the +:nodoc+ option marks this presenter as 'internal use only',
78
89
  # and causes formatters to display this as not indicated.
79
90
  #
91
+ # Setting the +:internal+ option marks this presenter as 'internal use only',
92
+ # and causes formatters to not display this unless documentation is generated with
93
+ # `--include-internal` flag.
94
+ #
80
95
  # @param [Class] target_class the target class of the presenter (i.e the model it presents)
81
96
  # @param [Hash] options options to record with the presenter
82
97
  # @option options [Boolean] :nodoc whether this presenter should not be output in the documentation.
83
98
  #
84
- def presents(target_class = :default, options = { nodoc: false })
99
+ def presents(target_class = :default, options = { nodoc: false, internal: false })
85
100
  raise "`presents` must be a class (in #{self.to_s})" \
86
101
  unless target_class.is_a?(Class) || target_class == :default || target_class.nil?
87
102
 
@@ -99,11 +114,18 @@ module Brainstem
99
114
  # controller or action as nondocumentable, instead, use the
100
115
  # +.nodoc!+ method in the desired context without a block.
101
116
  #
117
+ # Setting the +:internal+ option marks this title as 'internal use only',
118
+ # and causes formatters to fall back to the controller constant or to
119
+ # the action name as appropriate unless documentation is generated with
120
+ # `--include-internal` flag. If you are trying to set the entire
121
+ # controller or action as nondocumentable unless internal, instead,
122
+ # use the +.internal!+ method in the desired context without a block.
123
+ #
102
124
  # @param [String] text The title to set
103
125
  # @param [Hash] options options to record with the title
104
126
  # @option options [Boolean] :nodoc whether this title should not be output in the documentation.
105
127
  #
106
- def title(text, options = { nodoc: false })
128
+ def title(text, options = { nodoc: false, internal: false })
107
129
  configuration[brainstem_params_context][:title] = options.merge(info: text)
108
130
  end
109
131
 
@@ -114,11 +136,15 @@ module Brainstem
114
136
  # Setting the +:nodoc+ option marks this description as 'internal use
115
137
  # only', and causes formatters not to display a description.
116
138
  #
139
+ # Setting the +:internal+ option marks this description as 'internal use
140
+ # only', and causes formatters not to display a description unless documentation
141
+ # is generated with `--include-internal` flag.
142
+ #
117
143
  # @param [String] text The description to set
118
144
  # @param [Hash] options options to record with the description
119
145
  # @option options [Boolean] :nodoc whether this description should not be output in the documentation.
120
146
  #
121
- def description(text, options = { nodoc: false })
147
+ def description(text, options = { nodoc: false, internal: false })
122
148
  configuration[brainstem_params_context][:description] = options.merge(info: text)
123
149
  end
124
150
 
@@ -189,7 +215,7 @@ module Brainstem
189
215
  # Adds a param to the list of valid params, storing
190
216
  # the info sent with it.
191
217
  #
192
- # @param [Symbol] field_name the name of the param
218
+ # @param [Symbol] name the name of the param
193
219
  # @param [String, Symbol] type the data type of the field. If not specified, will default to `string`.
194
220
  # @param [Hash] options
195
221
  # @option options [String] :info the documentation for the param
@@ -200,11 +226,11 @@ module Brainstem
200
226
  # @option options [Boolean] :required if the param is required for
201
227
  # the endpoint
202
228
  # @option options [String, Symbol] :item_type The data type of the items contained in a field.
203
- # Ideally used when the data type of the field is an `array`, `object` or `hash`.
229
+ # Only used when the data type of the field is an `array`.
204
230
  #
205
231
  def valid(name, type = nil, options = {}, &block)
206
232
  valid_params = configuration[brainstem_params_context][:valid_params]
207
- param_config = format_field_configuration(valid_params, type, options, &block)
233
+ param_config = format_field_configuration(valid_params, name, type, options, &block)
208
234
 
209
235
  formatted_name = convert_to_proc(name)
210
236
  valid_params[formatted_name] = param_config
@@ -212,6 +238,26 @@ module Brainstem
212
238
  with_options(format_ancestry_options(formatted_name, param_config), &block) if block_given?
213
239
  end
214
240
 
241
+ #
242
+ # Adds a param that has a dynamic key to the list of valid params, storing
243
+ # the info sent with it.
244
+ #
245
+ # @param [String, Symbol] type the data type of the field. If not specified, will default to `string`.
246
+ # @param [Hash] options
247
+ # @option options [String] :info the documentation for the param
248
+ # @option options [String, Symbol] :root if this is a nested param,
249
+ # under which param should it be nested?
250
+ # @option options [Boolean] :nodoc should this param appear in the
251
+ # documentation?
252
+ # @option options [Boolean] :required if the param is required for
253
+ # the endpoint
254
+ # @option options [String, Symbol] :item_type The data type of the items contained in a field.
255
+ # Only used when the data type of the field is an `array`.
256
+ #
257
+ def valid_dynamic_param(type, options = {}, &block)
258
+ valid(DYNAMIC_KEY, type, options, &block)
259
+ end
260
+
215
261
  #
216
262
  # Allows defining a custom response structure for an action.
217
263
  #
@@ -220,7 +266,7 @@ module Brainstem
220
266
  # @option options [String] :info the documentation for the param
221
267
  # @option options [Boolean] :nodoc should this block appear in the documentation?
222
268
  # @option options [String, Symbol] :item_type The data type of the items contained in a field.
223
- # Ideally used when the data type of the response is an `array`.
269
+ # Only used when the data type of the response is an `array`.
224
270
  #
225
271
  def response(type, options = {}, &block)
226
272
  configuration[brainstem_params_context].nest! :custom_response
@@ -228,6 +274,7 @@ module Brainstem
228
274
 
229
275
  custom_response[:_config] = format_field_configuration(
230
276
  custom_response,
277
+ nil,
231
278
  type,
232
279
  options,
233
280
  &block
@@ -243,19 +290,32 @@ module Brainstem
243
290
  # @param [Hash] options
244
291
  # @option options [String] :info the documentation for the param
245
292
  # @option options [String, Symbol] :item_type The data type of the items contained in a field.
246
- # Ideally used when the data type of the response is an `array`.
293
+ # Only used when the data type of the response is an `array`.
247
294
  #
248
295
  def fields(name, type, options = {}, &block)
249
296
  custom_response = configuration[brainstem_params_context][:custom_response]
250
297
  raise "`fields` must be nested under a response block" if custom_response.nil?
251
298
 
252
299
  formatted_name = convert_to_proc(name)
253
- field_block_config = format_field_configuration(custom_response, type, options, &block)
300
+ field_block_config = format_field_configuration(custom_response, name, type, options, &block)
254
301
 
255
302
  custom_response[formatted_name] = field_block_config
256
303
  with_options(format_ancestry_options(formatted_name, field_block_config), &block)
257
304
  end
258
305
 
306
+ #
307
+ # Allows defining a field block with a dynamic key for a custom response
308
+ #
309
+ # @param [Symbol] type the data type of the response.
310
+ # @param [Hash] options
311
+ # @option options [String] :info the documentation for the param
312
+ # @option options [String, Symbol] :item_type The data type of the items contained in a field.
313
+ # Only used when the data type of the response is an `array`.
314
+ #
315
+ def dynamic_key_fields(type, options = {}, &block)
316
+ fields(DYNAMIC_KEY, type, options, &block)
317
+ end
318
+
259
319
  #
260
320
  # Allows defining a field either under a field block or the custom response block.
261
321
  #
@@ -264,14 +324,27 @@ module Brainstem
264
324
  # @param [Hash] options
265
325
  # @option options [String] :info the documentation for the param
266
326
  # @option options [String, Symbol] :item_type The data type of the items contained in a field.
267
- # Ideally used when the data type of the response is an `array`.
327
+ # Only used when the data type of the response is an `array`.
268
328
  #
269
329
  def field(name, type, options = {})
270
330
  custom_response = configuration[brainstem_params_context][:custom_response]
271
331
  raise "`fields` must be nested under a response block" if custom_response.nil?
272
332
 
273
333
  formatted_name = convert_to_proc(name)
274
- custom_response[formatted_name] = format_field_configuration(custom_response, type, options)
334
+ custom_response[formatted_name] = format_field_configuration(custom_response, name, type, options)
335
+ end
336
+
337
+ #
338
+ # Allows defining a field with a dynamic key either under a field block or the custom response block.
339
+ #
340
+ # @param [Symbol] type the data type of the response.
341
+ # @param [Hash] options
342
+ # @option options [String] :info the documentation for the param
343
+ # @option options [String, Symbol] :item_type The data type of the items contained in a field.
344
+ # Only used when the data type of the response is an `array`.
345
+ #
346
+ def dynamic_key_field(type, options = {})
347
+ field(DYNAMIC_KEY, type, options)
275
348
  end
276
349
 
277
350
  ####################################################
@@ -459,10 +532,12 @@ module Brainstem
459
532
  #
460
533
  # Formats the configuration of the param and returns the default configuration if not specified.
461
534
  #
462
- def format_field_configuration(configuration_map, type, options = {}, &block)
535
+ def format_field_configuration(configuration_map, name, type, options = {}, &block)
463
536
  field_config = options.with_indifferent_access
464
537
 
465
538
  field_config[:type] = type.to_s
539
+ field_config[:dynamic_key] = true if name.present? && name.to_sym == DYNAMIC_KEY
540
+
466
541
  if options.has_key?(:item_type)
467
542
  field_config[:item_type] = field_config[:item_type].to_s
468
543
  elsif field_config[:type] == 'array'
@@ -475,12 +550,7 @@ module Brainstem
475
550
  field_config[:nodoc] ||= !!parent_field_config[:nodoc]
476
551
  end
477
552
 
478
- # Rollup `required` attribute to ancestors if true
479
- if field_config[:required]
480
- (field_config[:ancestors] || []).reverse.each do |ancestor_key|
481
- configuration_map[ancestor_key][:required] = true if configuration_map.has_key?(ancestor_key)
482
- end
483
- end
553
+ field_config.delete(:nested_levels) if field_config[:nested_levels].to_i < 2
484
554
 
485
555
  DEFAULT_FIELD_CONFIG.merge(field_config).with_indifferent_access
486
556
  end
@@ -39,16 +39,20 @@ module Brainstem
39
39
  AssociationsBlock.new(configuration, &block)
40
40
  end
41
41
 
42
- def title(str, options = { nodoc: false })
42
+ def title(str, options = { nodoc: false, internal: false })
43
43
  configuration[:title] = options.merge(info: str)
44
44
  end
45
45
 
46
- def description(str, options = { nodoc: false })
46
+ def description(str, options = { nodoc: false, internal: false })
47
47
  configuration[:description] = options.merge(info: str)
48
48
  end
49
49
 
50
- def nodoc!
51
- configuration[:nodoc] = true
50
+ def nodoc!(description = true)
51
+ configuration[:nodoc] = description
52
+ end
53
+
54
+ def internal!(description = true)
55
+ configuration[:internal] = description
52
56
  end
53
57
 
54
58
  #
@@ -93,6 +97,8 @@ module Brainstem
93
97
  # @option options [String] :info Docstring for the sort order
94
98
  # @option options [Boolean] :nodoc Whether this sort order be
95
99
  # included in the generated documentation
100
+ # @option options [Boolean] :direction Whether this sort order
101
+ # has direction based ordering (asc/desc). Defaults to true
96
102
  #
97
103
  # @overload sort_order(name, options, &block)
98
104
  # @yieldparam scope [ActiveRecord::Relation] The scope representing
@@ -107,10 +113,12 @@ module Brainstem
107
113
  # @raise [ArgumentError] if neither an order string or block is given.
108
114
  #
109
115
  def sort_order(name, *args, &block)
110
- valid_options = %w(info nodoc)
111
- options = args.extract_options!
112
- .select { |k, v| valid_options.include?(k.to_s) }
113
- order = args.first
116
+ valid_options = %w(info nodoc internal direction)
117
+ options = args.extract_options!
118
+ .select { |k, v| valid_options.include?(k.to_s) }
119
+ .with_indifferent_access
120
+ options[:direction] = true unless options.has_key?(:direction)
121
+ order = args.first
114
122
 
115
123
  raise ArgumentError, "A sort order must be given" unless block_given? || order
116
124
  configuration[:sort_orders][name] = options.merge({
@@ -17,6 +17,18 @@ module Brainstem
17
17
  options[:info].presence
18
18
  end
19
19
 
20
+ def response_key
21
+ options[:response_key]
22
+ end
23
+
24
+ def polymorphic_classes
25
+ options[:polymorphic_classes]
26
+ end
27
+
28
+ def type
29
+ options[:type]
30
+ end
31
+
20
32
  def method_name
21
33
  if options[:dynamic] || options[:lookup]
22
34
  nil