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 +4 -4
- data/lib/odata/batch.rb +6 -6
- data/lib/odata/collection.rb +134 -74
- data/lib/odata/collection_filter.rb +40 -9
- data/lib/odata/collection_media.rb +53 -54
- data/lib/odata/collection_order.rb +46 -36
- data/lib/odata/common_logger.rb +34 -34
- data/lib/odata/entity.rb +86 -70
- data/lib/odata/error.rb +17 -4
- data/lib/odata/expand.rb +123 -0
- data/lib/odata/filter/parse.rb +4 -12
- data/lib/odata/filter/sequel.rb +11 -13
- data/lib/odata/filter/tree.rb +11 -15
- data/lib/odata/navigation_attribute.rb +36 -40
- data/lib/odata/select.rb +42 -0
- data/lib/odata/url_parameters.rb +51 -36
- data/lib/safrano.rb +5 -5
- data/lib/safrano/core.rb +10 -1
- data/lib/safrano/multipart.rb +16 -16
- data/lib/safrano/rack_app.rb +3 -3
- data/lib/safrano/request.rb +6 -6
- data/lib/safrano/response.rb +1 -1
- data/lib/safrano/service.rb +64 -119
- data/lib/safrano/version.rb +1 -1
- data/lib/sequel/plugins/join_by_paths.rb +11 -10
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6397b8b41d6241576f613abfa0e36c8f6973fa96542b60d388b15954aaa3a92b
|
4
|
+
data.tar.gz: 3f36fe0d18bd9659d4e3ce27f501ef1540e82f24a26418c28760aae869de85d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6f666fb8e2fb136fe7ca0c215e19c24648d9a1a4494c3cdb1a53f45561cf827c2670be56bcf74005828b3a9ebf78e2efc63e8b89db35fefb9f70d4c730e7ebb
|
7
|
+
data.tar.gz: 2cdfe6b8cb732184dacb256e5c114361f41da91f5a0f821c2f61a5b7ae541b0aa8f5ad45b23598c0d5301311c193f9f6e51d6e8b8def7eac78a54176f92cfd98
|
data/lib/odata/batch.rb
CHANGED
@@ -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 =
|
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,
|
107
|
+
[404, EMPTY_HASH, '$batch is not enabled ']
|
108
108
|
end
|
109
109
|
|
110
110
|
def odata_get(_req)
|
111
|
-
[404,
|
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,
|
141
|
+
[415, EMPTY_HASH, 'Unsupported Media Type']
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
145
|
def odata_get(_req)
|
146
|
-
[405,
|
146
|
+
[405, EMPTY_HASH, 'You cant GET $batch, POST it ']
|
147
147
|
end
|
148
148
|
end
|
149
149
|
end
|
data/lib/odata/collection.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
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
|
-
|
229
|
-
|
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
|
-
|
234
|
-
|
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
|
-
@
|
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
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
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
|
-
|
333
|
-
|
334
|
-
|
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
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
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
|
-
|
347
|
-
|
348
|
-
|
335
|
+
schema_enty.add_element('NavigationProperty', nattr)
|
336
|
+
end
|
349
337
|
end
|
350
338
|
|
351
339
|
D = 'd'.freeze
|
352
|
-
|
353
|
-
|
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
|
-
"#{
|
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
|
-
|
421
|
+
template: template,
|
364
422
|
uribase: @uribase,
|
365
423
|
icount: @inlinecount).to_json
|
366
|
-
"#{
|
424
|
+
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
367
425
|
end
|
368
426
|
|
369
427
|
def get_a
|
370
|
-
@
|
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
|
-
|
384
|
-
|
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
|
-
|
402
|
-
|
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
|
-
|
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
|
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,
|
636
|
+
return [422, EMPTY_HASH, ['Invalid attribute name: ', invalid.to_s]]
|
577
637
|
end
|
578
638
|
|
579
639
|
if req.accept?(APPJSON)
|