safrano 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/core_ext/Dir/iter.rb +18 -0
  3. data/lib/core_ext/Hash/transform.rb +21 -0
  4. data/lib/core_ext/Integer/edm.rb +13 -0
  5. data/lib/core_ext/REXML/Document/output.rb +16 -0
  6. data/lib/core_ext/String/convert.rb +25 -0
  7. data/lib/core_ext/String/edm.rb +13 -0
  8. data/lib/core_ext/dir.rb +3 -0
  9. data/lib/core_ext/hash.rb +3 -0
  10. data/lib/core_ext/integer.rb +3 -0
  11. data/lib/core_ext/rexml.rb +3 -0
  12. data/lib/core_ext/string.rb +5 -0
  13. data/lib/odata/attribute.rb +6 -2
  14. data/lib/odata/batch.rb +9 -7
  15. data/lib/odata/collection.rb +136 -642
  16. data/lib/odata/collection_filter.rb +16 -40
  17. data/lib/odata/collection_media.rb +56 -37
  18. data/lib/odata/collection_order.rb +5 -2
  19. data/lib/odata/common_logger.rb +2 -0
  20. data/lib/odata/complex_type.rb +152 -0
  21. data/lib/odata/edm/primitive_types.rb +184 -0
  22. data/lib/odata/entity.rb +53 -117
  23. data/lib/odata/error.rb +142 -37
  24. data/lib/odata/expand.rb +20 -17
  25. data/lib/odata/filter/base.rb +4 -1
  26. data/lib/odata/filter/error.rb +43 -27
  27. data/lib/odata/filter/parse.rb +33 -25
  28. data/lib/odata/filter/sequel.rb +97 -56
  29. data/lib/odata/filter/sequel_function_adapter.rb +50 -49
  30. data/lib/odata/filter/token.rb +10 -10
  31. data/lib/odata/filter/tree.rb +75 -41
  32. data/lib/odata/function_import.rb +166 -0
  33. data/lib/odata/model_ext.rb +618 -0
  34. data/lib/odata/navigation_attribute.rb +9 -24
  35. data/lib/odata/relations.rb +5 -5
  36. data/lib/odata/select.rb +17 -5
  37. data/lib/odata/transition.rb +71 -0
  38. data/lib/odata/url_parameters.rb +100 -24
  39. data/lib/odata/walker.rb +15 -7
  40. data/lib/safrano.rb +18 -38
  41. data/lib/safrano/contract.rb +143 -0
  42. data/lib/safrano/core.rb +12 -94
  43. data/lib/safrano/core_ext.rb +13 -0
  44. data/lib/safrano/deprecation.rb +73 -0
  45. data/lib/safrano/multipart.rb +25 -20
  46. data/lib/safrano/rack_app.rb +61 -62
  47. data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -1
  48. data/lib/safrano/request.rb +95 -37
  49. data/lib/safrano/response.rb +4 -2
  50. data/lib/safrano/sequel_join_by_paths.rb +2 -2
  51. data/lib/safrano/service.rb +132 -94
  52. data/lib/safrano/version.rb +3 -1
  53. data/lib/sequel/plugins/join_by_paths.rb +6 -19
  54. metadata +24 -5
@@ -1,72 +1,47 @@
1
- #!/usr/bin/env ruby
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'rack'
4
- require_relative '../odata/walker.rb'
5
- require_relative 'request.rb'
6
- require_relative 'response.rb'
4
+ require_relative '../odata/walker'
5
+ require_relative 'request'
6
+ require_relative 'response'
7
7
 
8
- module OData
8
+ module Safrano
9
9
  # handle GET PUT etc
10
10
  module MethodHandlers
11
11
  def odata_options
12
- # cf. stackoverflow.com/questions/22924678/sinatra-delete-response-headers
13
-
14
- x = if @walker.status == :end
15
- headers.delete('Content-Type')
16
- @response.headers.delete('Content-Type')
17
- [200, EMPTY_HASH, '']
18
- else
19
- odata_error
20
- end
21
- @response.headers['Content-Type'] = ''
22
- x
23
- end
24
-
25
- def odata_error
26
- return @walker.error.odata_get(@request) unless @walker.error.nil?
27
-
28
- # this is too critical; raise a real Exception
29
- raise 'Walker construction failed with a unknown Error '
12
+ @walker.finalize.tap_error { |err| return err.odata_get(@request) }
13
+ .if_valid do |context|
14
+ # cf. stackoverflow.com/questions/22924678/sinatra-delete-response-headers
15
+ headers.delete('Content-Type')
16
+ @response.headers.delete('Content-Type')
17
+ @response.headers['Content-Type'] = ''
18
+ [200, EMPTY_HASH, '']
19
+ end
30
20
  end
31
21
 
32
22
  def odata_delete
33
- if @walker.status == :end
34
- @walker.end_context.odata_delete(@request)
35
- else
36
- odata_error
37
- end
23
+ @walker.finalize.tap_error { |err| return err.odata_get(@request) }
24
+ .if_valid { |context| context.odata_delete(@request) }
38
25
  end
39
26
 
40
27
  def odata_put
41
- if @walker.status == :end
42
- @walker.end_context.odata_put(@request)
43
- else
44
- odata_error
45
- end
28
+ @walker.finalize.tap_error { |err| return err.odata_get(@request) }
29
+ .if_valid { |context| context.odata_put(@request) }
46
30
  end
47
31
 
48
32
  def odata_patch
49
- if @walker.status == :end
50
- @walker.end_context.odata_patch(@request)
51
- else
52
- odata_error
53
- end
33
+ @walker.finalize.tap_error { |err| return err.odata_get(@request) }
34
+ .if_valid { |context| context.odata_patch(@request) }
54
35
  end
55
36
 
56
37
  def odata_get
57
- if @walker.status == :end
58
- @walker.end_context.odata_get(@request)
59
- else
60
- odata_error
61
- end
38
+ @walker.finalize.tap_error { |err| return err.odata_get(@request) }
39
+ .if_valid { |context| context.odata_get(@request) }
62
40
  end
63
41
 
64
42
  def odata_post
65
- if @walker.status == :end
66
- @walker.end_context.odata_post(@request)
67
- else
68
- odata_error
69
- end
43
+ @walker.finalize.tap_error { |err| return err.odata_get(@request) }
44
+ .if_valid { |context| context.odata_post(@request) }
70
45
  end
71
46
 
72
47
  def odata_head
@@ -83,18 +58,15 @@ module OData
83
58
  'Pragma' => 'no-cache' }.freeze
84
59
  DATASERVICEVERSION = 'DataServiceVersion'.freeze
85
60
  include MethodHandlers
61
+
86
62
  def before
87
63
  @request.service_base = self.class.get_service_base
88
64
 
89
- neg_error = @request.negotiate_service_version
90
-
91
- raise RuntimeError if neg_error
92
-
93
- return false unless @request.service
94
-
95
- myhdrs = NOCACHE_HDRS.dup
96
- myhdrs[DATASERVICEVERSION] = @request.service.data_service_version
97
- headers myhdrs
65
+ @request.negotiate_service_version.tap_valid do
66
+ myhdrs = NOCACHE_HDRS.dup
67
+ myhdrs[DATASERVICEVERSION] = @request.service.data_service_version
68
+ headers myhdrs
69
+ end
98
70
  end
99
71
 
100
72
  # dispatch for all methods requiring parsing of the path
@@ -119,6 +91,11 @@ module OData
119
91
  end
120
92
  end
121
93
 
94
+ def dispatch_error(err)
95
+ @response.status, rsph, @response.body = err.odata_get(@request)
96
+ headers rsph
97
+ end
98
+
122
99
  def dispatch
123
100
  req_ret = if @request.request_method !~ METHODS_REGEXP
124
101
  [404, EMPTY_HASH, ['Did you get lost?']]
@@ -132,13 +109,23 @@ module OData
132
109
  end
133
110
 
134
111
  def call(env)
135
- @request = OData::Request.new(env)
136
- @response = OData::Response.new
112
+ # for thread safety
113
+ dup._call(env)
114
+ end
137
115
 
138
- before
116
+ def _call(env)
117
+ begin
118
+ @request = Safrano::Request.new(env)
119
+ @response = Safrano::Response.new
139
120
 
140
- dispatch
121
+ before.tap_error { |err| dispatch_error(err) }
122
+ .tap_valid { |res| dispatch }
141
123
 
124
+ # handle remaining Sequel errors that we couldnt prevent with our
125
+ # own pre-checks
126
+ rescue Sequel::Error => e
127
+ dispatch_error(SequelExceptionError.new(e))
128
+ end
142
129
  @response.finish
143
130
  end
144
131
 
@@ -167,10 +154,22 @@ module OData
167
154
  end
168
155
 
169
156
  def self.publish_service(&block)
170
- sbase = OData::ServiceBase.new
157
+ sbase = Safrano::ServiceBase.new
171
158
  sbase.instance_eval(&block) if block_given?
172
159
  sbase.finalize_publishing
173
160
  set_servicebase(sbase)
174
161
  end
175
162
  end
176
163
  end
164
+
165
+ # deprecated
166
+ # REMOVE 0.6
167
+ module OData
168
+ class ServerApp < Safrano::ServerApp
169
+ def self.publish_service(&block)
170
+ ::Safrano::Deprecation.deprecate('OData::ServerApp',
171
+ 'Use Safrano::ServerApp instead')
172
+ super
173
+ end
174
+ end
175
+ end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack'
2
4
  require 'rack/cors'
3
5
 
4
6
  module Rack
5
- module OData
7
+ module Safrano
6
8
  # just a Wrapper to ensure (force?) that mandatory middlewares are acutally
7
9
  # used
8
10
  class Builder < ::Rack::Builder
@@ -15,3 +17,18 @@ module Rack
15
17
  end
16
18
  end
17
19
  end
20
+
21
+ # deprecated
22
+ # REMOVE 0.6
23
+ module Rack
24
+ module OData
25
+ class Builder < ::Rack::Safrano::Builder
26
+ def initialize(default_app = nil, &block)
27
+ ::Safrano::Deprecation.deprecate('Rack::OData::Builder',
28
+ 'Use Rack::Safrano::Builder instead')
29
+
30
+ super
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack'
2
4
  require 'rfc2047'
3
5
 
4
- module OData
6
+ module Safrano
5
7
  # monkey patch deactivate Rack/multipart because it does not work on simple
6
8
  # OData $batch requests when the content-length
7
9
  # is not passed
@@ -15,6 +17,7 @@ module OData
15
17
  class AcceptEntry
16
18
  attr_accessor :params
17
19
  attr_reader :entry
20
+
18
21
  def initialize(entry)
19
22
  params = entry.scan(HEADER_PARAM).map! do |s|
20
23
  key, value = s.strip.split('=', 2)
@@ -94,7 +97,9 @@ module OData
94
97
  end
95
98
 
96
99
  def create_odata_walker
97
- @env['safrano.walker'] = @walker = Walker.new(@service, path_info, @content_id_references)
100
+ @env['safrano.walker'] = @walker = Walker.new(@service,
101
+ path_info,
102
+ @content_id_references)
98
103
  end
99
104
 
100
105
  def accept
@@ -157,44 +162,97 @@ module OData
157
162
  end
158
163
  end
159
164
 
160
- def negotiate_service_version
161
- maxv = if (rqv = env['HTTP_MAXDATASERVICEVERSION'])
162
- OData::ServiceBase.parse_data_service_version(rqv)
163
- else
164
- OData::MAX_DATASERVICE_VERSION
165
- end
166
- return OData::BadRequestError if maxv.nil?
167
- # client request an too old version --> 501
168
- return OData::NotImplementedError if maxv < OData::MIN_DATASERVICE_VERSION
169
-
170
- minv = if (rqv = env['HTTP_MINDATASERVICEVERSION'])
171
- OData::ServiceBase.parse_data_service_version(rqv)
172
- else
173
- OData::MIN_DATASERVICE_VERSION
174
- end
175
- return OData::BadRequestError if minv.nil?
176
- # client request an too new version --> 501
177
- return OData::NotImplementedError if minv > OData::MAX_DATASERVICE_VERSION
178
- return OData::BadRequestError if minv > maxv
179
-
180
- v = if (rqv = env['HTTP_DATASERVICEVERSION'])
181
- OData::ServiceBase.parse_data_service_version(rqv)
165
+ # input is the DataServiceVersion request header string, eg.
166
+ # '2.0;blabla' ---> Version -> 2
167
+ DATASERVICEVERSION_RGX = /\A([1234])(?:\.0);*\w*\z/.freeze
168
+
169
+ MAX_DTSV_PARSE_ERROR = Safrano::BadRequestError.new(
170
+ 'MaxDataServiceVersion could not be parsed'
171
+ ).freeze
172
+ def get_maxversion
173
+ if (rqv = env['HTTP_MAXDATASERVICEVERSION'])
174
+ if (m = DATASERVICEVERSION_RGX.match(rqv))
175
+ # client request an too old version --> 501
176
+ if (maxv = m[1]) < Safrano::MIN_DATASERVICE_VERSION
177
+ Safrano::VersionNotImplementedError
182
178
  else
183
- OData::MAX_DATASERVICE_VERSION
179
+ Contract.valid(maxv)
184
180
  end
181
+ else
182
+ MAX_DTSV_PARSE_ERROR
183
+ end
184
+ else
185
+ # not provided in request header --> take ours
186
+ Safrano::CV_MAX_DATASERVICE_VERSION
187
+ end
188
+ end
185
189
 
186
- return OData::BadRequestError if v.nil?
187
- return OData::NotImplementedError if v > OData::MAX_DATASERVICE_VERSION
188
- return OData::NotImplementedError if v < OData::MIN_DATASERVICE_VERSION
189
-
190
- @service = nil
191
- @service = case maxv
192
- when '1'
193
- @service_base.v1
194
- when '2', '3', '4'
195
- @service_base.v2
196
- end
197
- nil
190
+ DTSV_PARSE_ERROR = Safrano::BadRequestError.new(
191
+ 'DataServiceVersion could not be parsed'
192
+ ).freeze
193
+ def get_version
194
+ if (rqv = env['HTTP_DATASERVICEVERSION'])
195
+ if (m = DATASERVICEVERSION_RGX.match(rqv))
196
+ # client request an too new version --> 501
197
+ if ((v = m[1]) > Safrano::MAX_DATASERVICE_VERSION) or
198
+ (v < Safrano::MIN_DATASERVICE_VERSION)
199
+ Safrano::VersionNotImplementedError
200
+ else
201
+ Contract.valid(v)
202
+ end
203
+ else
204
+ DTSV_PARSE_ERROR
205
+ end
206
+ else
207
+ # not provided in request header --> take our maxv
208
+ Safrano::CV_MAX_DATASERVICE_VERSION
209
+ end
210
+ end
211
+
212
+ MIN_DTSV_PARSE_ERROR = Safrano::BadRequestError.new(
213
+ 'MinDataServiceVersion could not be parsed'
214
+ ).freeze
215
+ def get_minversion
216
+ if (rqv = env['HTTP_MINDATASERVICEVERSION'])
217
+ if (m = DATASERVICEVERSION_RGX.match(rqv))
218
+ # client request an too new version --> 501
219
+ if (minv = m[1]) > Safrano::MAX_DATASERVICE_VERSION
220
+ Safrano::VersionNotImplementedError
221
+ else
222
+ Contract.valid(minv)
223
+ end
224
+ else
225
+ MIN_DTSV_PARSE_ERROR
226
+ end
227
+ else
228
+ # not provided in request header --> take ours
229
+ Safrano::CV_MIN_DATASERVICE_VERSION
230
+ end
231
+ end
232
+
233
+ MAX_LT_MIN_DTSV_ERROR = Safrano::BadRequestError.new(
234
+ 'MinDataServiceVersion is larger as MaxDataServiceVersion'
235
+ ).freeze
236
+ def negotiate_service_version
237
+ get_maxversion.if_valid do |maxv|
238
+ get_minversion.if_valid do |minv|
239
+ return MAX_LT_MIN_DTSV_ERROR if minv > maxv
240
+
241
+ get_version.if_valid do |v|
242
+ @service = nil
243
+ @service = case maxv
244
+ when '1'
245
+ @service_base.v1
246
+ when '2', '3', '4'
247
+ @service_base.v2
248
+ else
249
+ return Safrano::VersionNotImplementedError
250
+ end
251
+
252
+ Contract::OK
253
+ end # valid get_version
254
+ end # valid get_minversion
255
+ end # valid get_maxversion
198
256
  end
199
257
  end
200
258
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack'
2
4
 
3
5
  # monkey patch deactivate Rack/multipart because it does not work on simple
4
6
  # OData $batch requests when the content-length is not passed
5
- module OData
7
+ module Safrano
6
8
  # borrowed fro Sinatra
7
9
  # The response object. See Rack::Response and Rack::Response::Helpers for
8
10
  # more info:
@@ -12,7 +14,7 @@ module OData
12
14
  DROP_BODY_RESPONSES = [204, 205, 304].freeze
13
15
  def initialize(*)
14
16
  super
15
- headers['Content-Type'] ||= 'text/html'
17
+ headers[CONTENT_TYPE] ||= APPJSON_UTF8
16
18
  end
17
19
 
18
20
  def body=(value)
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env ruby
1
+ # frozen_string_literal: true
2
2
 
3
- require_relative '../sequel/plugins/join_by_paths.rb'
3
+ require_relative '../sequel/plugins/join_by_paths'
4
4
 
5
5
  Sequel::Model.plugin Sequel::Plugins::JoinByPaths
@@ -1,14 +1,20 @@
1
- require 'rexml/document'
2
- require 'odata/relations.rb'
3
- require 'odata/batch.rb'
4
- require 'odata/error.rb'
5
- require 'odata/filter/sequel.rb'
1
+ # frozen_string_literal: true
6
2
 
7
- module OData
3
+ require 'rexml/document'
4
+ require 'odata/relations'
5
+ require 'odata/batch'
6
+ require 'odata/complex_type'
7
+ require 'odata/function_import'
8
+ require 'odata/error'
9
+ require 'odata/filter/sequel'
10
+ require 'set'
11
+ require 'odata/collection'
12
+
13
+ module Safrano
8
14
  # this module has all methods related to expand/defered output preparation
9
15
  # and will be included in Service class
10
16
  module ExpandHandler
11
- PATH_SPLITTER = %r{\A(\w+)\/?(.*)\z}.freeze
17
+ PATH_SPLITTER = %r{\A(\w+)/?(.*)\z}.freeze
12
18
  DEFERRED = '__deferred'.freeze
13
19
  URI = 'uri'.freeze
14
20
 
@@ -16,25 +22,6 @@ module OData
16
22
  { DEFERRED => { URI => "#{entity_uri}/#{attrib}" } }
17
23
  end
18
24
 
19
- # split expand argument into first and rest part
20
- def split_entity_expand_arg(exp_one)
21
- if exp_one.include?('/')
22
- m = PATH_SPLITTER.match(exp_one)
23
- cur_exp = m[1].strip
24
- rest_exp = m[2]
25
- # TODO: check errorhandling
26
- raise OData::ServerError if cur_exp.nil?
27
-
28
- k_s = cur_exp
29
-
30
- else
31
- k_s = exp_one.strip
32
- rest_exp = nil
33
- end
34
- k = k_s.to_sym
35
- yield k, k_s, rest_exp
36
- end
37
-
38
25
  # default v2
39
26
  # overriden in ServiceV1
40
27
  RESULTS_K = 'results'.freeze
@@ -43,11 +30,7 @@ module OData
43
30
  array.map! do |w|
44
31
  get_entity_odata_h(entity: w, template: template)
45
32
  end
46
- if icount
47
- { RESULTS_K => array, COUNT_K => icount }
48
- else
49
- { RESULTS_K => array }
50
- end
33
+ icount ? { RESULTS_K => array, COUNT_K => icount } : { RESULTS_K => array }
51
34
  end
52
35
 
53
36
  # handle $links ... Note: $expand seems to be ignored when $links
@@ -66,8 +49,10 @@ module OData
66
49
  case elmt
67
50
  when :all_values
68
51
  hres.merge! entity.casted_values
52
+
69
53
  when :selected_vals
70
54
  hres.merge! entity.casted_values(arg)
55
+
71
56
  when :expand_e
72
57
 
73
58
  arg.each do |attr, templ|
@@ -79,6 +64,7 @@ module OData
79
64
  EMPTYH
80
65
  end
81
66
  end
67
+
82
68
  when :expand_c
83
69
  arg.each do |attr, templ|
84
70
  next unless (encoll = entity.send(attr))
@@ -87,6 +73,7 @@ module OData
87
73
  hres[attr] = get_coll_odata_h(array: encoll, template: templ)
88
74
  # else error ?
89
75
  end
76
+
90
77
  when :deferr
91
78
  euri = entity.uri
92
79
  arg.each do |attr|
@@ -99,7 +86,7 @@ module OData
99
86
  end
100
87
  end
101
88
 
102
- module OData
89
+ module Safrano
103
90
  # xml namespace constants needed for the output of XML service and
104
91
  # and metadata
105
92
  module XMLNS
@@ -115,17 +102,18 @@ module OData
115
102
  end
116
103
 
117
104
  # Link to Model
118
- module OData
105
+ module Safrano
119
106
  MAX_DATASERVICE_VERSION = '2'.freeze
120
107
  MIN_DATASERVICE_VERSION = '1'.freeze
108
+ CV_MAX_DATASERVICE_VERSION = Contract.valid(MAX_DATASERVICE_VERSION).freeze
109
+ CV_MIN_DATASERVICE_VERSION = Contract.valid(MIN_DATASERVICE_VERSION).freeze
121
110
  include XMLNS
122
111
  # Base class for service. Subclass will be for V1, V2 etc...
123
112
  class ServiceBase
124
113
  include Safrano
125
114
  include ExpandHandler
126
115
 
127
- XML_PREAMBLE = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>' + \
128
- "\r\n".freeze
116
+ XML_PREAMBLE = %Q(<?xml version="1.0" encoding="utf-8" standalone="yes"?>\r\n).freeze
129
117
 
130
118
  # This is just a hash of entity Set Names to the corresponding Class
131
119
  # Example
@@ -148,6 +136,9 @@ module OData
148
136
  attr_accessor :meta
149
137
  attr_accessor :batch_handler
150
138
  attr_accessor :relman
139
+ attr_accessor :complex_types
140
+ attr_accessor :function_imports
141
+
151
142
  # Instance attributes for specialized Version specific Instances
152
143
  attr_accessor :v1
153
144
  attr_accessor :v2
@@ -161,37 +152,31 @@ module OData
161
152
  # because of the version subclasses that dont use "super" initialise
162
153
  # (todo: why not??)
163
154
  @meta = ServiceMeta.new(self)
164
- @batch_handler = OData::Batch::DisabledHandler.new
165
- @relman = OData::RelationManager.new
155
+ @batch_handler = Safrano::Batch::DisabledHandler.new
156
+ @relman = Safrano::RelationManager.new
157
+ @complex_types = Set.new
158
+ @function_imports = {}
166
159
  @cmap = {}
167
160
  instance_eval(&block) if block_given?
168
161
  end
169
162
 
170
- DATASERVICEVERSION_RGX = /\A([1234])(?:\.0);*\w*\z/.freeze
171
163
  TRAILING_SLASH = %r{/\z}.freeze
172
164
  DEFAULT_PATH_PREFIX = '/'.freeze
173
- DEFAULT_SERVER_URL = 'http://localhost:9494'
174
-
175
- # input is the DataServiceVersion request header string, eg.
176
- # '2.0;blabla' ---> Version -> 2
177
- def self.parse_data_service_version(inp)
178
- m = DATASERVICEVERSION_RGX.match(inp)
179
- m[1] if m
180
- end
165
+ DEFAULT_SERVER_URL = 'http://localhost:9494'.freeze
181
166
 
182
167
  def enable_batch
183
- @batch_handler = OData::Batch::EnabledHandler.new
168
+ @batch_handler = Safrano::Batch::EnabledHandler.new
184
169
  (@v1.batch_handler = @batch_handler) if @v1
185
170
  (@v2.batch_handler = @batch_handler) if @v2
186
171
  end
187
172
 
188
173
  def enable_v1_service
189
- @v1 = OData::ServiceV1.new
174
+ @v1 = Safrano::ServiceV1.new
190
175
  copy_attribs_to @v1
191
176
  end
192
177
 
193
178
  def enable_v2_service
194
- @v2 = OData::ServiceV2.new
179
+ @v2 = Safrano::ServiceV2.new
195
180
  copy_attribs_to @v2
196
181
  end
197
182
 
@@ -225,12 +210,10 @@ module OData
225
210
  def set_uribase
226
211
  @uribase = if @xpath_prefix.empty?
227
212
  @xserver_url
213
+ elsif @xpath_prefix[0] == '/'
214
+ "#{@xserver_url}#{@xpath_prefix}"
228
215
  else
229
- if @xpath_prefix[0] == '/'
230
- @xserver_url + @xpath_prefix
231
- else
232
- @xserver_url + '/' + @xpath_prefix
233
- end
216
+ "#{@xserver_url}/#{@xpath_prefix}"
234
217
  end
235
218
  (@v1.uribase = @uribase) if @v1
236
219
  (@v2.uribase = @uribase) if @v2
@@ -249,6 +232,8 @@ module OData
249
232
  other.meta = ServiceMeta.new(other) # hum ... #todo: versions as well ?
250
233
  other.relman = @relman
251
234
  other.batch_handler = @batch_handler
235
+ other.complex_types = @complex_types
236
+ other.function_imports = @function_imports
252
237
  other
253
238
  end
254
239
 
@@ -261,38 +246,43 @@ module OData
261
246
  def register_model(modelklass, entity_set_name = nil, is_media = false)
262
247
  # check that the provided klass is a Sequel Model
263
248
 
264
- raise(OData::API::ModelNameError, modelklass) unless modelklass.is_a? Sequel::Model::ClassMethods
249
+ raise(Safrano::API::ModelNameError, modelklass) unless modelklass.is_a? Sequel::Model::ClassMethods
265
250
 
266
- if modelklass.ancestors.include? OData::Entity
251
+ if modelklass.ancestors.include? Safrano::Entity
267
252
  # modules were already added previously;
268
253
  # cleanup state to avoid having data from previous calls
269
254
  # mostly usefull for testing (eg API)
270
255
  modelklass.reset
271
256
  elsif modelklass.primary_key.is_a?(Array) # first API call... (normal non-testing case)
272
- modelklass.extend OData::EntityClassMultiPK
273
- modelklass.include OData::EntityMultiPK
257
+ modelklass.extend Safrano::EntityClassMultiPK
258
+ modelklass.include Safrano::EntityMultiPK
274
259
  else
275
- modelklass.extend OData::EntityClassSinglePK
276
- modelklass.include OData::EntitySinglePK
260
+ modelklass.extend Safrano::EntityClassSinglePK
261
+ modelklass.include Safrano::EntitySinglePK
277
262
  end
263
+
278
264
  # Media/Non-media
279
265
  if is_media
280
- modelklass.extend OData::EntityClassMedia
266
+ modelklass.extend Safrano::EntityClassMedia
281
267
  # set default media handler . Can be overridden later with the
282
268
  # "use HandlerKlass, options" API
283
269
 
284
270
  modelklass.set_default_media_handler
285
271
  modelklass.api_check_media_fields
286
- modelklass.include OData::MediaEntity
272
+ modelklass.include Safrano::MediaEntity
287
273
  else
288
- modelklass.extend OData::EntityClassNonMedia
289
- modelklass.include OData::NonMediaEntity
274
+ modelklass.extend Safrano::EntityClassNonMedia
275
+ modelklass.include Safrano::NonMediaEntity
290
276
  end
291
277
 
292
278
  modelklass.prepare_pk
293
279
  modelklass.prepare_fields
294
280
  esname = (entity_set_name || modelklass).to_s.freeze
295
- modelklass.instance_eval { @entity_set_name = esname }
281
+ serv_namespace = @xnamespace
282
+ modelklass.instance_eval do
283
+ @entity_set_name = esname
284
+ @namespace = serv_namespace
285
+ end
296
286
  @cmap[esname] = modelklass
297
287
  set_collections_sorted(@cmap.values)
298
288
  end
@@ -313,6 +303,23 @@ module OData
313
303
  modelklass.deferred_iblock = block if block_given?
314
304
  end
315
305
 
306
+ def publish_complex_type(ctklass)
307
+ # check that the provided klass is a Safrano ComplexType
308
+
309
+ raise(Safrano::API::ComplexTypeNameError, ctklass) unless ctklass.superclass == Safrano::ComplexType
310
+
311
+ serv_namespace = @xnamespace
312
+ ctklass.instance_eval { @namespace = serv_namespace }
313
+
314
+ @complex_types.add ctklass
315
+ end
316
+
317
+ def function_import(name)
318
+ funcimp = Safrano::FunctionImport(name)
319
+ @function_imports[name] = funcimp
320
+ funcimp
321
+ end
322
+
316
323
  def cmap=(imap)
317
324
  @cmap = imap
318
325
  set_collections_sorted(@cmap.values)
@@ -352,14 +359,10 @@ module OData
352
359
  @collections.each(&:finalize_publishing)
353
360
 
354
361
  # finalize the uri's and include NoMappingBeforeOutput or MappingBeforeOutput as needed
355
- @collections.each { |klass|
362
+ @collections.each do |klass|
356
363
  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
- }
364
+ klass.include(klass.time_cols.empty? ? Safrano::NoMappingBeforeOutput : Safrano::MappingBeforeOutput)
365
+ end
363
366
 
364
367
  # build allowed transitions (requires that @collections are filled and sorted for having a
365
368
  # correct base_url_regexp)
@@ -368,11 +371,11 @@ module OData
368
371
  # mixin adapter specific modules where needed
369
372
  case Sequel::Model.db.adapter_scheme
370
373
  when :postgres
371
- OData::Filter::FuncTree.include OData::Filter::FuncTreePostgres
374
+ Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreePostgres
372
375
  when :sqlite
373
- OData::Filter::FuncTree.include OData::Filter::FuncTreeSqlite
376
+ Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreeSqlite
374
377
  else
375
- OData::Filter::FuncTree.include OData::Filter::FuncTreeDefault
378
+ Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreeDefault
376
379
  end
377
380
  end
378
381
 
@@ -389,19 +392,24 @@ module OData
389
392
  @collections.map(&:entity_set_name).join('|')
390
393
  end
391
394
 
395
+ def base_url_func_regexp
396
+ @function_imports.keys.join('|')
397
+ end
398
+
392
399
  def service
393
400
  hres = {}
394
401
  hres['d'] = { 'EntitySets' => @collections.map(&:type_name) }
395
402
  hres
396
403
  end
397
404
 
398
- def service_xml(req)
405
+ def service_xml(_req)
399
406
  doc = REXML::Document.new
400
407
  # separator required ? ?
401
408
  root = doc.add_element('service', 'xml:base' => @uribase)
402
409
 
403
410
  root.add_namespace('xmlns:atom', XMLNS::W3_2005_ATOM)
404
411
  root.add_namespace('xmlns:app', XMLNS::W3_2007_APP)
412
+
405
413
  # this generates the main xmlns attribute
406
414
  root.add_namespace(XMLNS::W3_2007_APP)
407
415
  wp = root.add_element 'workspace'
@@ -412,7 +420,7 @@ module OData
412
420
  @collections.each do |klass|
413
421
  col = wp.add_element('collection', 'href' => klass.entity_set_name)
414
422
  ct = col.add_element('atom:title')
415
- ct.text = klass.type_name
423
+ ct.text = klass.entity_set_name
416
424
  end
417
425
 
418
426
  XML_PREAMBLE + doc.to_pretty_xml
@@ -421,10 +429,18 @@ module OData
421
429
  def add_metadata_xml_entity_type(schema)
422
430
  @collections.each do |klass|
423
431
  enty = klass.add_metadata_rexml(schema)
424
- klass.add_metadata_navs_rexml(enty, @relman, @xnamespace)
432
+ klass.add_metadata_navs_rexml(enty, @relman)
425
433
  end
426
434
  end
427
435
 
436
+ def add_metadata_xml_complex_types(schema)
437
+ @complex_types.each { |ctklass| ctklass.add_metadata_rexml(schema) }
438
+ end
439
+
440
+ def add_metadata_xml_function_imports(ec)
441
+ @function_imports.each_value { |func| func.add_metadata_rexml(ec) }
442
+ end
443
+
428
444
  def add_metadata_xml_associations(schema)
429
445
  @relman.each_rel do |rel|
430
446
  rel.with_metadata_info(@xnamespace) do |name, bdinfo|
@@ -447,7 +463,7 @@ module OData
447
463
  # 3.a Entity set's
448
464
  ec.add_element('EntitySet',
449
465
  'Name' => klass.entity_set_name,
450
- 'EntityType' => "#{@xnamespace}.#{klass.type_name}")
466
+ 'EntityType' => klass.type_name)
451
467
  end
452
468
  # 3.b Association set's
453
469
  @relman.each_rel do |rel|
@@ -461,6 +477,9 @@ module OData
461
477
  assoc.add_element('End', assoend)
462
478
  end
463
479
  end
480
+
481
+ # 4 function imports
482
+ add_metadata_xml_function_imports(ec)
464
483
  end
465
484
 
466
485
  def metadata_xml(_req)
@@ -494,9 +513,12 @@ module OData
494
513
  schema = serv.add_element('Schema',
495
514
  'Namespace' => @xnamespace,
496
515
  'xmlns' => XMLNS::MSFT_ADO_2009_EDM)
497
- # 1. all EntityType
516
+ # 1. a. all EntityType
498
517
  add_metadata_xml_entity_type(schema)
499
518
 
519
+ # 1. b. all ComplexType
520
+ add_metadata_xml_complex_types(schema)
521
+
500
522
  # 2. Associations
501
523
  add_metadata_xml_associations(schema)
502
524
 
@@ -517,18 +539,36 @@ module OData
517
539
  ].freeze
518
540
 
519
541
  def build_allowed_transitions
520
- @allowed_transitions = (ALLOWED_TRANSITIONS_FIXED + [
521
- Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
522
- trans: 'transition_collection')
523
- ]).freeze
542
+ @allowed_transitions = if @function_imports.empty?
543
+ (ALLOWED_TRANSITIONS_FIXED + [
544
+ Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
545
+ trans: 'transition_collection')
546
+ ]).freeze
547
+ else
548
+ (ALLOWED_TRANSITIONS_FIXED + [
549
+ Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
550
+ trans: 'transition_collection'),
551
+ Safrano::Transition.new(%r{\A/(#{base_url_func_regexp})(.*)},
552
+ trans: 'transition_service_op')
553
+ ]).freeze
554
+ end
524
555
  end
525
556
 
526
557
  def allowed_transitions
527
558
  @allowed_transitions
528
559
  end
529
560
 
561
+ def thread_safe_collection(collklass)
562
+ Safrano::OData::Collection.new(collklass)
563
+ end
564
+
530
565
  def transition_collection(match_result)
531
- [@cmap[match_result[1]], :run] if match_result[1]
566
+ [thread_safe_collection(@cmap[match_result[1]]), :run] if match_result[1]
567
+ # [@cmap[match_result[1]], :run] if match_result[1]
568
+ end
569
+
570
+ def transition_service_op(match_result)
571
+ [@function_imports[match_result[1]], :run] if match_result[1]
532
572
  end
533
573
 
534
574
  def transition_batch(_match_result)
@@ -544,7 +584,7 @@ module OData
544
584
  end
545
585
 
546
586
  def transition_end(_match_result)
547
- [nil, :end]
587
+ Safrano::Transition::RESULT_END
548
588
  end
549
589
  end
550
590
 
@@ -552,14 +592,11 @@ module OData
552
592
 
553
593
  def odata_get(req)
554
594
  if req.accept?(APPXML)
555
- # app.headers 'Content-Type' => 'application/xml;charset=utf-8'
556
- # Doc: 2.2.3.7.1 Service Document As per [RFC5023], AtomPub Service
557
- # Documents MUST be
558
- # identified with the "application/atomsvc+xml" media type (see
559
- # [RFC5023] section 8).
560
- [200, CT_ATOMXML, [service_xml(req)]]
595
+ # OData V2 reference service implementations are returning app-xml-u8
596
+ # so we do
597
+ [200, CT_APPXML, [service_xml(req)]]
561
598
  else
562
- # this is returned by http://services.odata.org/V2/OData/OData.svc
599
+ # this is returned by http://services.odata.org/V2/OData/Safrano.svc
563
600
  415
564
601
  end
565
602
  end
@@ -615,6 +652,7 @@ module OData
615
652
  # a virtual entity for the service metadata
616
653
  class ServiceMeta
617
654
  attr_accessor :service
655
+
618
656
  def initialize(service)
619
657
  @service = service
620
658
  end
@@ -625,7 +663,7 @@ module OData
625
663
  end
626
664
 
627
665
  def transition_end(_match_result)
628
- [nil, :end]
666
+ Safrano::Transition::RESULT_END
629
667
  end
630
668
 
631
669
  def odata_get(req)