safrano 0.3.3 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/odata/attribute.rb +9 -8
  3. data/lib/odata/batch.rb +8 -8
  4. data/lib/odata/collection.rb +239 -92
  5. data/lib/odata/collection_filter.rb +40 -9
  6. data/lib/odata/collection_media.rb +159 -28
  7. data/lib/odata/collection_order.rb +46 -36
  8. data/lib/odata/common_logger.rb +37 -12
  9. data/lib/odata/entity.rb +188 -99
  10. data/lib/odata/error.rb +60 -12
  11. data/lib/odata/expand.rb +123 -0
  12. data/lib/odata/filter/base.rb +66 -0
  13. data/lib/odata/filter/error.rb +33 -0
  14. data/lib/odata/filter/parse.rb +6 -12
  15. data/lib/odata/filter/sequel.rb +42 -29
  16. data/lib/odata/filter/sequel_function_adapter.rb +147 -0
  17. data/lib/odata/filter/token.rb +5 -1
  18. data/lib/odata/filter/tree.rb +45 -29
  19. data/lib/odata/navigation_attribute.rb +60 -27
  20. data/lib/odata/relations.rb +2 -2
  21. data/lib/odata/select.rb +42 -0
  22. data/lib/odata/url_parameters.rb +51 -36
  23. data/lib/odata/walker.rb +6 -6
  24. data/lib/safrano.rb +23 -13
  25. data/lib/{safrano_core.rb → safrano/core.rb} +12 -4
  26. data/lib/{multipart.rb → safrano/multipart.rb} +17 -26
  27. data/lib/{odata_rack_builder.rb → safrano/odata_rack_builder.rb} +0 -1
  28. data/lib/{rack_app.rb → safrano/rack_app.rb} +12 -10
  29. data/lib/{request.rb → safrano/request.rb} +8 -14
  30. data/lib/{response.rb → safrano/response.rb} +1 -2
  31. data/lib/{sequel_join_by_paths.rb → safrano/sequel_join_by_paths.rb} +1 -1
  32. data/lib/{service.rb → safrano/service.rb} +162 -131
  33. data/lib/safrano/version.rb +3 -0
  34. data/lib/sequel/plugins/join_by_paths.rb +11 -10
  35. metadata +33 -16
  36. data/lib/version.rb +0 -4
@@ -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
@@ -109,13 +108,6 @@ module OData
109
108
  end
110
109
  end
111
110
 
112
- def uribase
113
- return @uribase if @uribase
114
-
115
- @uribase =
116
- "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}#{service.xpath_prefix}"
117
- end
118
-
119
111
  def accept?(type)
120
112
  preferred_type(type).to_s.include?(type)
121
113
  end
@@ -136,11 +128,13 @@ module OData
136
128
  def with_media_data
137
129
  if (filename = @env['HTTP_SLUG'])
138
130
 
139
- yield @env['rack.input'], content_type.split(';').first, filename
131
+ yield @env['rack.input'],
132
+ content_type.split(';').first,
133
+ Rfc2047.decode(filename)
140
134
 
141
135
  else
142
136
  ON_CGST_ERROR.call(self)
143
- return [400, {}, ['File upload error: Missing SLUG']]
137
+ [400, EMPTY_HASH, ['File upload error: Missing SLUG']]
144
138
  end
145
139
  end
146
140
 
@@ -151,15 +145,15 @@ module OData
151
145
  data = JSON.parse(body.read)
152
146
  rescue JSON::ParserError => e
153
147
  ON_CGST_ERROR.call(self)
154
- return [400, {}, ['JSON Parser Error while parsing payload : ',
155
- e.message]]
148
+ return [400, EMPTY_HASH, ['JSON Parser Error while parsing payload : ',
149
+ e.message]]
156
150
  end
157
151
 
158
152
  yield data
159
153
 
160
154
  else # TODO: other formats
161
155
 
162
- [415, {}, []]
156
+ [415, EMPTY_HASH, EMPTY_ARRAY]
163
157
  end
164
158
  end
165
159
 
@@ -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,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative './sequel/plugins/join_by_paths.rb'
3
+ require_relative '../sequel/plugins/join_by_paths.rb'
4
4
 
5
5
  Sequel::Model.plugin Sequel::Plugins::JoinByPaths
@@ -1,17 +1,19 @@
1
- #!/usr/bin/env ruby
2
-
3
1
  require 'rexml/document'
4
2
  require 'odata/relations.rb'
5
3
  require 'odata/batch.rb'
6
4
  require 'odata/error.rb'
5
+ require 'odata/filter/sequel.rb'
7
6
 
8
7
  module OData
9
8
  # this module has all methods related to expand/defered output preparation
10
9
  # and will be included in Service class
11
10
  module ExpandHandler
12
11
  PATH_SPLITTER = %r{\A(\w+)\/?(.*)\z}.freeze
13
- def get_deferred_odata_h(entity:, attrib:, uribase:)
14
- { '__deferred' => { 'uri' => "#{entity.uri(uribase)}/#{attrib}" } }
12
+ DEFERRED = '__deferred'.freeze
13
+ URI = 'uri'.freeze
14
+
15
+ def get_deferred_odata_h(entity_uri:, attrib:)
16
+ { DEFERRED => { URI => "#{entity_uri}/#{attrib}" } }
15
17
  end
16
18
 
17
19
  # split expand argument into first and rest part
@@ -23,109 +25,75 @@ module OData
23
25
  # TODO: check errorhandling
24
26
  raise OData::ServerError if cur_exp.nil?
25
27
 
26
- k = cur_exp.to_sym
28
+ k_s = cur_exp
29
+
27
30
  else
28
- k = exp_one.strip.to_sym
31
+ k_s = exp_one.strip
29
32
  rest_exp = nil
30
33
  end
31
- yield k, rest_exp
34
+ k = k_s.to_sym
35
+ yield k, k_s, rest_exp
32
36
  end
33
37
 
34
38
  # default v2
35
39
  # overriden in ServiceV1
36
- def get_coll_odata_h(array:, expand: nil, uribase:, icount: nil)
37
- res = array.map do |w|
38
- get_entity_odata_h(entity: w,
39
- expand: expand,
40
- uribase: uribase)
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
- { 'results' => res, '__count' => icount }
47
+ { RESULTS_K => array, COUNT_K => icount }
44
48
  else
45
- { 'results' => res }
46
- end
47
- end
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)
49
+ { RESULTS_K => array }
96
50
  end
97
51
  end
98
52
 
99
53
  # handle $links ... Note: $expand seems to be ignored when $links
100
54
  # are requested
101
- def get_entity_odata_link_h(entity:, uribase:)
102
- { uri: entity.uri(uribase) }
103
- end
104
-
105
- def get_entity_odata_h(entity:, expand: nil, uribase:)
106
- hres = entity.casted_values
107
- hres['__metadata'] = entity.metadata_h(uribase: uribase)
108
-
109
- nav_values_h = {}
110
- nav_coll_h = {}
55
+ def get_entity_odata_link_h(entity:)
56
+ { uri: entity.uri }
57
+ end
58
+
59
+ EMPTYH = {}.freeze
60
+ METADATA_K = '__metadata'.freeze
61
+ def get_entity_odata_h(entity:, template:)
62
+ # start with metadata
63
+ hres = { METADATA_K => entity.metadata_h }
64
+
65
+ template.each do |elmt, arg|
66
+ case elmt
67
+ when :all_values
68
+ hres.merge! entity.casted_values
69
+ when :selected_vals
70
+ hres.merge! entity.casted_values(arg)
71
+ when :expand_e
72
+
73
+ arg.each do |attr, templ|
74
+ enval = entity.send(attr)
75
+ hres[attr] = if enval
76
+ get_entity_odata_h(entity: enval, template: templ)
77
+ else
78
+ # FK is NULL --> nav_value is nil --> return empty json
79
+ EMPTYH
80
+ end
81
+ end
82
+ when :expand_c
83
+ arg.each do |attr, templ|
84
+ next unless (encoll = entity.send(attr))
111
85
 
112
- # handle expanded nav attributes
113
- unless expand.nil?
114
- handle_entity_expand(entity: entity, expand: expand,
115
- nav_values_h: nav_values_h,
116
- nav_coll_h: nav_coll_h,
117
- uribase: uribase)
86
+ # nav attributes that are a collection (x..n)
87
+ hres[attr] = get_coll_odata_h(array: encoll, template: templ)
88
+ # else error ?
89
+ end
90
+ when :deferr
91
+ euri = entity.uri
92
+ arg.each do |attr|
93
+ hres[attr] = get_deferred_odata_h(entity_uri: euri, attrib: attr)
94
+ end
95
+ end
118
96
  end
119
-
120
- # handle not expanded (deferred) nav attributes
121
- handle_entity_deferred_attribs(entity: entity,
122
- nav_values_h: nav_values_h,
123
- nav_coll_h: nav_coll_h,
124
- uribase: uribase)
125
- # merge ...
126
- hres.merge!(nav_values_h)
127
- hres.merge!(nav_coll_h)
128
-
129
97
  hres
130
98
  end
131
99
  end
@@ -175,7 +143,8 @@ module OData
175
143
  attr_accessor :xname
176
144
  attr_accessor :xnamespace
177
145
  attr_accessor :xpath_prefix
178
- # attr_accessor :xuribase
146
+ attr_accessor :xserver_url
147
+ attr_accessor :uribase
179
148
  attr_accessor :meta
180
149
  attr_accessor :batch_handler
181
150
  attr_accessor :relman
@@ -200,7 +169,8 @@ module OData
200
169
 
201
170
  DATASERVICEVERSION_RGX = /\A([1234])(?:\.0);*\w*\z/.freeze
202
171
  TRAILING_SLASH = %r{/\z}.freeze
203
- DEFAULT_PATH_PREFIX = '/'
172
+ DEFAULT_PATH_PREFIX = '/'.freeze
173
+ DEFAULT_SERVER_URL = 'http://localhost:9494'
204
174
 
205
175
  # input is the DataServiceVersion request header string, eg.
206
176
  # '2.0;blabla' ---> Version -> 2
@@ -243,40 +213,67 @@ module OData
243
213
  (@v1.xpath_prefix = @xpath_prefix) if @v1
244
214
  (@v2.xpath_prefix = @xpath_prefix) if @v2
245
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
+
246
223
  # end public API
247
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
+
248
239
  def copy_attribs_to(other)
249
240
  other.cmap = @cmap
250
241
  other.collections = @collections
242
+ other.allowed_transitions = @allowed_transitions
251
243
  other.xtitle = @xtitle
252
244
  other.xname = @xname
253
245
  other.xnamespace = @xnamespace
254
246
  other.xpath_prefix = @xpath_prefix
247
+ other.xserver_url = @xserver_url
248
+ other.uribase = @uribase
255
249
  other.meta = ServiceMeta.new(other) # hum ... #todo: versions as well ?
256
250
  other.relman = @relman
257
251
  other.batch_handler = @batch_handler
258
252
  other
259
253
  end
260
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
261
261
  def register_model(modelklass, entity_set_name = nil, is_media = false)
262
262
  # check that the provided klass is a Sequel Model
263
- unless modelklass.is_a? Sequel::Model::ClassMethods
264
- raise OData::API::ModelNameError, modelklass
265
- end
263
+
264
+ raise(OData::API::ModelNameError, modelklass) unless modelklass.is_a? Sequel::Model::ClassMethods
266
265
 
267
266
  if modelklass.ancestors.include? OData::Entity
268
267
  # modules were already added previously;
269
268
  # cleanup state to avoid having data from previous calls
270
269
  # mostly usefull for testing (eg API)
271
270
  modelklass.reset
272
- else # first API call... (normal non-testing case)
273
- if modelklass.primary_key.is_a?(Array)
274
- modelklass.extend OData::EntityClassMultiPK
275
- modelklass.include OData::EntityMultiPK
276
- else
277
- modelklass.extend OData::EntityClassSinglePK
278
- modelklass.include OData::EntitySinglePK
279
- end
271
+ elsif modelklass.primary_key.is_a?(Array) # first API call... (normal non-testing case)
272
+ modelklass.extend OData::EntityClassMultiPK
273
+ modelklass.include OData::EntityMultiPK
274
+ else
275
+ modelklass.extend OData::EntityClassSinglePK
276
+ modelklass.include OData::EntitySinglePK
280
277
  end
281
278
  # Media/Non-media
282
279
  if is_media
@@ -326,15 +323,14 @@ module OData
326
323
  # example: CrewMember must be matched before Crew otherwise we get error
327
324
  def set_collections_sorted(coll_data)
328
325
  @collections = coll_data
329
- if @collections
330
- @collections.sort_by! { |klass| klass.entity_set_name.size }.reverse!
331
- end
326
+ @collections.sort_by! { |klass| klass.entity_set_name.size }.reverse! if @collections
332
327
  @collections
333
328
  end
334
329
 
335
330
  # to be called at end of publishing block to ensure we get the right names
336
331
  # and additionally build the list of valid attribute path's used
337
332
  # for validation of $orderby or $filter params
333
+
338
334
  def finalize_publishing
339
335
  # build the cmap
340
336
  @cmap = {}
@@ -348,8 +344,36 @@ module OData
348
344
  # set default path prefix if path_prefix was not called
349
345
  path_prefix(DEFAULT_PATH_PREFIX) unless @xpath_prefix
350
346
 
351
- # and finally build the path list
352
- @collections.each(&:build_attribute_path_list)
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
+
352
+ @collections.each(&:finalize_publishing)
353
+
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
353
377
  end
354
378
 
355
379
  def execute_deferred_iblocks
@@ -374,7 +398,7 @@ module OData
374
398
  def service_xml(req)
375
399
  doc = REXML::Document.new
376
400
  # separator required ? ?
377
- root = doc.add_element('service', 'xml:base' => req.uribase)
401
+ root = doc.add_element('service', 'xml:base' => @uribase)
378
402
 
379
403
  root.add_namespace('xmlns:atom', XMLNS::W3_2005_ATOM)
380
404
  root.add_namespace('xmlns:app', XMLNS::W3_2007_APP)
@@ -397,7 +421,7 @@ module OData
397
421
  def add_metadata_xml_entity_type(schema)
398
422
  @collections.each do |klass|
399
423
  enty = klass.add_metadata_rexml(schema)
400
- klass.add_metadata_navs_rexml(enty, @cmap, @relman, @xnamespace)
424
+ klass.add_metadata_navs_rexml(enty, @relman, @xnamespace)
401
425
  end
402
426
  end
403
427
 
@@ -485,15 +509,22 @@ module OData
485
509
  # methods related to transitions to next state (cf. walker)
486
510
  module Transitions
487
511
  DOLLAR_ID_REGEXP = Regexp.new('\A\/\$').freeze
488
- def allowed_transitions
489
- @allowed_transitions = [
490
- Safrano::TransitionEnd,
491
- Safrano::TransitionMetadata,
492
- Safrano::TransitionBatch,
493
- Safrano::TransitionContentId,
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 + [
494
521
  Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
495
522
  trans: 'transition_collection')
496
- ]
523
+ ]).freeze
524
+ end
525
+
526
+ def allowed_transitions
527
+ @allowed_transitions
497
528
  end
498
529
 
499
530
  def transition_collection(match_result)
@@ -540,22 +571,22 @@ module OData
540
571
  @data_service_version = '1.0'
541
572
  end
542
573
 
543
- def get_coll_odata_links_h(array:, uribase:, icount: nil)
574
+ def get_coll_odata_links_h(array:, icount: nil)
544
575
  array.map do |w|
545
- get_entity_odata_link_h(entity: w, uribase: uribase)
576
+ get_entity_odata_link_h(entity: w)
546
577
  end
547
578
  end
548
579
 
549
- def get_coll_odata_h(array:, expand: nil, uribase:, icount: nil)
550
- array.map do |w|
580
+ def get_coll_odata_h(array:, template:, icount: nil)
581
+ array.map! do |w|
551
582
  get_entity_odata_h(entity: w,
552
- expand: expand,
553
- uribase: uribase)
583
+ template: template)
554
584
  end
585
+ array
555
586
  end
556
587
 
557
588
  def get_emptycoll_odata_h
558
- [{}]
589
+ EMPTY_HASH_IN_ARY
559
590
  end
560
591
  end
561
592
 
@@ -565,19 +596,19 @@ module OData
565
596
  @data_service_version = '2.0'
566
597
  end
567
598
 
568
- def get_coll_odata_links_h(array:, uribase:, icount: nil)
599
+ def get_coll_odata_links_h(array:, icount: nil)
569
600
  res = array.map do |w|
570
- get_entity_odata_link_h(entity: w, uribase: uribase)
601
+ get_entity_odata_link_h(entity: w)
571
602
  end
572
603
  if icount
573
- { 'results' => res, '__count' => icount }
604
+ { RESULTS_K => res, COUNT_K => icount }
574
605
  else
575
- { 'results' => res }
606
+ { RESULTS_K => res }
576
607
  end
577
608
  end
578
609
 
579
610
  def get_emptycoll_odata_h
580
- { 'results' => [{}] }
611
+ { RESULTS_K => EMPTY_HASH_IN_ARY }
581
612
  end
582
613
  end
583
614
 
@@ -588,9 +619,9 @@ module OData
588
619
  @service = service
589
620
  end
590
621
 
622
+ ALLOWED_TRANSITIONS_FIXED = [Safrano::TransitionEnd].freeze
591
623
  def allowed_transitions
592
- @allowed_transitions = [Safrano::Transition.new('',
593
- trans: 'transition_end')]
624
+ ALLOWED_TRANSITIONS_FIXED
594
625
  end
595
626
 
596
627
  def transition_end(_match_result)