safrano 0.0.6 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|