safrano 0.3.2 → 0.4.2
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 +1 -1
- data/lib/odata/batch.rb +24 -10
- data/lib/odata/collection.rb +242 -96
- data/lib/odata/collection_filter.rb +40 -9
- data/lib/odata/collection_media.rb +279 -0
- data/lib/odata/collection_order.rb +46 -36
- data/lib/odata/common_logger.rb +59 -0
- data/lib/odata/entity.rb +268 -54
- data/lib/odata/error.rb +58 -17
- data/lib/odata/expand.rb +123 -0
- data/lib/odata/filter/error.rb +6 -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 +150 -0
- data/lib/odata/relations.rb +2 -2
- data/lib/odata/select.rb +42 -0
- data/lib/odata/url_parameters.rb +51 -36
- data/lib/odata/walker.rb +12 -4
- data/lib/safrano.rb +23 -12
- data/lib/{safrano_core.rb → safrano/core.rb} +14 -3
- data/lib/{multipart.rb → safrano/multipart.rb} +51 -29
- data/lib/{odata_rack_builder.rb → safrano/odata_rack_builder.rb} +1 -1
- data/lib/{rack_app.rb → safrano/rack_app.rb} +15 -10
- data/lib/{request.rb → safrano/request.rb} +21 -8
- data/lib/{response.rb → safrano/response.rb} +1 -2
- data/lib/{sequel_join_by_paths.rb → safrano/sequel_join_by_paths.rb} +1 -1
- data/lib/{service.rb → safrano/service.rb} +93 -97
- data/lib/safrano/version.rb +3 -0
- data/lib/sequel/plugins/join_by_paths.rb +11 -10
- metadata +34 -15
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'rack'
|
4
|
-
require_relative 'odata/walker.rb'
|
4
|
+
require_relative '../odata/walker.rb'
|
5
5
|
require_relative 'request.rb'
|
6
6
|
require_relative 'response.rb'
|
7
7
|
|
@@ -14,7 +14,7 @@ module OData
|
|
14
14
|
x = if @walker.status == :end
|
15
15
|
headers.delete('Content-Type')
|
16
16
|
@response.headers.delete('Content-Type')
|
17
|
-
[200,
|
17
|
+
[200, EMPTY_HASH, '']
|
18
18
|
else
|
19
19
|
odata_error
|
20
20
|
end
|
@@ -26,12 +26,7 @@ module OData
|
|
26
26
|
return @walker.error.odata_get(@request) unless @walker.error.nil?
|
27
27
|
|
28
28
|
# this is too critical; raise a real Exception
|
29
|
-
# begin
|
30
29
|
raise 'Walker construction failed with a unknown Error '
|
31
|
-
# rescue StandardError
|
32
|
-
# binding.pry
|
33
|
-
# end
|
34
|
-
# [500, {}, 'Server Error']
|
35
30
|
end
|
36
31
|
|
37
32
|
def odata_delete
|
@@ -42,6 +37,14 @@ module OData
|
|
42
37
|
end
|
43
38
|
end
|
44
39
|
|
40
|
+
def odata_put
|
41
|
+
if @walker.status == :end
|
42
|
+
@walker.end_context.odata_put(@request)
|
43
|
+
else
|
44
|
+
odata_error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
45
48
|
def odata_patch
|
46
49
|
if @walker.status == :end
|
47
50
|
@walker.end_context.odata_patch(@request)
|
@@ -67,7 +70,7 @@ module OData
|
|
67
70
|
end
|
68
71
|
|
69
72
|
def odata_head
|
70
|
-
[200,
|
73
|
+
[200, EMPTY_HASH, [EMPTY_STRING]]
|
71
74
|
end
|
72
75
|
end
|
73
76
|
|
@@ -105,7 +108,9 @@ module OData
|
|
105
108
|
odata_delete
|
106
109
|
when 'OPTIONS'
|
107
110
|
odata_options
|
108
|
-
when '
|
111
|
+
when 'PUT'
|
112
|
+
odata_put
|
113
|
+
when 'PATCH', 'MERGE'
|
109
114
|
odata_patch
|
110
115
|
else
|
111
116
|
raise Error
|
@@ -114,7 +119,7 @@ module OData
|
|
114
119
|
|
115
120
|
def dispatch
|
116
121
|
req_ret = if @request.request_method !~ METHODS_REGEXP
|
117
|
-
[404,
|
122
|
+
[404, EMPTY_HASH, ['Did you get lost?']]
|
118
123
|
elsif @request.request_method == 'HEAD'
|
119
124
|
odata_head
|
120
125
|
else
|
@@ -1,6 +1,5 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
1
|
require 'rack'
|
2
|
+
require 'rfc2047'
|
4
3
|
|
5
4
|
module OData
|
6
5
|
# monkey patch deactivate Rack/multipart because it does not work on simple
|
@@ -10,6 +9,7 @@ module OData
|
|
10
9
|
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/.freeze
|
11
10
|
HEADER_VAL_RAW = '(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*'.freeze
|
12
11
|
HEADER_VAL_WITH_PAR = /(?:#{HEADER_VAL_RAW})\s*(?:;#{HEADER_PARAM})*/.freeze
|
12
|
+
ON_CGST_ERROR = (proc { |r| raise(Sequel::Rollback) if r.in_changeset })
|
13
13
|
|
14
14
|
# borowed from Sinatra
|
15
15
|
class AcceptEntry
|
@@ -94,7 +94,7 @@ module OData
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def create_odata_walker
|
97
|
-
@walker = Walker.new(@service, path_info, @content_id_references)
|
97
|
+
@env['safrano.walker'] = @walker = Walker.new(@service, path_info, @content_id_references)
|
98
98
|
end
|
99
99
|
|
100
100
|
def accept
|
@@ -132,22 +132,35 @@ module OData
|
|
132
132
|
end
|
133
133
|
end
|
134
134
|
|
135
|
-
def
|
135
|
+
def with_media_data
|
136
|
+
if (filename = @env['HTTP_SLUG'])
|
137
|
+
|
138
|
+
yield @env['rack.input'],
|
139
|
+
content_type.split(';').first,
|
140
|
+
Rfc2047.decode(filename)
|
141
|
+
|
142
|
+
else
|
143
|
+
ON_CGST_ERROR.call(self)
|
144
|
+
[400, EMPTY_HASH, ['File upload error: Missing SLUG']]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def with_parsed_data
|
136
149
|
if content_type == APPJSON
|
137
150
|
# Parse json payload
|
138
151
|
begin
|
139
152
|
data = JSON.parse(body.read)
|
140
153
|
rescue JSON::ParserError => e
|
141
|
-
|
142
|
-
return [400,
|
143
|
-
|
154
|
+
ON_CGST_ERROR.call(self)
|
155
|
+
return [400, EMPTY_HASH, ['JSON Parser Error while parsing payload : ',
|
156
|
+
e.message]]
|
144
157
|
end
|
145
158
|
|
146
159
|
yield data
|
147
160
|
|
148
161
|
else # TODO: other formats
|
149
162
|
|
150
|
-
[415,
|
163
|
+
[415, EMPTY_HASH, EMPTY_ARRAY]
|
151
164
|
end
|
152
165
|
end
|
153
166
|
|
@@ -1,4 +1,3 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
1
|
require 'rack'
|
3
2
|
|
4
3
|
# monkey patch deactivate Rack/multipart because it does not work on simple
|
@@ -35,7 +34,7 @@ module OData
|
|
35
34
|
|
36
35
|
if drop_body?
|
37
36
|
close
|
38
|
-
result =
|
37
|
+
result = EMPTY_ARRAY
|
39
38
|
end
|
40
39
|
|
41
40
|
if calculate_content_length?
|
@@ -1,5 +1,3 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
1
|
require 'rexml/document'
|
4
2
|
require 'odata/relations.rb'
|
5
3
|
require 'odata/batch.rb'
|
@@ -23,20 +21,22 @@ module OData
|
|
23
21
|
# TODO: check errorhandling
|
24
22
|
raise OData::ServerError if cur_exp.nil?
|
25
23
|
|
26
|
-
|
24
|
+
k_s = cur_exp
|
25
|
+
|
27
26
|
else
|
28
|
-
|
27
|
+
k_s = exp_one.strip
|
29
28
|
rest_exp = nil
|
30
29
|
end
|
31
|
-
|
30
|
+
k = k_s.to_sym
|
31
|
+
yield k, k_s, rest_exp
|
32
32
|
end
|
33
33
|
|
34
34
|
# default v2
|
35
35
|
# overriden in ServiceV1
|
36
|
-
def get_coll_odata_h(array:,
|
36
|
+
def get_coll_odata_h(array:, template:, uribase:, icount: nil)
|
37
37
|
res = array.map do |w|
|
38
38
|
get_entity_odata_h(entity: w,
|
39
|
-
|
39
|
+
template: template,
|
40
40
|
uribase: uribase)
|
41
41
|
end
|
42
42
|
if icount
|
@@ -46,89 +46,55 @@ module OData
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
# handle a single expand
|
50
|
-
def handle_entity_expand_one(entity:, exp_one:, nav_values_h:, nav_coll_h:,
|
51
|
-
uribase:)
|
52
|
-
|
53
|
-
split_entity_expand_arg(exp_one) do |first, rest_exp|
|
54
|
-
if (enval = entity.nav_values[first])
|
55
|
-
nav_values_h[first.to_s] = get_entity_odata_h(entity: enval,
|
56
|
-
expand: rest_exp,
|
57
|
-
uribase: uribase)
|
58
|
-
elsif (encoll = entity.nav_coll[first])
|
59
|
-
# nav attributes that are a collection (x..n)
|
60
|
-
nav_coll_h[first.to_s] = get_coll_odata_h(array: encoll,
|
61
|
-
expand: rest_exp,
|
62
|
-
uribase: uribase)
|
63
|
-
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def handle_entity_expand(entity:, expand:, nav_values_h:,
|
69
|
-
nav_coll_h:, uribase:)
|
70
|
-
expand.strip!
|
71
|
-
explist = expand.split(',')
|
72
|
-
# handle multiple expands
|
73
|
-
explist.each do |exp|
|
74
|
-
handle_entity_expand_one(entity: entity, exp_one: exp,
|
75
|
-
nav_values_h: nav_values_h,
|
76
|
-
nav_coll_h: nav_coll_h,
|
77
|
-
uribase: uribase)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def handle_entity_deferred_attribs(entity:, nav_values_h:,
|
82
|
-
nav_coll_h:, uribase:)
|
83
|
-
entity.nav_values.each_key do |ksy|
|
84
|
-
ks = ksy.to_s
|
85
|
-
next if nav_values_h.key?(ks)
|
86
|
-
|
87
|
-
nav_values_h[ks] = get_deferred_odata_h(entity: entity,
|
88
|
-
attrib: ks, uribase: uribase)
|
89
|
-
end
|
90
|
-
entity.nav_coll.each_key do |ksy|
|
91
|
-
ks = ksy.to_s
|
92
|
-
next if nav_coll_h.key?(ks)
|
93
|
-
|
94
|
-
nav_coll_h[ks] = get_deferred_odata_h(entity: entity, attrib: ks,
|
95
|
-
uribase: uribase)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
49
|
# handle $links ... Note: $expand seems to be ignored when $links
|
100
50
|
# are requested
|
101
51
|
def get_entity_odata_link_h(entity:, uribase:)
|
102
52
|
{ uri: entity.uri(uribase) }
|
103
53
|
end
|
104
54
|
|
105
|
-
|
106
|
-
|
107
|
-
|
55
|
+
EMPTYH = {}.freeze
|
56
|
+
def get_entity_odata_h(entity:, template:, uribase:)
|
57
|
+
# start with metadata
|
58
|
+
hres = { '__metadata' => entity.metadata_h(uribase: uribase) }
|
108
59
|
|
109
|
-
|
110
|
-
|
60
|
+
template.each do |elmt, arg|
|
61
|
+
case elmt
|
62
|
+
when :all_values
|
63
|
+
hres.merge! entity.casted_values
|
64
|
+
when :selected_vals
|
65
|
+
hres.merge! entity.casted_values(arg)
|
66
|
+
when :expand_e
|
111
67
|
|
112
|
-
|
113
|
-
|
68
|
+
arg.each do |attr, templ|
|
69
|
+
enval = entity.send(attr)
|
70
|
+
hres[attr] = if enval
|
114
71
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
72
|
+
get_entity_odata_h(entity: enval,
|
73
|
+
template: templ,
|
74
|
+
uribase: uribase)
|
75
|
+
else
|
76
|
+
# FK is NULL --> nav_value is nil --> return empty json
|
77
|
+
EMPTYH
|
78
|
+
end
|
79
|
+
end
|
80
|
+
when :expand_c
|
81
|
+
arg.each do |attr, templ|
|
82
|
+
next unless (encoll = entity.send(attr))
|
83
|
+
|
84
|
+
# nav attributes that are a collection (x..n)
|
85
|
+
hres[attr] = get_coll_odata_h(array: encoll,
|
86
|
+
template: templ,
|
87
|
+
uribase: uribase)
|
88
|
+
# else error ?
|
89
|
+
end
|
90
|
+
when :deferr
|
91
|
+
arg.each do |attr|
|
92
|
+
hres[attr] = get_deferred_odata_h(entity: entity,
|
93
|
+
attrib: attr,
|
94
|
+
uribase: uribase)
|
95
|
+
end
|
96
|
+
end
|
121
97
|
end
|
122
|
-
|
123
|
-
# handle not expanded (deferred) nav attributes
|
124
|
-
handle_entity_deferred_attribs(entity: entity,
|
125
|
-
nav_values_h: nav_values_h,
|
126
|
-
nav_coll_h: nav_coll_h,
|
127
|
-
uribase: uribase)
|
128
|
-
# merge ...
|
129
|
-
hres.merge!(nav_values_h)
|
130
|
-
hres.merge!(nav_coll_h)
|
131
|
-
|
132
98
|
hres
|
133
99
|
end
|
134
100
|
end
|
@@ -203,6 +169,7 @@ module OData
|
|
203
169
|
|
204
170
|
DATASERVICEVERSION_RGX = /\A([1234])(?:\.0);*\w*\z/.freeze
|
205
171
|
TRAILING_SLASH = %r{/\z}.freeze
|
172
|
+
DEFAULT_PATH_PREFIX = '/'.freeze
|
206
173
|
|
207
174
|
# input is the DataServiceVersion request header string, eg.
|
208
175
|
# '2.0;blabla' ---> Version -> 2
|
@@ -260,19 +227,36 @@ module OData
|
|
260
227
|
other
|
261
228
|
end
|
262
229
|
|
263
|
-
def register_model(modelklass, entity_set_name = nil)
|
230
|
+
def register_model(modelklass, entity_set_name = nil, is_media = false)
|
264
231
|
# check that the provided klass is a Sequel Model
|
265
|
-
unless modelklass.is_a? Sequel::Model::ClassMethods
|
266
|
-
raise OData::API::ModelNameError, modelklass
|
267
|
-
end
|
268
232
|
|
269
|
-
|
233
|
+
raise(OData::API::ModelNameError, modelklass) unless modelklass.is_a? Sequel::Model::ClassMethods
|
234
|
+
|
235
|
+
if modelklass.ancestors.include? OData::Entity
|
236
|
+
# modules were already added previously;
|
237
|
+
# cleanup state to avoid having data from previous calls
|
238
|
+
# mostly usefull for testing (eg API)
|
239
|
+
modelklass.reset
|
240
|
+
elsif modelklass.primary_key.is_a?(Array) # first API call... (normal non-testing case)
|
270
241
|
modelklass.extend OData::EntityClassMultiPK
|
271
242
|
modelklass.include OData::EntityMultiPK
|
272
243
|
else
|
273
244
|
modelklass.extend OData::EntityClassSinglePK
|
274
245
|
modelklass.include OData::EntitySinglePK
|
275
246
|
end
|
247
|
+
# Media/Non-media
|
248
|
+
if is_media
|
249
|
+
modelklass.extend OData::EntityClassMedia
|
250
|
+
# set default media handler . Can be overridden later with the
|
251
|
+
# "use HandlerKlass, options" API
|
252
|
+
|
253
|
+
modelklass.set_default_media_handler
|
254
|
+
modelklass.api_check_media_fields
|
255
|
+
modelklass.include OData::MediaEntity
|
256
|
+
else
|
257
|
+
modelklass.extend OData::EntityClassNonMedia
|
258
|
+
modelklass.include OData::NonMediaEntity
|
259
|
+
end
|
276
260
|
|
277
261
|
modelklass.prepare_pk
|
278
262
|
modelklass.prepare_fields
|
@@ -290,6 +274,14 @@ module OData
|
|
290
274
|
modelklass.deferred_iblock = block if block_given?
|
291
275
|
end
|
292
276
|
|
277
|
+
def publish_media_model(modelklass, entity_set_name = nil, &block)
|
278
|
+
register_model(modelklass, entity_set_name, true)
|
279
|
+
# we need to execute the passed block in a deferred step
|
280
|
+
# after all models have been registered (due to rel. dependancies)
|
281
|
+
# modelklass.instance_eval(&block) if block_given?
|
282
|
+
modelklass.deferred_iblock = block if block_given?
|
283
|
+
end
|
284
|
+
|
293
285
|
def cmap=(imap)
|
294
286
|
@cmap = imap
|
295
287
|
set_collections_sorted(@cmap.values)
|
@@ -300,15 +292,14 @@ module OData
|
|
300
292
|
# example: CrewMember must be matched before Crew otherwise we get error
|
301
293
|
def set_collections_sorted(coll_data)
|
302
294
|
@collections = coll_data
|
303
|
-
if @collections
|
304
|
-
@collections.sort_by! { |klass| klass.entity_set_name.size }.reverse!
|
305
|
-
end
|
295
|
+
@collections.sort_by! { |klass| klass.entity_set_name.size }.reverse! if @collections
|
306
296
|
@collections
|
307
297
|
end
|
308
298
|
|
309
299
|
# to be called at end of publishing block to ensure we get the right names
|
310
300
|
# and additionally build the list of valid attribute path's used
|
311
301
|
# for validation of $orderby or $filter params
|
302
|
+
|
312
303
|
def finalize_publishing
|
313
304
|
# build the cmap
|
314
305
|
@cmap = {}
|
@@ -319,8 +310,13 @@ module OData
|
|
319
310
|
# now that we know all model klasses we can handle relationships
|
320
311
|
execute_deferred_iblocks
|
321
312
|
|
322
|
-
#
|
323
|
-
|
313
|
+
# set default path prefix if path_prefix was not called
|
314
|
+
path_prefix(DEFAULT_PATH_PREFIX) unless @xpath_prefix
|
315
|
+
|
316
|
+
@collections.each(&:finalize_publishing)
|
317
|
+
|
318
|
+
# finalize the media handlers
|
319
|
+
@collections.each { |klass| }
|
324
320
|
end
|
325
321
|
|
326
322
|
def execute_deferred_iblocks
|
@@ -368,7 +364,7 @@ module OData
|
|
368
364
|
def add_metadata_xml_entity_type(schema)
|
369
365
|
@collections.each do |klass|
|
370
366
|
enty = klass.add_metadata_rexml(schema)
|
371
|
-
klass.add_metadata_navs_rexml(enty, @
|
367
|
+
klass.add_metadata_navs_rexml(enty, @relman, @xnamespace)
|
372
368
|
end
|
373
369
|
end
|
374
370
|
|
@@ -497,7 +493,7 @@ module OData
|
|
497
493
|
# Documents MUST be
|
498
494
|
# identified with the "application/atomsvc+xml" media type (see
|
499
495
|
# [RFC5023] section 8).
|
500
|
-
[200, CT_ATOMXML, service_xml(req)]
|
496
|
+
[200, CT_ATOMXML, [service_xml(req)]]
|
501
497
|
else
|
502
498
|
# this is returned by http://services.odata.org/V2/OData/OData.svc
|
503
499
|
415
|
@@ -517,16 +513,16 @@ module OData
|
|
517
513
|
end
|
518
514
|
end
|
519
515
|
|
520
|
-
def get_coll_odata_h(array:,
|
516
|
+
def get_coll_odata_h(array:, template:, uribase:, icount: nil)
|
521
517
|
array.map do |w|
|
522
518
|
get_entity_odata_h(entity: w,
|
523
|
-
|
519
|
+
template: template,
|
524
520
|
uribase: uribase)
|
525
521
|
end
|
526
522
|
end
|
527
523
|
|
528
524
|
def get_emptycoll_odata_h
|
529
|
-
|
525
|
+
EMPTY_HASH_IN_ARY
|
530
526
|
end
|
531
527
|
end
|
532
528
|
|
@@ -548,7 +544,7 @@ module OData
|
|
548
544
|
end
|
549
545
|
|
550
546
|
def get_emptycoll_odata_h
|
551
|
-
{ 'results' =>
|
547
|
+
{ 'results' => EMPTY_HASH_IN_ARY }
|
552
548
|
end
|
553
549
|
end
|
554
550
|
|
@@ -570,7 +566,7 @@ module OData
|
|
570
566
|
|
571
567
|
def odata_get(req)
|
572
568
|
if req.accept?(APPXML)
|
573
|
-
[200, CT_APPXML, @service.metadata_xml(req)]
|
569
|
+
[200, CT_APPXML, [@service.metadata_xml(req)]]
|
574
570
|
else
|
575
571
|
415
|
576
572
|
end
|