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 +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)
|