safrano 0.3.2 → 0.4.2
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/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
|