brainstem 1.4.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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.