safrano 0.4.1 → 0.4.6

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/core_ext/Dir/iter.rb +18 -0
  3. data/lib/core_ext/Hash/transform.rb +21 -0
  4. data/lib/core_ext/Integer/edm.rb +13 -0
  5. data/lib/core_ext/REXML/Document/output.rb +16 -0
  6. data/lib/core_ext/String/convert.rb +25 -0
  7. data/lib/core_ext/String/edm.rb +13 -0
  8. data/lib/core_ext/dir.rb +3 -0
  9. data/lib/core_ext/hash.rb +3 -0
  10. data/lib/core_ext/integer.rb +3 -0
  11. data/lib/core_ext/rexml.rb +3 -0
  12. data/lib/core_ext/string.rb +5 -0
  13. data/lib/odata/attribute.rb +15 -10
  14. data/lib/odata/batch.rb +15 -13
  15. data/lib/odata/collection.rb +144 -535
  16. data/lib/odata/collection_filter.rb +47 -40
  17. data/lib/odata/collection_media.rb +155 -99
  18. data/lib/odata/collection_order.rb +50 -37
  19. data/lib/odata/common_logger.rb +36 -34
  20. data/lib/odata/complex_type.rb +152 -0
  21. data/lib/odata/edm/primitive_types.rb +184 -0
  22. data/lib/odata/entity.rb +183 -216
  23. data/lib/odata/error.rb +195 -31
  24. data/lib/odata/expand.rb +126 -0
  25. data/lib/odata/filter/base.rb +74 -0
  26. data/lib/odata/filter/error.rb +49 -6
  27. data/lib/odata/filter/parse.rb +44 -36
  28. data/lib/odata/filter/sequel.rb +136 -67
  29. data/lib/odata/filter/sequel_function_adapter.rb +148 -0
  30. data/lib/odata/filter/token.rb +26 -19
  31. data/lib/odata/filter/tree.rb +113 -63
  32. data/lib/odata/function_import.rb +168 -0
  33. data/lib/odata/model_ext.rb +639 -0
  34. data/lib/odata/navigation_attribute.rb +44 -61
  35. data/lib/odata/relations.rb +5 -5
  36. data/lib/odata/select.rb +54 -0
  37. data/lib/odata/transition.rb +71 -0
  38. data/lib/odata/url_parameters.rb +128 -37
  39. data/lib/odata/walker.rb +20 -10
  40. data/lib/safrano.rb +17 -37
  41. data/lib/safrano/contract.rb +143 -0
  42. data/lib/safrano/core.rb +29 -104
  43. data/lib/safrano/core_ext.rb +13 -0
  44. data/lib/safrano/deprecation.rb +73 -0
  45. data/lib/safrano/multipart.rb +39 -43
  46. data/lib/safrano/rack_app.rb +68 -67
  47. data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -2
  48. data/lib/safrano/request.rb +102 -51
  49. data/lib/safrano/response.rb +5 -3
  50. data/lib/safrano/sequel_join_by_paths.rb +2 -2
  51. data/lib/safrano/service.rb +274 -219
  52. data/lib/safrano/version.rb +3 -1
  53. data/lib/sequel/plugins/join_by_paths.rb +17 -29
  54. metadata +34 -11
data/lib/odata/entity.rb CHANGED
@@ -1,44 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'rexml/document'
3
5
  require 'safrano.rb'
4
- require 'odata/collection.rb' # required for self.class.entity_type_name ??
6
+ require 'odata/model_ext.rb' # required for self.class.entity_type_name ??
5
7
  require_relative 'navigation_attribute'
6
8
 
7
- module OData
9
+ module Safrano
8
10
  # this will be mixed in the Model classes (subclasses of Sequel Model)
9
11
  module EntityBase
10
12
  attr_reader :params
11
- attr_reader :uribase
12
13
 
13
- include EntityBase::NavigationInfo
14
-
14
+ include Safrano::NavigationInfo
15
+
15
16
  # methods related to transitions to next state (cf. walker)
16
17
  module Transitions
17
18
  def allowed_transitions
18
- alltr = [
19
- Safrano::TransitionEnd,
20
- Safrano::TransitionCount,
21
- Safrano::TransitionLinks,
22
- Safrano::TransitionValue,
23
- Safrano::Transition.new(self.class.transition_attribute_regexp,
24
- trans: 'transition_attribute')
25
- ]
26
- if (ncurgx = self.class.nav_collection_url_regexp)
27
- alltr <<
28
- Safrano::Transition.new(%r{\A/(#{ncurgx})(.*)\z},
29
- trans: 'transition_nav_collection')
30
-
31
- end
32
- if (neurgx = self.class.nav_entity_url_regexp)
33
- alltr <<
34
- Safrano::Transition.new(%r{\A/(#{neurgx})(.*)\z},
35
- trans: 'transition_nav_entity')
36
- end
37
- alltr
19
+ self.class.entity_allowed_transitions
38
20
  end
39
21
 
40
22
  def transition_end(_match_result)
41
- [nil, :end]
23
+ Safrano::Transition::RESULT_END
42
24
  end
43
25
 
44
26
  def transition_count(_match_result)
@@ -56,8 +38,7 @@ module OData
56
38
 
57
39
  def transition_attribute(match_result)
58
40
  attrib = match_result[1]
59
- # [values[attrib.to_sym], :run]
60
- [OData::Attribute.new(self, attrib), :run]
41
+ [Safrano::Attribute.new(self, attrib), :run]
61
42
  end
62
43
 
63
44
  def transition_nav_collection(match_result)
@@ -69,17 +50,26 @@ module OData
69
50
  attrib = match_result[1]
70
51
  [get_related_entity(attrib), :run]
71
52
  end
53
+
54
+ def transition_invalid_attribute(match_result)
55
+ invalid_attrib = match_result[1]
56
+ [nil, :error, Safrano::ErrorNotFoundSegment.new(invalid_attrib)]
57
+ end
72
58
  end
73
59
 
74
60
  include Transitions
75
61
 
62
+ # for testing only?
63
+ def ==(other)
64
+ ((self.class.type_name == other.class.type_name) and (@values == other.values))
65
+ end
66
+
76
67
  def nav_values
77
68
  @nav_values = {}
78
69
 
79
70
  self.class.nav_entity_attribs&.each_key do |na_str|
80
71
  @nav_values[na_str.to_sym] = send(na_str)
81
72
  end
82
-
83
73
  @nav_values
84
74
  end
85
75
 
@@ -91,50 +81,43 @@ module OData
91
81
  @nav_coll
92
82
  end
93
83
 
94
- def uri(uriba)
95
- "#{uriba}/#{self.class.entity_set_name}(#{pk_uri})"
84
+ def uri
85
+ @odata_pk ||= "(#{pk_uri})"
86
+ "#{self.class.uri}#{@odata_pk}"
96
87
  end
88
+
97
89
  D = 'd'.freeze
98
- DJopen = '{"d":'.freeze
99
- DJclose = '}'.freeze
90
+ DJ_OPEN = '{"d":'.freeze
91
+ DJ_CLOSE = '}'.freeze
92
+
100
93
  # Json formatter for a single entity (probably OData V1/V2 like)
101
- def to_odata_json(service:)
102
- innerj = service.get_entity_odata_h(entity: self,
103
- expand: @params['$expand'],
104
- uribase: @uribase).to_json
105
- "#{DJopen}#{innerj}#{DJclose}"
94
+ def to_odata_json(request:)
95
+ template = self.class.output_template(expand_list: @uparms.expand.template,
96
+ select: @uparms.select)
97
+ innerj = request.service.get_entity_odata_h(entity: self,
98
+ template: template).to_json
99
+ "#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
106
100
  end
107
101
 
108
102
  # Json formatter for a single entity reached by navigation $links
109
103
  def to_odata_onelink_json(service:)
110
- innerj = service.get_entity_odata_link_h(entity: self,
111
- uribase: @uribase).to_json
112
- "#{DJopen}#{innerj}#{DJclose}"
104
+ innerj = service.get_entity_odata_link_h(entity: self).to_json
105
+ "#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
113
106
  end
114
107
 
115
-
116
- # needed for proper datetime output
117
- # TODO design/performance
118
- def casted_values
119
- # WARNING; this code is duplicated in attribute.rb
120
- # (and the inverted transformation is in test/client.rb)
121
- # will require a more systematic solution some day
122
- values_for_odata.transform_values! { |v|
123
- case v
124
- when Time
125
- # try to get back the database time zone and value
126
- (v + v.gmt_offset).utc.to_datetime
127
- else
128
- v
129
- end
130
- }
108
+ def selected_values_for_odata(cols)
109
+ allvals = values_for_odata
110
+ selvals = {}
111
+ cols.map(&:to_sym).each { |k| selvals[k] = allvals[k] if allvals.key?(k) }
112
+ selvals
131
113
  end
132
114
 
133
- # post paylod expects the new entity in an array
134
- def to_odata_post_json(service:)
135
- innerj = service.get_coll_odata_h(array: [self],
136
- uribase: @uribase).to_json
137
- "#{DJopen}#{innerj}#{DJclose}"
115
+ # some clients wrongly expect post payload with the new entity in an array
116
+ # TODO quirks array mode !
117
+ def to_odata_array_json(request:)
118
+ innerj = request.service.get_coll_odata_h(array: [self],
119
+ template: self.class.default_template).to_json
120
+ "#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
138
121
  end
139
122
 
140
123
  def type_name
@@ -143,32 +126,37 @@ module OData
143
126
 
144
127
  def copy_request_infos(req)
145
128
  @params = req.params
146
- @uribase = req.uribase
147
129
  @do_links = req.walker.do_links
130
+ @uparms = UrlParameters4Single.new(self, @params)
148
131
  end
149
132
 
150
- # Finally Process REST verbs...
151
- def odata_get(req)
152
- copy_request_infos(req)
153
-
133
+ def odata_get_output(req)
154
134
  if req.walker.media_value
155
135
  odata_media_value_get(req)
156
136
  elsif req.accept?(APPJSON)
137
+ # json is default content type so we dont need to specify it here again
157
138
  if req.walker.do_links
158
- [200, CT_JSON, [to_odata_onelink_json(service: req.service)]]
139
+ [200, EMPTY_HASH, [to_odata_onelink_json(service: req.service)]]
159
140
  else
160
- [200, CT_JSON, [to_odata_json(service: req.service)]]
141
+ [200, EMPTY_HASH, [to_odata_json(request: req)]]
161
142
  end
162
143
  else # TODO: other formats
163
144
  415
164
145
  end
165
146
  end
166
-
147
+
148
+ # Finally Process REST verbs...
149
+ def odata_get(req)
150
+ copy_request_infos(req)
151
+ @uparms.check_all.tap_valid { return odata_get_output(req) }
152
+ .tap_error { |e| return e.odata_get(req) }
153
+ end
154
+
167
155
  DELETE_REL_AND_ENTY = lambda do |entity, assoc, parent|
168
- OData.remove_nav_relation(entity, assoc, parent)
156
+ Safrano.remove_nav_relation(assoc, parent)
169
157
  entity.destroy(transaction: false)
170
158
  end
171
-
159
+
172
160
  def odata_delete_relation_and_entity(req, assoc, parent)
173
161
  if parent
174
162
  if req.in_changeset
@@ -182,52 +170,47 @@ module OData
182
170
  else
183
171
  destroy(transaction: false)
184
172
  end
185
- rescue StandardError => e
186
- raise SequelAdapterError.new(e)
173
+ rescue StandardError => e
174
+ raise SequelAdapterError.new(e)
187
175
  end
188
-
176
+
189
177
  # TODO: differentiate between POST/PUT/PATCH/MERGE
190
178
  def odata_post(req)
191
179
  if req.walker.media_value
192
180
  odata_media_value_put(req)
193
- else
194
- if req.accept?(APPJSON)
195
- data.delete('__metadata')
196
-
197
- if req.in_changeset
198
- set_fields(data, self.class.data_fields, missing: :skip)
199
- save(transaction: false)
200
- else
201
- update_fields(data, self.class.data_fields, missing: :skip)
202
- end
203
-
204
- [202, {}, to_odata_post_json(service: req.service)]
205
- else # TODO: other formats
206
- 415
181
+ elsif req.accept?(APPJSON)
182
+ data.delete('__metadata')
183
+
184
+ if req.in_changeset
185
+ set_fields(data, self.class.data_fields, missing: :skip)
186
+ save(transaction: false)
187
+ else
188
+ update_fields(data, self.class.data_fields, missing: :skip)
207
189
  end
190
+
191
+ [202, EMPTY_HASH, to_odata_post_json(service: req.service)]
192
+ else # TODO: other formats
193
+ 415
208
194
  end
209
195
  end
210
196
 
211
197
  def odata_put(req)
212
198
  if req.walker.media_value
213
199
  odata_media_value_put(req)
214
- else
215
- if req.accept?(APPJSON)
216
- data = JSON.parse(req.body.read)
217
- @uribase = req.uribase
218
- data.delete('__metadata')
219
-
220
- if req.in_changeset
221
- set_fields(data, self.class.data_fields, missing: :skip)
222
- save(transaction: false)
223
- else
224
- update_fields(data, self.class.data_fields, missing: :skip)
225
- end
200
+ elsif req.accept?(APPJSON)
201
+ data = JSON.parse(req.body.read)
202
+ data.delete('__metadata')
226
203
 
227
- [204, {}, []]
228
- else # TODO: other formats
229
- 415
204
+ if req.in_changeset
205
+ set_fields(data, self.class.data_fields, missing: :skip)
206
+ save(transaction: false)
207
+ else
208
+ update_fields(data, self.class.data_fields, missing: :skip)
230
209
  end
210
+
211
+ ARY_204_EMPTY_HASH_ARY
212
+ else # TODO: other formats
213
+ 415
231
214
  end
232
215
  end
233
216
 
@@ -237,14 +220,12 @@ module OData
237
220
 
238
221
  # validate payload column names
239
222
  if (invalid = self.class.invalid_hash_data?(data))
240
- ::OData::Request::ON_CGST_ERROR.call(req)
241
- return [422, {}, ['Invalid attribute name: ', invalid.to_s]]
223
+ ::Safrano::Request::ON_CGST_ERROR.call(req)
224
+ return [422, EMPTY_HASH, ['Invalid attribute name: ', invalid.to_s]]
242
225
  end
243
226
  # TODO: check values/types
244
227
 
245
228
  my_data_fields = self.class.data_fields
246
- @uribase = req.uribase
247
- # if req.accept?('application/json')
248
229
 
249
230
  if req.in_changeset
250
231
  set_fields(data, my_data_fields, missing: :skip)
@@ -253,75 +234,17 @@ module OData
253
234
  update_fields(data, my_data_fields, missing: :skip)
254
235
  end
255
236
  # patch should return 204 + no content
256
- [204, {}, []]
237
+ ARY_204_EMPTY_HASH_ARY
257
238
  end
258
239
  end
259
240
 
260
- # redefinitions of the main methods for a navigated collection
261
- # (eg. all Books of Author[2] is Author[2].Books.all )
262
- module NavigationRedefinitions
263
- def all
264
- @child_method.call
265
- end
266
-
267
- def count
268
- @child_method.call.count
269
- end
270
-
271
- def dataset
272
- @child_dataset_method.call
273
- end
274
-
275
- def navigated_dataset
276
- @child_dataset_method.call
277
- end
278
-
279
- def each
280
- y = @child_method.call
281
- y.each { |enty| yield enty }
282
- end
283
-
284
- # TODO design... this is not DRY
285
- def slug_field
286
- superclass.slug_field
287
- end
288
-
289
- def type_name
290
- superclass.type_name
291
- end
292
-
293
- def media_handler
294
- superclass.media_handler
295
- end
296
-
297
- def to_a
298
- y = @child_method.call
299
- y.to_a
300
- end
301
- end
302
- # GetRelated that returns a anonymous Class (ie. representing a collection)
303
- # subtype of the related object Class ( childklass )
241
+ # GetRelated that returns a collection object representing
242
+ # wrapping the related object Class ( childklass )
304
243
  # (...to_many relationship )
305
244
  def get_related(childattrib)
306
- parent = self
307
- childklass = self.class.nav_collection_attribs[childattrib]
308
- Class.new(childklass) do
309
- # this makes use of Sequel's Model relationships; eg this is
310
- # 'Race[12].Edition'
311
- # where Race[12] would be our self and 'Edition' is the
312
- # childattrib(collection)
313
- @child_method = parent.method(childattrib.to_sym)
314
- @child_dataset_method = parent.method("#{childattrib}_dataset".to_sym)
315
- @nav_parent = parent
316
- @navattr_reflection = parent.class.association_reflections[childattrib.to_sym]
317
- prepare_pk
318
- prepare_fields
319
- # Now in this anonymous Class we can refine the "all, count and []
320
- # methods, to take into account the relationship
321
- extend NavigationRedefinitions
322
- end
245
+ Safrano::OData::NavigatedCollection.new(childattrib, self)
323
246
  end
324
-
247
+
325
248
  # GetRelatedEntity that returns an single related Entity
326
249
  # (...to_one relationship )
327
250
  def get_related_entity(childattrib)
@@ -334,35 +257,36 @@ module OData
334
257
  # then we return a Nil... wrapper object. This object then
335
258
  # allows to receive a POST operation that would actually create the nav attribute entity
336
259
 
337
- ret = method(childattrib.to_sym).call || OData::NilNavigationAttribute.new
338
-
260
+ ret = method(childattrib.to_sym).call || Safrano::NilNavigationAttribute.new
261
+
339
262
  ret.set_relation_info(self, childattrib)
340
-
263
+
341
264
  ret
342
265
  end
343
266
  end
344
- # end of module ODataEntity
267
+
268
+ # end of module SafranoEntity
345
269
  module Entity
346
270
  include EntityBase
347
271
  end
348
272
 
349
273
  module NonMediaEntity
350
274
  # non media entity metadata for json h
351
- def metadata_h(uribase:)
352
- { uri: uri(uribase),
275
+ def metadata_h
276
+ { uri: uri,
353
277
  type: type_name }
354
278
  end
355
279
 
356
280
  def values_for_odata
357
- values.dup
281
+ values
358
282
  end
359
283
 
360
284
  def odata_delete(req)
361
285
  if req.accept?(APPJSON)
362
- # delete
286
+ # delete
363
287
  begin
364
288
  odata_delete_relation_and_entity(req, @navattr_reflection, @nav_parent)
365
- [200, CT_JSON, [{ 'd' => req.service.get_emptycoll_odata_h }.to_json]]
289
+ [200, EMPTY_HASH, [{ 'd' => req.service.get_emptycoll_odata_h }.to_json]]
366
290
  rescue SequelAdapterError => e
367
291
  BadRequestSequelAdapterError.new(e).odata_get(req)
368
292
  end
@@ -372,44 +296,64 @@ module OData
372
296
  end
373
297
 
374
298
  # in case of a non media entity, we have to return an error on $value request
375
- def odata_media_value_get(req)
376
- return BadRequestNonMediaValue.odata_get
299
+ def odata_media_value_get(_req)
300
+ BadRequestNonMediaValue.odata_get
377
301
  end
378
302
 
379
303
  # in case of a non media entity, we have to return an error on $value PUT
380
- def odata_media_value_put(req)
381
- return BadRequestNonMediaValue.odata_get
304
+ def odata_media_value_put(_req)
305
+ BadRequestNonMediaValue.odata_get
306
+ end
307
+ end
308
+
309
+ module MappingBeforeOutput
310
+ # needed for proper datetime output
311
+ def casted_values(cols = nil)
312
+ vals = case cols
313
+ when nil
314
+ # we need to dup the model values as we need to change it before passing to_json,
315
+ # but we dont want to interfere with Sequel's owned data
316
+ # (eg because then in worst case it could happen that we write back changed values to DB)
317
+ values_for_odata.dup
318
+ else
319
+ selected_values_for_odata(cols)
320
+ end
321
+ self.class.time_cols.each { |tc| vals[tc] = vals[tc]&.iso8601 if vals.key?(tc) }
322
+ vals
323
+ end
324
+ end
325
+ module NoMappingBeforeOutput
326
+ # current model does not have eg. Time fields--> no special mapping, just to_json is fine
327
+ # --> we can use directly the model.values (values_for_odata) withoud dup'ing it as we dont
328
+ # need to change it, just output as is
329
+ def casted_values(cols = nil)
330
+ case cols
331
+ when nil
332
+ values_for_odata
333
+ else
334
+ selected_values_for_odata(cols)
335
+ end
382
336
  end
383
337
  end
384
338
 
385
339
  module MediaEntity
386
340
  # media entity metadata for json h
387
- def metadata_h(uribase:)
388
- { uri: uri(uribase),
341
+ def metadata_h
342
+ { uri: uri,
389
343
  type: type_name,
390
- media_src: media_src(uribase),
391
- edit_media: edit_media(uribase),
344
+ media_src: media_src,
345
+ edit_media: edit_media,
392
346
  content_type: @values[:content_type] }
393
347
  end
394
348
 
395
- def media_src(uribase)
349
+ def media_src
396
350
  version = self.class.media_handler.ressource_version(self)
397
- "#{uri(uribase)}/$value?version=#{version}"
398
- end
399
-
400
- def edit_media(uribase)
401
- "#{uri(uribase)}/$value"
402
- end
403
-
404
- # directory where to put/find the media files for this entity-type
405
- def klass_dir
406
- type_name
351
+ "#{uri}/$value?version=#{version}"
407
352
  end
408
353
 
409
- # # this is just ModelKlass/pk as a single string
410
- # def qualified_media_path_id
411
- # "#{self.class}/#{media_path_id}"
412
- # end
354
+ def edit_media
355
+ "#{uri}/$value"
356
+ end
413
357
 
414
358
  def values_for_odata
415
359
  ret = values.dup
@@ -422,13 +366,12 @@ module OData
422
366
  # delete the MR
423
367
  # delegate to the media handler on collection(ie class) level
424
368
  # TODO error handling
425
-
426
-
427
- self.class.media_handler.odata_delete(request: req, entity: self)
369
+
370
+ self.class.media_handler.odata_delete(entity: self)
428
371
  # delete the relation(s) to parent(s) (if any) and then entity
429
372
  odata_delete_relation_and_entity(req, @navattr_reflection, @nav_parent)
430
373
  # result
431
- [200, CT_JSON, [{ 'd' => req.service.get_emptycoll_odata_h }.to_json]]
374
+ [200, EMPTY_HASH, [{ 'd' => req.service.get_emptycoll_odata_h }.to_json]]
432
375
  else # TODO: other formats
433
376
  415
434
377
  end
@@ -444,17 +387,20 @@ module OData
444
387
  def odata_media_value_put(req)
445
388
  model = self.class
446
389
  req.with_media_data do |data, mimetype, filename|
447
- emdata = { :content_type => mimetype }
390
+ emdata = { content_type: mimetype }
448
391
  if req.in_changeset
449
392
  set_fields(emdata, model.data_fields, missing: :skip)
450
393
  save(transaction: false)
451
394
  else
395
+
452
396
  update_fields(emdata, model.data_fields, missing: :skip)
397
+
453
398
  end
454
399
  model.media_handler.replace_file(data: data,
455
400
  entity: self,
456
401
  filename: filename)
457
- [204, {}, []]
402
+
403
+ ARY_204_EMPTY_HASH_ARY
458
404
  end
459
405
  end
460
406
  end
@@ -469,6 +415,7 @@ module OData
469
415
  def media_path_id
470
416
  pk.to_s
471
417
  end
418
+
472
419
  def media_path_ids
473
420
  [pk]
474
421
  end
@@ -478,17 +425,37 @@ module OData
478
425
  module EntityMultiPK
479
426
  include Entity
480
427
  def pk_uri
481
- # pk_hash is provided by Sequel
482
- self.pk_hash.map { |k, v| "#{k}='#{v}'" }.join(',')
428
+ pku = +''
429
+ self.class.odata_upk_parts.each_with_index { |upart, i|
430
+ pku = "#{pku}#{upart}#{pk[i]}"
431
+ }
432
+ pku
483
433
  end
484
434
 
485
435
  def media_path_id
486
- self.pk_hash.values.join('_')
436
+ pk_hash.values.join(SPACE)
487
437
  end
488
-
438
+
489
439
  def media_path_ids
490
- self.pk_hash.values
440
+ pk_hash.values
441
+ end
442
+ end
443
+
444
+ module EntityCreateStandardOutput
445
+ # Json formatter for a create entity POST call / Standard version; return as json object
446
+ def to_odata_create_json(request:)
447
+ # TODO Perf: reduce method call overhead
448
+ # we added this redirection for readability and flexibility
449
+ to_odata_json(request: request)
450
+ end
451
+ end
452
+
453
+ module EntityCreateArrayOutput
454
+ # Json formatter for a create entity POST call Array version
455
+ def to_odata_create_json(request:)
456
+ # TODO Perf: reduce method call overhead
457
+ # we added this redirection for readability and flexibility
458
+ to_odata_array_json(request: request)
491
459
  end
492
460
  end
493
- end
494
- # end of Module OData
461
+ end # end of Module OData