safrano 0.4.2 → 0.5.0
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/core_ext/Dir/iter.rb +18 -0
- data/lib/core_ext/Hash/transform.rb +21 -0
- data/lib/core_ext/Integer/edm.rb +13 -0
- data/lib/core_ext/REXML/Document/output.rb +16 -0
- data/lib/core_ext/String/convert.rb +25 -0
- data/lib/core_ext/String/edm.rb +13 -0
- data/lib/core_ext/dir.rb +3 -0
- data/lib/core_ext/hash.rb +3 -0
- data/lib/core_ext/integer.rb +3 -0
- data/lib/core_ext/rexml.rb +3 -0
- data/lib/core_ext/string.rb +5 -0
- data/lib/odata/attribute.rb +15 -10
- data/lib/odata/batch.rb +9 -7
- data/lib/odata/collection.rb +140 -591
- data/lib/odata/collection_filter.rb +18 -42
- data/lib/odata/collection_media.rb +111 -54
- data/lib/odata/collection_order.rb +5 -2
- data/lib/odata/common_logger.rb +2 -0
- data/lib/odata/complex_type.rb +152 -0
- data/lib/odata/edm/primitive_types.rb +184 -0
- data/lib/odata/entity.rb +123 -172
- data/lib/odata/error.rb +183 -32
- data/lib/odata/expand.rb +20 -17
- data/lib/odata/filter/base.rb +74 -0
- data/lib/odata/filter/error.rb +49 -6
- data/lib/odata/filter/parse.rb +41 -25
- data/lib/odata/filter/sequel.rb +133 -62
- data/lib/odata/filter/sequel_function_adapter.rb +148 -0
- data/lib/odata/filter/token.rb +26 -19
- data/lib/odata/filter/tree.rb +106 -52
- data/lib/odata/function_import.rb +168 -0
- data/lib/odata/model_ext.rb +639 -0
- data/lib/odata/navigation_attribute.rb +13 -26
- data/lib/odata/relations.rb +5 -5
- data/lib/odata/select.rb +17 -5
- data/lib/odata/transition.rb +71 -0
- data/lib/odata/url_parameters.rb +100 -24
- data/lib/odata/walker.rb +20 -10
- data/lib/safrano.rb +18 -38
- data/lib/safrano/contract.rb +143 -0
- data/lib/safrano/core.rb +23 -107
- data/lib/safrano/core_ext.rb +13 -0
- data/lib/safrano/deprecation.rb +73 -0
- data/lib/safrano/multipart.rb +29 -33
- data/lib/safrano/rack_app.rb +66 -65
- data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -2
- data/lib/safrano/request.rb +96 -45
- data/lib/safrano/response.rb +4 -2
- data/lib/safrano/sequel_join_by_paths.rb +2 -2
- data/lib/safrano/service.rb +240 -130
- data/lib/safrano/version.rb +3 -1
- data/lib/sequel/plugins/join_by_paths.rb +6 -19
- metadata +32 -11
data/lib/safrano/rack_app.rb
CHANGED
@@ -1,72 +1,47 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rack'
|
4
|
-
require_relative '../odata/walker
|
5
|
-
require_relative 'request
|
6
|
-
require_relative 'response
|
4
|
+
require_relative '../odata/walker'
|
5
|
+
require_relative 'request'
|
6
|
+
require_relative 'response'
|
7
7
|
|
8
|
-
module
|
8
|
+
module Safrano
|
9
9
|
# handle GET PUT etc
|
10
10
|
module MethodHandlers
|
11
11
|
def odata_options
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
34
|
-
|
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
|
-
|
42
|
-
|
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
|
-
|
50
|
-
|
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
|
-
|
58
|
-
|
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
|
-
|
66
|
-
|
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
|
@@ -77,22 +52,21 @@ module OData
|
|
77
52
|
# the main Rack server app. Source: the Rack docu/examples and partly
|
78
53
|
# inspired from Sinatra
|
79
54
|
class ServerApp
|
80
|
-
METHODS_REGEXP = Regexp.new('HEAD|OPTIONS|GET|POST|PATCH|MERGE|PUT|DELETE')
|
55
|
+
METHODS_REGEXP = Regexp.new('HEAD|OPTIONS|GET|POST|PATCH|MERGE|PUT|DELETE').freeze
|
56
|
+
NOCACHE_HDRS = { 'Cache-Control' => 'no-cache',
|
57
|
+
'Expires' => '-1',
|
58
|
+
'Pragma' => 'no-cache' }.freeze
|
59
|
+
DATASERVICEVERSION = 'DataServiceVersion'
|
81
60
|
include MethodHandlers
|
82
|
-
def before
|
83
|
-
headers 'Cache-Control' => 'no-cache'
|
84
|
-
headers 'Expires' => '-1'
|
85
|
-
headers 'Pragma' => 'no-cache'
|
86
61
|
|
62
|
+
def before
|
87
63
|
@request.service_base = self.class.get_service_base
|
88
64
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
headers 'DataServiceVersion' => @request.service.data_service_version
|
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
|
96
70
|
end
|
97
71
|
|
98
72
|
# dispatch for all methods requiring parsing of the path
|
@@ -117,6 +91,11 @@ module OData
|
|
117
91
|
end
|
118
92
|
end
|
119
93
|
|
94
|
+
def dispatch_error(err)
|
95
|
+
@response.status, rsph, @response.body = err.odata_get(@request)
|
96
|
+
headers rsph
|
97
|
+
end
|
98
|
+
|
120
99
|
def dispatch
|
121
100
|
req_ret = if @request.request_method !~ METHODS_REGEXP
|
122
101
|
[404, EMPTY_HASH, ['Did you get lost?']]
|
@@ -130,13 +109,23 @@ module OData
|
|
130
109
|
end
|
131
110
|
|
132
111
|
def call(env)
|
133
|
-
|
134
|
-
|
112
|
+
# for thread safety
|
113
|
+
dup._call(env)
|
114
|
+
end
|
135
115
|
|
136
|
-
|
116
|
+
def _call(env)
|
117
|
+
begin
|
118
|
+
@request = Safrano::Request.new(env)
|
119
|
+
@response = Safrano::Response.new
|
137
120
|
|
138
|
-
|
121
|
+
before.tap_error { |err| dispatch_error(err) }
|
122
|
+
.tap_valid { |res| dispatch }
|
139
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
|
140
129
|
@response.finish
|
141
130
|
end
|
142
131
|
|
@@ -165,10 +154,22 @@ module OData
|
|
165
154
|
end
|
166
155
|
|
167
156
|
def self.publish_service(&block)
|
168
|
-
sbase =
|
157
|
+
sbase = Safrano::ServiceBase.new
|
169
158
|
sbase.instance_eval(&block) if block_given?
|
170
159
|
sbase.finalize_publishing
|
171
160
|
set_servicebase(sbase)
|
172
161
|
end
|
173
162
|
end
|
174
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
|
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
|
@@ -10,9 +12,23 @@ module Rack
|
|
10
12
|
super(default_app) {}
|
11
13
|
use ::Rack::Cors
|
12
14
|
instance_eval(&block) if block_given?
|
13
|
-
use ::Rack::Lint
|
14
15
|
use ::Rack::ContentLength
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
18
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
|
data/lib/safrano/request.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack'
|
2
4
|
require 'rfc2047'
|
3
5
|
|
4
|
-
module
|
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
|
8
10
|
class Request < Rack::Request
|
9
11
|
HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/.freeze
|
10
|
-
HEADER_VAL_RAW = '(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*'
|
12
|
+
HEADER_VAL_RAW = '(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*'
|
11
13
|
HEADER_VAL_WITH_PAR = /(?:#{HEADER_VAL_RAW})\s*(?:;#{HEADER_PARAM})*/.freeze
|
12
14
|
ON_CGST_ERROR = (proc { |r| raise(Sequel::Rollback) if r.in_changeset })
|
13
15
|
|
@@ -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,
|
100
|
+
@env['safrano.walker'] = @walker = Walker.new(@service,
|
101
|
+
path_info,
|
102
|
+
@content_id_references)
|
98
103
|
end
|
99
104
|
|
100
105
|
def accept
|
@@ -108,13 +113,6 @@ module OData
|
|
108
113
|
end
|
109
114
|
end
|
110
115
|
|
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
116
|
def accept?(type)
|
119
117
|
preferred_type(type).to_s.include?(type)
|
120
118
|
end
|
@@ -164,44 +162,97 @@ module OData
|
|
164
162
|
end
|
165
163
|
end
|
166
164
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
178
|
+
else
|
179
|
+
Contract.valid(maxv)
|
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
|
189
|
+
|
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) ||
|
198
|
+
(v < Safrano::MIN_DATASERVICE_VERSION)
|
199
|
+
Safrano::VersionNotImplementedError
|
189
200
|
else
|
190
|
-
|
201
|
+
Contract.valid(v)
|
191
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
|
192
232
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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
|
205
256
|
end
|
206
257
|
end
|
207
258
|
end
|
data/lib/safrano/response.rb
CHANGED
@@ -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
|
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[
|
17
|
+
headers[CONTENT_TYPE] ||= APPJSON_UTF8
|
16
18
|
end
|
17
19
|
|
18
20
|
def body=(value)
|
data/lib/safrano/service.rb
CHANGED
@@ -1,97 +1,83 @@
|
|
1
|
-
|
2
|
-
require 'odata/relations.rb'
|
3
|
-
require 'odata/batch.rb'
|
4
|
-
require 'odata/error.rb'
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
|
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
|
7
14
|
# this module has all methods related to expand/defered output preparation
|
8
15
|
# and will be included in Service class
|
9
16
|
module ExpandHandler
|
10
|
-
PATH_SPLITTER = %r{\A(\w+)
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
# split expand argument into first and rest part
|
16
|
-
def split_entity_expand_arg(exp_one)
|
17
|
-
if exp_one.include?('/')
|
18
|
-
m = PATH_SPLITTER.match(exp_one)
|
19
|
-
cur_exp = m[1].strip
|
20
|
-
rest_exp = m[2]
|
21
|
-
# TODO: check errorhandling
|
22
|
-
raise OData::ServerError if cur_exp.nil?
|
17
|
+
PATH_SPLITTER = %r{\A(\w+)/?(.*)\z}.freeze
|
18
|
+
DEFERRED = '__deferred'
|
19
|
+
URI = 'uri'
|
23
20
|
|
24
|
-
|
25
|
-
|
26
|
-
else
|
27
|
-
k_s = exp_one.strip
|
28
|
-
rest_exp = nil
|
29
|
-
end
|
30
|
-
k = k_s.to_sym
|
31
|
-
yield k, k_s, rest_exp
|
21
|
+
def get_deferred_odata_h(entity_uri:, attrib:)
|
22
|
+
{ DEFERRED => { URI => "#{entity_uri}/#{attrib}" } }
|
32
23
|
end
|
33
24
|
|
34
25
|
# default v2
|
35
26
|
# overriden in ServiceV1
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
if icount
|
43
|
-
{ 'results' => res, '__count' => icount }
|
44
|
-
else
|
45
|
-
{ 'results' => res }
|
27
|
+
RESULTS_K = 'results'
|
28
|
+
COUNT_K = '__count'
|
29
|
+
def get_coll_odata_h(array:, template:, icount: nil)
|
30
|
+
array.map! do |w|
|
31
|
+
get_entity_odata_h(entity: w, template: template)
|
46
32
|
end
|
33
|
+
icount ? { RESULTS_K => array, COUNT_K => icount } : { RESULTS_K => array }
|
47
34
|
end
|
48
35
|
|
49
36
|
# handle $links ... Note: $expand seems to be ignored when $links
|
50
37
|
# are requested
|
51
|
-
def get_entity_odata_link_h(entity
|
52
|
-
{ uri: entity.uri
|
38
|
+
def get_entity_odata_link_h(entity:)
|
39
|
+
{ uri: entity.uri }
|
53
40
|
end
|
54
41
|
|
55
42
|
EMPTYH = {}.freeze
|
56
|
-
|
43
|
+
METADATA_K = '__metadata'
|
44
|
+
def get_entity_odata_h(entity:, template:)
|
57
45
|
# start with metadata
|
58
|
-
hres = {
|
46
|
+
hres = { METADATA_K => entity.metadata_h }
|
59
47
|
|
60
48
|
template.each do |elmt, arg|
|
61
49
|
case elmt
|
62
50
|
when :all_values
|
63
51
|
hres.merge! entity.casted_values
|
52
|
+
|
64
53
|
when :selected_vals
|
65
54
|
hres.merge! entity.casted_values(arg)
|
55
|
+
|
66
56
|
when :expand_e
|
67
57
|
|
68
58
|
arg.each do |attr, templ|
|
69
59
|
enval = entity.send(attr)
|
70
60
|
hres[attr] = if enval
|
71
|
-
|
72
|
-
get_entity_odata_h(entity: enval,
|
73
|
-
template: templ,
|
74
|
-
uribase: uribase)
|
61
|
+
get_entity_odata_h(entity: enval, template: templ)
|
75
62
|
else
|
76
63
|
# FK is NULL --> nav_value is nil --> return empty json
|
77
64
|
EMPTYH
|
78
65
|
end
|
79
66
|
end
|
67
|
+
|
80
68
|
when :expand_c
|
81
69
|
arg.each do |attr, templ|
|
82
70
|
next unless (encoll = entity.send(attr))
|
83
71
|
|
84
72
|
# nav attributes that are a collection (x..n)
|
85
|
-
hres[attr] = get_coll_odata_h(array: encoll,
|
86
|
-
template: templ,
|
87
|
-
uribase: uribase)
|
73
|
+
hres[attr] = get_coll_odata_h(array: encoll, template: templ)
|
88
74
|
# else error ?
|
89
75
|
end
|
76
|
+
|
90
77
|
when :deferr
|
78
|
+
euri = entity.uri
|
91
79
|
arg.each do |attr|
|
92
|
-
hres[attr] = get_deferred_odata_h(
|
93
|
-
attrib: attr,
|
94
|
-
uribase: uribase)
|
80
|
+
hres[attr] = get_deferred_odata_h(entity_uri: euri, attrib: attr)
|
95
81
|
end
|
96
82
|
end
|
97
83
|
end
|
@@ -100,33 +86,33 @@ module OData
|
|
100
86
|
end
|
101
87
|
end
|
102
88
|
|
103
|
-
module
|
89
|
+
module Safrano
|
104
90
|
# xml namespace constants needed for the output of XML service and
|
105
91
|
# and metadata
|
106
92
|
module XMLNS
|
107
|
-
MSFT_ADO = 'http://schemas.microsoft.com/ado'
|
108
|
-
MSFT_ADO_2009_EDM = "#{MSFT_ADO}/2009/11/edm"
|
109
|
-
MSFT_ADO_2007_EDMX = "#{MSFT_ADO}/2007/06/edmx"
|
110
|
-
MSFT_ADO_2007_META = MSFT_ADO
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
W3_2007_APP = 'http://www.w3.org/2007/app'.freeze
|
93
|
+
MSFT_ADO = 'http://schemas.microsoft.com/ado'
|
94
|
+
MSFT_ADO_2009_EDM = "#{MSFT_ADO}/2009/11/edm"
|
95
|
+
MSFT_ADO_2007_EDMX = "#{MSFT_ADO}/2007/06/edmx"
|
96
|
+
MSFT_ADO_2007_META = "#{MSFT_ADO}/2007/08/dataservices/metadata"
|
97
|
+
|
98
|
+
W3_2005_ATOM = 'http://www.w3.org/2005/Atom'
|
99
|
+
W3_2007_APP = 'http://www.w3.org/2007/app'
|
115
100
|
end
|
116
101
|
end
|
117
102
|
|
118
103
|
# Link to Model
|
119
|
-
module
|
120
|
-
MAX_DATASERVICE_VERSION = '2'
|
121
|
-
MIN_DATASERVICE_VERSION = '1'
|
104
|
+
module Safrano
|
105
|
+
MAX_DATASERVICE_VERSION = '2'
|
106
|
+
MIN_DATASERVICE_VERSION = '1'
|
107
|
+
CV_MAX_DATASERVICE_VERSION = Contract.valid(MAX_DATASERVICE_VERSION).freeze
|
108
|
+
CV_MIN_DATASERVICE_VERSION = Contract.valid(MIN_DATASERVICE_VERSION).freeze
|
122
109
|
include XMLNS
|
123
110
|
# Base class for service. Subclass will be for V1, V2 etc...
|
124
111
|
class ServiceBase
|
125
112
|
include Safrano
|
126
113
|
include ExpandHandler
|
127
114
|
|
128
|
-
XML_PREAMBLE =
|
129
|
-
"\r\n".freeze
|
115
|
+
XML_PREAMBLE = %Q(<?xml version="1.0" encoding="utf-8" standalone="yes"?>\r\n)
|
130
116
|
|
131
117
|
# This is just a hash of entity Set Names to the corresponding Class
|
132
118
|
# Example
|
@@ -144,10 +130,14 @@ module OData
|
|
144
130
|
attr_accessor :xname
|
145
131
|
attr_accessor :xnamespace
|
146
132
|
attr_accessor :xpath_prefix
|
147
|
-
|
133
|
+
attr_accessor :xserver_url
|
134
|
+
attr_accessor :uribase
|
148
135
|
attr_accessor :meta
|
149
136
|
attr_accessor :batch_handler
|
150
137
|
attr_accessor :relman
|
138
|
+
attr_accessor :complex_types
|
139
|
+
attr_accessor :function_imports
|
140
|
+
|
151
141
|
# Instance attributes for specialized Version specific Instances
|
152
142
|
attr_accessor :v1
|
153
143
|
attr_accessor :v2
|
@@ -161,36 +151,31 @@ module OData
|
|
161
151
|
# because of the version subclasses that dont use "super" initialise
|
162
152
|
# (todo: why not??)
|
163
153
|
@meta = ServiceMeta.new(self)
|
164
|
-
@batch_handler =
|
165
|
-
@relman =
|
154
|
+
@batch_handler = Safrano::Batch::DisabledHandler.new
|
155
|
+
@relman = Safrano::RelationManager.new
|
156
|
+
@complex_types = Set.new
|
157
|
+
@function_imports = {}
|
166
158
|
@cmap = {}
|
167
159
|
instance_eval(&block) if block_given?
|
168
160
|
end
|
169
161
|
|
170
|
-
DATASERVICEVERSION_RGX = /\A([1234])(?:\.0);*\w*\z/.freeze
|
171
162
|
TRAILING_SLASH = %r{/\z}.freeze
|
172
|
-
DEFAULT_PATH_PREFIX = '/'
|
173
|
-
|
174
|
-
# input is the DataServiceVersion request header string, eg.
|
175
|
-
# '2.0;blabla' ---> Version -> 2
|
176
|
-
def self.parse_data_service_version(inp)
|
177
|
-
m = DATASERVICEVERSION_RGX.match(inp)
|
178
|
-
m[1] if m
|
179
|
-
end
|
163
|
+
DEFAULT_PATH_PREFIX = '/'
|
164
|
+
DEFAULT_SERVER_URL = 'http://localhost:9494'
|
180
165
|
|
181
166
|
def enable_batch
|
182
|
-
@batch_handler =
|
167
|
+
@batch_handler = Safrano::Batch::EnabledHandler.new
|
183
168
|
(@v1.batch_handler = @batch_handler) if @v1
|
184
169
|
(@v2.batch_handler = @batch_handler) if @v2
|
185
170
|
end
|
186
171
|
|
187
172
|
def enable_v1_service
|
188
|
-
@v1 =
|
173
|
+
@v1 = Safrano::ServiceV1.new
|
189
174
|
copy_attribs_to @v1
|
190
175
|
end
|
191
176
|
|
192
177
|
def enable_v2_service
|
193
|
-
@v2 =
|
178
|
+
@v2 = Safrano::ServiceV2.new
|
194
179
|
copy_attribs_to @v2
|
195
180
|
end
|
196
181
|
|
@@ -212,56 +197,97 @@ module OData
|
|
212
197
|
(@v1.xpath_prefix = @xpath_prefix) if @v1
|
213
198
|
(@v2.xpath_prefix = @xpath_prefix) if @v2
|
214
199
|
end
|
200
|
+
|
201
|
+
def server_url(surl)
|
202
|
+
@xserver_url = surl.sub(TRAILING_SLASH, '')
|
203
|
+
(@v1.xserver_url = @xserver_url) if @v1
|
204
|
+
(@v2.xserver_url = @xserver_url) if @v2
|
205
|
+
end
|
206
|
+
|
207
|
+
# keep the bug active for now, but allow to activate the fix,
|
208
|
+
# later we will change the default to be fixed
|
209
|
+
def bugfix_create_response(bool = false)
|
210
|
+
@bugfix_create_response = bool
|
211
|
+
end
|
212
|
+
|
215
213
|
# end public API
|
216
214
|
|
215
|
+
def set_uribase
|
216
|
+
@uribase = if @xpath_prefix.empty?
|
217
|
+
@xserver_url
|
218
|
+
elsif @xpath_prefix[0] == '/'
|
219
|
+
"#{@xserver_url}#{@xpath_prefix}"
|
220
|
+
else
|
221
|
+
"#{@xserver_url}/#{@xpath_prefix}"
|
222
|
+
end
|
223
|
+
(@v1.uribase = @uribase) if @v1
|
224
|
+
(@v2.uribase = @uribase) if @v2
|
225
|
+
end
|
226
|
+
|
217
227
|
def copy_attribs_to(other)
|
218
228
|
other.cmap = @cmap
|
219
229
|
other.collections = @collections
|
230
|
+
other.allowed_transitions = @allowed_transitions
|
220
231
|
other.xtitle = @xtitle
|
221
232
|
other.xname = @xname
|
222
233
|
other.xnamespace = @xnamespace
|
223
234
|
other.xpath_prefix = @xpath_prefix
|
235
|
+
other.xserver_url = @xserver_url
|
236
|
+
other.uribase = @uribase
|
224
237
|
other.meta = ServiceMeta.new(other) # hum ... #todo: versions as well ?
|
225
238
|
other.relman = @relman
|
226
239
|
other.batch_handler = @batch_handler
|
240
|
+
other.complex_types = @complex_types
|
241
|
+
other.function_imports = @function_imports
|
227
242
|
other
|
228
243
|
end
|
229
244
|
|
245
|
+
# this is a central place. We extend Sequel models with OData functionality
|
246
|
+
# The included/extended modules depends on the properties(eg, pks, field types) of the model
|
247
|
+
# we differentiate
|
248
|
+
# * Single/Multi PK
|
249
|
+
# * Media/Non-Media entity
|
250
|
+
# Putting this logic here in modules loaded once on start shall result in less runtime overhead
|
230
251
|
def register_model(modelklass, entity_set_name = nil, is_media = false)
|
231
252
|
# check that the provided klass is a Sequel Model
|
232
253
|
|
233
|
-
raise(
|
254
|
+
raise(Safrano::API::ModelNameError, modelklass) unless modelklass.is_a? Sequel::Model::ClassMethods
|
234
255
|
|
235
|
-
if modelklass.ancestors.include?
|
256
|
+
if modelklass.ancestors.include? Safrano::Entity
|
236
257
|
# modules were already added previously;
|
237
258
|
# cleanup state to avoid having data from previous calls
|
238
259
|
# mostly usefull for testing (eg API)
|
239
260
|
modelklass.reset
|
240
261
|
elsif modelklass.primary_key.is_a?(Array) # first API call... (normal non-testing case)
|
241
|
-
modelklass.extend
|
242
|
-
modelklass.include
|
262
|
+
modelklass.extend Safrano::EntityClassMultiPK
|
263
|
+
modelklass.include Safrano::EntityMultiPK
|
243
264
|
else
|
244
|
-
modelklass.extend
|
245
|
-
modelklass.include
|
265
|
+
modelklass.extend Safrano::EntityClassSinglePK
|
266
|
+
modelklass.include Safrano::EntitySinglePK
|
246
267
|
end
|
268
|
+
|
247
269
|
# Media/Non-media
|
248
270
|
if is_media
|
249
|
-
modelklass.extend
|
271
|
+
modelklass.extend Safrano::EntityClassMedia
|
250
272
|
# set default media handler . Can be overridden later with the
|
251
273
|
# "use HandlerKlass, options" API
|
252
274
|
|
253
275
|
modelklass.set_default_media_handler
|
254
276
|
modelklass.api_check_media_fields
|
255
|
-
modelklass.include
|
277
|
+
modelklass.include Safrano::MediaEntity
|
256
278
|
else
|
257
|
-
modelklass.extend
|
258
|
-
modelklass.include
|
279
|
+
modelklass.extend Safrano::EntityClassNonMedia
|
280
|
+
modelklass.include Safrano::NonMediaEntity
|
259
281
|
end
|
260
282
|
|
261
283
|
modelklass.prepare_pk
|
262
284
|
modelklass.prepare_fields
|
263
285
|
esname = (entity_set_name || modelklass).to_s.freeze
|
264
|
-
|
286
|
+
serv_namespace = @xnamespace
|
287
|
+
modelklass.instance_eval do
|
288
|
+
@entity_set_name = esname
|
289
|
+
@namespace = serv_namespace
|
290
|
+
end
|
265
291
|
@cmap[esname] = modelklass
|
266
292
|
set_collections_sorted(@cmap.values)
|
267
293
|
end
|
@@ -282,6 +308,23 @@ module OData
|
|
282
308
|
modelklass.deferred_iblock = block if block_given?
|
283
309
|
end
|
284
310
|
|
311
|
+
def publish_complex_type(ctklass)
|
312
|
+
# check that the provided klass is a Safrano ComplexType
|
313
|
+
|
314
|
+
raise(Safrano::API::ComplexTypeNameError, ctklass) unless ctklass.superclass == Safrano::ComplexType
|
315
|
+
|
316
|
+
serv_namespace = @xnamespace
|
317
|
+
ctklass.instance_eval { @namespace = serv_namespace }
|
318
|
+
|
319
|
+
@complex_types.add ctklass
|
320
|
+
end
|
321
|
+
|
322
|
+
def function_import(name)
|
323
|
+
funcimp = Safrano::FunctionImport(name)
|
324
|
+
@function_imports[name] = funcimp
|
325
|
+
funcimp
|
326
|
+
end
|
327
|
+
|
285
328
|
def cmap=(imap)
|
286
329
|
@cmap = imap
|
287
330
|
set_collections_sorted(@cmap.values)
|
@@ -313,10 +356,35 @@ module OData
|
|
313
356
|
# set default path prefix if path_prefix was not called
|
314
357
|
path_prefix(DEFAULT_PATH_PREFIX) unless @xpath_prefix
|
315
358
|
|
359
|
+
# set default server url if server_url was not called
|
360
|
+
server_url(DEFAULT_SERVER_URL) unless @xserver_url
|
361
|
+
|
362
|
+
set_uribase
|
363
|
+
|
316
364
|
@collections.each(&:finalize_publishing)
|
317
365
|
|
318
|
-
# finalize the
|
319
|
-
@collections.each
|
366
|
+
# finalize the uri's and include NoMappingBeforeOutput or MappingBeforeOutput as needed
|
367
|
+
@collections.each do |klass|
|
368
|
+
klass.build_uri(@uribase)
|
369
|
+
klass.include(klass.time_cols.empty? ? Safrano::NoMappingBeforeOutput : Safrano::MappingBeforeOutput)
|
370
|
+
|
371
|
+
# Output create (POST) as single entity (Standard) or as array (non-standard buggy)
|
372
|
+
klass.include ( @bugfix_create_response ? Safrano::EntityCreateStandardOutput : Safrano::EntityCreateArrayOutput)
|
373
|
+
end
|
374
|
+
|
375
|
+
# build allowed transitions (requires that @collections are filled and sorted for having a
|
376
|
+
# correct base_url_regexp)
|
377
|
+
build_allowed_transitions
|
378
|
+
|
379
|
+
# mixin adapter specific modules where needed
|
380
|
+
case Sequel::Model.db.adapter_scheme
|
381
|
+
when :postgres
|
382
|
+
Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreePostgres
|
383
|
+
when :sqlite
|
384
|
+
Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreeSqlite
|
385
|
+
else
|
386
|
+
Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreeDefault
|
387
|
+
end
|
320
388
|
end
|
321
389
|
|
322
390
|
def execute_deferred_iblocks
|
@@ -332,19 +400,24 @@ module OData
|
|
332
400
|
@collections.map(&:entity_set_name).join('|')
|
333
401
|
end
|
334
402
|
|
403
|
+
def base_url_func_regexp
|
404
|
+
@function_imports.keys.join('|')
|
405
|
+
end
|
406
|
+
|
335
407
|
def service
|
336
408
|
hres = {}
|
337
409
|
hres['d'] = { 'EntitySets' => @collections.map(&:type_name) }
|
338
410
|
hres
|
339
411
|
end
|
340
412
|
|
341
|
-
def service_xml(
|
413
|
+
def service_xml(_req)
|
342
414
|
doc = REXML::Document.new
|
343
415
|
# separator required ? ?
|
344
|
-
root = doc.add_element('service', 'xml:base' =>
|
416
|
+
root = doc.add_element('service', 'xml:base' => @uribase)
|
345
417
|
|
346
418
|
root.add_namespace('xmlns:atom', XMLNS::W3_2005_ATOM)
|
347
419
|
root.add_namespace('xmlns:app', XMLNS::W3_2007_APP)
|
420
|
+
|
348
421
|
# this generates the main xmlns attribute
|
349
422
|
root.add_namespace(XMLNS::W3_2007_APP)
|
350
423
|
wp = root.add_element 'workspace'
|
@@ -355,7 +428,7 @@ module OData
|
|
355
428
|
@collections.each do |klass|
|
356
429
|
col = wp.add_element('collection', 'href' => klass.entity_set_name)
|
357
430
|
ct = col.add_element('atom:title')
|
358
|
-
ct.text = klass.
|
431
|
+
ct.text = klass.entity_set_name
|
359
432
|
end
|
360
433
|
|
361
434
|
XML_PREAMBLE + doc.to_pretty_xml
|
@@ -364,10 +437,18 @@ module OData
|
|
364
437
|
def add_metadata_xml_entity_type(schema)
|
365
438
|
@collections.each do |klass|
|
366
439
|
enty = klass.add_metadata_rexml(schema)
|
367
|
-
klass.add_metadata_navs_rexml(enty, @relman
|
440
|
+
klass.add_metadata_navs_rexml(enty, @relman)
|
368
441
|
end
|
369
442
|
end
|
370
443
|
|
444
|
+
def add_metadata_xml_complex_types(schema)
|
445
|
+
@complex_types.each { |ctklass| ctklass.add_metadata_rexml(schema) }
|
446
|
+
end
|
447
|
+
|
448
|
+
def add_metadata_xml_function_imports(ec)
|
449
|
+
@function_imports.each_value { |func| func.add_metadata_rexml(ec) }
|
450
|
+
end
|
451
|
+
|
371
452
|
def add_metadata_xml_associations(schema)
|
372
453
|
@relman.each_rel do |rel|
|
373
454
|
rel.with_metadata_info(@xnamespace) do |name, bdinfo|
|
@@ -390,7 +471,7 @@ module OData
|
|
390
471
|
# 3.a Entity set's
|
391
472
|
ec.add_element('EntitySet',
|
392
473
|
'Name' => klass.entity_set_name,
|
393
|
-
'EntityType' =>
|
474
|
+
'EntityType' => klass.type_name)
|
394
475
|
end
|
395
476
|
# 3.b Association set's
|
396
477
|
@relman.each_rel do |rel|
|
@@ -404,6 +485,9 @@ module OData
|
|
404
485
|
assoc.add_element('End', assoend)
|
405
486
|
end
|
406
487
|
end
|
488
|
+
|
489
|
+
# 4 function imports
|
490
|
+
add_metadata_xml_function_imports(ec)
|
407
491
|
end
|
408
492
|
|
409
493
|
def metadata_xml(_req)
|
@@ -437,9 +521,12 @@ module OData
|
|
437
521
|
schema = serv.add_element('Schema',
|
438
522
|
'Namespace' => @xnamespace,
|
439
523
|
'xmlns' => XMLNS::MSFT_ADO_2009_EDM)
|
440
|
-
# 1. all EntityType
|
524
|
+
# 1. a. all EntityType
|
441
525
|
add_metadata_xml_entity_type(schema)
|
442
526
|
|
527
|
+
# 1. b. all ComplexType
|
528
|
+
add_metadata_xml_complex_types(schema)
|
529
|
+
|
443
530
|
# 2. Associations
|
444
531
|
add_metadata_xml_associations(schema)
|
445
532
|
|
@@ -451,20 +538,45 @@ module OData
|
|
451
538
|
|
452
539
|
# methods related to transitions to next state (cf. walker)
|
453
540
|
module Transitions
|
454
|
-
DOLLAR_ID_REGEXP = Regexp.new('\A\/\$')
|
541
|
+
DOLLAR_ID_REGEXP = Regexp.new('\A\/\$')
|
542
|
+
ALLOWED_TRANSITIONS_FIXED = [
|
543
|
+
Safrano::TransitionEnd,
|
544
|
+
Safrano::TransitionMetadata,
|
545
|
+
Safrano::TransitionBatch,
|
546
|
+
Safrano::TransitionContentId
|
547
|
+
].freeze
|
548
|
+
|
549
|
+
def build_allowed_transitions
|
550
|
+
@allowed_transitions = if @function_imports.empty?
|
551
|
+
(ALLOWED_TRANSITIONS_FIXED + [
|
552
|
+
Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
|
553
|
+
trans: 'transition_collection')
|
554
|
+
]).freeze
|
555
|
+
else
|
556
|
+
(ALLOWED_TRANSITIONS_FIXED + [
|
557
|
+
Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
|
558
|
+
trans: 'transition_collection'),
|
559
|
+
Safrano::Transition.new(%r{\A/(#{base_url_func_regexp})(.*)},
|
560
|
+
trans: 'transition_service_op')
|
561
|
+
]).freeze
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
455
565
|
def allowed_transitions
|
456
|
-
@allowed_transitions
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
|
462
|
-
trans: 'transition_collection')
|
463
|
-
]
|
566
|
+
@allowed_transitions
|
567
|
+
end
|
568
|
+
|
569
|
+
def thread_safe_collection(collklass)
|
570
|
+
Safrano::OData::Collection.new(collklass)
|
464
571
|
end
|
465
572
|
|
466
573
|
def transition_collection(match_result)
|
467
|
-
[@cmap[match_result[1]], :run] if match_result[1]
|
574
|
+
[thread_safe_collection(@cmap[match_result[1]]), :run] if match_result[1]
|
575
|
+
# [@cmap[match_result[1]], :run] if match_result[1]
|
576
|
+
end
|
577
|
+
|
578
|
+
def transition_service_op(match_result)
|
579
|
+
[@function_imports[match_result[1]], :run] if match_result[1]
|
468
580
|
end
|
469
581
|
|
470
582
|
def transition_batch(_match_result)
|
@@ -480,7 +592,7 @@ module OData
|
|
480
592
|
end
|
481
593
|
|
482
594
|
def transition_end(_match_result)
|
483
|
-
|
595
|
+
Safrano::Transition::RESULT_END
|
484
596
|
end
|
485
597
|
end
|
486
598
|
|
@@ -488,14 +600,11 @@ module OData
|
|
488
600
|
|
489
601
|
def odata_get(req)
|
490
602
|
if req.accept?(APPXML)
|
491
|
-
#
|
492
|
-
#
|
493
|
-
|
494
|
-
# identified with the "application/atomsvc+xml" media type (see
|
495
|
-
# [RFC5023] section 8).
|
496
|
-
[200, CT_ATOMXML, [service_xml(req)]]
|
603
|
+
# OData V2 reference service implementations are returning app-xml-u8
|
604
|
+
# so we do
|
605
|
+
[200, CT_APPXML, [service_xml(req)]]
|
497
606
|
else
|
498
|
-
# this is returned by http://services.odata.org/V2/OData/
|
607
|
+
# this is returned by http://services.odata.org/V2/OData/Safrano.svc
|
499
608
|
415
|
500
609
|
end
|
501
610
|
end
|
@@ -507,18 +616,18 @@ module OData
|
|
507
616
|
@data_service_version = '1.0'
|
508
617
|
end
|
509
618
|
|
510
|
-
def get_coll_odata_links_h(array:,
|
619
|
+
def get_coll_odata_links_h(array:, icount: nil)
|
511
620
|
array.map do |w|
|
512
|
-
get_entity_odata_link_h(entity: w
|
621
|
+
get_entity_odata_link_h(entity: w)
|
513
622
|
end
|
514
623
|
end
|
515
624
|
|
516
|
-
def get_coll_odata_h(array:, template:,
|
517
|
-
array.map do |w|
|
625
|
+
def get_coll_odata_h(array:, template:, icount: nil)
|
626
|
+
array.map! do |w|
|
518
627
|
get_entity_odata_h(entity: w,
|
519
|
-
template: template
|
520
|
-
uribase: uribase)
|
628
|
+
template: template)
|
521
629
|
end
|
630
|
+
array
|
522
631
|
end
|
523
632
|
|
524
633
|
def get_emptycoll_odata_h
|
@@ -532,36 +641,37 @@ module OData
|
|
532
641
|
@data_service_version = '2.0'
|
533
642
|
end
|
534
643
|
|
535
|
-
def get_coll_odata_links_h(array:,
|
644
|
+
def get_coll_odata_links_h(array:, icount: nil)
|
536
645
|
res = array.map do |w|
|
537
|
-
get_entity_odata_link_h(entity: w
|
646
|
+
get_entity_odata_link_h(entity: w)
|
538
647
|
end
|
539
648
|
if icount
|
540
|
-
{
|
649
|
+
{ RESULTS_K => res, COUNT_K => icount }
|
541
650
|
else
|
542
|
-
{
|
651
|
+
{ RESULTS_K => res }
|
543
652
|
end
|
544
653
|
end
|
545
654
|
|
546
655
|
def get_emptycoll_odata_h
|
547
|
-
{
|
656
|
+
{ RESULTS_K => EMPTY_HASH_IN_ARY }
|
548
657
|
end
|
549
658
|
end
|
550
659
|
|
551
660
|
# a virtual entity for the service metadata
|
552
661
|
class ServiceMeta
|
553
662
|
attr_accessor :service
|
663
|
+
|
554
664
|
def initialize(service)
|
555
665
|
@service = service
|
556
666
|
end
|
557
667
|
|
668
|
+
ALLOWED_TRANSITIONS_FIXED = [Safrano::TransitionEnd].freeze
|
558
669
|
def allowed_transitions
|
559
|
-
|
560
|
-
trans: 'transition_end')]
|
670
|
+
ALLOWED_TRANSITIONS_FIXED
|
561
671
|
end
|
562
672
|
|
563
673
|
def transition_end(_match_result)
|
564
|
-
|
674
|
+
Safrano::Transition::RESULT_END
|
565
675
|
end
|
566
676
|
|
567
677
|
def odata_get(req)
|