rest_framework 1.0.0.beta2 → 1.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.
@@ -11,7 +11,7 @@ module RESTFramework::Mixins::BaseControllerMixin
11
11
  title: nil,
12
12
  description: nil,
13
13
  version: nil,
14
- inflect_acronyms: ["ID", "IDs", "REST", "API", "APIs"].freeze,
14
+ inflect_acronyms: [ "ID", "IDs", "REST", "API", "APIs" ].freeze,
15
15
  openapi_include_children: false,
16
16
 
17
17
  # Options related to serialization.
@@ -38,14 +38,14 @@ module RESTFramework::Mixins::BaseControllerMixin
38
38
 
39
39
  # Default action for API root.
40
40
  def root
41
- render_api({message: "This is the API root."})
41
+ render(api: { message: "This is the API root." })
42
42
  end
43
43
 
44
44
  module ClassMethods
45
45
  # By default, this is the name of the controller class, titleized and with any custom inflection
46
46
  # acronyms applied.
47
47
  def get_title
48
- return self.title || RESTFramework::Utils.inflect(
48
+ self.title || RESTFramework::Utils.inflect(
49
49
  self.name.demodulize.chomp("Controller").titleize(keep_id_suffix: true),
50
50
  self.inflect_acronyms,
51
51
  )
@@ -53,10 +53,7 @@ module RESTFramework::Mixins::BaseControllerMixin
53
53
 
54
54
  # Get a label from a field/column name, titleized and inflected.
55
55
  def label_for(s)
56
- return RESTFramework::Utils.inflect(
57
- s.to_s.titleize(keep_id_suffix: true),
58
- self.inflect_acronyms,
59
- )
56
+ RESTFramework::Utils.inflect(s.to_s.titleize(keep_id_suffix: true), self.inflect_acronyms)
60
57
  end
61
58
 
62
59
  # Define any behavior to execute at the end of controller definition.
@@ -71,50 +68,58 @@ module RESTFramework::Mixins::BaseControllerMixin
71
68
  end
72
69
  # :nocov:
73
70
 
74
- def openapi_paths(routes, tag)
75
- response_content_types = [
71
+ def openapi_response_content_types
72
+ @openapi_response_content_types ||= [
76
73
  "text/html",
77
74
  self.serialize_to_json ? "application/json" : nil,
78
75
  self.serialize_to_xml ? "application/xml" : nil,
79
76
  ].compact
80
- request_content_types = [
77
+ end
78
+
79
+ def openapi_request_content_types
80
+ @openapi_request_content_types ||= [
81
81
  "application/json",
82
- "application/xml",
83
82
  "application/x-www-form-urlencoded",
84
83
  "multipart/form-data",
85
84
  ]
85
+ end
86
+
87
+ def openapi_paths(routes, tag)
88
+ resp_cts = self.openapi_response_content_types
89
+ req_cts = self.openapi_request_content_types
86
90
 
87
- return routes.group_by { |r| r[:concat_path] }.map { |concat_path, routes|
91
+ routes.group_by { |r| r[:concat_path] }.map { |concat_path, routes|
88
92
  [
89
93
  concat_path.gsub(/:([0-9A-Za-z_-]+)/, "{\\1}"),
90
94
  routes.map { |route|
91
- metadata = route[:route].defaults[:metadata] || {}
92
- summary = metadata[:label].presence || self.label_for(route[:action])
93
- description = metadata[:description].presence
94
- remaining_metadata = metadata.except(:label, :description).presence
95
-
96
- [
97
- route[:verb].downcase,
98
- {
99
- tags: [tag],
100
- summary: summary,
101
- description: description,
102
- responses: {
103
- 200 => {
104
- content: response_content_types.map { |ct|
105
- [ct, {}]
106
- }.to_h,
107
- description: "",
108
- },
109
- },
110
- requestBody: route[:verb].in?(["GET", "DELETE", "OPTIONS", "TRACE"]) ? nil : {
111
- content: request_content_types.map { |ct|
112
- [ct, {}]
113
- }.to_h,
114
- },
115
- "x-rrf-metadata": remaining_metadata,
116
- }.compact,
117
- ]
95
+ metadata = RESTFramework::ROUTE_METADATA[route[:path]] || {}
96
+ summary = metadata.delete(:label).presence || self.label_for(route[:action])
97
+ description = metadata.delete(:description).presence
98
+ extra_action = RESTFramework::EXTRA_ACTION_ROUTES.include?(route[:path])
99
+ error_response = { "$ref" => "#/components/responses/BadRequest" }
100
+ not_found_response = { "$ref" => "#/components/responses/NotFound" }
101
+ spec = { tags: [ tag ], summary: summary, description: description }.compact
102
+
103
+ # All routes should have a successful response.
104
+ spec[:responses] = {
105
+ 200 => { content: resp_cts.map { |ct| [ ct, {} ] }.to_h, description: "Success" },
106
+ }
107
+
108
+ # Builtin POST, PUT, PATCH, and DELETE should have a 400 and 404 response.
109
+ if route[:verb].in?([ "POST", "PUT", "PATCH", "DELETE" ]) && !extra_action
110
+ spec[:responses][400] = error_response
111
+ spec[:responses][404] = not_found_response
112
+ end
113
+
114
+ # All POST, PUT, PATCH should have a request body.
115
+ if route[:verb].in?([ "POST", "PUT", "PATCH" ])
116
+ spec[:requestBody] ||= { content: req_cts.map { |ct| [ ct, {} ] }.to_h }
117
+ end
118
+
119
+ # Add remaining metadata as an extension.
120
+ spec["x-rrf-metadata"] = metadata if metadata.present?
121
+
122
+ next route[:verb].downcase, spec
118
123
  }.to_h.merge(
119
124
  {
120
125
  parameters: routes.first[:route].required_parts.map { |p|
@@ -122,7 +127,7 @@ module RESTFramework::Mixins::BaseControllerMixin
122
127
  name: p,
123
128
  in: "path",
124
129
  required: true,
125
- schema: {type: "integer"},
130
+ schema: { type: "integer" },
126
131
  }
127
132
  },
128
133
  },
@@ -134,16 +139,49 @@ module RESTFramework::Mixins::BaseControllerMixin
134
139
  def openapi_document(request, route_group_name, routes)
135
140
  server = request.base_url + request.original_fullpath.gsub(/\?.*/, "")
136
141
 
137
- return {
142
+ {
138
143
  openapi: "3.1.1",
139
144
  info: {
140
145
  title: self.get_title,
141
146
  description: self.description,
142
147
  version: self.version.to_s,
143
148
  }.compact,
144
- servers: [{url: server}],
149
+ servers: [ { url: server } ],
145
150
  paths: self.openapi_paths(routes, route_group_name),
146
- tags: [{name: route_group_name, description: self.description}.compact],
151
+ tags: [ { name: route_group_name, description: self.description }.compact ],
152
+ components: {
153
+ schemas: {
154
+ "Error" => {
155
+ type: "object",
156
+ required: [ "message" ],
157
+ properties: {
158
+ message: { type: "string" },
159
+ errors: { type: "object" },
160
+ exception: { type: "string" },
161
+ },
162
+ },
163
+ },
164
+ responses: {
165
+ "BadRequest": {
166
+ description: "Bad Request",
167
+ content: self.openapi_response_content_types.map { |ct|
168
+ [
169
+ ct,
170
+ ct == "text/html" ? {} : { schema: { "$ref" => "#/components/schemas/Error" } },
171
+ ]
172
+ }.to_h,
173
+ },
174
+ "NotFound": {
175
+ description: "Not Found",
176
+ content: self.openapi_response_content_types.map { |ct|
177
+ [
178
+ ct,
179
+ ct == "text/html" ? {} : { schema: { "$ref" => "#/components/schemas/Error" } },
180
+ ]
181
+ }.to_h,
182
+ },
183
+ },
184
+ },
147
185
  }.compact
148
186
  end
149
187
  end
@@ -182,6 +220,7 @@ module RESTFramework::Mixins::BaseControllerMixin
182
220
  base.rescue_from(
183
221
  ActionController::ParameterMissing,
184
222
  ActionController::UnpermittedParameters,
223
+ ActionDispatch::Http::Parameters::ParseError,
185
224
  ActiveRecord::AssociationTypeMismatch,
186
225
  ActiveRecord::NotNullViolation,
187
226
  ActiveRecord::RecordNotFound,
@@ -195,7 +234,7 @@ module RESTFramework::Mixins::BaseControllerMixin
195
234
  end
196
235
 
197
236
  # Use `TracePoint` hook to automatically call `rrf_finalize`.
198
- unless RESTFramework.config.disable_auto_finalize
237
+ if RESTFramework.config.auto_finalize
199
238
  # :nocov:
200
239
  TracePoint.trace(:end) do |t|
201
240
  next if base != t.self
@@ -211,12 +250,12 @@ module RESTFramework::Mixins::BaseControllerMixin
211
250
  end
212
251
 
213
252
  def get_serializer_class
214
- return self.class.serializer_class
253
+ self.class.serializer_class
215
254
  end
216
255
 
217
256
  # Serialize the given data using the `serializer_class`.
218
257
  def serialize(data, **kwargs)
219
- return RESTFramework::Utils.wrap_ams(self.get_serializer_class).new(
258
+ RESTFramework::Utils.wrap_ams(self.get_serializer_class).new(
220
259
  data, controller: self, **kwargs
221
260
  ).serialize
222
261
  end
@@ -229,8 +268,8 @@ module RESTFramework::Mixins::BaseControllerMixin
229
268
  400
230
269
  end
231
270
 
232
- render_api(
233
- {
271
+ render(
272
+ api: {
234
273
  message: e.message,
235
274
  errors: e.try(:record).try(:errors),
236
275
  exception: RESTFramework.config.show_backtrace ? e.full_message : nil,
@@ -240,7 +279,7 @@ module RESTFramework::Mixins::BaseControllerMixin
240
279
  end
241
280
 
242
281
  def route_groups
243
- return @route_groups ||= RESTFramework::Utils.get_routes(Rails.application.routes, request)
282
+ @route_groups ||= RESTFramework::Utils.get_routes(Rails.application.routes, request)
244
283
  end
245
284
 
246
285
  # Render a browsable API for `html` format, along with basic `json`/`xml` formats, and with
@@ -341,11 +380,11 @@ module RESTFramework::Mixins::BaseControllerMixin
341
380
  end
342
381
  end
343
382
 
344
- return document
383
+ document
345
384
  end
346
385
 
347
386
  def options
348
- render_api(self.openapi_document)
387
+ render(api: self.openapi_document)
349
388
  end
350
389
  end
351
390
 
@@ -7,17 +7,17 @@ module RESTFramework::Mixins::BulkCreateModelMixin
7
7
  # overloads the existing collection `POST` endpoint, so we add a special key to the OpenAPI
8
8
  # metadata to indicate bulk create is supported.
9
9
  def openapi_document
10
- return super.merge({"x-rrf-bulk-create": true})
10
+ super.merge({ "x-rrf-bulk-create": true })
11
11
  end
12
12
 
13
13
  def create
14
14
  if params[:_json].is_a?(Array)
15
15
  records = self.create_all!
16
16
  serialized_records = self.bulk_serialize(records)
17
- return render_api(serialized_records)
17
+ return render(api: serialized_records)
18
18
  end
19
19
 
20
- return super
20
+ super
21
21
  end
22
22
 
23
23
  # Perform the `create` call, and return the collection of (possibly) created records.
@@ -25,9 +25,7 @@ module RESTFramework::Mixins::BulkCreateModelMixin
25
25
  create_data = self.get_create_params(bulk_mode: true)[:_json]
26
26
 
27
27
  # Perform bulk create in a transaction.
28
- return ActiveRecord::Base.transaction do
29
- next self.get_create_from.create(create_data)
30
- end
28
+ ActiveRecord::Base.transaction { self.get_create_from.create(create_data) }
31
29
  end
32
30
  end
33
31
 
@@ -36,23 +34,21 @@ module RESTFramework::Mixins::BulkUpdateModelMixin
36
34
  def update_all
37
35
  records = self.update_all!
38
36
  serialized_records = self.bulk_serialize(records)
39
- render_api(serialized_records)
37
+ render(api: serialized_records)
40
38
  end
41
39
 
42
40
  # Perform the `update` call and return the collection of (possibly) updated records.
43
41
  def update_all!
44
42
  pk = self.class.get_model.primary_key
45
- update_data = if params[:_json].is_a?(Array)
43
+ data = if params[:_json].is_a?(Array)
46
44
  self.get_create_params(bulk_mode: :update)[:_json].index_by { |r| r[pk] }
47
45
  else
48
46
  create_params = self.get_create_params
49
- {create_params[pk] => create_params}
47
+ { create_params[pk] => create_params }
50
48
  end
51
49
 
52
50
  # Perform bulk update in a transaction.
53
- return ActiveRecord::Base.transaction do
54
- next self.get_recordset.update(update_data.keys, update_data.values)
55
- end
51
+ ActiveRecord::Base.transaction { self.get_recordset.update(data.keys, data.values) }
56
52
  end
57
53
  end
58
54
 
@@ -62,12 +58,11 @@ module RESTFramework::Mixins::BulkDestroyModelMixin
62
58
  if params[:_json].is_a?(Array)
63
59
  records = self.destroy_all!
64
60
  serialized_records = self.bulk_serialize(records)
65
- return render_api(serialized_records)
61
+ return render(api: serialized_records)
66
62
  end
67
63
 
68
- render_api(
69
- {message: "Bulk destroy requires an array of primary keys as input."},
70
- status: 400,
64
+ render(
65
+ api: { message: "Bulk destroy requires an array of primary keys as input." }, status: 400,
71
66
  )
72
67
  end
73
68
 
@@ -77,9 +72,7 @@ module RESTFramework::Mixins::BulkDestroyModelMixin
77
72
  destroy_data = self.request.request_parameters[:_json]
78
73
 
79
74
  # Perform bulk destroy in a transaction.
80
- return ActiveRecord::Base.transaction do
81
- next self.get_recordset.where(pk => destroy_data).destroy_all
82
- end
75
+ ActiveRecord::Base.transaction { self.get_recordset.where(pk => destroy_data).destroy_all }
83
76
  end
84
77
  end
85
78