brainstem 1.4.1 → 2.0.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/README.md +119 -0
  4. data/docs/api_doc_generator.markdown +45 -4
  5. data/docs/brainstem_executable.markdown +1 -1
  6. data/docs/oas_2_docgen.png +0 -0
  7. data/docs/oas_2_docgen_ascii.txt +78 -0
  8. data/lib/brainstem/api_docs.rb +23 -9
  9. data/lib/brainstem/api_docs/abstract_collection.rb +0 -13
  10. data/lib/brainstem/api_docs/atlas.rb +0 -14
  11. data/lib/brainstem/api_docs/builder.rb +0 -14
  12. data/lib/brainstem/api_docs/controller.rb +7 -16
  13. data/lib/brainstem/api_docs/controller_collection.rb +0 -3
  14. data/lib/brainstem/api_docs/endpoint.rb +73 -19
  15. data/lib/brainstem/api_docs/endpoint_collection.rb +0 -7
  16. data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +0 -2
  17. data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +1 -9
  18. data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +1 -9
  19. data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +39 -24
  20. data/lib/brainstem/api_docs/formatters/markdown/helper.rb +0 -13
  21. data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +22 -35
  22. data/lib/brainstem/api_docs/formatters/open_api_specification/helper.rb +66 -0
  23. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter.rb +57 -0
  24. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter.rb +311 -0
  25. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter.rb +197 -0
  26. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_collection_formatter.rb +60 -0
  27. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter.rb +162 -0
  28. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/info_formatter.rb +126 -0
  29. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter.rb +132 -0
  30. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/security_definitions_formatter.rb +99 -0
  31. data/lib/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter.rb +123 -0
  32. data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +0 -7
  33. data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +1 -20
  34. data/lib/brainstem/api_docs/presenter.rb +21 -27
  35. data/lib/brainstem/api_docs/presenter_collection.rb +1 -11
  36. data/lib/brainstem/api_docs/resolver.rb +1 -8
  37. data/lib/brainstem/api_docs/sinks/abstract_sink.rb +0 -4
  38. data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +0 -9
  39. data/lib/brainstem/api_docs/sinks/open_api_specification_sink.rb +234 -0
  40. data/lib/brainstem/api_docs/sinks/stdout_sink.rb +0 -5
  41. data/lib/brainstem/cli.rb +0 -13
  42. data/lib/brainstem/cli/abstract_command.rb +0 -7
  43. data/lib/brainstem/cli/generate_api_docs_command.rb +48 -24
  44. data/lib/brainstem/concerns/controller_dsl.rb +288 -145
  45. data/lib/brainstem/concerns/formattable.rb +0 -5
  46. data/lib/brainstem/concerns/optional.rb +0 -1
  47. data/lib/brainstem/concerns/presenter_dsl.rb +2 -21
  48. data/lib/brainstem/dsl/configuration.rb +0 -11
  49. data/lib/brainstem/presenter.rb +0 -4
  50. data/lib/brainstem/version.rb +1 -1
  51. data/spec/brainstem/api_docs/abstract_collection_spec.rb +0 -11
  52. data/spec/brainstem/api_docs/atlas_spec.rb +0 -6
  53. data/spec/brainstem/api_docs/builder_spec.rb +0 -4
  54. data/spec/brainstem/api_docs/controller_collection_spec.rb +0 -2
  55. data/spec/brainstem/api_docs/controller_spec.rb +29 -18
  56. data/spec/brainstem/api_docs/endpoint_collection_spec.rb +0 -6
  57. data/spec/brainstem/api_docs/endpoint_spec.rb +343 -13
  58. data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +0 -2
  59. data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +0 -1
  60. data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +0 -5
  61. data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +94 -8
  62. data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +0 -8
  63. data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +0 -7
  64. data/spec/brainstem/api_docs/formatters/open_api_specification/helper_spec.rb +210 -0
  65. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/controller_formatter_spec.rb +81 -0
  66. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/param_definitions_formatter_spec.rb +672 -0
  67. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint/response_definitions_formatter_spec.rb +335 -0
  68. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_collection_formatter_spec.rb +59 -0
  69. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/endpoint_formatter_spec.rb +308 -0
  70. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/info_formatter_spec.rb +89 -0
  71. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/presenter_formatter_spec.rb +430 -0
  72. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/security_definitions_formatter_spec.rb +190 -0
  73. data/spec/brainstem/api_docs/formatters/open_api_specification/version_2/tags_formatter_spec.rb +217 -0
  74. data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +0 -2
  75. data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +0 -2
  76. data/spec/brainstem/api_docs/presenter_collection_spec.rb +0 -2
  77. data/spec/brainstem/api_docs/presenter_spec.rb +58 -18
  78. data/spec/brainstem/api_docs/resolver_spec.rb +0 -1
  79. data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +0 -2
  80. data/spec/brainstem/api_docs/sinks/open_api_specification_sink_spec.rb +371 -0
  81. data/spec/brainstem/api_docs_spec.rb +2 -0
  82. data/spec/brainstem/cli/abstract_command_spec.rb +0 -4
  83. data/spec/brainstem/cli/generate_api_docs_command_spec.rb +53 -2
  84. data/spec/brainstem/concerns/controller_dsl_spec.rb +430 -64
  85. data/spec/brainstem/concerns/presenter_dsl_spec.rb +0 -20
  86. data/spec/brainstem/preloader_spec.rb +0 -7
  87. data/spec/brainstem/presenter_spec.rb +0 -1
  88. data/spec/dummy/rails.rb +0 -1
  89. data/spec/spec_helpers/db.rb +0 -1
  90. metadata +37 -2
@@ -12,23 +12,19 @@ module Brainstem
12
12
  puts_method.call(output)
13
13
  end
14
14
 
15
-
16
15
  ########################################################################
17
16
  private
18
17
  ########################################################################
19
18
 
20
-
21
19
  def valid_options
22
20
  super | [ :puts_method ]
23
21
  end
24
22
 
25
-
26
23
  #
27
24
  # Storage for holding the writing method.
28
25
  #
29
26
  attr_writer :puts_method
30
27
 
31
-
32
28
  #
33
29
  # Callable method for writing data to a buffer (by default stdout).
34
30
  #
@@ -37,7 +33,6 @@ module Brainstem
37
33
  def puts_method
38
34
  @puts_method ||= $stdout.method(:puts)
39
35
  end
40
-
41
36
  end
42
37
  end
43
38
  end
@@ -2,7 +2,6 @@
2
2
  Dir.glob(File.expand_path('../cli/**/*.rb', __FILE__)).each { |f| require f }
3
3
  require 'brainstem/concerns/optional'
4
4
 
5
-
6
5
  #
7
6
  # General manager for CLI requests. Takes incoming user input and routes to a
8
7
  # subcommand.
@@ -21,7 +20,6 @@ module Brainstem
21
20
  new(args, options).call
22
21
  end
23
22
 
24
-
25
23
  #
26
24
  # Creates a new instance of the Cli to respond to user input.
27
25
  #
@@ -35,7 +33,6 @@ module Brainstem
35
33
  self.requested_command = args.shift
36
34
  end
37
35
 
38
-
39
36
  #
40
37
  # Routes to an application endpoint depending on given options.
41
38
  #
@@ -51,24 +48,20 @@ module Brainstem
51
48
  self
52
49
  end
53
50
 
54
-
55
51
  #
56
52
  # Holds a copy of the initial given args for debugging purposes.
57
53
  #
58
54
  attr_accessor :_args
59
55
 
60
-
61
56
  #
62
57
  # Storage for the extracted command.
63
58
  #
64
59
  attr_accessor :requested_command
65
60
 
66
-
67
61
  ################################################################################
68
62
  private
69
63
  ################################################################################
70
64
 
71
-
72
65
  #
73
66
  # A whitelist of valid options that can be applied to the instance.
74
67
  #
@@ -80,7 +73,6 @@ module Brainstem
80
73
  ]
81
74
  end
82
75
 
83
-
84
76
  #
85
77
  # A basic routing table where the keys are the command to invoke, and where
86
78
  # the value is a callable object or class that will be called with the
@@ -92,7 +84,6 @@ module Brainstem
92
84
  { 'generate' => Brainstem::CLI::GenerateApiDocsCommand }
93
85
  end
94
86
 
95
-
96
87
  #
97
88
  # Retrieves the help text and subs any placeholder values.
98
89
  #
@@ -101,13 +92,11 @@ module Brainstem
101
92
  .gsub('EXECUTABLE_NAME', EXECUTABLE_NAME)
102
93
  end
103
94
 
104
-
105
95
  #
106
96
  # Stores the method we should call to run the user command.
107
97
  #
108
98
  attr_writer :command_method
109
99
 
110
-
111
100
  #
112
101
  # Reader for the method to invoke. By default, will output the help text
113
102
  # when called.
@@ -122,13 +111,11 @@ module Brainstem
122
111
  end
123
112
  end
124
113
 
125
-
126
114
  #
127
115
  # Stores the method we should use to log messages.
128
116
  #
129
117
  attr_writer :log_method
130
118
 
131
-
132
119
  #
133
120
  # Reader for the method to log. By default, will print to stdout when
134
121
  # called.
@@ -25,7 +25,6 @@ module Brainstem
25
25
  instance
26
26
  end
27
27
 
28
-
29
28
  #
30
29
  # Returns a new instance of the command with options set.
31
30
  #
@@ -35,7 +34,6 @@ module Brainstem
35
34
  extract_options!
36
35
  end
37
36
 
38
-
39
37
  #
40
38
  # Returns the hash of default options used as a base into which cli args
41
39
  # are merged.
@@ -44,7 +42,6 @@ module Brainstem
44
42
  {}
45
43
  end
46
44
 
47
-
48
45
  #
49
46
  # Kicks off execution of app-level code. Has available to it +options+,
50
47
  # which contains the options extracted from the command line.
@@ -54,19 +51,16 @@ module Brainstem
54
51
  "Override #call and implement your application logic."
55
52
  end
56
53
 
57
-
58
54
  #
59
55
  # Storage for given options.
60
56
  #
61
57
  attr_accessor :options
62
58
 
63
-
64
59
  #
65
60
  # Storage for passed, unparsed args.
66
61
  #
67
62
  attr_accessor :args
68
63
 
69
-
70
64
  #
71
65
  # Extracts command-line options for this specific command based on the
72
66
  # +OptionParser+ specified in +self.option_parser+.
@@ -77,7 +71,6 @@ module Brainstem
77
71
  option_parser.order!(args)
78
72
  end
79
73
 
80
-
81
74
  #
82
75
  # An +OptionParser+ instance that specifies how options should be
83
76
  # extracted specific to this command.
@@ -24,19 +24,16 @@ module Brainstem
24
24
  module CLI
25
25
  class GenerateApiDocsCommand < AbstractCommand
26
26
 
27
-
28
27
  def call
29
28
  ensure_sink_specified!
30
29
  construct_builder!
31
30
  present_atlas!
32
31
  end
33
32
 
34
-
35
33
  def default_sink_method
36
34
  Brainstem::ApiDocs::Sinks::ControllerPresenterMultifileSink.method(:new)
37
35
  end
38
36
 
39
-
40
37
  def default_options
41
38
  {
42
39
  sink: {
@@ -55,10 +52,8 @@ module Brainstem
55
52
  }
56
53
  end
57
54
 
58
-
59
55
  attr_accessor :builder
60
56
 
61
-
62
57
  #########################################################################
63
58
  private
64
59
  #########################################################################
@@ -70,7 +65,6 @@ module Brainstem
70
65
  @builder = Brainstem::ApiDocs::Builder.new(builder_options)
71
66
  end
72
67
 
73
-
74
68
  #
75
69
  # Hands the atlas over to the sink.
76
70
  #
@@ -78,7 +72,6 @@ module Brainstem
78
72
  sink_method.call(sink_options) << builder.atlas
79
73
  end
80
74
 
81
-
82
75
  #
83
76
  # Raises an error unless the user specified a destination for the output.
84
77
  #
@@ -86,7 +79,6 @@ module Brainstem
86
79
  raise Brainstem::ApiDocs::NoSinkSpecifiedException unless sink_method
87
80
  end
88
81
 
89
-
90
82
  #
91
83
  # Utility method for retrieving the sink.
92
84
  #
@@ -94,7 +86,6 @@ module Brainstem
94
86
  @sink_method ||= options[:sink][:method]
95
87
  end
96
88
 
97
-
98
89
  #
99
90
  # Utility method for retrieving builder options.
100
91
  #
@@ -102,7 +93,6 @@ module Brainstem
102
93
  @builder_options ||= options[:builder]
103
94
  end
104
95
 
105
-
106
96
  #
107
97
  # Utility method for retrieving sink options.
108
98
  #
@@ -110,7 +100,6 @@ module Brainstem
110
100
  @sink_options ||= options[:sink][:options]
111
101
  end
112
102
 
113
-
114
103
  #
115
104
  # Defines the option parser for this command.
116
105
  #
@@ -121,39 +110,27 @@ module Brainstem
121
110
  OptionParser.new do |opts|
122
111
  opts.banner = "Usage: generate [options]"
123
112
 
124
- opts.on('-m', '--multifile-presenters-and-controllers',
125
- 'dumps presenters and controllers to separate files (default)') do |o|
126
- options[:sink][:method] = \
127
- Brainstem::ApiDocs::Sinks::ControllerPresenterMultifileSink.method(:new)
128
- end
129
-
130
-
131
113
  opts.on('--host-env-file=PATH', "path to host app's entry file") do |o|
132
114
  options[:builder][:args_for_introspector][:rails_environment_file] = o
133
115
  end
134
116
 
135
-
136
117
  opts.on('-o RELATIVE_DIR', '--output-dir=RELATIVE_DIR',
137
118
  'specifies directory which to output if relevant') do |o|
138
119
  options[:sink][:options][:write_path] = o
139
120
  end
140
121
 
141
-
142
122
  opts.on('--base-presenter-class=CLASS', "which class to look up presenters on") do |o|
143
123
  options[:builder][:args_for_introspector][:base_presenter_class] = o
144
124
  end
145
125
 
146
-
147
126
  opts.on('--base-controller-class=CLASS', "which class to look up controllers on") do |o|
148
127
  options[:builder][:args_for_introspector][:base_controller_class] = o
149
128
  end
150
129
 
151
-
152
130
  opts.on('--base-application-class=CLASS', "which class to look up routes on") do |o|
153
131
  options[:builder][:args_for_introspector][:base_application_class] = o
154
132
  end
155
133
 
156
-
157
134
  opts.on('--controller-matches=MATCH',
158
135
  'a case-sensitive regexp used to winnow the list of '\
159
136
  'controllers. It is matched against the constant, not '\
@@ -164,10 +141,57 @@ module Brainstem
164
141
  options[:builder][:args_for_atlas][:controller_matches].push(matcher)
165
142
  end
166
143
 
167
-
168
144
  opts.on('--markdown', 'use markdown format') do |o|
169
145
  options[:sink][:options][:format] = :markdown
170
146
  end
147
+
148
+ opts.on('-m', '--multifile-presenters-and-controllers',
149
+ 'dumps presenters and controllers to separate files (default)') do |o|
150
+ if options[:sink][:options][:format] == :oas_v2
151
+ raise NotImplementedError.new("Multi File support for Open API Specification is not supported yet")
152
+ else
153
+ options[:sink][:method] = Brainstem::ApiDocs::Sinks::ControllerPresenterMultifileSink.method(:new)
154
+ end
155
+ end
156
+
157
+ opts.on('--output-extension=EXTENSION', 'extension that should be used for output files') do |extension|
158
+ options[:sink][:options][:output_extension] = extension
159
+ end
160
+
161
+ #########################################################
162
+ # #
163
+ # Open API Specification generation specific commands: #
164
+ # #
165
+ #########################################################
166
+
167
+ # Future proofing for different Open API Specification versions.
168
+ opts.on('--open-api-specification=VERSION',
169
+ 'dumps an Open API Specification for presenters and controllers in a single file') do |oas_version|
170
+ case oas_version.to_i
171
+ when 2
172
+ options[:sink][:options][:format] = :oas_v2
173
+ else
174
+ raise NotImplementedError.new(
175
+ "Please specify the version of Open API Specification to be generated e.g. --open-api-specification=2"
176
+ )
177
+ end
178
+ options[:sink][:method] = Brainstem::ApiDocs::Sinks::OpenApiSpecificationSink.method(:new)
179
+ end
180
+
181
+ opts.on('--api-version=API_VERSION',
182
+ 'sets the version of the generated documentation') do |api_version|
183
+ options[:sink][:options][:api_version] = api_version
184
+ end
185
+
186
+ opts.on('--ignore-tagging',
187
+ 'does not add the tag definitions in the Open API Specification') do |api_version|
188
+ options[:sink][:options][:ignore_tagging] = true
189
+ end
190
+
191
+ opts.on('--oas-filename-pattern=PATTERN',
192
+ 'defines the naming pattern of the Open API Specification file') do |pattern|
193
+ options[:sink][:options][:oas_filename_pattern] = pattern
194
+ end
171
195
  end
172
196
  end
173
197
  end
@@ -13,7 +13,6 @@ module Brainstem
13
13
  reset_configuration!
14
14
  end
15
15
 
16
-
17
16
  module ClassMethods
18
17
  def reset_configuration!
19
18
  configuration.nest! :_default
@@ -22,10 +21,11 @@ module Brainstem
22
21
  default.nest! :transforms
23
22
  default.nonheritable! :title
24
23
  default.nonheritable! :description
24
+ default.nonheritable! :tag
25
+ default.nonheritable! :tag_groups
25
26
  end
26
27
  end
27
28
 
28
-
29
29
  #
30
30
  # In order to correctly scope the DSL, we must have a context under
31
31
  # which keys are stored. The default context is _default (to avoid
@@ -42,7 +42,6 @@ module Brainstem
42
42
  #
43
43
  attr_accessor :brainstem_params_context
44
44
 
45
-
46
45
  #
47
46
  # Container method that sets up base scoping for the configuration.
48
47
  #
@@ -52,7 +51,6 @@ module Brainstem
52
51
  self.brainstem_params_context = nil
53
52
  end
54
53
 
55
-
56
54
  #
57
55
  # Temporary implementation to track controllers that have been documented.
58
56
  #
@@ -60,7 +58,6 @@ module Brainstem
60
58
  configuration[brainstem_params_context][:documented] = true
61
59
  end
62
60
 
63
-
64
61
  #
65
62
  # Specifies that the scope should not be documented. Setting this on
66
63
  # the default context will force the controller to be undocumented,
@@ -71,40 +68,91 @@ module Brainstem
71
68
  configuration[brainstem_params_context][:nodoc] = true
72
69
  end
73
70
 
71
+ #
72
+ # Specifies which presenter is used for the controller / action.
73
+ # By default, expects presentation on all methods, and falls back to the
74
+ # class derived from +brainstem_model_name+ if a name is not
75
+ # given.
76
+ #
77
+ # Setting the +:nodoc+ option marks this presenter as 'internal use only',
78
+ # and causes formatters to display this as not indicated.
79
+ #
80
+ # @param [Class] target_class the target class of the presenter (i.e the model it presents)
81
+ # @param [Hash] options options to record with the presenter
82
+ # @option options [Boolean] :nodoc whether this presenter should not be output in the documentation.
83
+ #
84
+ def presents(target_class = :default, options = { nodoc: false })
85
+ raise "`presents` must be a class (in #{self.to_s})" \
86
+ unless target_class.is_a?(Class) || target_class == :default || target_class.nil?
87
+
88
+ target_class = brainstem_model_class if target_class == :default
89
+ configuration[brainstem_params_context][:presents] = options.merge(target_class: target_class)
90
+ end
74
91
 
75
92
  #
76
- # Changes context to a specific action context. Allows specification
77
- # of per-action configuration.
93
+ # Specifies a title to be used in the description of a class. Can also
94
+ # be used for method section titles.
78
95
  #
79
- # Instead of using this method, it's advised simply to use +actions+
80
- # with a single method name. While marked as private, since it is
81
- # usually used within a +class_eval+ block thanks to
82
- # +brainstem_params+, this has little effect.
96
+ # Setting the +:nodoc+ option marks this title as 'internal use only',
97
+ # and causes formatters to fall back to the controller constant or to
98
+ # the action name as appropriate. If you are trying to set the entire
99
+ # controller or action as nondocumentable, instead, use the
100
+ # +.nodoc!+ method in the desired context without a block.
83
101
  #
84
- # Originally, this method was named +action+ for parity with the plural
85
- # version. However, this conflicts in multiple ways with Rails, so it
86
- # has been renamed.
102
+ # @param [String] text The title to set
103
+ # @param [Hash] options options to record with the title
104
+ # @option options [Boolean] :nodoc whether this title should not be output in the documentation.
87
105
  #
88
- # @private
106
+ def title(text, options = { nodoc: false })
107
+ configuration[brainstem_params_context][:title] = options.merge(info: text)
108
+ end
109
+
89
110
  #
90
- # @param [Symbol] name the name of the context
91
- # @param [Proc] block the proc to be evaluated in the context
111
+ # Specifies a low-level description of a particular context, usually
112
+ # (but not exclusively) reserved for methods.
92
113
  #
93
- def action_context(name, &block)
94
- new_context = name.to_sym
95
- old_context = self.brainstem_params_context
96
- self.brainstem_params_context = new_context
114
+ # Setting the +:nodoc+ option marks this description as 'internal use
115
+ # only', and causes formatters not to display a description.
116
+ #
117
+ # @param [String] text The description to set
118
+ # @param [Hash] options options to record with the description
119
+ # @option options [Boolean] :nodoc whether this description should not be output in the documentation.
120
+ #
121
+ def description(text, options = { nodoc: false })
122
+ configuration[brainstem_params_context][:description] = options.merge(info: text)
123
+ end
97
124
 
98
- self.configuration[new_context] ||= Brainstem::DSL::Configuration.new(
99
- self.configuration[DEFAULT_BRAINSTEM_PARAMS_CONTEXT]
100
- )
125
+ ####################################################
126
+ # Used only for Open API Specification generation. #
127
+ ####################################################
128
+ #
129
+ # Specifies the tag name to be used in tagging a class.
130
+ #
131
+ # @param [String] tag_name The name of the tag.
132
+ #
133
+ def tag(tag_name)
134
+ unless brainstem_params_context == DEFAULT_BRAINSTEM_PARAMS_CONTEXT
135
+ raise "`tag` is not endpoint specific and is defined on the controller"
136
+ end
101
137
 
102
- class_eval(&block)
103
- self.brainstem_params_context = old_context
138
+ configuration[brainstem_params_context][:tag] = tag_name
104
139
  end
105
140
 
106
- private :action_context
141
+ ####################################################
142
+ # Used only for Open API Specification generation. #
143
+ ####################################################
144
+ #
145
+ # Specifies an array of tag names to group the class under. Used for the x-tags OAS vendor extension.
146
+ #
147
+ # @param [Array<String>] tag_group_names Array of tag group names
148
+ #
149
+ def tag_groups(*tag_group_names)
150
+ unless brainstem_params_context == DEFAULT_BRAINSTEM_PARAMS_CONTEXT
151
+ raise "`tag_groups` is not endpoint specific and is defined on the controller"
152
+ end
107
153
 
154
+ configuration[brainstem_params_context][:tag_groups] = tag_group_names.flatten
155
+ end
108
156
 
109
157
  #
110
158
  # Invokes +action+ for each symbol in the argument list. Used to
@@ -114,7 +162,6 @@ module Brainstem
114
162
  axns.flatten.each { |name| action_context name, &block }
115
163
  end
116
164
 
117
-
118
165
  #
119
166
  # Allows the bulk specification of +:root+ options. Useful for
120
167
  # denoting parameters which are nested under a resource.
@@ -138,45 +185,193 @@ module Brainstem
138
185
  with_options(format_root_ancestry_options(root), &block)
139
186
  end
140
187
 
141
-
142
188
  #
143
189
  # Adds a param to the list of valid params, storing
144
190
  # the info sent with it.
145
191
  #
146
192
  # @param [Symbol] field_name the name of the param
147
- # @param [String,Symbol] type the data type of the field. If not specified, will default to `string`.
193
+ # @param [String, Symbol] type the data type of the field. If not specified, will default to `string`.
148
194
  # @param [Hash] options
149
195
  # @option options [String] :info the documentation for the param
150
- # @option options [String,Symbol] :root if this is a nested param,
196
+ # @option options [String, Symbol] :root if this is a nested param,
151
197
  # under which param should it be nested?
152
198
  # @option options [Boolean] :nodoc should this param appear in the
153
199
  # documentation?
154
200
  # @option options [Boolean] :required if the param is required for
155
201
  # the endpoint
156
- # @option options [String,Symbol] :item_type The data type of the items contained in a field.
202
+ # @option options [String, Symbol] :item_type The data type of the items contained in a field.
157
203
  # Ideally used when the data type of the field is an `array`, `object` or `hash`.
158
204
  #
159
- def valid(field_name, type = nil, options = {}, &block)
205
+ def valid(name, type = nil, options = {}, &block)
160
206
  valid_params = configuration[brainstem_params_context][:valid_params]
161
- field_config = format_field_configuration(type, options, &block)
207
+ param_config = format_field_configuration(valid_params, type, options, &block)
162
208
 
163
- # Inherit `nodoc` attribute from parent
164
- parent_key = (options[:ancestors] || []).reverse.first
165
- field_config[:nodoc] = true if parent_key && valid_params[parent_key] && valid_params[parent_key][:nodoc]
209
+ formatted_name = convert_to_proc(name)
210
+ valid_params[formatted_name] = param_config
166
211
 
167
- # Rollup `required` attribute to ancestors if true
168
- if field_config[:required]
169
- (options[:ancestors] || []).reverse.each do |ancestor_key|
170
- valid_params[ancestor_key][:required] = true if valid_params.has_key?(ancestor_key)
171
- end
212
+ with_options(format_ancestry_options(formatted_name, param_config), &block) if block_given?
213
+ end
214
+
215
+ #
216
+ # Allows defining a custom response structure for an action.
217
+ #
218
+ # @param [Symbol] type the data type of the response.
219
+ # @param [Hash] options
220
+ # @option options [String] :info the documentation for the param
221
+ # @option options [Boolean] :nodoc should this block appear in the documentation?
222
+ # @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`.
224
+ #
225
+ def response(type, options = {}, &block)
226
+ configuration[brainstem_params_context].nest! :custom_response
227
+ custom_response = configuration[brainstem_params_context][:custom_response]
228
+
229
+ custom_response[:_config] = format_field_configuration(
230
+ custom_response,
231
+ type,
232
+ options,
233
+ &block
234
+ )
235
+ class_eval(&block) if block_given?
236
+ end
237
+
238
+ #
239
+ # Allows defining a field block for a custom response
240
+ #
241
+ # @param [Symbol] name the name of the field block of the response.
242
+ # @param [Symbol] type the data type of the response.
243
+ # @param [Hash] options
244
+ # @option options [String] :info the documentation for the param
245
+ # @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`.
247
+ #
248
+ def fields(name, type, options = {}, &block)
249
+ custom_response = configuration[brainstem_params_context][:custom_response]
250
+ raise "`fields` must be nested under a response block" if custom_response.nil?
251
+
252
+ formatted_name = convert_to_proc(name)
253
+ field_block_config = format_field_configuration(custom_response, type, options, &block)
254
+
255
+ custom_response[formatted_name] = field_block_config
256
+ with_options(format_ancestry_options(formatted_name, field_block_config), &block)
257
+ end
258
+
259
+ #
260
+ # Allows defining a field either under a field block or the custom response block.
261
+ #
262
+ # @param [Symbol] name the name of the field of the response.
263
+ # @param [Symbol] type the data type of the response.
264
+ # @param [Hash] options
265
+ # @option options [String] :info the documentation for the param
266
+ # @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`.
268
+ #
269
+ def field(name, type, options = {})
270
+ custom_response = configuration[brainstem_params_context][:custom_response]
271
+ raise "`fields` must be nested under a response block" if custom_response.nil?
272
+
273
+ formatted_name = convert_to_proc(name)
274
+ custom_response[formatted_name] = format_field_configuration(custom_response, type, options)
275
+ end
276
+
277
+ ####################################################
278
+ # Used only for Open API Specification generation. #
279
+ ####################################################
280
+ #
281
+ # Unique string used to identify the operation. The id MUST be unique among all operations
282
+ # described in the API. Tools and libraries MAY use the operation_id to uniquely identify an
283
+ # operation, therefore, it is recommended to follow common programming naming conventions.
284
+ #
285
+ # @param [String] unique_id
286
+ #
287
+ def operation_id(unique_id)
288
+ if brainstem_params_context == DEFAULT_BRAINSTEM_PARAMS_CONTEXT
289
+ raise "`operation_id` is endpoint specific and cannot be defined on the controller"
172
290
  end
173
291
 
174
- procified_field_name = format_field_name(field_name)
175
- valid_params[procified_field_name] = field_config
292
+ configuration[brainstem_params_context][:operation_id] = unique_id
293
+ end
294
+
295
+ ####################################################
296
+ # Used only for Open API Specification generation. #
297
+ ####################################################
298
+ #
299
+ # A list of MIME types the endpoints can consume. This overrides the default consumes definition
300
+ # on the Info object in the Open API Specification.
301
+ #
302
+ # @param [Array<String>] mime_types Array of mime types
303
+ #
304
+ def consumes(*mime_types)
305
+ configuration[brainstem_params_context][:consumes] = mime_types.flatten
306
+ end
307
+
308
+ ####################################################
309
+ # Used only for Open API Specification generation. #
310
+ ####################################################
311
+ #
312
+ # A list of MIME types the endpoints can produce. This overrides the default produces definition
313
+ # on the Info object in the Open API Specification.
314
+ #
315
+ # @param [Array<String>] mime_types Array of mime types
316
+ #
317
+ def produces(*mime_types)
318
+ configuration[brainstem_params_context][:produces] = mime_types.flatten
319
+ end
176
320
 
177
- with_options(format_field_ancestry_options(procified_field_name, field_config), &block) if block_given?
321
+ ####################################################
322
+ # Used only for Open API Specification generation. #
323
+ ####################################################
324
+ #
325
+ # A declaration of which security schemes are applied for this operation. The list of values
326
+ # describes alternative security schemes that can be used. This definition overrides any declared
327
+ # top-level security. To remove a top-level security declaration, an empty array can be used.
328
+ #
329
+ # @param [Array<Hash>] schemes Array of security schemes applicable to the endpoint
330
+ #
331
+ def security(*schemes)
332
+ configuration[brainstem_params_context][:security] = schemes.flatten
178
333
  end
179
334
 
335
+ ####################################################
336
+ # Used only for Open API Specification generation. #
337
+ ####################################################
338
+ #
339
+ # Additional external documentation for this operation.
340
+ # e.g {
341
+ # "description": "Find more info here",
342
+ # "url": "https://swagger.io"
343
+ # }
344
+ #
345
+ # @param [Hash] doc_config Hash with the `description` & `url` properties of the external documentation.
346
+ #
347
+ def external_doc(doc_config)
348
+ configuration[brainstem_params_context][:external_doc] = doc_config
349
+ end
350
+
351
+ ####################################################
352
+ # Used only for Open API Specification generation. #
353
+ ####################################################
354
+ #
355
+ # The transfer protocol for the operation. Values MUST be from the list: "http", "https", "ws", "wss".
356
+ # The value overrides the default schemes definition in the Info Object.
357
+ #
358
+ # @param [Hash] schemes Array of schemes applicable to the endpoint
359
+ #
360
+ def schemes(*schemes)
361
+ configuration[brainstem_params_context][:schemes] = schemes.flatten
362
+ end
363
+
364
+ ####################################################
365
+ # Used only for Open API Specification generation. #
366
+ ####################################################
367
+ #
368
+ # Declares this operation to be deprecated. Usage of the declared operation should be refrained.
369
+ #
370
+ # @param [Hash] schemes Array of schemes applicable to the endpoint
371
+ #
372
+ def deprecated(deprecated)
373
+ configuration[brainstem_params_context][:deprecated] = deprecated
374
+ end
180
375
 
181
376
  #
182
377
  # Adds a transform to the list of transforms. Used to rename incoming
@@ -198,68 +393,37 @@ module Brainstem
198
393
  end
199
394
  alias_method :transforms, :transform
200
395
 
201
-
202
- #
203
- # Specifies which presenter is used for the controller / action.
204
- # By default, expects presentation on all methods, and falls back to the
205
- # class derived from +brainstem_model_name+ if a name is not
206
- # given.
207
- #
208
- # Setting the +:nodoc+ option marks this presenter as 'internal use only',
209
- # and causes formatters to display this as not indicated.
210
- #
211
- # @param [Class] target_class the target class of the presenter (i.e
212
- # the model it presents)
213
- # @param [Hash] options options to record with the presenter
214
- # @option [Boolean] options :nodoc whether this presenter should not
215
- # be output in the documentation.
216
396
  #
397
+ # Changes context to a specific action context. Allows specification
398
+ # of per-action configuration.
217
399
  #
218
- def presents(target_class = :default, options = { nodoc: false })
219
- raise "`presents` must be a class (in #{self.to_s})" \
220
- unless target_class.is_a?(Class) || target_class == :default || target_class.nil?
221
-
222
- target_class = brainstem_model_class if target_class == :default
223
- configuration[brainstem_params_context][:presents] = \
224
- options.merge(target_class: target_class)
225
- end
226
-
227
-
400
+ # Instead of using this method, it's advised simply to use +actions+
401
+ # with a single method name. While marked as private, since it is
402
+ # usually used within a +class_eval+ block thanks to
403
+ # +brainstem_params+, this has little effect.
228
404
  #
229
- # Specifies a low-level description of a particular context, usually
230
- # (but not exclusively) reserved for methods.
405
+ # Originally, this method was named +action+ for parity with the plural
406
+ # version. However, this conflicts in multiple ways with Rails, so it
407
+ # has been renamed.
231
408
  #
232
- # Setting the +:nodoc+ option marks this description as 'internal use
233
- # only', and causes formatters not to display a description.
409
+ # @private
234
410
  #
235
- # @param [String] text The description to set
236
- # @param [Hash] options options to record with the description
237
- # @option [Boolean] options :nodoc whether this description should not
238
- # be output in the documentation.
411
+ # @param [Symbol] name the name of the context
412
+ # @param [Proc] block the proc to be evaluated in the context
239
413
  #
240
- def description(text, options = { nodoc: false })
241
- configuration[brainstem_params_context][:description] = options.merge(info: text)
242
- end
414
+ def action_context(name, &block)
415
+ new_context = name.to_sym
416
+ old_context = self.brainstem_params_context
417
+ self.brainstem_params_context = new_context
243
418
 
419
+ self.configuration[new_context] ||= Brainstem::DSL::Configuration.new(
420
+ self.configuration[DEFAULT_BRAINSTEM_PARAMS_CONTEXT]
421
+ )
244
422
 
245
- #
246
- # Specifies a title to be used in the description of a class. Can also
247
- # be used for method section titles.
248
- #
249
- # Setting the +:nodoc+ option marks this title as 'internal use only',
250
- # and causes formatters to fall back to the controller constant or to
251
- # the action name as appropriate. If you are trying to set the entire
252
- # controller or action as nondocumentable, instead, use the discrete
253
- # +.nodoc!+ method in the desired context without a block.
254
- #
255
- # @param [String] text The title to set
256
- # @param [Hash] options options to record with the title
257
- # @option [Boolean] options :nodoc whether this title should not be
258
- # output in the documentation.
259
- #
260
- def title(text, options = { nodoc: false })
261
- configuration[brainstem_params_context][:title] = options.merge(info: text)
423
+ class_eval(&block)
424
+ self.brainstem_params_context = old_context
262
425
  end
426
+ private :action_context
263
427
 
264
428
  #
265
429
  # Converts the field name into a Proc.
@@ -267,11 +431,10 @@ module Brainstem
267
431
  # @param [String, Symbol, Proc] text The title to set
268
432
  # @return [Proc]
269
433
  #
270
- def format_field_name(field_name_or_proc)
434
+ def convert_to_proc(field_name_or_proc)
271
435
  field_name_or_proc.respond_to?(:call) ? field_name_or_proc : Proc.new { field_name_or_proc.to_s }
272
436
  end
273
- alias_method :format_root_name, :format_field_name
274
-
437
+ alias_method :format_root_name, :convert_to_proc
275
438
 
276
439
  #
277
440
  # Formats the ancestry options of the field. Returns a hash with ancestors & root.
@@ -283,67 +446,49 @@ module Brainstem
283
446
  { root: root_proc, ancestors: ancestors }.with_indifferent_access.reject { |_, v| v.blank? }
284
447
  end
285
448
 
286
-
287
449
  #
288
- # Formats the ancestry options of the field. Returns a hash with ancestors & root.
450
+ # Formats the ancestry options of the field. Returns a hash with ancestors.
289
451
  #
290
- def format_field_ancestry_options(field_name_proc, options = {})
452
+ def format_ancestry_options(field_name_proc, options = {})
291
453
  ancestors = options[:ancestors].try(:dup) || []
292
454
  ancestors << field_name_proc
293
455
 
294
456
  { ancestors: ancestors }.with_indifferent_access.reject { |_, v| v.blank? }
295
457
  end
296
458
 
297
-
298
459
  #
299
- # Formats the configuration of the field and returns the default configuration if not specified.
460
+ # Formats the configuration of the param and returns the default configuration if not specified.
300
461
  #
301
- def format_field_configuration(type = nil, options = {}, &block)
302
- options = type if type.is_a?(Hash) && options.empty?
462
+ def format_field_configuration(configuration_map, type, options = {}, &block)
463
+ field_config = options.with_indifferent_access
303
464
 
304
- options[:type] = sanitize_param_data_type(type, &block)
305
- options[:item_type] = options[:item_type].to_s if options.has_key?(:item_type)
306
-
307
- DEFAULT_PARAM_OPTIONS.merge(options).with_indifferent_access
308
- end
309
-
310
- DEFAULT_PARAM_OPTIONS = { nodoc: false, required: false }
311
- private_constant :DEFAULT_PARAM_OPTIONS
465
+ field_config[:type] = type.to_s
466
+ if options.has_key?(:item_type)
467
+ field_config[:item_type] = field_config[:item_type].to_s
468
+ elsif field_config[:type] == 'array'
469
+ field_config[:item_type] = block_given? ? 'hash' : 'string'
470
+ end
312
471
 
472
+ # Inherit `nodoc` attribute from parent
473
+ parent_key = (field_config[:ancestors] || []).reverse.first
474
+ if parent_key && (parent_field_config = configuration_map[parent_key])
475
+ field_config[:nodoc] ||= !!parent_field_config[:nodoc]
476
+ end
313
477
 
314
- #
315
- # Returns the type of the param and adds a deprecation warning if not specified.
316
- #
317
- def sanitize_param_data_type(type, &block)
318
- if type.is_a?(Hash) || type.blank?
319
- deprecated_type_warning
320
- type = block_given? ? DEFAULT_BLOCK_DATA_TYPE : DEFAULT_DATA_TYPE
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
321
483
  end
322
484
 
323
- type.to_s
485
+ DEFAULT_FIELD_CONFIG.merge(field_config).with_indifferent_access
324
486
  end
325
487
 
326
- DEFAULT_DATA_TYPE = 'string'
327
- private_constant :DEFAULT_DATA_TYPE
328
-
329
- DEFAULT_BLOCK_DATA_TYPE = 'hash'
330
- private_constant :DEFAULT_BLOCK_DATA_TYPE
331
-
332
-
333
- #
334
- # Adds deprecation warning if the type argument is not specified when defining a valid param.
335
- #
336
- def deprecated_type_warning
337
- ActiveSupport::Deprecation.warn(
338
- 'Please specify the `type` of the parameter as the second argument. If not specified, '\
339
- 'it will default to `:string`. This default behavior will be deprecated in the next major '\
340
- 'version and will need to be explicitly specified. e.g. `post.valid :message, :text, required: true`',
341
- caller
342
- )
343
- end
488
+ DEFAULT_FIELD_CONFIG = { nodoc: false, required: false }
489
+ private_constant :DEFAULT_FIELD_CONFIG
344
490
  end
345
491
 
346
-
347
492
  def valid_params_tree(requested_context = action_name.to_sym)
348
493
  contextual_key(requested_context, :valid_params)
349
494
  .to_h
@@ -380,7 +525,6 @@ module Brainstem
380
525
  end
381
526
  alias_method :brainstem_valid_params_for, :brainstem_valid_params
382
527
 
383
-
384
528
  #
385
529
  # Lists all incoming param keys that will be rewritten to use a different
386
530
  # name for internal usage for the current action.
@@ -401,7 +545,6 @@ module Brainstem
401
545
  end
402
546
  alias_method :transforms_for, :transforms
403
547
 
404
-
405
548
  #
406
549
  # Retrieves a specific key in a given context, or if that doesn't exist,
407
550
  # falls back to the parent context.