safrano 0.4.1 → 0.4.6

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