safrano 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/odata/attribute.rb +8 -7
- data/lib/odata/collection.rb +86 -34
- data/lib/odata/entity.rb +65 -69
- data/lib/odata/error.rb +18 -0
- data/lib/odata/filter/base.rb +66 -0
- data/lib/odata/filter/error.rb +27 -0
- data/lib/odata/filter/parse.rb +2 -0
- data/lib/odata/filter/sequel.rb +32 -17
- data/lib/odata/filter/sequel_function_adapter.rb +147 -0
- data/lib/odata/filter/token.rb +5 -1
- data/lib/odata/filter/tree.rb +34 -14
- data/lib/odata/navigation_attribute.rb +4 -2
- data/lib/odata/walker.rb +6 -4
- data/lib/safrano/core.rb +0 -2
- data/lib/safrano/multipart.rb +0 -9
- data/lib/safrano/odata_rack_builder.rb +0 -1
- data/lib/safrano/rack_app.rb +8 -6
- data/lib/safrano/request.rb +0 -7
- data/lib/safrano/service.rb +111 -47
- data/lib/safrano/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 497482614bfc6a87f8a42b9378a4fc8099ddb5b4f180d747b3717a6952f027b0
|
4
|
+
data.tar.gz: cd5236302671643fd9fbd9bcd200034f305ce7069e5d56ab2db2225dcfb4ce87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4415ca4dc24362f3c179f0cc40f1d73fac6fb7207c0571ef63f1af318fc345e0be8043d8aa1b5ab48e0ead1f5c904dcab07b84061b3ca47f6b7d3c38696d4ae8
|
7
|
+
data.tar.gz: 6646d12aadb3827b43c013a3da6c6223d072e5676f8676eac0db59ad8e3ac80d1a64ceb0f5e87d52bb75e5b556eecda701e2d473c4bdd3b650cb680b6ce19b96
|
data/lib/odata/attribute.rb
CHANGED
@@ -13,15 +13,14 @@ module OData
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def value
|
16
|
-
# WARNING
|
17
|
-
# (and the inverted transformation is in test/client.rb)
|
18
|
-
# will require a more systematic solution some day
|
19
|
-
# WARNING 2... this require more work to handle the timezones topci
|
16
|
+
# WARNING ... this require more work to handle the timezones topci
|
20
17
|
# currently it is just set to make some minimal testcase work
|
21
18
|
case (v = @entity.values[@name.to_sym])
|
22
19
|
when Time
|
23
20
|
# try to get back the database time zone and value
|
24
|
-
(v + v.gmt_offset).utc.to_datetime
|
21
|
+
# (v + v.gmt_offset).utc.to_datetime
|
22
|
+
v.iso8601
|
23
|
+
|
25
24
|
else
|
26
25
|
v
|
27
26
|
end
|
@@ -57,9 +56,11 @@ module OData
|
|
57
56
|
[self, :end_with_value]
|
58
57
|
end
|
59
58
|
|
59
|
+
ALLOWED_TRANSITIONS = [Safrano::TransitionEnd,
|
60
|
+
Safrano::TransitionValue].freeze
|
61
|
+
|
60
62
|
def allowed_transitions
|
61
|
-
|
62
|
-
Safrano::TransitionValue]
|
63
|
+
ALLOWED_TRANSITIONS
|
63
64
|
end
|
64
65
|
end
|
65
66
|
include Transitions
|
data/lib/odata/collection.rb
CHANGED
@@ -55,6 +55,8 @@ module OData
|
|
55
55
|
attr_reader :data_fields
|
56
56
|
attr_reader :inlinecount
|
57
57
|
attr_reader :default_template
|
58
|
+
attr_reader :uri
|
59
|
+
attr_reader :time_cols
|
58
60
|
|
59
61
|
# Sequel associations pointing to this model. Sequel provides association
|
60
62
|
# reflection information on the "from" side. But in some cases
|
@@ -90,7 +92,11 @@ module OData
|
|
90
92
|
# the class-name is not the OData Type. In these subclass we redefine "type_name"
|
91
93
|
# thus when we need the Odata type name, we shall use this method instead of just the collection class name
|
92
94
|
def type_name
|
93
|
-
|
95
|
+
@type_name
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_type_name
|
99
|
+
@type_name = to_s
|
94
100
|
end
|
95
101
|
|
96
102
|
# convention: default for entity_set_name is the type name
|
@@ -102,9 +108,15 @@ module OData
|
|
102
108
|
# TODO: automatically reset all attributes?
|
103
109
|
@deferred_iblock = nil
|
104
110
|
@entity_set_name = nil
|
111
|
+
@uri = nil
|
105
112
|
@uparms = nil
|
106
113
|
@params = nil
|
107
114
|
@cx = nil
|
115
|
+
@@time_cols = nil
|
116
|
+
end
|
117
|
+
|
118
|
+
def build_uri(uribase)
|
119
|
+
@uri = "#{uribase}/#{entity_set_name}"
|
108
120
|
end
|
109
121
|
|
110
122
|
def execute_deferred_iblock
|
@@ -159,15 +171,7 @@ module OData
|
|
159
171
|
end
|
160
172
|
|
161
173
|
def odata_get_apply_params
|
162
|
-
|
163
|
-
@cx = @uparms.apply_to_dataset(@cx)
|
164
|
-
rescue OData::Filter::Parser::ErrorWrongColumnName
|
165
|
-
@error = BadRequestFilterParseError
|
166
|
-
return
|
167
|
-
rescue OData::Filter::Parser::ErrorFunctionArgumentType
|
168
|
-
@error = BadRequestFilterParseError
|
169
|
-
return
|
170
|
-
end
|
174
|
+
@cx = @uparms.apply_to_dataset(@cx)
|
171
175
|
odata_get_inlinecount_w_sequel
|
172
176
|
|
173
177
|
@cx = @cx.offset(@params['$skip']) if @params['$skip']
|
@@ -217,17 +221,19 @@ module OData
|
|
217
221
|
@attribute_path_list = attribute_path_list
|
218
222
|
end
|
219
223
|
|
220
|
-
|
224
|
+
MAX_DEPTH = 6
|
225
|
+
def attribute_path_list(depth = 0)
|
226
|
+
ret = @columns.map(&:to_s)
|
221
227
|
# break circles
|
222
|
-
return
|
228
|
+
return ret if depth > MAX_DEPTH
|
229
|
+
|
230
|
+
depth += 1
|
223
231
|
|
224
|
-
ret = @columns.map(&:to_s)
|
225
|
-
nodes.add entity_set_name
|
226
232
|
@nav_entity_attribs&.each do |a, k|
|
227
|
-
ret.concat(k.attribute_path_list(
|
233
|
+
ret.concat(k.attribute_path_list(depth).map { |kc| "#{a}/#{kc}" })
|
228
234
|
end
|
229
235
|
@nav_collection_attribs&.each do |a, k|
|
230
|
-
ret.concat(k.attribute_path_list(
|
236
|
+
ret.concat(k.attribute_path_list(depth).map { |kc| "#{a}/#{kc}" })
|
231
237
|
end
|
232
238
|
ret
|
233
239
|
end
|
@@ -267,15 +273,31 @@ module OData
|
|
267
273
|
end
|
268
274
|
end
|
269
275
|
|
276
|
+
# validation/error handling methods.
|
277
|
+
# normal processing is done in the passed proc
|
278
|
+
|
279
|
+
def with_validated_get(req)
|
280
|
+
begin
|
281
|
+
initialize_dataset
|
282
|
+
return yield unless (@error = check_url_params)
|
283
|
+
rescue OData::Filter::Parser::ErrorWrongColumnName
|
284
|
+
@error = BadRequestFilterParseError
|
285
|
+
rescue OData::Filter::Parser::ErrorFunctionArgumentType
|
286
|
+
@error = BadRequestFilterParseError
|
287
|
+
rescue OData::Filter::FunctionNotImplemented => e
|
288
|
+
@error = e.odata_error
|
289
|
+
rescue OData::Filter::Parser::ErrorInvalidFunction => e
|
290
|
+
@error = e.odata_error
|
291
|
+
end
|
292
|
+
|
293
|
+
@error.odata_get(req) if @error
|
294
|
+
end
|
295
|
+
|
270
296
|
# on model class level we return the collection
|
271
297
|
def odata_get(req)
|
272
298
|
@params = req.params
|
273
|
-
@uribase = req.uribase
|
274
|
-
initialize_dataset
|
275
299
|
|
276
|
-
|
277
|
-
@error.odata_get(req)
|
278
|
-
else
|
300
|
+
with_validated_get(req) do
|
279
301
|
odata_get_apply_params
|
280
302
|
odata_get_output(req)
|
281
303
|
end
|
@@ -340,8 +362,7 @@ module OData
|
|
340
362
|
DJ_OPEN = '{"d":'.freeze
|
341
363
|
DJ_CLOSE = '}'.freeze
|
342
364
|
def to_odata_links_json(service:)
|
343
|
-
innerj = service.get_coll_odata_links_h(array:
|
344
|
-
uribase: @uribase,
|
365
|
+
innerj = service.get_coll_odata_links_h(array: @cx.all,
|
345
366
|
icount: @inlinecount).to_json
|
346
367
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
347
368
|
end
|
@@ -417,17 +438,12 @@ module OData
|
|
417
438
|
|
418
439
|
def to_odata_json(service:)
|
419
440
|
template = output_template(@uparms)
|
420
|
-
innerj = service.get_coll_odata_h(array:
|
441
|
+
innerj = service.get_coll_odata_h(array: @cx.all,
|
421
442
|
template: template,
|
422
|
-
uribase: @uribase,
|
423
443
|
icount: @inlinecount).to_json
|
424
444
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
425
445
|
end
|
426
446
|
|
427
|
-
def get_a
|
428
|
-
@cx.all
|
429
|
-
end
|
430
|
-
|
431
447
|
# this functionally similar to the Sequel Rels (many_to_one etc)
|
432
448
|
# We need to base this on the Sequel rels, or extend them
|
433
449
|
def add_nav_prop_collection(assoc_symb, attr_name_str = nil)
|
@@ -476,14 +492,22 @@ module OData
|
|
476
492
|
# alias_method :add_nav_prop_single, :addNavEntityAttrib
|
477
493
|
|
478
494
|
def finalize_publishing
|
495
|
+
build_type_name
|
496
|
+
|
479
497
|
# finalize media handler
|
480
498
|
@media_handler.register(self) if @media_handler
|
481
499
|
|
482
500
|
# build default output template structure
|
483
501
|
@default_template = build_default_template
|
484
502
|
|
485
|
-
#
|
503
|
+
# Time columns
|
504
|
+
@time_cols = db_schema.select { |c, v| v[:type] == :datetime }.map { |c, v| c }
|
505
|
+
|
506
|
+
# and finally build the path list and allowed tr's
|
486
507
|
build_attribute_path_list
|
508
|
+
|
509
|
+
build_allowed_transitions
|
510
|
+
build_entity_allowed_transitions
|
487
511
|
end
|
488
512
|
|
489
513
|
def prepare_pk
|
@@ -574,10 +598,38 @@ module OData
|
|
574
598
|
end
|
575
599
|
|
576
600
|
def allowed_transitions
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
601
|
+
@allowed_transitions
|
602
|
+
end
|
603
|
+
|
604
|
+
def entity_allowed_transitions
|
605
|
+
@entity_allowed_transitions
|
606
|
+
end
|
607
|
+
|
608
|
+
def build_allowed_transitions
|
609
|
+
@allowed_transitions = [Safrano::TransitionEnd,
|
610
|
+
Safrano::TransitionCount,
|
611
|
+
Safrano::Transition.new(entity_id_url_regexp,
|
612
|
+
trans: 'transition_id')].freeze
|
613
|
+
end
|
614
|
+
|
615
|
+
def build_entity_allowed_transitions
|
616
|
+
@entity_allowed_transitions = [
|
617
|
+
Safrano::TransitionEnd,
|
618
|
+
Safrano::TransitionCount,
|
619
|
+
Safrano::TransitionLinks,
|
620
|
+
Safrano::TransitionValue,
|
621
|
+
Safrano::Transition.new(transition_attribute_regexp, trans: 'transition_attribute')
|
622
|
+
]
|
623
|
+
if (ncurgx = @nav_collection_url_regexp)
|
624
|
+
@entity_allowed_transitions <<
|
625
|
+
Safrano::Transition.new(%r{\A/(#{ncurgx})(.*)\z}, trans: 'transition_nav_collection')
|
626
|
+
end
|
627
|
+
if (neurgx = @nav_entity_url_regexp)
|
628
|
+
@entity_allowed_transitions <<
|
629
|
+
Safrano::Transition.new(%r{\A/(#{neurgx})(.*)\z}, trans: 'transition_nav_entity')
|
630
|
+
end
|
631
|
+
@entity_allowed_transitions.freeze
|
632
|
+
@entity_allowed_transitions
|
581
633
|
end
|
582
634
|
end
|
583
635
|
include Transitions
|
data/lib/odata/entity.rb
CHANGED
@@ -8,33 +8,13 @@ module OData
|
|
8
8
|
# this will be mixed in the Model classes (subclasses of Sequel Model)
|
9
9
|
module EntityBase
|
10
10
|
attr_reader :params
|
11
|
-
attr_reader :uribase
|
12
11
|
|
13
12
|
include EntityBase::NavigationInfo
|
14
13
|
|
15
14
|
# methods related to transitions to next state (cf. walker)
|
16
15
|
module Transitions
|
17
16
|
def allowed_transitions
|
18
|
-
|
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
|
17
|
+
self.class.entity_allowed_transitions
|
38
18
|
end
|
39
19
|
|
40
20
|
def transition_end(_match_result)
|
@@ -90,9 +70,11 @@ module OData
|
|
90
70
|
@nav_coll
|
91
71
|
end
|
92
72
|
|
93
|
-
def uri
|
94
|
-
"
|
73
|
+
def uri
|
74
|
+
@odata_pk ||= "(#{pk_uri})"
|
75
|
+
"#{self.class.uri}#{@odata_pk}"
|
95
76
|
end
|
77
|
+
|
96
78
|
D = 'd'.freeze
|
97
79
|
DJ_OPEN = '{"d":'.freeze
|
98
80
|
DJ_CLOSE = '}'.freeze
|
@@ -101,15 +83,13 @@ module OData
|
|
101
83
|
def to_odata_json(service:)
|
102
84
|
template = self.class.output_template(@uparms)
|
103
85
|
innerj = service.get_entity_odata_h(entity: self,
|
104
|
-
template: template
|
105
|
-
uribase: @uribase).to_json
|
86
|
+
template: template).to_json
|
106
87
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
107
88
|
end
|
108
89
|
|
109
90
|
# Json formatter for a single entity reached by navigation $links
|
110
91
|
def to_odata_onelink_json(service:)
|
111
|
-
innerj = service.get_entity_odata_link_h(entity: self
|
112
|
-
uribase: @uribase).to_json
|
92
|
+
innerj = service.get_entity_odata_link_h(entity: self).to_json
|
113
93
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
114
94
|
end
|
115
95
|
|
@@ -120,36 +100,10 @@ module OData
|
|
120
100
|
selvals
|
121
101
|
end
|
122
102
|
|
123
|
-
# needed for proper datetime output
|
124
|
-
# TODO: design/performance
|
125
|
-
def casted_values(cols = nil)
|
126
|
-
vals = case cols
|
127
|
-
when nil
|
128
|
-
values_for_odata
|
129
|
-
else
|
130
|
-
selected_values_for_odata(cols)
|
131
|
-
end
|
132
|
-
|
133
|
-
# WARNING; this code is duplicated in attribute.rb
|
134
|
-
# (and the inverted transformation is in test/client.rb)
|
135
|
-
# will require a more systematic solution some day
|
136
|
-
|
137
|
-
vals.transform_values! do |v|
|
138
|
-
case v
|
139
|
-
when Time
|
140
|
-
# try to get back the database time zone and value
|
141
|
-
(v + v.gmt_offset).utc.to_datetime
|
142
|
-
else
|
143
|
-
v
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
103
|
# post paylod expects the new entity in an array
|
149
104
|
def to_odata_post_json(service:)
|
150
105
|
innerj = service.get_coll_odata_h(array: [self],
|
151
|
-
template: self.class.default_template
|
152
|
-
uribase: @uribase).to_json
|
106
|
+
template: self.class.default_template).to_json
|
153
107
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
154
108
|
end
|
155
109
|
|
@@ -159,7 +113,6 @@ module OData
|
|
159
113
|
|
160
114
|
def copy_request_infos(req)
|
161
115
|
@params = req.params
|
162
|
-
@uribase = req.uribase
|
163
116
|
@do_links = req.walker.do_links
|
164
117
|
@uparms = UrlParameters4Single.new(@params)
|
165
118
|
end
|
@@ -227,7 +180,6 @@ module OData
|
|
227
180
|
odata_media_value_put(req)
|
228
181
|
elsif req.accept?(APPJSON)
|
229
182
|
data = JSON.parse(req.body.read)
|
230
|
-
@uribase = req.uribase
|
231
183
|
data.delete('__metadata')
|
232
184
|
|
233
185
|
if req.in_changeset
|
@@ -255,8 +207,6 @@ module OData
|
|
255
207
|
# TODO: check values/types
|
256
208
|
|
257
209
|
my_data_fields = self.class.data_fields
|
258
|
-
@uribase = req.uribase
|
259
|
-
# if req.accept?('application/json')
|
260
210
|
|
261
211
|
if req.in_changeset
|
262
212
|
set_fields(data, my_data_fields, missing: :skip)
|
@@ -302,14 +252,30 @@ module OData
|
|
302
252
|
superclass.type_name
|
303
253
|
end
|
304
254
|
|
255
|
+
def time_cols
|
256
|
+
superclass.time_cols
|
257
|
+
end
|
258
|
+
|
305
259
|
def media_handler
|
306
260
|
superclass.media_handler
|
307
261
|
end
|
308
262
|
|
263
|
+
def uri
|
264
|
+
superclass.uri
|
265
|
+
end
|
266
|
+
|
309
267
|
def default_template
|
310
268
|
superclass.default_template
|
311
269
|
end
|
312
270
|
|
271
|
+
def allowed_transitions
|
272
|
+
superclass.allowed_transitions
|
273
|
+
end
|
274
|
+
|
275
|
+
def entity_allowed_transitions
|
276
|
+
superclass.entity_allowed_transitions
|
277
|
+
end
|
278
|
+
|
313
279
|
def to_a
|
314
280
|
y = @child_method.call
|
315
281
|
y.to_a
|
@@ -364,13 +330,13 @@ module OData
|
|
364
330
|
|
365
331
|
module NonMediaEntity
|
366
332
|
# non media entity metadata for json h
|
367
|
-
def metadata_h
|
368
|
-
{ uri: uri
|
333
|
+
def metadata_h
|
334
|
+
{ uri: uri,
|
369
335
|
type: type_name }
|
370
336
|
end
|
371
337
|
|
372
338
|
def values_for_odata
|
373
|
-
values
|
339
|
+
values
|
374
340
|
end
|
375
341
|
|
376
342
|
def odata_delete(req)
|
@@ -398,23 +364,53 @@ module OData
|
|
398
364
|
end
|
399
365
|
end
|
400
366
|
|
367
|
+
module MappingBeforeOutput
|
368
|
+
# needed for proper datetime output
|
369
|
+
def casted_values(cols = nil)
|
370
|
+
vals = case cols
|
371
|
+
when nil
|
372
|
+
# we need to dup the model values as we need to change it before passing to_json,
|
373
|
+
# but we dont want to interfere with Sequel's owned data
|
374
|
+
# (eg because then in worst case it could happen that we write back changed values to DB)
|
375
|
+
values_for_odata.dup
|
376
|
+
else
|
377
|
+
selected_values_for_odata(cols)
|
378
|
+
end
|
379
|
+
self.class.time_cols.each { |tc| vals[tc] = vals[tc]&.iso8601 if vals.key?(tc) }
|
380
|
+
vals
|
381
|
+
end
|
382
|
+
end
|
383
|
+
module NoMappingBeforeOutput
|
384
|
+
# current model does not have eg. Time fields--> no special mapping, just to_json is fine
|
385
|
+
# --> we can use directly the model.values (values_for_odata) withoud dup'ing it as we dont
|
386
|
+
# need to change it, just output as is
|
387
|
+
def casted_values(cols = nil)
|
388
|
+
case cols
|
389
|
+
when nil
|
390
|
+
values_for_odata
|
391
|
+
else
|
392
|
+
selected_values_for_odata(cols)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
401
397
|
module MediaEntity
|
402
398
|
# media entity metadata for json h
|
403
|
-
def metadata_h
|
404
|
-
{ uri: uri
|
399
|
+
def metadata_h
|
400
|
+
{ uri: uri,
|
405
401
|
type: type_name,
|
406
|
-
media_src: media_src
|
407
|
-
edit_media: edit_media
|
402
|
+
media_src: media_src,
|
403
|
+
edit_media: edit_media,
|
408
404
|
content_type: @values[:content_type] }
|
409
405
|
end
|
410
406
|
|
411
|
-
def media_src
|
407
|
+
def media_src
|
412
408
|
version = self.class.media_handler.ressource_version(self)
|
413
|
-
"#{uri
|
409
|
+
"#{uri}/$value?version=#{version}"
|
414
410
|
end
|
415
411
|
|
416
|
-
def edit_media
|
417
|
-
"#{uri
|
412
|
+
def edit_media
|
413
|
+
"#{uri}/$value"
|
418
414
|
end
|
419
415
|
|
420
416
|
# directory where to put/find the media files for this entity-type
|