safrano 0.4.1 → 0.4.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc504918971031d5c89c44a26e970a65da7e2cbd3da865e65138e95f90a2460d
4
- data.tar.gz: dd418e35d807619d101b385434ab4f886e7a0b9d10267471a09418e50d14db49
3
+ metadata.gz: 6397b8b41d6241576f613abfa0e36c8f6973fa96542b60d388b15954aaa3a92b
4
+ data.tar.gz: 3f36fe0d18bd9659d4e3ce27f501ef1540e82f24a26418c28760aae869de85d9
5
5
  SHA512:
6
- metadata.gz: af6cdb6492826c77f0d43846492e53bb5fdaf023de23f0a73496fec5076818291300d248f7d82874886cef99ac864b5632e928061ff8dfe3bf2dafb29cd0593e
7
- data.tar.gz: 86893ddd4605f7d7eaa063c2cf869923db1c17acb42d69e880ab9fc92523743abaab205b18147452b5f55050a65676b2786458b6fcca560c65c4160bf069357d
6
+ metadata.gz: f6f666fb8e2fb136fe7ca0c215e19c24648d9a1a4494c3cdb1a53f45561cf827c2670be56bcf74005828b3a9ebf78e2efc63e8b89db35fefb9f70d4c730e7ebb
7
+ data.tar.gz: 2cdfe6b8cb732184dacb256e5c114361f41da91f5a0f821c2f61a5b7ae541b0aa8f5ad45b23598c0d5301311c193f9f6e51d6e8b8def7eac78a54176f92cfd98
@@ -59,7 +59,7 @@ module OData
59
59
  # TODO: test ?
60
60
  if (logga = @full_req.env['safrano.logger_mw'])
61
61
  logga.batch_log(env, status, header, began_at)
62
- # TODO check why/if we need Rack::Utils::HeaderHash.new(header)
62
+ # TODO: check why/if we need Rack::Utils::HeaderHash.new(header)
63
63
  # and Rack::BodyProxy.new(body) ?
64
64
  end
65
65
  [status, header, body]
@@ -71,7 +71,7 @@ module OData
71
71
 
72
72
  headers.each do |name, value|
73
73
  env_key = name.upcase.tr('-', '_')
74
- env_key = 'HTTP_' + env_key unless env_key == 'CONTENT_TYPE'
74
+ env_key = "HTTP_#{env_key}" unless env_key == 'CONTENT_TYPE'
75
75
  converted_headers[env_key] = value
76
76
  end
77
77
 
@@ -104,11 +104,11 @@ module OData
104
104
  # jaune d'oeuf
105
105
  class DisabledHandler < HandlerBase
106
106
  def odata_post(_req)
107
- [404, {}, '$batch is not enabled ']
107
+ [404, EMPTY_HASH, '$batch is not enabled ']
108
108
  end
109
109
 
110
110
  def odata_get(_req)
111
- [404, {}, '$batch is not enabled ']
111
+ [404, EMPTY_HASH, '$batch is not enabled ']
112
112
  end
113
113
  end
114
114
  # battre le tout
@@ -138,12 +138,12 @@ module OData
138
138
 
139
139
  [202, resp_hdrs, @mult_response.body[0]]
140
140
  else
141
- [415, {}, 'Unsupported Media Type']
141
+ [415, EMPTY_HASH, 'Unsupported Media Type']
142
142
  end
143
143
  end
144
144
 
145
145
  def odata_get(_req)
146
- [405, {}, 'You cant GET $batch, POST it ']
146
+ [405, EMPTY_HASH, 'You cant GET $batch, POST it ']
147
147
  end
148
148
  end
149
149
  end
@@ -8,6 +8,8 @@ require_relative '../safrano/core.rb'
8
8
  require_relative 'error.rb'
9
9
  require_relative 'collection_filter.rb'
10
10
  require_relative 'collection_order.rb'
11
+ require_relative 'expand.rb'
12
+ require_relative 'select.rb'
11
13
  require_relative 'url_parameters.rb'
12
14
  require_relative 'collection_media.rb'
13
15
 
@@ -43,23 +45,23 @@ module OData
43
45
  # we will add this to our Model classes with "extend" --> self is the Class
44
46
  module EntityClassBase
45
47
  SINGLE_PK_URL_REGEXP = /\A\(\s*'?([\w\s]+)'?\s*\)(.*)/.freeze
46
- ONLY_INTEGER_RGX = /\A[+-]?\d+\z/
48
+ ONLY_INTEGER_RGX = /\A[+-]?\d+\z/.freeze
47
49
 
48
50
  attr_reader :nav_collection_url_regexp
49
51
  attr_reader :nav_entity_url_regexp
50
52
  attr_reader :entity_id_url_regexp
51
- attr_reader :attrib_paths_url_regexp
52
53
  attr_reader :nav_collection_attribs
53
54
  attr_reader :nav_entity_attribs
54
55
  attr_reader :data_fields
55
56
  attr_reader :inlinecount
56
-
57
- # Sequel associations pointing to this model. Sequel provides association
57
+ attr_reader :default_template
58
+
59
+ # Sequel associations pointing to this model. Sequel provides association
58
60
  # reflection information on the "from" side. But in some cases
59
61
  # we will need the reverted way
60
62
  # finally not needed and not used yet
61
63
  # attr_accessor :assocs_to
62
-
64
+
63
65
  # set to parent entity in case the collection is a nav.collection
64
66
  # nil otherwise
65
67
  attr_reader :nav_parent
@@ -69,9 +71,6 @@ module OData
69
71
  # dataset
70
72
  attr_accessor :cx
71
73
 
72
- # array of the objects --> dataset.to_a
73
- attr_accessor :ax
74
-
75
74
  # url params
76
75
  attr_reader :params
77
76
 
@@ -105,7 +104,6 @@ module OData
105
104
  @entity_set_name = nil
106
105
  @uparms = nil
107
106
  @params = nil
108
- @ax = nil
109
107
  @cx = nil
110
108
  end
111
109
 
@@ -130,8 +128,7 @@ module OData
130
128
  new_entity.save(transaction: false)
131
129
  OData.create_nav_relation(new_entity, assoc, parent)
132
130
  parent.save(transaction: false)
133
- else
134
- # not supported
131
+ # else # not supported
135
132
  end
136
133
  end
137
134
  def odata_create_save_entity_and_rel(req, new_entity, assoc, parent)
@@ -199,9 +196,7 @@ module OData
199
196
  def check_u_p_inlinecount
200
197
  return unless (icp = @params['$inlinecount'])
201
198
 
202
- unless (icp == 'allpages') || (icp == 'none')
203
- return BadRequestInlineCountParamError
204
- end
199
+ return BadRequestInlineCountParamError unless (icp == 'allpages') || (icp == 'none')
205
200
 
206
201
  nil
207
202
  end
@@ -214,26 +209,25 @@ module OData
214
209
  @uparms.check_order
215
210
  end
216
211
 
212
+ def check_u_p_expand
213
+ @uparms.check_expand
214
+ end
215
+
217
216
  def build_attribute_path_list
218
217
  @attribute_path_list = attribute_path_list
219
- @attrib_paths_url_regexp = @attribute_path_list.join('|')
220
218
  end
221
219
 
222
220
  def attribute_path_list(nodes = Set.new)
223
221
  # break circles
224
- return [] if nodes.include?(entity_set_name)
222
+ return EMPTY_ARRAY if nodes.include?(entity_set_name)
225
223
 
226
224
  ret = @columns.map(&:to_s)
227
225
  nodes.add entity_set_name
228
- if @nav_entity_attribs
229
- @nav_entity_attribs.each do |a, k|
230
- ret.concat(k.attribute_path_list(nodes).map { |kc| "#{a}/#{kc}" })
231
- end
226
+ @nav_entity_attribs&.each do |a, k|
227
+ ret.concat(k.attribute_path_list(nodes).map { |kc| "#{a}/#{kc}" })
232
228
  end
233
- if @nav_collection_attribs
234
- @nav_collection_attribs.each do |a, k|
235
- ret.concat(k.attribute_path_list(nodes).map { |kc| "#{a}/#{kc}" })
236
- end
229
+ @nav_collection_attribs&.each do |a, k|
230
+ ret.concat(k.attribute_path_list(nodes).map { |kc| "#{a}/#{kc}" })
237
231
  end
238
232
  ret
239
233
  end
@@ -242,20 +236,18 @@ module OData
242
236
  return nil unless @params
243
237
 
244
238
  check_u_p_top || check_u_p_skip || check_u_p_orderby ||
245
- check_u_p_filter || check_u_p_inlinecount
239
+ check_u_p_filter || check_u_p_expand || check_u_p_inlinecount
246
240
  end
247
241
 
248
242
  def initialize_dataset
249
243
  @cx = self
250
- @ax = nil
251
244
  @cx = navigated_dataset if @cx.nav_parent
252
245
  @model = if @cx.respond_to? :model
253
246
  @cx.model
254
247
  else
255
248
  @cx
256
249
  end
257
- @jh = @model.join_by_paths_helper
258
- @uparms = UrlParameters.new(@jh, @params)
250
+ @uparms = UrlParameters4Coll.new(@model, @params)
259
251
  end
260
252
 
261
253
  # finally return the requested output according to format, options etc
@@ -296,10 +288,10 @@ module OData
296
288
  # add metadata xml to the passed REXML schema object
297
289
  def add_metadata_rexml(schema)
298
290
  enty = if @media_handler
299
- schema.add_element('EntityType', 'Name' => to_s, 'HasStream' => 'true' )
300
- else
301
- schema.add_element('EntityType', 'Name' => to_s)
302
- end
291
+ schema.add_element('EntityType', 'Name' => to_s, 'HasStream' => 'true')
292
+ else
293
+ schema.add_element('EntityType', 'Name' => to_s)
294
+ end
303
295
  # with their properties
304
296
  db_schema.each do |pnam, prop|
305
297
  if prop[:primary_key] == true
@@ -323,66 +315,130 @@ module OData
323
315
  association_reflection(assoc.to_sym)[:type],
324
316
  xnamespace,
325
317
  assoc)
326
-
327
318
  end
328
319
 
329
320
  # and their Nav attributes == Sequel Model association
330
321
  def add_metadata_navs_rexml(schema_enty, relman, xnamespace)
331
-
332
- @nav_entity_attribs.each{|ne,klass|
333
- nattr = metadata_nav_rexml_attribs(ne,
334
- klass,
335
- relman,
322
+ @nav_entity_attribs&.each do |ne, klass|
323
+ nattr = metadata_nav_rexml_attribs(ne,
324
+ klass,
325
+ relman,
336
326
  xnamespace)
337
327
  schema_enty.add_element('NavigationProperty', nattr)
338
- } if @nav_entity_attribs
339
-
340
-
341
- @nav_collection_attribs.each{|nc,klass|
342
- nattr = metadata_nav_rexml_attribs(nc,
343
- klass,
344
- relman,
328
+ end
329
+
330
+ @nav_collection_attribs&.each do |nc, klass|
331
+ nattr = metadata_nav_rexml_attribs(nc,
332
+ klass,
333
+ relman,
345
334
  xnamespace)
346
- schema_enty.add_element('NavigationProperty', nattr)
347
- } if @nav_collection_attribs
348
-
335
+ schema_enty.add_element('NavigationProperty', nattr)
336
+ end
349
337
  end
350
338
 
351
339
  D = 'd'.freeze
352
- DJopen = '{"d":'.freeze
353
- DJclose = '}'.freeze
340
+ DJ_OPEN = '{"d":'.freeze
341
+ DJ_CLOSE = '}'.freeze
354
342
  def to_odata_links_json(service:)
355
343
  innerj = service.get_coll_odata_links_h(array: get_a,
356
344
  uribase: @uribase,
357
345
  icount: @inlinecount).to_json
358
- "#{DJopen}#{innerj}#{DJclose}"
346
+ "#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
347
+ end
348
+
349
+ # def output_template(expand: nil, select: nil)
350
+ def output_template(uparms)
351
+ # output_template_deep(expand_list: expand_list, select: select)
352
+ output_template_deep(expand_list: uparms.expand.template, select: uparms.select)
353
+ end
354
+
355
+ # Recursive
356
+ def output_template_deep(expand_list:, select: OData::SelectBase::ALL)
357
+ return default_template if expand_list.empty? && select.all_props?
358
+
359
+ template = {}
360
+ expand_e = {}
361
+ expand_c = {}
362
+ deferr = []
363
+
364
+ # 1. handle non-navigation properties, only consider $select
365
+ # 2. handle navigations properties, need to check $select and $expand
366
+ if select.all_props?
367
+ template[:all_values] = EMPTYH
368
+ # include all nav attributes -->
369
+ @nav_entity_attribs&.each do |attr, klass|
370
+ if expand_list.key?(attr)
371
+ expand_e[attr] = klass.output_template_deep(expand_list: expand_list[attr])
372
+ else
373
+ deferr << attr
374
+ end
375
+ end
376
+
377
+ @nav_collection_attribs&.each do |attr, klass|
378
+ if expand_list.key?(attr)
379
+ expand_c[attr] = klass.output_template_deep(expand_list: expand_list[attr])
380
+ else
381
+ deferr << attr
382
+ end
383
+ end
384
+
385
+ else
386
+ template[:selected_vals] = @columns.map(&:to_s) & select.props
387
+ # include only selected nav attribs-->need additional intersection step
388
+ if @nav_entity_attribs
389
+ selected_nav_e = @nav_entity_attribs.keys & select.props
390
+
391
+ selected_nav_e&.each do |attr|
392
+ if expand_list.key?(attr)
393
+ klass = @nav_entity_attribs[attr]
394
+ expand_e[attr] = klass.output_template_deep(expand_list: expand_list[attr])
395
+ else
396
+ deferr << attr
397
+ end
398
+ end
399
+ end
400
+ if @nav_collection_attribs
401
+ selected_nav_c = @nav_collection_attribs.keys & select.props
402
+ selected_nav_c&.each do |attr|
403
+ if expand_list.key?(attr)
404
+ klass = @nav_collection_attribs[attr]
405
+ expand_c[attr] = klass.output_template_deep(expand_list: expand_list[attr])
406
+ else
407
+ deferr << attr
408
+ end
409
+ end
410
+ end
411
+ end
412
+ template[:expand_e] = expand_e if expand_e
413
+ template[:expand_c] = expand_c if expand_c
414
+ template[:deferr] = deferr if deferr
415
+ template
359
416
  end
360
417
 
361
418
  def to_odata_json(service:)
419
+ template = output_template(@uparms)
362
420
  innerj = service.get_coll_odata_h(array: get_a,
363
- expand: @params['$expand'],
421
+ template: template,
364
422
  uribase: @uribase,
365
423
  icount: @inlinecount).to_json
366
- "#{DJopen}#{innerj}#{DJclose}"
424
+ "#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
367
425
  end
368
426
 
369
427
  def get_a
370
- @ax.nil? ? @cx.to_a : @ax
428
+ @cx.all
371
429
  end
372
430
 
373
431
  # this functionally similar to the Sequel Rels (many_to_one etc)
374
432
  # We need to base this on the Sequel rels, or extend them
375
433
  def add_nav_prop_collection(assoc_symb, attr_name_str = nil)
376
434
  @nav_collection_attribs = (@nav_collection_attribs || {})
377
- # @assocs_to = ( @assocs_to || [] )
378
435
  # DONE: Error handling. This requires that associations
379
436
  # have been properly defined with Sequel before
380
437
  assoc = all_association_reflections.find do |a|
381
438
  a[:name] == assoc_symb && a[:model] == self
382
439
  end
383
- unless assoc
384
- raise OData::API::ModelAssociationNameError.new(self, assoc_symb)
385
- end
440
+
441
+ raise OData::API::ModelAssociationNameError.new(self, assoc_symb) unless assoc
386
442
 
387
443
  attr_class = assoc[:class_name].constantize
388
444
  lattr_name_str = (attr_name_str || assoc_symb.to_s)
@@ -392,24 +448,29 @@ module OData
392
448
 
393
449
  def add_nav_prop_single(assoc_symb, attr_name_str = nil)
394
450
  @nav_entity_attribs = (@nav_entity_attribs || {})
395
- # @assocs_to = ( @assocs_to || [])
396
451
  # DONE: Error handling. This requires that associations
397
452
  # have been properly defined with Sequel before
398
453
  assoc = all_association_reflections.find do |a|
399
454
  a[:name] == assoc_symb && a[:model] == self
400
455
  end
401
- unless assoc
402
- raise OData::API::ModelAssociationNameError.new(self, assoc_symb)
403
- end
456
+
457
+ raise OData::API::ModelAssociationNameError.new(self, assoc_symb) unless assoc
404
458
 
405
459
  attr_class = assoc[:class_name].constantize
406
460
  lattr_name_str = (attr_name_str || assoc_symb.to_s)
407
461
  @nav_entity_attribs[lattr_name_str] = attr_class
408
462
  @nav_entity_url_regexp = @nav_entity_attribs.keys.join('|')
409
- # attr_class.assocs_to = ( attr_class.assocs_to || [] )
410
- # attr_class.assocs_to << assoc
411
463
  end
412
464
 
465
+ EMPTYH = {}.freeze
466
+
467
+ def build_default_template
468
+ template = { all_values: EMPTYH }
469
+ if @nav_entity_attribs || @nav_collection_attribs
470
+ template[:deferr] = (@nav_entity_attribs&.keys || []) + (@nav_collection_attribs&.keys || EMPTY_ARRAY)
471
+ end
472
+ template
473
+ end
413
474
  # old names...
414
475
  # alias_method :add_nav_prop_collection, :addNavCollectionAttrib
415
476
  # alias_method :add_nav_prop_single, :addNavEntityAttrib
@@ -417,11 +478,14 @@ module OData
417
478
  def finalize_publishing
418
479
  # finalize media handler
419
480
  @media_handler.register(self) if @media_handler
420
-
481
+
482
+ # build default output template structure
483
+ @default_template = build_default_template
484
+
421
485
  # and finally build the path list
422
486
  build_attribute_path_list
423
487
  end
424
-
488
+
425
489
  def prepare_pk
426
490
  if primary_key.is_a? Array
427
491
  @pk_names = []
@@ -470,11 +534,7 @@ module OData
470
534
  def check_odata_val_type(val, type)
471
535
  case type
472
536
  when :integer
473
- if (val =~ ONLY_INTEGER_RGX)
474
- [true, Integer(val)]
475
- else
476
- [false, val]
477
- end
537
+ val =~ ONLY_INTEGER_RGX ? [true, Integer(val)] : [false, val]
478
538
  when :string
479
539
  [true, val]
480
540
  else
@@ -535,7 +595,7 @@ module OData
535
595
  md.shift # remove first element which is the whole match
536
596
  mdc = []
537
597
  error = false
538
- primary_key.each_with_index { |pk, i|
598
+ primary_key.each_with_index do |pk, i|
539
599
  ck, casted = check_odata_val_type(md[i], db_schema[pk][:type])
540
600
  if ck
541
601
  mdc << casted
@@ -543,7 +603,7 @@ module OData
543
603
  error = true
544
604
  break
545
605
  end
546
- }
606
+ end
547
607
  if error
548
608
  [false, md]
549
609
  else
@@ -573,7 +633,7 @@ module OData
573
633
  # validate payload column names
574
634
  if (invalid = invalid_hash_data?(data))
575
635
  ::OData::Request::ON_CGST_ERROR.call(req)
576
- return [422, {}, ['Invalid attribute name: ', invalid.to_s]]
636
+ return [422, EMPTY_HASH, ['Invalid attribute name: ', invalid.to_s]]
577
637
  end
578
638
 
579
639
  if req.accept?(APPJSON)