safrano 0.0.6 → 0.0.8
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/collection.rb +42 -7
- data/lib/odata/collection_filter.rb +153 -144
- data/lib/odata/entity.rb +7 -0
- data/lib/odata/error.rb +5 -0
- data/lib/odata/walker.rb +9 -3
- data/lib/safrano_core.rb +2 -0
- data/lib/service.rb +24 -8
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd0dda1e8d7fc80e01fc15b50b0893a31b189bb95e0138c8c1e49f4d3cdb11d5
|
4
|
+
data.tar.gz: 4155a3d6ca1dcd2817aab51bc85208ada311b8a120d4ed90e5a8fcd6c05d23b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1b61fcb3776b7435821121eb41b21afcf5fba422cb7665182ca7dc4d5e11155c3ba92a41eee7c9dc20730728c838033aa4b30367fd0e518d108547b7fbb7bf5
|
7
|
+
data.tar.gz: 270e6f1942254b239aed16b0bdb4ba8c5f48eba6fa676f9e9da9758e984c1cdd37a72cd355ed30326881013342538bf59b4de7b9143aabd2104d4c3fdf7faa0d
|
data/lib/odata/collection.rb
CHANGED
@@ -50,6 +50,7 @@ module OData
|
|
50
50
|
attr_reader :nav_collection_attribs
|
51
51
|
attr_reader :nav_entity_attribs
|
52
52
|
attr_reader :data_fields
|
53
|
+
attr_reader :inlinecount
|
53
54
|
|
54
55
|
attr_accessor :namespace
|
55
56
|
|
@@ -131,12 +132,25 @@ module OData
|
|
131
132
|
@cx = @cx.select_all(entity_set_name.to_sym)
|
132
133
|
end
|
133
134
|
|
135
|
+
def odata_get_inlinecount_w_sequel
|
136
|
+
return unless (icp = @params['$inlinecount'])
|
137
|
+
|
138
|
+
@inlinecount = if icp == 'allpages'
|
139
|
+
if is_a? Sequel::Model::ClassMethods
|
140
|
+
@cx.count
|
141
|
+
else
|
142
|
+
@cx.dataset.count
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
134
147
|
def odata_get_apply_params_w_sequel
|
135
148
|
@left_assocs = Set.new
|
136
149
|
@right_assocs = Set.new
|
137
150
|
odata_get_apply_filter_w_sequel
|
138
151
|
odata_get_apply_order_w_sequel
|
139
152
|
odata_get_do_assoc_joins_w_sequel
|
153
|
+
odata_get_inlinecount_w_sequel
|
140
154
|
|
141
155
|
@cx = @cx.offset(@params['$skip']) if @params['$skip']
|
142
156
|
@cx = @cx.limit(@params['$top']) if @params['$top']
|
@@ -169,6 +183,16 @@ module OData
|
|
169
183
|
return BadRequestError if iskip.nil? || (iskip < 0)
|
170
184
|
end
|
171
185
|
|
186
|
+
def check_u_p_inlinecount
|
187
|
+
return unless (icp = @params['$inlinecount'])
|
188
|
+
|
189
|
+
unless (icp == 'allpages') || (icp == 'none')
|
190
|
+
return BadRequestInlineCountParamError
|
191
|
+
end
|
192
|
+
|
193
|
+
nil
|
194
|
+
end
|
195
|
+
|
172
196
|
def check_u_p_filter
|
173
197
|
return unless @params['$filter']
|
174
198
|
|
@@ -182,7 +206,8 @@ module OData
|
|
182
206
|
end
|
183
207
|
|
184
208
|
def check_u_p_orderby
|
185
|
-
# TODO: this should be moved into OData::Order somehow,
|
209
|
+
# TODO: this should be moved into OData::Order somehow,
|
210
|
+
# at least partly
|
186
211
|
return unless (pordlist = @params['$orderby'].dup)
|
187
212
|
|
188
213
|
pordlist.split(',').each do |pord|
|
@@ -224,7 +249,7 @@ module OData
|
|
224
249
|
def check_url_params
|
225
250
|
return nil unless @params
|
226
251
|
|
227
|
-
check_u_p_top || check_u_p_skip || check_u_p_orderby || check_u_p_filter
|
252
|
+
check_u_p_top || check_u_p_skip || check_u_p_orderby || check_u_p_filter || check_u_p_inlinecount
|
228
253
|
end
|
229
254
|
|
230
255
|
def initialize_dataset
|
@@ -248,19 +273,29 @@ module OData
|
|
248
273
|
[200, { 'Content-Type' => 'text/plain;charset=utf-8' },
|
249
274
|
@cx.count.to_s]
|
250
275
|
elsif req.accept?('application/json')
|
251
|
-
|
252
|
-
|
276
|
+
if req.walker.do_links
|
277
|
+
[200, { 'Content-Type' => 'application/json;charset=utf-8' },
|
278
|
+
to_odata_links_json(service: req.service)]
|
279
|
+
else
|
280
|
+
[200, { 'Content-Type' => 'application/json;charset=utf-8' },
|
281
|
+
to_odata_json(service: req.service)]
|
282
|
+
end
|
253
283
|
else # TODO: other formats
|
254
284
|
406
|
255
285
|
end
|
256
286
|
end
|
257
287
|
end
|
258
288
|
|
289
|
+
def to_odata_links_json(service:)
|
290
|
+
{ 'd' => service.get_coll_odata_links_h(array: get_a,
|
291
|
+
uribase: @uribase) }.to_json
|
292
|
+
end
|
293
|
+
|
259
294
|
def to_odata_json(service:)
|
260
|
-
expand = @params['$expand']
|
261
295
|
{ 'd' => service.get_coll_odata_h(array: get_a,
|
262
|
-
expand: expand,
|
263
|
-
uribase: @uribase
|
296
|
+
expand: @params['$expand'],
|
297
|
+
uribase: @uribase,
|
298
|
+
icount: @inlinecount) }.to_json
|
264
299
|
end
|
265
300
|
|
266
301
|
def get_a
|
@@ -40,32 +40,6 @@ class String
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
# Handles filtering with ruby expressions
|
44
|
-
# (eval and DB full scan used --> better avoid using this)
|
45
|
-
module FilterWithRuby
|
46
|
-
# this module requires the @fn attribute to exist where it is used
|
47
|
-
def fn=(farg)
|
48
|
-
@fn = farg
|
49
|
-
@fn_tab = farg.split('/').map(&:to_sym)
|
50
|
-
end
|
51
|
-
|
52
|
-
# returns the attribute named "@fn" of object inp. This version assumes that
|
53
|
-
# @fn can be a path like "address/city"
|
54
|
-
def get_value(inp, colescev = '')
|
55
|
-
obj = inp
|
56
|
-
@fn_tab.each do |csymb|
|
57
|
-
tmp = obj.send(csymb)
|
58
|
-
if tmp.nil?
|
59
|
-
obj = colescev
|
60
|
-
break
|
61
|
-
else
|
62
|
-
obj = tmp
|
63
|
-
end
|
64
|
-
end
|
65
|
-
obj
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
43
|
# filter base class and subclass in our OData namespace
|
70
44
|
module OData
|
71
45
|
# handles associations in filter arguments
|
@@ -120,11 +94,6 @@ module OData
|
|
120
94
|
new(matx)
|
121
95
|
end
|
122
96
|
|
123
|
-
# handle complex deep (relations) expressions with Ruby
|
124
|
-
def self.new_full_match_complexpr_by_ruby(filterstr)
|
125
|
-
ComplexFilterByRuby.new(filterstr)
|
126
|
-
end
|
127
|
-
|
128
97
|
def apply_to_dataset(dtcx)
|
129
98
|
dtcx
|
130
99
|
end
|
@@ -137,7 +106,6 @@ module OData
|
|
137
106
|
|
138
107
|
# well...
|
139
108
|
class EqualFilter < Filter
|
140
|
-
include FilterWithRuby
|
141
109
|
include FilterWithAssoc
|
142
110
|
EQL = '[eE][qQ]|[LlgGNn][eETt]'.freeze
|
143
111
|
|
@@ -147,7 +115,7 @@ module OData
|
|
147
115
|
|
148
116
|
def initialize(matx)
|
149
117
|
super(matx)
|
150
|
-
|
118
|
+
@fn = matx[1]
|
151
119
|
get_assoc if @fn
|
152
120
|
|
153
121
|
@val = matx[3] ? matx[3].gsub("''", "'") : matx[4]
|
@@ -172,29 +140,34 @@ module OData
|
|
172
140
|
y
|
173
141
|
end
|
174
142
|
|
175
|
-
def
|
176
|
-
seqfn = get_qualified_fn(dtcx)
|
177
|
-
# using @val in Procs below does not work reliably. Using a local var
|
178
|
-
# seems to work better
|
179
|
-
argval = @val
|
143
|
+
def apply_op_to_dataset(dtcx, lefval, rightval)
|
180
144
|
case @op
|
181
145
|
when 'eq'
|
182
|
-
dtcx.where(
|
146
|
+
dtcx.where(lefval => rightval)
|
183
147
|
when 'ne'
|
184
|
-
dtcx.exclude(
|
148
|
+
dtcx.exclude(lefval => rightval)
|
185
149
|
when 'le'
|
186
|
-
dtcx.where {
|
150
|
+
dtcx.where { lefval <= rightval }
|
187
151
|
when 'ge'
|
188
|
-
dtcx.where {
|
152
|
+
dtcx.where { lefval >= rightval }
|
189
153
|
when 'lt'
|
190
|
-
dtcx.where {
|
154
|
+
dtcx.where { lefval < rightval }
|
191
155
|
when 'gt'
|
192
|
-
dtcx.where {
|
156
|
+
dtcx.where { lefval > rightval }
|
193
157
|
else
|
194
158
|
raise OData::FilterParseError
|
195
159
|
end
|
196
160
|
end
|
197
161
|
|
162
|
+
def apply_to_dataset(dtcx)
|
163
|
+
seqfn = get_qualified_fn(dtcx)
|
164
|
+
# using @val in Procs below does not work reliably. Using a local var
|
165
|
+
# seems to work better
|
166
|
+
argval = @val
|
167
|
+
|
168
|
+
apply_op_to_dataset(dtcx, seqfn, argval)
|
169
|
+
end
|
170
|
+
|
198
171
|
def apply_to_object(inp)
|
199
172
|
case @op
|
200
173
|
when 'eq'
|
@@ -207,7 +180,132 @@ module OData
|
|
207
180
|
end
|
208
181
|
end
|
209
182
|
|
210
|
-
|
183
|
+
class Func2a_EqualFilter < EqualFilter
|
184
|
+
FUNC = 'concat'.freeze
|
185
|
+
attr_reader :funcname
|
186
|
+
|
187
|
+
# like for concat(Name, 'xyz') EQ 'blablux'
|
188
|
+
def self.qualified_regexp(pathsrx)
|
189
|
+
/\s*(#{FUNC})\((#{pathsrx}),\s*(?:#{QUO})\)\s+(#{EQL})\s+(?:#{QUO})\s*/
|
190
|
+
end
|
191
|
+
|
192
|
+
def initialize(matx)
|
193
|
+
# super(matx)
|
194
|
+
@funcname = matx[1]
|
195
|
+
@fn = matx[2]
|
196
|
+
get_assoc if @fn
|
197
|
+
@farg = matx[3].gsub("''", "'")
|
198
|
+
@op = matx[4].downcase
|
199
|
+
|
200
|
+
@val = matx[5].gsub("''", "'")
|
201
|
+
end
|
202
|
+
|
203
|
+
def apply_to_dataset(dtcx)
|
204
|
+
seqfn = get_qualified_fn(dtcx)
|
205
|
+
# using @val in Procs below does not work reliably. Using a local var
|
206
|
+
# seems to work better
|
207
|
+
argval = @val
|
208
|
+
seqfunc = case @funcname
|
209
|
+
when 'concat'
|
210
|
+
Sequel.join([seqfn, @farg])
|
211
|
+
else
|
212
|
+
raise OData::FilterParseError
|
213
|
+
end
|
214
|
+
apply_op_to_dataset(dtcx, seqfunc, argval)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
class Func2b_EqualFilter < EqualFilter
|
218
|
+
FUNC = 'concat'.freeze
|
219
|
+
attr_reader :funcname
|
220
|
+
|
221
|
+
# like for concat('xyz', Name) EQ 'blablux'
|
222
|
+
def self.qualified_regexp(pathsrx)
|
223
|
+
/\s*(#{FUNC})\((?:#{QUO}),\s*(#{pathsrx})\)\s+(#{EQL})\s+(?:#{QUO})\s*/
|
224
|
+
end
|
225
|
+
|
226
|
+
def initialize(matx)
|
227
|
+
# super(matx)
|
228
|
+
@funcname = matx[1]
|
229
|
+
@fn = matx[3]
|
230
|
+
get_assoc if @fn
|
231
|
+
@farg = matx[2].gsub("''", "'")
|
232
|
+
@op = matx[4].downcase
|
233
|
+
|
234
|
+
@val = matx[5].gsub("''", "'")
|
235
|
+
end
|
236
|
+
|
237
|
+
def apply_to_dataset(dtcx)
|
238
|
+
seqfn = get_qualified_fn(dtcx)
|
239
|
+
# using @val in Procs below does not work reliably. Using a local var
|
240
|
+
# seems to work better
|
241
|
+
argval = @val
|
242
|
+
seqfunc = case @funcname
|
243
|
+
when 'concat'
|
244
|
+
Sequel.join([@farg, seqfn])
|
245
|
+
else
|
246
|
+
raise OData::FilterParseError
|
247
|
+
end
|
248
|
+
apply_op_to_dataset(dtcx, seqfunc, argval)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
class Func2c_EqualFilter < EqualFilter
|
253
|
+
FUNC = 'concat'.freeze
|
254
|
+
attr_reader :funcname
|
255
|
+
|
256
|
+
# like for concat(first_ame, last_name) EQ 'blablux'
|
257
|
+
def self.qualified_regexp(pathsrx)
|
258
|
+
/\s*(#{FUNC})\((#{pathsrx}),\s*(#{pathsrx})\)\s+(#{EQL})\s+(?:#{QUO})\s*/
|
259
|
+
end
|
260
|
+
|
261
|
+
def get_assoc
|
262
|
+
@assoc, @fn = @fn.split('/') if @fn.include?('/')
|
263
|
+
assoc1, @fn1 = @fn1.split('/') if @fn1.include?('/')
|
264
|
+
if assoc1 != @assoc
|
265
|
+
# TODO... handle this
|
266
|
+
raise OData::ServerError
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def get_qualified_fn1(dtcx)
|
271
|
+
seqtn = if dtcx.respond_to? :table_name
|
272
|
+
@assoc ? @assoc.to_sym : dtcx.table_name
|
273
|
+
else
|
274
|
+
@assoc ? @assoc.to_sym : dtcx.model.table_name
|
275
|
+
end
|
276
|
+
Sequel[seqtn][@fn1.to_sym]
|
277
|
+
end
|
278
|
+
|
279
|
+
def initialize(matx)
|
280
|
+
# super(matx)
|
281
|
+
@funcname = matx[1]
|
282
|
+
@fn = matx[2]
|
283
|
+
@fn1 = matx[3]
|
284
|
+
get_assoc if @fn
|
285
|
+
|
286
|
+
@op = matx[4].downcase
|
287
|
+
|
288
|
+
@val = matx[5].gsub("''", "'")
|
289
|
+
end
|
290
|
+
|
291
|
+
def apply_to_dataset(dtcx)
|
292
|
+
seqfn = get_qualified_fn(dtcx)
|
293
|
+
seqfn1 = get_qualified_fn1(dtcx)
|
294
|
+
# using @val in Procs below does not work reliably. Using a local var
|
295
|
+
# seems to work better
|
296
|
+
argval = @val
|
297
|
+
seqfunc = case @funcname
|
298
|
+
when 'concat'
|
299
|
+
Sequel.join([seqfn, seqfn1])
|
300
|
+
else
|
301
|
+
raise OData::FilterParseError
|
302
|
+
end
|
303
|
+
apply_op_to_dataset(dtcx, seqfunc, argval)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# equality expressions with functions having 1 parameter
|
308
|
+
# like for length(name) eq 2
|
211
309
|
class FuncEqualFilter < EqualFilter
|
212
310
|
FUNC = 'length|trim|tolower|toupper'.freeze
|
213
311
|
|
@@ -220,29 +318,12 @@ module OData
|
|
220
318
|
def initialize(matx)
|
221
319
|
super(matx)
|
222
320
|
@funcname = matx[1]
|
223
|
-
|
321
|
+
@fn = matx[2]
|
224
322
|
get_assoc if @fn
|
225
323
|
|
226
|
-
@val = matx[4] ? matx[4].gsub("''", "'") : matx[5]
|
227
|
-
|
228
324
|
@op = matx[3].downcase
|
229
|
-
end
|
230
|
-
|
231
|
-
# ugly hack... but working
|
232
|
-
def gen_sql(dtcx)
|
233
|
-
# handle ambiguous column names (joins) by qualifiying the names
|
234
|
-
# seqfn = @assoc ? Sequel[@assoc.to_sym][@fn.to_sym] : @fn.to_sym
|
235
|
-
# handle ambiguous column names (joins) by qualifiying the names
|
236
|
-
# for the main table as well
|
237
|
-
# TODO: find a better way to differentiate between the two types
|
238
|
-
# of dtcx : Model or Dataset
|
239
325
|
|
240
|
-
|
241
|
-
# qualified fieldnames could be needed (uses attrib path regexp)
|
242
|
-
x = apply_to_dataset(dtcx)
|
243
|
-
# ugly ugly hack :-(
|
244
|
-
y = x.unordered.sql.sub(x.unfiltered.unordered.sql + ' WHERE', '')
|
245
|
-
y
|
326
|
+
@val = matx[4] ? matx[4].gsub("''", "'") : matx[5]
|
246
327
|
end
|
247
328
|
|
248
329
|
def apply_to_dataset(dtcx)
|
@@ -264,22 +345,7 @@ module OData
|
|
264
345
|
raise OData::FilterParseError
|
265
346
|
end
|
266
347
|
|
267
|
-
|
268
|
-
when 'eq'
|
269
|
-
dtcx.where(seqfunc => argval)
|
270
|
-
when 'ne'
|
271
|
-
dtcx.exclude(seqfunc => argval)
|
272
|
-
when 'le'
|
273
|
-
dtcx.where { seqfunc <= argval }
|
274
|
-
when 'ge'
|
275
|
-
dtcx.where { seqfunc >= argval }
|
276
|
-
when 'lt'
|
277
|
-
dtcx.where { seqfunc < argval }
|
278
|
-
when 'gt'
|
279
|
-
dtcx.where { seqfunc > argval }
|
280
|
-
else
|
281
|
-
raise OData::FilterParseError
|
282
|
-
end
|
348
|
+
apply_op_to_dataset(dtcx, seqfunc, argval)
|
283
349
|
end
|
284
350
|
end
|
285
351
|
|
@@ -290,7 +356,6 @@ module OData
|
|
290
356
|
|
291
357
|
# for substringof('value', field)
|
292
358
|
class SubstringOfFilterSig1 < FilterWithKeyword
|
293
|
-
include FilterWithRuby
|
294
359
|
include FilterWithAssoc
|
295
360
|
|
296
361
|
def self.qualified_regexp(paths_rx)
|
@@ -303,8 +368,8 @@ module OData
|
|
303
368
|
|
304
369
|
def initialize(matx)
|
305
370
|
super(matx)
|
306
|
-
|
307
|
-
|
371
|
+
|
372
|
+
@fn = matx[4]
|
308
373
|
get_assoc if @fn
|
309
374
|
# @val = matx[2].strip_single_quote
|
310
375
|
@val = matx[2] ? matx[2].gsub("''", "'") : matx[3]
|
@@ -328,19 +393,14 @@ module OData
|
|
328
393
|
def apply_to_dataset(dtcx)
|
329
394
|
dtcx.where(whcl(dtcx))
|
330
395
|
end
|
331
|
-
|
332
|
-
def apply_to_object(inp)
|
333
|
-
get_value(inp).include?(@val)
|
334
|
-
end
|
335
396
|
end
|
336
397
|
# for substringof(field,'value')
|
337
398
|
class SubstringOfFilterSig2 < FilterWithKeyword
|
338
|
-
include FilterWithRuby
|
339
399
|
include FilterWithAssoc
|
340
400
|
|
341
401
|
def initialize(matx)
|
342
402
|
super(matx)
|
343
|
-
|
403
|
+
@fn = matx[2]
|
344
404
|
get_assoc if @fn
|
345
405
|
@val = matx[3]
|
346
406
|
@keyword = matx[1]
|
@@ -351,15 +411,10 @@ module OData
|
|
351
411
|
def apply_to_dataset(dtcx)
|
352
412
|
dtcx
|
353
413
|
end
|
354
|
-
|
355
|
-
def apply_to_object(inp)
|
356
|
-
get_value(inp).include?(@val)
|
357
|
-
end
|
358
414
|
end
|
359
415
|
|
360
416
|
# Filter by start/end with
|
361
417
|
class StartOrEndsWithFilter < FilterWithKeyword
|
362
|
-
include FilterWithRuby
|
363
418
|
include FilterWithAssoc
|
364
419
|
# DONE: better handle quotes vs not-quotes. Unblanced quotes are now handled
|
365
420
|
# example : startswith(year, 190')
|
@@ -372,21 +427,11 @@ module OData
|
|
372
427
|
super(matx)
|
373
428
|
# DONE: the regexp should only match on known field names...
|
374
429
|
# --> use qualified_regexp(attr_paths_rgx)
|
375
|
-
|
430
|
+
@fn = matx[2]
|
376
431
|
get_assoc if @fn
|
377
432
|
# double quotes need to be unescaped
|
378
433
|
@val = matx[3] ? matx[3].gsub("''", "'") : matx[4]
|
379
434
|
@keyword = matx[1]
|
380
|
-
ve = Regexp.escape(@val)
|
381
|
-
# used for "filter with ruby"
|
382
|
-
case @keyword
|
383
|
-
when 'startswith'
|
384
|
-
@startend_rgx = Regexp.new("\A#{ve}")
|
385
|
-
when 'endswith'
|
386
|
-
@startend_rgx = Regexp.new("#{ve}\z")
|
387
|
-
else
|
388
|
-
raise FilterParseError
|
389
|
-
end
|
390
435
|
end
|
391
436
|
|
392
437
|
def whcl(dtcx)
|
@@ -412,10 +457,6 @@ module OData
|
|
412
457
|
def apply_to_dataset(dtcx)
|
413
458
|
dtcx.where(whcl(dtcx))
|
414
459
|
end
|
415
|
-
|
416
|
-
def apply_to_object(inp)
|
417
|
-
get_value(inp) =~ @startend_rgx
|
418
|
-
end
|
419
460
|
end
|
420
461
|
|
421
462
|
FILTER_CLASSES = [EqualFilter,
|
@@ -435,6 +476,9 @@ module OData
|
|
435
476
|
# list of active Subfilter classes. The ordering is important
|
436
477
|
SUBFILTERS = [EqualFilter,
|
437
478
|
FuncEqualFilter,
|
479
|
+
Func2a_EqualFilter,
|
480
|
+
Func2b_EqualFilter,
|
481
|
+
Func2c_EqualFilter,
|
438
482
|
SubstringOfFilterSig1,
|
439
483
|
StartOrEndsWithFilter].freeze
|
440
484
|
# for counting number of AND OR's
|
@@ -487,39 +531,4 @@ module OData
|
|
487
531
|
dtcx.where(Sequel.lit(@osql))
|
488
532
|
end
|
489
533
|
end
|
490
|
-
|
491
|
-
# Handle complex filter expression with Ruby
|
492
|
-
class ComplexFilterByRuby < Filter
|
493
|
-
def initialize(filterstr)
|
494
|
-
@subf = []
|
495
|
-
@f = nil
|
496
|
-
@rbcode = filterstr.dup
|
497
|
-
@rbcode.with_mask_quoted_substrings! do |_s|
|
498
|
-
SUBFILTERS.each { |klass| init_subfilter(klass) }
|
499
|
-
downcase_and_or
|
500
|
-
end
|
501
|
-
end
|
502
|
-
|
503
|
-
# downcase AND OR in @rbcode otherwise it is seen as a Ruby constant
|
504
|
-
# instead as a ruby langu keyword
|
505
|
-
def downcase_and_or
|
506
|
-
@rbcode.gsub!(/(OR|Or|AND|And)/) do |_mx|
|
507
|
-
Regexp.last_match[1].downcase
|
508
|
-
end
|
509
|
-
end
|
510
|
-
|
511
|
-
def init_subfilter(subfiltclass)
|
512
|
-
@rbcode.gsub!(subfiltclass.regexp) do |_mx|
|
513
|
-
@f = subfiltclass.new(Regexp.last_match)
|
514
|
-
@subf << @f
|
515
|
-
"@subf[#{@subf.size - 1}].apply_to_object(@o)"
|
516
|
-
end
|
517
|
-
end
|
518
|
-
|
519
|
-
def apply_to_object(inp)
|
520
|
-
@o = inp
|
521
|
-
b = binding
|
522
|
-
eval(@rbcode, b)
|
523
|
-
end
|
524
|
-
end
|
525
534
|
end
|
data/lib/odata/entity.rb
CHANGED
@@ -17,6 +17,7 @@ module OData
|
|
17
17
|
alltr = [
|
18
18
|
Safrano::TransitionEnd,
|
19
19
|
Safrano::TransitionCount,
|
20
|
+
Safrano::TransitionLinks,
|
20
21
|
Safrano::Transition.new(%r{\A/(#{aurgx})(.*)\z},
|
21
22
|
trans: 'transition_attribute')
|
22
23
|
]
|
@@ -42,6 +43,10 @@ module OData
|
|
42
43
|
[self, :end]
|
43
44
|
end
|
44
45
|
|
46
|
+
def transition_links(_match_result)
|
47
|
+
[self, :run_with_links]
|
48
|
+
end
|
49
|
+
|
45
50
|
def transition_attribute(match_result)
|
46
51
|
attrib = match_result[1]
|
47
52
|
# [values[attrib.to_sym], :run]
|
@@ -97,6 +102,7 @@ module OData
|
|
97
102
|
def to_odata_json(service:)
|
98
103
|
{ 'd' => service.get_entity_odata_h(entity: self,
|
99
104
|
expand: @params['$expand'],
|
105
|
+
# links: @do_links,
|
100
106
|
uribase: @uribase) }.to_json
|
101
107
|
end
|
102
108
|
|
@@ -120,6 +126,7 @@ module OData
|
|
120
126
|
def odata_get(req)
|
121
127
|
@params = req.params
|
122
128
|
@uribase = req.uribase
|
129
|
+
@do_links = req.walker.do_links
|
123
130
|
if req.accept?('application/json')
|
124
131
|
[200, { 'Content-Type' => 'application/json;charset=utf-8' },
|
125
132
|
to_odata_json(service: req.service)]
|
data/lib/odata/error.rb
CHANGED
@@ -49,6 +49,11 @@ module OData
|
|
49
49
|
HTTP_CODE = 400
|
50
50
|
@msg = 'Bad Request: Syntax error in Filter'
|
51
51
|
end
|
52
|
+
# for $inlinecount error
|
53
|
+
class BadRequestInlineCountParamError < BadRequestError
|
54
|
+
HTTP_CODE = 400
|
55
|
+
@msg = 'Bad Request: wrong $inlinecount parameter'
|
56
|
+
end
|
52
57
|
# http not found
|
53
58
|
class ErrorNotFound
|
54
59
|
extend Error
|
data/lib/odata/walker.rb
CHANGED
@@ -25,6 +25,9 @@ module OData
|
|
25
25
|
# is $value requested?
|
26
26
|
attr_reader :raw_value
|
27
27
|
|
28
|
+
# are $links requested ?
|
29
|
+
attr_reader :do_links
|
30
|
+
|
28
31
|
def initialize(service, path)
|
29
32
|
path = URI.decode_www_form_component(path)
|
30
33
|
@context = service
|
@@ -82,13 +85,16 @@ module OData
|
|
82
85
|
@path_remain = @tr_next.path_remain
|
83
86
|
@path_done << @tr_next.path_done
|
84
87
|
# little hack's
|
85
|
-
|
88
|
+
case @status
|
89
|
+
when :end_with_count
|
86
90
|
@do_count = true
|
87
91
|
@status == :end
|
88
|
-
|
89
|
-
if @status == :end_with_value
|
92
|
+
when :end_with_value
|
90
93
|
@raw_value = true
|
91
94
|
@status == :end
|
95
|
+
when :run_with_links
|
96
|
+
@do_links = true
|
97
|
+
@status = :run
|
92
98
|
end
|
93
99
|
else
|
94
100
|
@context = nil
|
data/lib/safrano_core.rb
CHANGED
@@ -85,5 +85,7 @@ module Safrano
|
|
85
85
|
trans: 'transition_count')
|
86
86
|
TransitionValue = Transition.new('(\A\/\$value)(.*)\z',
|
87
87
|
trans: 'transition_value')
|
88
|
+
TransitionLinks = Transition.new('(\A\/\$links)(.*)\z',
|
89
|
+
trans: 'transition_links')
|
88
90
|
attr_accessor :allowed_transitions
|
89
91
|
end
|
data/lib/service.rb
CHANGED
@@ -74,6 +74,12 @@ module OData
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
+
# handle $links ... Note: $expand seems to be ignored when $links
|
78
|
+
# are requested
|
79
|
+
def get_entity_odata_link_h(entity:, uribase:)
|
80
|
+
hres = { uri: entity.uri(uribase) }
|
81
|
+
end
|
82
|
+
|
77
83
|
def get_entity_odata_h(entity:, expand: nil, uribase:)
|
78
84
|
hres = {}
|
79
85
|
hres['__metadata'] = entity.metadata_h(uribase: uribase)
|
@@ -511,7 +517,7 @@ module OData
|
|
511
517
|
@data_service_version = '1.0'
|
512
518
|
end
|
513
519
|
|
514
|
-
def get_coll_odata_h(array:, expand: nil, uribase:)
|
520
|
+
def get_coll_odata_h(array:, expand: nil, uribase:, icount: nil)
|
515
521
|
array.map do |w|
|
516
522
|
get_entity_odata_h(entity: w,
|
517
523
|
expand: expand,
|
@@ -530,13 +536,23 @@ module OData
|
|
530
536
|
@data_service_version = '2.0'
|
531
537
|
end
|
532
538
|
|
533
|
-
def
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
539
|
+
def get_coll_odata_links_h(array:, uribase:)
|
540
|
+
array.map do |w|
|
541
|
+
get_entity_odata_link_h(entity: w, uribase: uribase)
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def get_coll_odata_h(array:, expand: nil, uribase:, icount: nil)
|
546
|
+
res = array.map do |w|
|
547
|
+
get_entity_odata_h(entity: w,
|
548
|
+
expand: expand,
|
549
|
+
uribase: uribase)
|
550
|
+
end
|
551
|
+
if icount
|
552
|
+
{ 'results' => res, '__count' => icount }
|
553
|
+
else
|
554
|
+
{ 'results' => res }
|
555
|
+
end
|
540
556
|
end
|
541
557
|
|
542
558
|
def get_emptycoll_odata_h
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safrano
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- D.M.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-04-
|
11
|
+
date: 2019-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -66,8 +66,7 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0.51'
|
69
|
-
description: Safrano is a small
|
70
|
-
Rack and Sequel.
|
69
|
+
description: Safrano is a small OData server framework based on Ruby, Rack and Sequel.
|
71
70
|
email: dm@0data.dev
|
72
71
|
executables: []
|
73
72
|
extensions: []
|