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
@@ -140,9 +140,11 @@ module OData
|
|
140
140
|
[self, :end_with_value]
|
141
141
|
end
|
142
142
|
|
143
|
+
ALLOWED_TRANSITIONS = [Safrano::TransitionEnd,
|
144
|
+
Safrano::TransitionValue].freeze
|
145
|
+
|
143
146
|
def allowed_transitions
|
144
|
-
|
145
|
-
Safrano::TransitionValue]
|
147
|
+
ALLOWED_TRANSITIONS
|
146
148
|
end
|
147
149
|
end
|
148
150
|
include Transitions
|
data/lib/odata/walker.rb
CHANGED
@@ -27,9 +27,11 @@ module OData
|
|
27
27
|
|
28
28
|
# are $links requested ?
|
29
29
|
attr_reader :do_links
|
30
|
-
|
30
|
+
NIL_SERVICE_FATAL = 'Walker is called with a nil service'.freeze
|
31
|
+
EMPTYSTR = ''.freeze
|
32
|
+
SLASH = '/'.freeze
|
31
33
|
def initialize(service, path, content_id_refs = nil)
|
32
|
-
raise
|
34
|
+
raise NIL_SERVICE_FATAL unless service
|
33
35
|
|
34
36
|
path = URI.decode_www_form_component(path)
|
35
37
|
@context = service
|
@@ -51,12 +53,12 @@ module OData
|
|
51
53
|
end
|
52
54
|
|
53
55
|
def unprefixed(prefix, path)
|
54
|
-
if (prefix ==
|
56
|
+
if (prefix == EMPTYSTR) || (prefix == SLASH)
|
55
57
|
path
|
56
58
|
else
|
57
59
|
# path.sub!(/\A#{prefix}/, '')
|
58
60
|
# TODO check
|
59
|
-
path.sub(/\A#{prefix}/,
|
61
|
+
path.sub(/\A#{prefix}/, EMPTYSTR)
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
data/lib/safrano/core.rb
CHANGED
data/lib/safrano/multipart.rb
CHANGED
@@ -15,8 +15,6 @@ module MIME
|
|
15
15
|
class Parser
|
16
16
|
HMD_RGX = /^([\w-]+)\s*:\s*(.*)/.freeze
|
17
17
|
|
18
|
-
CRLF_LINE_RGX = /^#{CRLF}$/.freeze
|
19
|
-
|
20
18
|
attr_accessor :lines
|
21
19
|
attr_accessor :target
|
22
20
|
def initialize
|
@@ -57,7 +55,6 @@ module MIME
|
|
57
55
|
if (hmd = HMD_RGX.match(line))
|
58
56
|
@target_hd[hmd[1].downcase] = hmd[2].strip
|
59
57
|
|
60
|
-
# elsif CRLF_LINE_RGX =~ line
|
61
58
|
elsif CRLF == line
|
62
59
|
@target_ct = @target_hd[CTT_TYPE_LC] || TEXT_PLAIN
|
63
60
|
@state = new_content
|
@@ -199,7 +196,6 @@ module MIME
|
|
199
196
|
# Parser for Text::Plain
|
200
197
|
class Parser
|
201
198
|
HMD_RGX = /^([\w-]+)\s*:\s*(.*)/.freeze
|
202
|
-
CRLF_LINE_RGX = /^#{CRLF}$/.freeze
|
203
199
|
def initialize(target)
|
204
200
|
@state = :h
|
205
201
|
@lines = []
|
@@ -213,7 +209,6 @@ module MIME
|
|
213
209
|
def parse_head(line)
|
214
210
|
if (hmd = HMD_RGX.match(line))
|
215
211
|
@target.hd[hmd[1].downcase] = hmd[2].strip
|
216
|
-
# elsif CRLF_LINE_RGX =~ line
|
217
212
|
elsif CRLF == line
|
218
213
|
@state = :b
|
219
214
|
else
|
@@ -442,7 +437,6 @@ module MIME
|
|
442
437
|
def unparse
|
443
438
|
b = "#{@http_method} #{@uri} HTTP/1.1#{CRLF}"
|
444
439
|
@hd.each { |k, v| b << "#{k}: #{v}#{CRLF}" }
|
445
|
-
# @hd.each { |k, v| b << k.to_s << ': ' << v.to_s << CRLF }
|
446
440
|
b << CRLF
|
447
441
|
b << @content if @content != ''
|
448
442
|
b
|
@@ -473,7 +467,6 @@ module MIME
|
|
473
467
|
b = String.new(APPLICATION_HTTP_11)
|
474
468
|
b << "#{@status} #{StatusMessage[@status]} #{CRLF}"
|
475
469
|
@hd.each { |k, v| b << "#{k}: #{v}#{CRLF}" }
|
476
|
-
# @hd.each { |k, v| b << k.to_s << ': ' << v.to_s << CRLF }
|
477
470
|
b << CRLF
|
478
471
|
b << @content.join if @content
|
479
472
|
b
|
@@ -490,7 +483,6 @@ module MIME
|
|
490
483
|
# Parser for Http Media
|
491
484
|
class Parser
|
492
485
|
HMD_RGX = /^([\w-]+)\s*:\s*(.*)/.freeze
|
493
|
-
CRLF_LINE_RGX = /^#{CRLF}$/.freeze
|
494
486
|
|
495
487
|
def initialize(target)
|
496
488
|
@state = :http
|
@@ -523,7 +515,6 @@ module MIME
|
|
523
515
|
if (hmd = HMD_RGX.match(line))
|
524
516
|
@target.content.hd[hmd[1].downcase] = hmd[2].strip
|
525
517
|
elsif CRLF == line
|
526
|
-
# elsif CRLF_LINE_RGX =~ line
|
527
518
|
@state = :b
|
528
519
|
else
|
529
520
|
@body_lines << line
|
data/lib/safrano/rack_app.rb
CHANGED
@@ -77,13 +77,13 @@ module OData
|
|
77
77
|
# the main Rack server app. Source: the Rack docu/examples and partly
|
78
78
|
# inspired from Sinatra
|
79
79
|
class ServerApp
|
80
|
-
METHODS_REGEXP = Regexp.new('HEAD|OPTIONS|GET|POST|PATCH|MERGE|PUT|DELETE')
|
80
|
+
METHODS_REGEXP = Regexp.new('HEAD|OPTIONS|GET|POST|PATCH|MERGE|PUT|DELETE').freeze
|
81
|
+
NOCACHE_HDRS = { 'Cache-Control' => 'no-cache',
|
82
|
+
'Expires' => '-1',
|
83
|
+
'Pragma' => 'no-cache' }.freeze
|
84
|
+
DATASERVICEVERSION = 'DataServiceVersion'.freeze
|
81
85
|
include MethodHandlers
|
82
86
|
def before
|
83
|
-
headers 'Cache-Control' => 'no-cache'
|
84
|
-
headers 'Expires' => '-1'
|
85
|
-
headers 'Pragma' => 'no-cache'
|
86
|
-
|
87
87
|
@request.service_base = self.class.get_service_base
|
88
88
|
|
89
89
|
neg_error = @request.negotiate_service_version
|
@@ -92,7 +92,9 @@ module OData
|
|
92
92
|
|
93
93
|
return false unless @request.service
|
94
94
|
|
95
|
-
|
95
|
+
myhdrs = NOCACHE_HDRS.dup
|
96
|
+
myhdrs[DATASERVICEVERSION] = @request.service.data_service_version
|
97
|
+
headers myhdrs
|
96
98
|
end
|
97
99
|
|
98
100
|
# dispatch for all methods requiring parsing of the path
|
data/lib/safrano/request.rb
CHANGED
@@ -108,13 +108,6 @@ module OData
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
|
-
def uribase
|
112
|
-
return @uribase if @uribase
|
113
|
-
|
114
|
-
@uribase =
|
115
|
-
"#{env['rack.url_scheme']}://#{env['HTTP_HOST']}#{service.xpath_prefix}"
|
116
|
-
end
|
117
|
-
|
118
111
|
def accept?(type)
|
119
112
|
preferred_type(type).to_s.include?(type)
|
120
113
|
end
|
data/lib/safrano/service.rb
CHANGED
@@ -2,14 +2,18 @@ require 'rexml/document'
|
|
2
2
|
require 'odata/relations.rb'
|
3
3
|
require 'odata/batch.rb'
|
4
4
|
require 'odata/error.rb'
|
5
|
+
require 'odata/filter/sequel.rb'
|
5
6
|
|
6
7
|
module OData
|
7
8
|
# this module has all methods related to expand/defered output preparation
|
8
9
|
# and will be included in Service class
|
9
10
|
module ExpandHandler
|
10
11
|
PATH_SPLITTER = %r{\A(\w+)\/?(.*)\z}.freeze
|
11
|
-
|
12
|
-
|
12
|
+
DEFERRED = '__deferred'.freeze
|
13
|
+
URI = 'uri'.freeze
|
14
|
+
|
15
|
+
def get_deferred_odata_h(entity_uri:, attrib:)
|
16
|
+
{ DEFERRED => { URI => "#{entity_uri}/#{attrib}" } }
|
13
17
|
end
|
14
18
|
|
15
19
|
# split expand argument into first and rest part
|
@@ -33,29 +37,30 @@ module OData
|
|
33
37
|
|
34
38
|
# default v2
|
35
39
|
# overriden in ServiceV1
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
40
|
+
RESULTS_K = 'results'.freeze
|
41
|
+
COUNT_K = '__count'.freeze
|
42
|
+
def get_coll_odata_h(array:, template:, icount: nil)
|
43
|
+
array.map! do |w|
|
44
|
+
get_entity_odata_h(entity: w, template: template)
|
41
45
|
end
|
42
46
|
if icount
|
43
|
-
{
|
47
|
+
{ RESULTS_K => array, COUNT_K => icount }
|
44
48
|
else
|
45
|
-
{
|
49
|
+
{ RESULTS_K => array }
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
49
53
|
# handle $links ... Note: $expand seems to be ignored when $links
|
50
54
|
# are requested
|
51
|
-
def get_entity_odata_link_h(entity
|
52
|
-
{ uri: entity.uri
|
55
|
+
def get_entity_odata_link_h(entity:)
|
56
|
+
{ uri: entity.uri }
|
53
57
|
end
|
54
58
|
|
55
59
|
EMPTYH = {}.freeze
|
56
|
-
|
60
|
+
METADATA_K = '__metadata'.freeze
|
61
|
+
def get_entity_odata_h(entity:, template:)
|
57
62
|
# start with metadata
|
58
|
-
hres = {
|
63
|
+
hres = { METADATA_K => entity.metadata_h }
|
59
64
|
|
60
65
|
template.each do |elmt, arg|
|
61
66
|
case elmt
|
@@ -68,10 +73,7 @@ module OData
|
|
68
73
|
arg.each do |attr, templ|
|
69
74
|
enval = entity.send(attr)
|
70
75
|
hres[attr] = if enval
|
71
|
-
|
72
|
-
get_entity_odata_h(entity: enval,
|
73
|
-
template: templ,
|
74
|
-
uribase: uribase)
|
76
|
+
get_entity_odata_h(entity: enval, template: templ)
|
75
77
|
else
|
76
78
|
# FK is NULL --> nav_value is nil --> return empty json
|
77
79
|
EMPTYH
|
@@ -82,16 +84,13 @@ module OData
|
|
82
84
|
next unless (encoll = entity.send(attr))
|
83
85
|
|
84
86
|
# nav attributes that are a collection (x..n)
|
85
|
-
hres[attr] = get_coll_odata_h(array: encoll,
|
86
|
-
template: templ,
|
87
|
-
uribase: uribase)
|
87
|
+
hres[attr] = get_coll_odata_h(array: encoll, template: templ)
|
88
88
|
# else error ?
|
89
89
|
end
|
90
90
|
when :deferr
|
91
|
+
euri = entity.uri
|
91
92
|
arg.each do |attr|
|
92
|
-
hres[attr] = get_deferred_odata_h(
|
93
|
-
attrib: attr,
|
94
|
-
uribase: uribase)
|
93
|
+
hres[attr] = get_deferred_odata_h(entity_uri: euri, attrib: attr)
|
95
94
|
end
|
96
95
|
end
|
97
96
|
end
|
@@ -144,7 +143,8 @@ module OData
|
|
144
143
|
attr_accessor :xname
|
145
144
|
attr_accessor :xnamespace
|
146
145
|
attr_accessor :xpath_prefix
|
147
|
-
|
146
|
+
attr_accessor :xserver_url
|
147
|
+
attr_accessor :uribase
|
148
148
|
attr_accessor :meta
|
149
149
|
attr_accessor :batch_handler
|
150
150
|
attr_accessor :relman
|
@@ -170,6 +170,7 @@ module OData
|
|
170
170
|
DATASERVICEVERSION_RGX = /\A([1234])(?:\.0);*\w*\z/.freeze
|
171
171
|
TRAILING_SLASH = %r{/\z}.freeze
|
172
172
|
DEFAULT_PATH_PREFIX = '/'.freeze
|
173
|
+
DEFAULT_SERVER_URL = 'http://localhost:9494'
|
173
174
|
|
174
175
|
# input is the DataServiceVersion request header string, eg.
|
175
176
|
# '2.0;blabla' ---> Version -> 2
|
@@ -212,21 +213,51 @@ module OData
|
|
212
213
|
(@v1.xpath_prefix = @xpath_prefix) if @v1
|
213
214
|
(@v2.xpath_prefix = @xpath_prefix) if @v2
|
214
215
|
end
|
216
|
+
|
217
|
+
def server_url(surl)
|
218
|
+
@xserver_url = surl.sub(TRAILING_SLASH, '')
|
219
|
+
(@v1.xserver_url = @xserver_url) if @v1
|
220
|
+
(@v2.xserver_url = @xserver_url) if @v2
|
221
|
+
end
|
222
|
+
|
215
223
|
# end public API
|
216
224
|
|
225
|
+
def set_uribase
|
226
|
+
@uribase = if @xpath_prefix.empty?
|
227
|
+
@xserver_url
|
228
|
+
else
|
229
|
+
if @xpath_prefix[0] == '/'
|
230
|
+
@xserver_url + @xpath_prefix
|
231
|
+
else
|
232
|
+
@xserver_url + '/' + @xpath_prefix
|
233
|
+
end
|
234
|
+
end
|
235
|
+
(@v1.uribase = @uribase) if @v1
|
236
|
+
(@v2.uribase = @uribase) if @v2
|
237
|
+
end
|
238
|
+
|
217
239
|
def copy_attribs_to(other)
|
218
240
|
other.cmap = @cmap
|
219
241
|
other.collections = @collections
|
242
|
+
other.allowed_transitions = @allowed_transitions
|
220
243
|
other.xtitle = @xtitle
|
221
244
|
other.xname = @xname
|
222
245
|
other.xnamespace = @xnamespace
|
223
246
|
other.xpath_prefix = @xpath_prefix
|
247
|
+
other.xserver_url = @xserver_url
|
248
|
+
other.uribase = @uribase
|
224
249
|
other.meta = ServiceMeta.new(other) # hum ... #todo: versions as well ?
|
225
250
|
other.relman = @relman
|
226
251
|
other.batch_handler = @batch_handler
|
227
252
|
other
|
228
253
|
end
|
229
254
|
|
255
|
+
# this is a central place. We extend Sequel models with OData functionality
|
256
|
+
# The included/extended modules depends on the properties(eg, pks, field types) of the model
|
257
|
+
# we differentiate
|
258
|
+
# * Single/Multi PK
|
259
|
+
# * Media/Non-Media entity
|
260
|
+
# Putting this logic here in modules loaded once on start shall result in less runtime overhead
|
230
261
|
def register_model(modelklass, entity_set_name = nil, is_media = false)
|
231
262
|
# check that the provided klass is a Sequel Model
|
232
263
|
|
@@ -313,10 +344,36 @@ module OData
|
|
313
344
|
# set default path prefix if path_prefix was not called
|
314
345
|
path_prefix(DEFAULT_PATH_PREFIX) unless @xpath_prefix
|
315
346
|
|
347
|
+
# set default server url if server_url was not called
|
348
|
+
server_url(DEFAULT_SERVER_URL) unless @xserver_url
|
349
|
+
|
350
|
+
set_uribase
|
351
|
+
|
316
352
|
@collections.each(&:finalize_publishing)
|
317
353
|
|
318
|
-
# finalize the
|
319
|
-
@collections.each { |klass|
|
354
|
+
# finalize the uri's and include NoMappingBeforeOutput or MappingBeforeOutput as needed
|
355
|
+
@collections.each { |klass|
|
356
|
+
klass.build_uri(@uribase)
|
357
|
+
if klass.time_cols.empty?
|
358
|
+
klass.include OData::NoMappingBeforeOutput
|
359
|
+
else
|
360
|
+
klass.include OData::MappingBeforeOutput
|
361
|
+
end
|
362
|
+
}
|
363
|
+
|
364
|
+
# build allowed transitions (requires that @collections are filled and sorted for having a
|
365
|
+
# correct base_url_regexp)
|
366
|
+
build_allowed_transitions
|
367
|
+
|
368
|
+
# mixin adapter specific modules where needed
|
369
|
+
case Sequel::Model.db.adapter_scheme
|
370
|
+
when :postgres
|
371
|
+
OData::Filter::FuncTree.include OData::Filter::FuncTreePostgres
|
372
|
+
when :sqlite
|
373
|
+
OData::Filter::FuncTree.include OData::Filter::FuncTreeSqlite
|
374
|
+
else
|
375
|
+
OData::Filter::FuncTree.include OData::Filter::FuncTreeDefault
|
376
|
+
end
|
320
377
|
end
|
321
378
|
|
322
379
|
def execute_deferred_iblocks
|
@@ -341,7 +398,7 @@ module OData
|
|
341
398
|
def service_xml(req)
|
342
399
|
doc = REXML::Document.new
|
343
400
|
# separator required ? ?
|
344
|
-
root = doc.add_element('service', 'xml:base' =>
|
401
|
+
root = doc.add_element('service', 'xml:base' => @uribase)
|
345
402
|
|
346
403
|
root.add_namespace('xmlns:atom', XMLNS::W3_2005_ATOM)
|
347
404
|
root.add_namespace('xmlns:app', XMLNS::W3_2007_APP)
|
@@ -452,15 +509,22 @@ module OData
|
|
452
509
|
# methods related to transitions to next state (cf. walker)
|
453
510
|
module Transitions
|
454
511
|
DOLLAR_ID_REGEXP = Regexp.new('\A\/\$').freeze
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
512
|
+
ALLOWED_TRANSITIONS_FIXED = [
|
513
|
+
Safrano::TransitionEnd,
|
514
|
+
Safrano::TransitionMetadata,
|
515
|
+
Safrano::TransitionBatch,
|
516
|
+
Safrano::TransitionContentId
|
517
|
+
].freeze
|
518
|
+
|
519
|
+
def build_allowed_transitions
|
520
|
+
@allowed_transitions = (ALLOWED_TRANSITIONS_FIXED + [
|
461
521
|
Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
|
462
522
|
trans: 'transition_collection')
|
463
|
-
]
|
523
|
+
]).freeze
|
524
|
+
end
|
525
|
+
|
526
|
+
def allowed_transitions
|
527
|
+
@allowed_transitions
|
464
528
|
end
|
465
529
|
|
466
530
|
def transition_collection(match_result)
|
@@ -507,18 +571,18 @@ module OData
|
|
507
571
|
@data_service_version = '1.0'
|
508
572
|
end
|
509
573
|
|
510
|
-
def get_coll_odata_links_h(array:,
|
574
|
+
def get_coll_odata_links_h(array:, icount: nil)
|
511
575
|
array.map do |w|
|
512
|
-
get_entity_odata_link_h(entity: w
|
576
|
+
get_entity_odata_link_h(entity: w)
|
513
577
|
end
|
514
578
|
end
|
515
579
|
|
516
|
-
def get_coll_odata_h(array:, template:,
|
517
|
-
array.map do |w|
|
580
|
+
def get_coll_odata_h(array:, template:, icount: nil)
|
581
|
+
array.map! do |w|
|
518
582
|
get_entity_odata_h(entity: w,
|
519
|
-
template: template
|
520
|
-
uribase: uribase)
|
583
|
+
template: template)
|
521
584
|
end
|
585
|
+
array
|
522
586
|
end
|
523
587
|
|
524
588
|
def get_emptycoll_odata_h
|
@@ -532,19 +596,19 @@ module OData
|
|
532
596
|
@data_service_version = '2.0'
|
533
597
|
end
|
534
598
|
|
535
|
-
def get_coll_odata_links_h(array:,
|
599
|
+
def get_coll_odata_links_h(array:, icount: nil)
|
536
600
|
res = array.map do |w|
|
537
|
-
get_entity_odata_link_h(entity: w
|
601
|
+
get_entity_odata_link_h(entity: w)
|
538
602
|
end
|
539
603
|
if icount
|
540
|
-
{
|
604
|
+
{ RESULTS_K => res, COUNT_K => icount }
|
541
605
|
else
|
542
|
-
{
|
606
|
+
{ RESULTS_K => res }
|
543
607
|
end
|
544
608
|
end
|
545
609
|
|
546
610
|
def get_emptycoll_odata_h
|
547
|
-
{
|
611
|
+
{ RESULTS_K => EMPTY_HASH_IN_ARY }
|
548
612
|
end
|
549
613
|
end
|
550
614
|
|
@@ -555,9 +619,9 @@ module OData
|
|
555
619
|
@service = service
|
556
620
|
end
|
557
621
|
|
622
|
+
ALLOWED_TRANSITIONS_FIXED = [Safrano::TransitionEnd].freeze
|
558
623
|
def allowed_transitions
|
559
|
-
|
560
|
-
trans: 'transition_end')]
|
624
|
+
ALLOWED_TRANSITIONS_FIXED
|
561
625
|
end
|
562
626
|
|
563
627
|
def transition_end(_match_result)
|