safrano 0.4.1 → 0.4.2

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