safrano 0.7.1 → 0.8.1
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/Date/format.rb +1 -0
- data/lib/core_ext/DateTime/format.rb +1 -0
- data/lib/core_ext/Hash/transform.rb +2 -2
- data/lib/core_ext/Time/format.rb +1 -1
- data/lib/odata/attribute.rb +4 -5
- data/lib/odata/batch.rb +3 -1
- data/lib/odata/collection.rb +3 -0
- data/lib/odata/collection_media.rb +5 -4
- data/lib/odata/complex_type.rb +3 -1
- data/lib/odata/entity.rb +6 -9
- data/lib/odata/error.rb +15 -3
- data/lib/odata/filter/base.rb +1 -0
- data/lib/odata/filter/error.rb +3 -0
- data/lib/odata/filter/sequel.rb +6 -10
- data/lib/odata/filter/sequel_datetime_adapter.rb +1 -0
- data/lib/odata/filter/sequel_function_adapter.rb +1 -0
- data/lib/odata/filter/tree.rb +28 -26
- data/lib/odata/function_import.rb +7 -0
- data/lib/odata/model_ext.rb +17 -14
- data/lib/odata/navigation_attribute.rb +6 -0
- data/lib/odata/request/json.rb +1 -0
- data/lib/odata/transition.rb +134 -27
- data/lib/odata/walker.rb +5 -25
- data/lib/safrano/rack_app.rb +6 -1
- data/lib/safrano/rack_builder.rb +63 -2
- data/lib/safrano/request.rb +11 -8
- data/lib/safrano/service.rb +12 -5
- data/lib/safrano/type_mapping.rb +6 -3
- data/lib/safrano/version.rb +1 -1
- data/lib/sequel/plugins/join_by_paths.rb +2 -2
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 351f486db0cd9cfa46ac9770e739dce4fcf9b4fe4a564799febc2d23eea2f713
|
|
4
|
+
data.tar.gz: b214e28350f97dc2a442950803773accb428f8316de8482884c56c81b0aab754
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e1a12730936b436c30c909970b8d31e90358b87f10242f76c7f8b238def70391fb20bc0568825495045a095db6d799b8da8c07c9f524914eecf0a072d083d40
|
|
7
|
+
data.tar.gz: 8a1fdd4ca3c8d4512bd6ef6890a653a1ad1e052daad13ee5355d51248c652a938cf75df16d42d97a42ee6149f111e69cce215102b69c414b58920cf4b62180a2
|
data/lib/core_ext/Date/format.rb
CHANGED
data/lib/core_ext/Time/format.rb
CHANGED
|
@@ -15,7 +15,7 @@ module Safrano
|
|
|
15
15
|
return unless (md = instr.match(REGEX))
|
|
16
16
|
|
|
17
17
|
sec, milli = md[1].to_i.divmod(1000)
|
|
18
|
-
secm = milli.zero? ? sec : sec + Float(milli) / 1000
|
|
18
|
+
secm = milli.zero? ? sec : sec + (Float(milli) / 1000)
|
|
19
19
|
if md[3].nil? # no offset
|
|
20
20
|
# ::Time.at(sec, milli, :millisecond) # not supported in ruby 2.4
|
|
21
21
|
::Time.gm(1970, 1, 1) + secm
|
data/lib/odata/attribute.rb
CHANGED
|
@@ -13,6 +13,7 @@ module Safrano
|
|
|
13
13
|
def initialize(entity, name)
|
|
14
14
|
@entity = entity
|
|
15
15
|
@name = name
|
|
16
|
+
@allowed_transitions = ALLOWED_TRANSITIONS
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def value
|
|
@@ -20,11 +21,7 @@ module Safrano
|
|
|
20
21
|
# currently it is just set to make some minimal testcase work
|
|
21
22
|
# See also model_ext.rb
|
|
22
23
|
case (v = @entity.values[@name.to_sym])
|
|
23
|
-
when Date
|
|
24
|
-
v.to_edm_json
|
|
25
|
-
when Time
|
|
26
|
-
v.to_edm_json
|
|
27
|
-
when DateTime
|
|
24
|
+
when Date, Time, DateTime
|
|
28
25
|
v.to_edm_json
|
|
29
26
|
else
|
|
30
27
|
v
|
|
@@ -68,6 +65,8 @@ module Safrano
|
|
|
68
65
|
def allowed_transitions
|
|
69
66
|
Transitions::ALLOWED_TRANSITIONS
|
|
70
67
|
end
|
|
68
|
+
|
|
69
|
+
include Safrano::Transitions::GetNextTrans::BySimpleDetect
|
|
71
70
|
end
|
|
72
71
|
include Transitions
|
|
73
72
|
end
|
data/lib/odata/batch.rb
CHANGED
|
@@ -103,7 +103,7 @@ module Safrano
|
|
|
103
103
|
|
|
104
104
|
# $batch Handler
|
|
105
105
|
class HandlerBase
|
|
106
|
-
TREND = Safrano::Transition.new('', trans:
|
|
106
|
+
TREND = Safrano::Transition.new('', trans: :transition_end)
|
|
107
107
|
def allowed_transitions
|
|
108
108
|
@allowed_transitions = [TREND]
|
|
109
109
|
end
|
|
@@ -111,6 +111,8 @@ module Safrano
|
|
|
111
111
|
def transition_end(_match_result)
|
|
112
112
|
Safrano::Transition::RESULT_END
|
|
113
113
|
end
|
|
114
|
+
|
|
115
|
+
include Safrano::Transitions::GetNextTrans::ForJustTransitionEnd
|
|
114
116
|
end
|
|
115
117
|
|
|
116
118
|
# $batch disabled Handler
|
data/lib/odata/collection.rb
CHANGED
|
@@ -20,6 +20,7 @@ module Safrano
|
|
|
20
20
|
|
|
21
21
|
def initialize(modelk)
|
|
22
22
|
@modelk = modelk
|
|
23
|
+
@allowed_transitions = @modelk.allowed_transitions
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def allowed_transitions
|
|
@@ -52,6 +53,8 @@ module Safrano
|
|
|
52
53
|
end
|
|
53
54
|
end
|
|
54
55
|
|
|
56
|
+
include Safrano::Transitions::GetNextTrans::ByLongestMatch
|
|
57
|
+
|
|
55
58
|
# pkid can be a single value for single-pk models, or an array.
|
|
56
59
|
# type checking/convertion is done in check_odata_key_type
|
|
57
60
|
def find_by_odata_key(pkid)
|
|
@@ -14,6 +14,7 @@ module Safrano
|
|
|
14
14
|
Contract::OK
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
|
+
|
|
17
18
|
# Simple static File/Directory based media store handler
|
|
18
19
|
# similar to Rack::Static
|
|
19
20
|
# with a flat directory structure
|
|
@@ -35,11 +36,11 @@ module Safrano
|
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def create_abs_class_dir
|
|
38
|
-
FileUtils.makedirs @abs_klass_dir
|
|
39
|
+
FileUtils.makedirs @abs_klass_dir
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
def create_abs_temp_dir
|
|
42
|
-
FileUtils.makedirs @abs_temp_dir
|
|
43
|
+
FileUtils.makedirs @abs_temp_dir
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
def finalize
|
|
@@ -114,7 +115,7 @@ module Safrano
|
|
|
114
115
|
# and ensure the directory exists
|
|
115
116
|
def with_media_directory(entity)
|
|
116
117
|
mpi = abs_media_directory(entity)
|
|
117
|
-
|
|
118
|
+
FileUtils.mkdir_p mpi
|
|
118
119
|
yield Pathname(mpi)
|
|
119
120
|
end
|
|
120
121
|
|
|
@@ -200,7 +201,7 @@ module Safrano
|
|
|
200
201
|
def with_media_directory(entity)
|
|
201
202
|
mpi = abs_media_directory(entity)
|
|
202
203
|
|
|
203
|
-
FileUtils.makedirs mpi
|
|
204
|
+
FileUtils.makedirs mpi
|
|
204
205
|
yield Pathname(mpi)
|
|
205
206
|
end
|
|
206
207
|
|
data/lib/odata/complex_type.rb
CHANGED
|
@@ -21,6 +21,8 @@ module Safrano
|
|
|
21
21
|
Safrano::Transition::RESULT_END
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
include Safrano::Transitions::GetNextTrans::ForJustTransitionEnd
|
|
25
|
+
|
|
24
26
|
# we will have this on class and instance level for making things simpler first
|
|
25
27
|
class << self
|
|
26
28
|
attr_reader :klassmod
|
|
@@ -202,7 +204,7 @@ module Safrano
|
|
|
202
204
|
end
|
|
203
205
|
|
|
204
206
|
def metadata_h
|
|
205
|
-
{
|
|
207
|
+
{ type: type_name }
|
|
206
208
|
end
|
|
207
209
|
|
|
208
210
|
def casted_values
|
data/lib/odata/entity.rb
CHANGED
|
@@ -19,6 +19,8 @@ module Safrano
|
|
|
19
19
|
self.class.entity_allowed_transitions
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
include Safrano::Transitions::GetNextTrans::ByLongestMatchDyn
|
|
23
|
+
|
|
22
24
|
def transition_end(_match_result)
|
|
23
25
|
Safrano::Transition::RESULT_END
|
|
24
26
|
end
|
|
@@ -184,16 +186,9 @@ module Safrano
|
|
|
184
186
|
if req.walker.media_value
|
|
185
187
|
odata_media_value_put(req)
|
|
186
188
|
elsif req.accept?(APPJSON)
|
|
187
|
-
data.delete('__metadata')
|
|
188
189
|
|
|
189
|
-
|
|
190
|
-
set_fields(data, self.class.data_fields, missing: :skip)
|
|
191
|
-
save(transaction: false)
|
|
192
|
-
else
|
|
193
|
-
update_fields(data, self.class.data_fields, missing: :skip)
|
|
194
|
-
end
|
|
190
|
+
AlreadyExistsUnprocessableError.odata_get(req)
|
|
195
191
|
|
|
196
|
-
[202, EMPTY_HASH, to_odata_post_json(service: req.service)]
|
|
197
192
|
else # TODO: other formats
|
|
198
193
|
415
|
|
199
194
|
end
|
|
@@ -361,7 +356,7 @@ module Safrano
|
|
|
361
356
|
# real implementation for replacing $value for a media entity
|
|
362
357
|
def odata_media_value_put(req)
|
|
363
358
|
model = self.class
|
|
364
|
-
req.with_media_data do |data, mimetype,
|
|
359
|
+
req.with_media_data do |data, mimetype, _filename|
|
|
365
360
|
emdata = { content_type: mimetype }
|
|
366
361
|
if req.in_changeset
|
|
367
362
|
set_fields(emdata, model.data_fields, missing: :skip)
|
|
@@ -392,6 +387,7 @@ module Safrano
|
|
|
392
387
|
[pk_uri]
|
|
393
388
|
end
|
|
394
389
|
end
|
|
390
|
+
|
|
395
391
|
module PKUriWithoutFunc
|
|
396
392
|
def pk_uri
|
|
397
393
|
pk
|
|
@@ -405,6 +401,7 @@ module Safrano
|
|
|
405
401
|
[pk]
|
|
406
402
|
end
|
|
407
403
|
end
|
|
404
|
+
|
|
408
405
|
# for a single public key
|
|
409
406
|
module EntitySinglePK
|
|
410
407
|
include Entity
|
data/lib/odata/error.rb
CHANGED
|
@@ -143,6 +143,10 @@ module Safrano
|
|
|
143
143
|
end
|
|
144
144
|
end
|
|
145
145
|
|
|
146
|
+
class AlreadyExistsUnprocessableError < UnprocessableEntityError
|
|
147
|
+
@msg = 'The ressource you are trying to create already exists'
|
|
148
|
+
end
|
|
149
|
+
|
|
146
150
|
# http Bad Req.
|
|
147
151
|
class BadRequestError
|
|
148
152
|
extend ErrorClass
|
|
@@ -161,6 +165,7 @@ module Safrano
|
|
|
161
165
|
@msg = "Bad Request: empty media file #{path}"
|
|
162
166
|
end
|
|
163
167
|
end
|
|
168
|
+
|
|
164
169
|
# Generic failed changeset
|
|
165
170
|
class BadRequestFailedChangeSet < BadRequestError
|
|
166
171
|
@msg = 'Bad Request: Failed changeset '
|
|
@@ -170,6 +175,7 @@ module Safrano
|
|
|
170
175
|
class BadRequestNonMediaValue < BadRequestError
|
|
171
176
|
@msg = 'Bad Request: $value request for a non-media entity'
|
|
172
177
|
end
|
|
178
|
+
|
|
173
179
|
class BadRequestSequelAdapterError < BadRequestError
|
|
174
180
|
include ErrorInstance
|
|
175
181
|
def initialize(err)
|
|
@@ -197,6 +203,7 @@ module Safrano
|
|
|
197
203
|
@msg = "Bad Request: the $expand path #{path} is invalid for entityset #{model.entity_set_name}"
|
|
198
204
|
end
|
|
199
205
|
end
|
|
206
|
+
|
|
200
207
|
# for invalid properti(es) in $select param
|
|
201
208
|
class BadRequestSelectInvalidProps < BadRequestError
|
|
202
209
|
include ErrorInstance
|
|
@@ -217,7 +224,7 @@ module Safrano
|
|
|
217
224
|
|
|
218
225
|
# http not found
|
|
219
226
|
class ErrorNotFound
|
|
220
|
-
extend
|
|
227
|
+
extend ErrorClass
|
|
221
228
|
HTTP_CODE = 404
|
|
222
229
|
@msg = 'The requested ressource was not found'
|
|
223
230
|
end
|
|
@@ -238,16 +245,17 @@ module Safrano
|
|
|
238
245
|
|
|
239
246
|
# not implemented (Safrano specific)
|
|
240
247
|
class NotImplementedError
|
|
241
|
-
extend
|
|
248
|
+
extend ErrorClass
|
|
242
249
|
HTTP_CODE = 501
|
|
243
250
|
end
|
|
244
251
|
|
|
245
252
|
# version not implemented (Safrano specific)
|
|
246
253
|
class VersionNotImplementedError
|
|
247
|
-
extend
|
|
254
|
+
extend ErrorClass
|
|
248
255
|
HTTP_CODE = 501
|
|
249
256
|
@msg = 'The requested OData version is not yet supported'
|
|
250
257
|
end
|
|
258
|
+
|
|
251
259
|
# batch not implemented (Safrano specific)
|
|
252
260
|
class BatchNotImplementedError < RuntimeError
|
|
253
261
|
@msg = 'Not implemented: OData batch'
|
|
@@ -266,22 +274,26 @@ module Safrano
|
|
|
266
274
|
@msg = xmsg
|
|
267
275
|
end
|
|
268
276
|
end
|
|
277
|
+
|
|
269
278
|
class FilterUnknownFunctionError < BadRequestError
|
|
270
279
|
include ErrorInstance
|
|
271
280
|
def initialize(badfuncname)
|
|
272
281
|
@msg = "Bad request: unknown function #{badfuncname} in $filter"
|
|
273
282
|
end
|
|
274
283
|
end
|
|
284
|
+
|
|
275
285
|
class FilterParseErrorWrongColumnName < BadRequestError
|
|
276
286
|
extend ErrorClass
|
|
277
287
|
@msg = 'Bad request: invalid property name in $filter'
|
|
278
288
|
end
|
|
289
|
+
|
|
279
290
|
class FilterParseWrappedError < BadRequestError
|
|
280
291
|
include ErrorInstance
|
|
281
292
|
def initialize(exception)
|
|
282
293
|
@msg = exception.to_s
|
|
283
294
|
end
|
|
284
295
|
end
|
|
296
|
+
|
|
285
297
|
class ServiceOperationParameterMissing < BadRequestError
|
|
286
298
|
include ErrorInstance
|
|
287
299
|
def initialize(missing:, sopname:)
|
data/lib/odata/filter/base.rb
CHANGED
data/lib/odata/filter/error.rb
CHANGED
|
@@ -35,6 +35,7 @@ module Safrano
|
|
|
35
35
|
@cur_typ = cur.class
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
|
+
|
|
38
39
|
# Invalid Tokens
|
|
39
40
|
class ErrorInvalidToken < Error
|
|
40
41
|
include ::Safrano::ErrorInstance
|
|
@@ -43,6 +44,7 @@ module Safrano
|
|
|
43
44
|
@msg = "Bad Request: invalid token #{tok} in $filter"
|
|
44
45
|
end
|
|
45
46
|
end
|
|
47
|
+
|
|
46
48
|
# Unmached closed
|
|
47
49
|
class ErrorUnmatchedClose < Error
|
|
48
50
|
include ::Safrano::ErrorInstance
|
|
@@ -73,6 +75,7 @@ module Safrano
|
|
|
73
75
|
@msg = "Bad Request: wrong number of parameters for function #{cur.parent.value} in $filter"
|
|
74
76
|
end
|
|
75
77
|
end
|
|
78
|
+
|
|
76
79
|
# Invalid separator in this context (missing parenthesis?)
|
|
77
80
|
class ErrorInvalidSeparator < Error
|
|
78
81
|
include ::Safrano::ErrorInstance
|
data/lib/odata/filter/sequel.rb
CHANGED
|
@@ -51,21 +51,17 @@ module Safrano
|
|
|
51
51
|
when :substringof
|
|
52
52
|
|
|
53
53
|
# there are multiple possible argument types (but all should return edm.string)
|
|
54
|
-
if args[0].is_a?(QString)
|
|
55
|
-
# substringof('Rhum', name) -->
|
|
56
|
-
# name contains substr 'Rhum'
|
|
57
|
-
Contract.collect_result!(args[1].leuqes(jh),
|
|
58
|
-
args[0].leuqes_substringof_sig1(jh)) do |l1, l0|
|
|
59
|
-
Sequel.like(l1, l0)
|
|
60
|
-
end
|
|
61
54
|
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
if args[0].is_a?(QString) ||
|
|
56
|
+
# substringof('Rhum', name) -->
|
|
57
|
+
# name contains substr 'Rhum'
|
|
58
|
+
|
|
59
|
+
# special non standard (ui5 client) case ?
|
|
60
|
+
(args[0].is_a?(Literal) && args[1].is_a?(Literal))
|
|
64
61
|
Contract.collect_result!(args[1].leuqes(jh),
|
|
65
62
|
args[0].leuqes_substringof_sig1(jh)) do |l1, l0|
|
|
66
63
|
Sequel.like(l1, l0)
|
|
67
64
|
end
|
|
68
|
-
|
|
69
65
|
elsif args[1].is_a?(QString)
|
|
70
66
|
substringof_sig2(jh) # adapter specific
|
|
71
67
|
else
|
|
@@ -64,6 +64,7 @@ module Safrano
|
|
|
64
64
|
Safrano::FilterFunctionNotImplementedError.new("$filter function 'ceiling' is not implemented in sqlite adapter")
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
|
+
|
|
67
68
|
# re-useable module with math floor/ceil functions for those adapters having these SQL funcs
|
|
68
69
|
module MathFloorCeilFuncTree
|
|
69
70
|
def floor(lq)
|
data/lib/odata/filter/tree.rb
CHANGED
|
@@ -355,14 +355,14 @@ module Safrano
|
|
|
355
355
|
|
|
356
356
|
# Numbers (floating point, ints, dec)
|
|
357
357
|
class FPNumber
|
|
358
|
-
def initialize(val)
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
end
|
|
358
|
+
# def initialize(val)
|
|
359
|
+
# 1.53f --> value 1.53
|
|
360
|
+
# 1.53d --> value 1.53
|
|
361
|
+
# 1.53 --> value 1.53
|
|
362
|
+
# Note: the tokenizer has already dropped the not usefull string parts
|
|
363
|
+
# Note : we dont differentiate between Float and Double here
|
|
364
|
+
# super(val)
|
|
365
|
+
# end
|
|
366
366
|
|
|
367
367
|
def accept?(tok, typ)
|
|
368
368
|
case typ
|
|
@@ -396,14 +396,13 @@ module Safrano
|
|
|
396
396
|
end
|
|
397
397
|
|
|
398
398
|
class DecimalLit
|
|
399
|
-
def initialize(val)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
end
|
|
399
|
+
# def initialize(val)
|
|
400
|
+
# 1.53m --> value 1.53
|
|
401
|
+
# Warning, this assumes that the m|M part in the input is really not optional
|
|
402
|
+
# Note: the tokenizer has already dropped the not usefull string parts
|
|
403
|
+
# cf. DECIMALRGX in token.rb
|
|
404
|
+
# super(val)
|
|
405
|
+
# end
|
|
407
406
|
|
|
408
407
|
def accept?(tok, typ)
|
|
409
408
|
case typ
|
|
@@ -418,6 +417,7 @@ module Safrano
|
|
|
418
417
|
:decimal
|
|
419
418
|
end
|
|
420
419
|
end
|
|
420
|
+
|
|
421
421
|
# Literals are unquoted words without /
|
|
422
422
|
class Literal
|
|
423
423
|
def accept?(tok, typ)
|
|
@@ -465,11 +465,11 @@ module Safrano
|
|
|
465
465
|
|
|
466
466
|
# DateTimeLit
|
|
467
467
|
class DateTimeLit
|
|
468
|
-
def initialize(val)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
end
|
|
468
|
+
# def initialize(val)
|
|
469
|
+
# datetime'2000-12-12T12:00:53' --> value 2000-12-12T12:00:53
|
|
470
|
+
# Note: the tokenizer has already dropped the not usefull string parts
|
|
471
|
+
# super(val)
|
|
472
|
+
# end
|
|
473
473
|
|
|
474
474
|
def accept?(tok, typ)
|
|
475
475
|
case typ
|
|
@@ -484,13 +484,14 @@ module Safrano
|
|
|
484
484
|
:datetime
|
|
485
485
|
end
|
|
486
486
|
end
|
|
487
|
+
|
|
487
488
|
# DateTimeOffsetLit
|
|
488
489
|
class DateTimeOffsetLit
|
|
489
|
-
def initialize(val)
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
end
|
|
490
|
+
# def initialize(val)
|
|
491
|
+
# datetimeoffset'2000-12-12T12:00:53+02:00' --> value 2000-12-12T12:00:53+02:00
|
|
492
|
+
# Note: the tokenizer has already dropped the not usefull string parts
|
|
493
|
+
# super(val)
|
|
494
|
+
# end
|
|
494
495
|
|
|
495
496
|
def accept?(tok, typ)
|
|
496
497
|
case typ
|
|
@@ -505,6 +506,7 @@ module Safrano
|
|
|
505
506
|
:datetimeoffset
|
|
506
507
|
end
|
|
507
508
|
end
|
|
509
|
+
|
|
508
510
|
# Quoted Strings
|
|
509
511
|
class QString
|
|
510
512
|
DBL_QO = "''"
|
|
@@ -17,6 +17,7 @@ module Safrano
|
|
|
17
17
|
super(msg)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
|
+
|
|
20
21
|
class ProcRedefinition < StandardError
|
|
21
22
|
def initialize(fnam)
|
|
22
23
|
msg = "Function import #{fnam}: Block/lambda Redefinition . Provide definition either as a return code block or with .definition(lambda) but not both"
|
|
@@ -39,6 +40,12 @@ module Safrano
|
|
|
39
40
|
[Safrano::TransitionExecuteFunc]
|
|
40
41
|
end
|
|
41
42
|
|
|
43
|
+
include Safrano::Transitions::GetNextTrans::ForJustTransitionEnd
|
|
44
|
+
|
|
45
|
+
def get_next_transresult(path_remain)
|
|
46
|
+
Safrano::TransitionExecuteFunc.result(path_remain)
|
|
47
|
+
end
|
|
48
|
+
|
|
42
49
|
def input(**parmtypes)
|
|
43
50
|
@input = {}
|
|
44
51
|
parmtypes.each do |k, t|
|
data/lib/odata/model_ext.rb
CHANGED
|
@@ -445,17 +445,17 @@ module Safrano
|
|
|
445
445
|
|
|
446
446
|
# check if key needs casting. Important for later entity-uri generation !
|
|
447
447
|
return unless primary_key.is_a? Symbol # single key field
|
|
448
|
-
|
|
449
|
-
# guid key as guid'xxx-yyy-zzz'
|
|
448
|
+
|
|
449
|
+
# guid key as guid'xxx-yyy-zzz'
|
|
450
450
|
metadata = @cols_metadata[primary_key]
|
|
451
|
-
|
|
451
|
+
|
|
452
452
|
if metadata[:edm_type] == 'Edm.Guid'
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
453
|
+
props = db_schema[primary_key]
|
|
454
|
+
@pk_castfunc = if props[:type] == :blob # Edm.Guid but as 16 byte binary Blob on DB level, eg in Sqlite
|
|
455
|
+
lambda { |x| "guid'#{UUIDTools::UUID.parse_raw(x)}'" unless x.nil? }
|
|
456
|
+
else
|
|
457
|
+
lambda { |x| "guid'#{x}'" unless x.nil? }
|
|
458
|
+
end
|
|
459
459
|
else
|
|
460
460
|
@pk_castfunc = @casted_cols[primary_key]
|
|
461
461
|
end
|
|
@@ -607,12 +607,15 @@ module Safrano
|
|
|
607
607
|
end
|
|
608
608
|
end
|
|
609
609
|
end
|
|
610
|
+
|
|
610
611
|
# methods related to transitions to next state (cf. walker)
|
|
611
612
|
module Transitions
|
|
612
613
|
def allowed_transitions
|
|
613
614
|
@allowed_transitions
|
|
614
615
|
end
|
|
615
616
|
|
|
617
|
+
include Safrano::Transitions::GetNextTrans::BySimpleDetect
|
|
618
|
+
|
|
616
619
|
def entity_allowed_transitions
|
|
617
620
|
@entity_allowed_transitions
|
|
618
621
|
end
|
|
@@ -621,7 +624,7 @@ module Safrano
|
|
|
621
624
|
@allowed_transitions = [Safrano::TransitionEnd,
|
|
622
625
|
Safrano::TransitionCount,
|
|
623
626
|
Safrano::Transition.new(entity_id_url_regexp,
|
|
624
|
-
trans:
|
|
627
|
+
trans: :transition_id)].freeze
|
|
625
628
|
end
|
|
626
629
|
|
|
627
630
|
def build_entity_allowed_transitions
|
|
@@ -630,17 +633,17 @@ module Safrano
|
|
|
630
633
|
Safrano::TransitionCount,
|
|
631
634
|
Safrano::TransitionLinks,
|
|
632
635
|
Safrano::TransitionValue,
|
|
633
|
-
Safrano::Transition.new(transition_attribute_regexp, trans:
|
|
636
|
+
Safrano::Transition.new(transition_attribute_regexp, trans: :transition_attribute)
|
|
634
637
|
]
|
|
635
638
|
if (ncurgx = @nav_collection_url_regexp)
|
|
636
639
|
@entity_allowed_transitions <<
|
|
637
|
-
Safrano::Transition.new(%r{\A/(#{ncurgx})(.*)\z}, trans:
|
|
640
|
+
Safrano::Transition.new(%r{\A/(#{ncurgx})(.*)\z}, trans: :transition_nav_collection)
|
|
638
641
|
end
|
|
639
642
|
if (neurgx = @nav_entity_url_regexp)
|
|
640
643
|
@entity_allowed_transitions <<
|
|
641
|
-
Safrano::Transition.new(%r{\A/(#{neurgx})(.*)\z}, trans:
|
|
644
|
+
Safrano::Transition.new(%r{\A/(#{neurgx})(.*)\z}, trans: :transition_nav_entity)
|
|
642
645
|
end
|
|
643
|
-
@entity_allowed_transitions << Safrano::Transition.new(%r{\A/(\w+)(.*)\z}, trans:
|
|
646
|
+
@entity_allowed_transitions << Safrano::Transition.new(%r{\A/(\w+)(.*)\z}, trans: :transition_invalid_attribute)
|
|
644
647
|
@entity_allowed_transitions.freeze
|
|
645
648
|
@entity_allowed_transitions
|
|
646
649
|
end
|
|
@@ -78,6 +78,10 @@ module Safrano
|
|
|
78
78
|
# Represents a named but nil-valued navigation-attribute of an Entity
|
|
79
79
|
# (usually resulting from a NULL FK db value)
|
|
80
80
|
class NilNavigationAttribute
|
|
81
|
+
def initialize
|
|
82
|
+
@allowed_transitions = ALLOWED_TRANSITIONS
|
|
83
|
+
end
|
|
84
|
+
|
|
81
85
|
include Safrano::NavigationInfo
|
|
82
86
|
def odata_get(req)
|
|
83
87
|
if req.walker.media_value
|
|
@@ -131,6 +135,8 @@ module Safrano
|
|
|
131
135
|
def allowed_transitions
|
|
132
136
|
ALLOWED_TRANSITIONS
|
|
133
137
|
end
|
|
138
|
+
|
|
139
|
+
include Safrano::Transitions::GetNextTrans::BySimpleDetect
|
|
134
140
|
end
|
|
135
141
|
include Transitions
|
|
136
142
|
end
|
data/lib/odata/request/json.rb
CHANGED
data/lib/odata/transition.rb
CHANGED
|
@@ -4,16 +4,78 @@ require_relative 'error'
|
|
|
4
4
|
|
|
5
5
|
# our main namespace
|
|
6
6
|
module Safrano
|
|
7
|
+
module Transitions
|
|
8
|
+
module GetNextTrans
|
|
9
|
+
module BySimpleDetect
|
|
10
|
+
def get_next_transresult(path_remain)
|
|
11
|
+
# current url-parsing context
|
|
12
|
+
# has no ambiguous next match and we dont need to find longest match
|
|
13
|
+
# but it's sufficient to find the one matching (or nil)
|
|
14
|
+
tres_next = nil
|
|
15
|
+
@allowed_transitions.detect { |t| tres_next = t.result(path_remain) }
|
|
16
|
+
tres_next
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module ForJustTransitionEnd
|
|
21
|
+
def get_next_transresult(path_remain)
|
|
22
|
+
Safrano::TransitionEnd.result(path_remain)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Transitions::GetNextTrans::ByLongestMatch
|
|
27
|
+
module ByLongestMatch
|
|
28
|
+
def get_next_transresult(path_remain)
|
|
29
|
+
# current url-parsing context
|
|
30
|
+
# has ambiguous next match and we need to find longest match
|
|
31
|
+
# example: current context is "the top level service" and we have
|
|
32
|
+
# entity types Race and RaceType
|
|
33
|
+
|
|
34
|
+
match_len = -1
|
|
35
|
+
tres_next = nil
|
|
36
|
+
|
|
37
|
+
@allowed_transitions.each { |t|
|
|
38
|
+
if (res = t.longer_match(path_remain, match_len))
|
|
39
|
+
tres_next = res
|
|
40
|
+
match_len = tres_next.match_length
|
|
41
|
+
end
|
|
42
|
+
}
|
|
43
|
+
tres_next
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# same as ByLongestMatch but use the getter method allowed_transitions instead of
|
|
48
|
+
# directly @allowed_transitions
|
|
49
|
+
module ByLongestMatchDyn
|
|
50
|
+
def get_next_transresult(path_remain)
|
|
51
|
+
# current url-parsing context
|
|
52
|
+
# has ambiguous next match and we need to find longest match
|
|
53
|
+
# example: current context is "the top level service" and we have
|
|
54
|
+
# entity types Race and RaceType
|
|
55
|
+
|
|
56
|
+
match_len = -1
|
|
57
|
+
tres_next = nil
|
|
58
|
+
|
|
59
|
+
allowed_transitions.each { |t|
|
|
60
|
+
if (res = t.longer_match(path_remain, match_len))
|
|
61
|
+
tres_next = res
|
|
62
|
+
match_len = tres_next.match_length
|
|
63
|
+
end
|
|
64
|
+
}
|
|
65
|
+
tres_next
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
7
71
|
# represents a state transition when navigating/parsing the url path
|
|
8
72
|
# from left to right
|
|
9
73
|
class Transition
|
|
10
|
-
attr_accessor :trans
|
|
11
|
-
attr_accessor :match_result
|
|
12
|
-
attr_accessor :
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
EMPTYSTR = ''
|
|
16
|
-
SLASH = '/'
|
|
74
|
+
# attr_accessor :trans
|
|
75
|
+
# attr_accessor :match_result
|
|
76
|
+
# attr_accessor :trans_result
|
|
77
|
+
# attr_accessor :rgx
|
|
78
|
+
# attr_reader :remain_idx
|
|
17
79
|
|
|
18
80
|
RESULT_BAD_REQ_ERR = [nil, :error, ::Safrano::BadRequestError].freeze
|
|
19
81
|
RESULT_NOT_FOUND_ERR = [nil, :error, ::Safrano::ErrorNotFound].freeze
|
|
@@ -25,31 +87,58 @@ module Safrano
|
|
|
25
87
|
Regexp.new(arg)
|
|
26
88
|
else
|
|
27
89
|
arg
|
|
28
|
-
end
|
|
90
|
+
end.freeze
|
|
91
|
+
@trans = trans.freeze
|
|
92
|
+
@remain_idx = remain_idx.freeze
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def result(str)
|
|
96
|
+
return unless (mres = @rgx.match(str))
|
|
97
|
+
|
|
98
|
+
TransitionResult.new(trans: @trans, match_result: mres, remain_idx: @remain_idx)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# return match-result for str, if longer as min_match_length
|
|
102
|
+
def longer_match(str, min_match_length)
|
|
103
|
+
return unless (mres = @rgx.match(str))
|
|
104
|
+
return unless (mres.match_length(1) > min_match_length)
|
|
105
|
+
|
|
106
|
+
TransitionResult.new(trans: @trans, match_result: mres, remain_idx: @remain_idx)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class TransitionResult
|
|
111
|
+
attr_reader :match
|
|
112
|
+
|
|
113
|
+
EMPTYSTR = ''
|
|
114
|
+
SLASH = '/'
|
|
115
|
+
|
|
116
|
+
def initialize(trans:, match_result:, remain_idx:)
|
|
29
117
|
@trans = trans
|
|
118
|
+
@match = match_result
|
|
30
119
|
@remain_idx = remain_idx
|
|
31
120
|
end
|
|
32
121
|
|
|
33
|
-
def
|
|
34
|
-
@
|
|
122
|
+
def match_length
|
|
123
|
+
@match.match_length(1)
|
|
35
124
|
end
|
|
36
125
|
|
|
37
126
|
# remain_idx is the index of the last match-data. ususally its 2
|
|
38
127
|
# but can be overidden
|
|
39
128
|
def path_remain
|
|
40
|
-
@
|
|
129
|
+
@match[@remain_idx] if @match && @match[@remain_idx]
|
|
41
130
|
end
|
|
42
131
|
|
|
43
132
|
def path_done
|
|
44
|
-
if @
|
|
45
|
-
@
|
|
133
|
+
if @match
|
|
134
|
+
@match[1] || EMPTYSTR
|
|
46
135
|
else
|
|
47
136
|
EMPTYSTR
|
|
48
137
|
end
|
|
49
138
|
end
|
|
50
139
|
|
|
51
140
|
def do_transition(ctx)
|
|
52
|
-
ctx.
|
|
141
|
+
ctx.__send__(@trans, @match)
|
|
53
142
|
end
|
|
54
143
|
end
|
|
55
144
|
|
|
@@ -59,8 +148,30 @@ module Safrano
|
|
|
59
148
|
@trans = trans
|
|
60
149
|
end
|
|
61
150
|
|
|
62
|
-
def
|
|
151
|
+
def result(str)
|
|
152
|
+
@str = str
|
|
153
|
+
InplaceTransitionResult.new(trans: @trans, match_result: @str)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# return match-result for str, if longer as min_match_length
|
|
157
|
+
def longer_match(str, min_match_length)
|
|
63
158
|
@str = str
|
|
159
|
+
return unless (@str.size > min_match_length)
|
|
160
|
+
|
|
161
|
+
InplaceTransitionResult.new(trans: @trans, match_result: @str)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Transition that does not move/change the input
|
|
166
|
+
class InplaceTransitionResult < TransitionResult
|
|
167
|
+
def initialize(trans:, match_result:)
|
|
168
|
+
@trans = trans
|
|
169
|
+
@match = match_result
|
|
170
|
+
@str = match_result
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def match_length
|
|
174
|
+
@str.size
|
|
64
175
|
end
|
|
65
176
|
|
|
66
177
|
def path_remain
|
|
@@ -70,26 +181,22 @@ module Safrano
|
|
|
70
181
|
def path_done
|
|
71
182
|
EMPTYSTR
|
|
72
183
|
end
|
|
73
|
-
|
|
74
|
-
def do_transition(ctx)
|
|
75
|
-
ctx.method(@trans).call(@str)
|
|
76
|
-
end
|
|
77
184
|
end
|
|
78
185
|
|
|
79
|
-
TransitionEnd = Transition.new('\A(\/?)\z', trans:
|
|
80
|
-
TransitionExecuteFunc = InplaceTransition.new(trans:
|
|
186
|
+
TransitionEnd = Transition.new('\A(\/?)\z', trans: :transition_end)
|
|
187
|
+
TransitionExecuteFunc = InplaceTransition.new(trans: :transition_execute_func)
|
|
81
188
|
TransitionMetadata = Transition.new('\A(\/\$metadata)(.*)',
|
|
82
|
-
trans:
|
|
189
|
+
trans: :transition_metadata)
|
|
83
190
|
TransitionBatch = Transition.new('\A(\/\$batch)(.*)',
|
|
84
|
-
trans:
|
|
191
|
+
trans: :transition_batch)
|
|
85
192
|
TransitionContentId = Transition.new('\A(\/\$(\d+))(.*)',
|
|
86
|
-
trans:
|
|
193
|
+
trans: :transition_content_id,
|
|
87
194
|
remain_idx: 3)
|
|
88
195
|
TransitionCount = Transition.new('(\A\/\$count)(.*)\z',
|
|
89
|
-
trans:
|
|
196
|
+
trans: :transition_count)
|
|
90
197
|
TransitionValue = Transition.new('(\A\/\$value)(.*)\z',
|
|
91
|
-
trans:
|
|
198
|
+
trans: :transition_value)
|
|
92
199
|
TransitionLinks = Transition.new('(\A\/\$links)(.*)\z',
|
|
93
|
-
trans:
|
|
200
|
+
trans: :transition_links)
|
|
94
201
|
attr_accessor :allowed_transitions
|
|
95
202
|
end
|
data/lib/odata/walker.rb
CHANGED
|
@@ -65,30 +65,10 @@ module Safrano
|
|
|
65
65
|
if (prefix == EMPTYSTR) || (prefix == SLASH)
|
|
66
66
|
path
|
|
67
67
|
else
|
|
68
|
-
# path.sub!(/\A#{prefix}/, '')
|
|
69
|
-
# TODO: check
|
|
70
68
|
path.sub(/\A#{prefix}/, EMPTYSTR)
|
|
71
69
|
end
|
|
72
70
|
end
|
|
73
71
|
|
|
74
|
-
def get_next_transition
|
|
75
|
-
# handle multiple valid transitions
|
|
76
|
-
# like when we have attributes that are substring of each other
|
|
77
|
-
# --> instead of using detect (ie take first transition)
|
|
78
|
-
# we need to use select and then find the longest match
|
|
79
|
-
|
|
80
|
-
valid_tr = @context.allowed_transitions.map(&:dup).select do |t|
|
|
81
|
-
t.do_match(@path_remain)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# HACK: (wanted: a better one) to make attributes that are substrings of each other
|
|
85
|
-
# work well
|
|
86
|
-
return unless valid_tr
|
|
87
|
-
return (@tr_next = nil) if valid_tr.empty?
|
|
88
|
-
|
|
89
|
-
@tr_next = valid_tr.size == 1 ? valid_tr.first : valid_tr.max_by { |t| t.match_result[1].size }
|
|
90
|
-
end
|
|
91
|
-
|
|
92
72
|
# perform a content-id ($batch changeset ref) transition
|
|
93
73
|
def do_run_with_content_id
|
|
94
74
|
if @content_id_refs.is_a? Hash
|
|
@@ -137,7 +117,7 @@ module Safrano
|
|
|
137
117
|
end
|
|
138
118
|
|
|
139
119
|
def do_next_transition
|
|
140
|
-
@context, @status, @error = @
|
|
120
|
+
@context, @status, @error = @tres_next.do_transition(@context)
|
|
141
121
|
# little hack's
|
|
142
122
|
case @status
|
|
143
123
|
# we dont have the content-id references data on service level
|
|
@@ -151,8 +131,8 @@ module Safrano
|
|
|
151
131
|
end
|
|
152
132
|
|
|
153
133
|
@contexts << @context
|
|
154
|
-
@path_remain = @
|
|
155
|
-
@path_done << @
|
|
134
|
+
@path_remain = @tres_next.path_remain
|
|
135
|
+
@path_done << @tres_next.path_done
|
|
156
136
|
|
|
157
137
|
# little hack's
|
|
158
138
|
state_mappings
|
|
@@ -160,8 +140,8 @@ module Safrano
|
|
|
160
140
|
|
|
161
141
|
def eo
|
|
162
142
|
while @context
|
|
163
|
-
|
|
164
|
-
if @
|
|
143
|
+
@tres_next = @context.get_next_transresult(@path_remain)
|
|
144
|
+
if @tres_next
|
|
165
145
|
do_next_transition
|
|
166
146
|
else
|
|
167
147
|
@context = nil
|
data/lib/safrano/rack_app.rb
CHANGED
|
@@ -8,7 +8,7 @@ require_relative 'response'
|
|
|
8
8
|
module Safrano
|
|
9
9
|
# Note there is a strong 1 to 1 relation between an app instance
|
|
10
10
|
# and a published service. --> actually means also
|
|
11
|
-
# we only support
|
|
11
|
+
# we only support one service per App-class because publishing is
|
|
12
12
|
# made on app class level
|
|
13
13
|
class ServerApp
|
|
14
14
|
def initialize
|
|
@@ -37,6 +37,11 @@ module Safrano
|
|
|
37
37
|
@service_base
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# needed for safrano-rack_builder
|
|
41
|
+
def get_path_prefix
|
|
42
|
+
self.class.get_service_base.xpath_prefix
|
|
43
|
+
end
|
|
44
|
+
|
|
40
45
|
def self.set_servicebase(sbase)
|
|
41
46
|
@service_base = sbase
|
|
42
47
|
@service_base.enable_v1_service
|
data/lib/safrano/rack_builder.rb
CHANGED
|
@@ -7,12 +7,73 @@ module Rack
|
|
|
7
7
|
module Safrano
|
|
8
8
|
# just a Wrapper to ensure (force?) that mandatory middlewares are acutally
|
|
9
9
|
# used
|
|
10
|
+
LOCALHOST_ANY_PORT_RGX = /\A(?:https?:\/\/)?localhost(?::\d+)?\z/.freeze
|
|
11
|
+
CORS_RO_METHODS = %i[get head options].freeze
|
|
12
|
+
CORS_BATCH_METHODS = %i[post head options].freeze
|
|
13
|
+
CORS_RW_METHODS = %i[get post put patch delete head options].freeze
|
|
10
14
|
class Builder < ::Rack::Builder
|
|
11
15
|
def initialize(default_app = nil, &block)
|
|
12
16
|
super(default_app) {}
|
|
13
|
-
|
|
17
|
+
@middlewares = []
|
|
18
|
+
|
|
14
19
|
instance_eval(&block) if block_given?
|
|
15
|
-
|
|
20
|
+
prepend_rackcors_ifneeded
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def use(middleware, *args, &block)
|
|
24
|
+
@middlewares << middleware
|
|
25
|
+
super(middleware, *args, &block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def prepend_rackcors_ifneeded
|
|
29
|
+
return if stack_has_rackcors
|
|
30
|
+
|
|
31
|
+
# get the safrano app path prefix
|
|
32
|
+
# normally @run is a Safrano Server app
|
|
33
|
+
return unless @run.is_a? ::Safrano::ServerApp
|
|
34
|
+
|
|
35
|
+
service_path_prefix = @run.get_path_prefix.dup
|
|
36
|
+
|
|
37
|
+
# due to a bug in rack-cors
|
|
38
|
+
# we cant use the batch ressource path as
|
|
39
|
+
# a string but need to pass it as a regexp
|
|
40
|
+
# ( bug fixed in rack-cors git / new release? but still need to workaround it for
|
|
41
|
+
# current/old releases ),
|
|
42
|
+
batch_path_regexp = if service_path_prefix.empty?
|
|
43
|
+
# Ressource /$batch
|
|
44
|
+
/\A\/\$batch\z/
|
|
45
|
+
else
|
|
46
|
+
# Ressource like /foo/bar/baz/$batch
|
|
47
|
+
service_path_prefix.sub!(::Safrano::TRAILING_SLASH_RGX, '')
|
|
48
|
+
service_path_prefix.sub!(::Safrano::LEADING_SLASH_RGX, '')
|
|
49
|
+
# now is foo/bar/baz
|
|
50
|
+
path_prefix_rgx = Regexp.escape("/#{service_path_prefix}/$batch")
|
|
51
|
+
# now escaped path regexp /foo/bar/baz/$batch
|
|
52
|
+
# finaly just add start / end anchors
|
|
53
|
+
/\A#{path_prefix_rgx}\z/
|
|
54
|
+
end
|
|
55
|
+
# this will append rack-cors mw
|
|
56
|
+
# per default allow GET * from everywhere
|
|
57
|
+
# allow POST $batch from localhost only
|
|
58
|
+
use ::Rack::Cors do
|
|
59
|
+
allow do
|
|
60
|
+
origins LOCALHOST_ANY_PORT_RGX
|
|
61
|
+
resource batch_path_regexp, headers: :any, methods: CORS_BATCH_METHODS
|
|
62
|
+
resource '*', headers: :any, methods: CORS_RW_METHODS
|
|
63
|
+
end
|
|
64
|
+
allow do
|
|
65
|
+
origins '*'
|
|
66
|
+
resource '*', headers: :any, methods: CORS_RO_METHODS
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# we need it in first place... move last element to beginin of mw stack
|
|
71
|
+
rackcors = @use.delete_at(-1)
|
|
72
|
+
@use.insert(0, rackcors)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def stack_has_rackcors
|
|
76
|
+
@middlewares.find { |mw| mw == Rack::Cors }
|
|
16
77
|
end
|
|
17
78
|
end
|
|
18
79
|
end
|
data/lib/safrano/request.rb
CHANGED
|
@@ -7,14 +7,16 @@ module Safrano
|
|
|
7
7
|
# handle GET PUT etc
|
|
8
8
|
module MethodHandlers
|
|
9
9
|
def odata_options
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
# cf. stackoverflow.com/questions/22924678/sinatra-delete-response-headers
|
|
11
|
+
headers.delete('Content-Type')
|
|
12
|
+
@response.headers.delete('Content-Type')
|
|
13
|
+
@response.headers['Content-Type'] = ''
|
|
14
|
+
|
|
15
|
+
# we only let rack-cors handle Cors OPTIONS .
|
|
16
|
+
# otherwise dont do it...
|
|
17
|
+
# see https://www.mnot.net/blog/2012/10/29/NO_OPTIONS
|
|
18
|
+
# 501 not implemented
|
|
19
|
+
[501, EMPTY_HASH, '']
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def odata_delete
|
|
@@ -97,6 +99,7 @@ module Safrano
|
|
|
97
99
|
@type
|
|
98
100
|
end
|
|
99
101
|
end
|
|
102
|
+
|
|
100
103
|
## original coding:
|
|
101
104
|
# # The set of media-types. Requests that do not indicate
|
|
102
105
|
# # one of the media types presents in this list will not be eligible
|
data/lib/safrano/service.rb
CHANGED
|
@@ -107,7 +107,10 @@ module Safrano
|
|
|
107
107
|
MIN_DATASERVICE_VERSION = '1'
|
|
108
108
|
CV_MAX_DATASERVICE_VERSION = Contract.valid(MAX_DATASERVICE_VERSION).freeze
|
|
109
109
|
CV_MIN_DATASERVICE_VERSION = Contract.valid(MIN_DATASERVICE_VERSION).freeze
|
|
110
|
+
TRAILING_SLASH_RGX = %r{/\z}.freeze
|
|
111
|
+
LEADING_SLASH_RGX = %r{\A/}.freeze
|
|
110
112
|
include XMLNS
|
|
113
|
+
GENERIC_415_RESP = [415, {}, ['']].freeze
|
|
111
114
|
# Base class for service. Subclass will be for V1, V2 etc...
|
|
112
115
|
class ServiceBase
|
|
113
116
|
include Safrano
|
|
@@ -166,7 +169,6 @@ module Safrano
|
|
|
166
169
|
instance_eval(&block) if block_given?
|
|
167
170
|
end
|
|
168
171
|
|
|
169
|
-
TRAILING_SLASH = %r{/\z}.freeze
|
|
170
172
|
DEFAULT_PATH_PREFIX = '/'
|
|
171
173
|
DEFAULT_SERVER_URL = 'http://localhost:9494'
|
|
172
174
|
|
|
@@ -200,13 +202,14 @@ module Safrano
|
|
|
200
202
|
end
|
|
201
203
|
|
|
202
204
|
def path_prefix(path_pr)
|
|
203
|
-
@xpath_prefix = path_pr.sub(
|
|
205
|
+
@xpath_prefix = path_pr.sub(TRAILING_SLASH_RGX, '')
|
|
206
|
+
@xpath_prefix.freeze
|
|
204
207
|
(@v1.xpath_prefix = @xpath_prefix) if @v1
|
|
205
208
|
(@v2.xpath_prefix = @xpath_prefix) if @v2
|
|
206
209
|
end
|
|
207
210
|
|
|
208
211
|
def server_url(surl)
|
|
209
|
-
@xserver_url = surl.sub(
|
|
212
|
+
@xserver_url = surl.sub(TRAILING_SLASH_RGX, '')
|
|
210
213
|
(@v1.xserver_url = @xserver_url) if @v1
|
|
211
214
|
(@v2.xserver_url = @xserver_url) if @v2
|
|
212
215
|
end
|
|
@@ -618,6 +621,8 @@ module Safrano
|
|
|
618
621
|
Safrano::TransitionContentId
|
|
619
622
|
].freeze
|
|
620
623
|
|
|
624
|
+
include Safrano::Transitions::GetNextTrans::ByLongestMatch
|
|
625
|
+
|
|
621
626
|
def build_allowed_transitions
|
|
622
627
|
@allowed_transitions = if @function_imports.empty?
|
|
623
628
|
(ALLOWED_TRANSITIONS_FIXED + [
|
|
@@ -677,7 +682,7 @@ module Safrano
|
|
|
677
682
|
[200, CT_APPXML, [service_xml(req)]]
|
|
678
683
|
else
|
|
679
684
|
# this is returned by http://services.odata.org/V2/OData/Safrano.svc
|
|
680
|
-
|
|
685
|
+
GENERIC_415_RESP
|
|
681
686
|
end
|
|
682
687
|
end
|
|
683
688
|
end
|
|
@@ -746,11 +751,13 @@ module Safrano
|
|
|
746
751
|
Safrano::Transition::RESULT_END
|
|
747
752
|
end
|
|
748
753
|
|
|
754
|
+
include Safrano::Transitions::GetNextTrans::ForJustTransitionEnd
|
|
755
|
+
|
|
749
756
|
def odata_get(req)
|
|
750
757
|
if req.accept?(APPXML)
|
|
751
758
|
[200, CT_APPXML, [@service.metadata_xml(req)]]
|
|
752
759
|
else
|
|
753
|
-
|
|
760
|
+
GENERIC_415_RESP
|
|
754
761
|
end
|
|
755
762
|
end
|
|
756
763
|
end
|
data/lib/safrano/type_mapping.rb
CHANGED
|
@@ -12,6 +12,7 @@ module Safrano
|
|
|
12
12
|
attr_reader :castfunc
|
|
13
13
|
attr_reader :edm_type
|
|
14
14
|
end
|
|
15
|
+
|
|
15
16
|
# Model attribute (column) specific mapping
|
|
16
17
|
class AttributeTypeMapping < TypeMapping
|
|
17
18
|
attr_reader :attr_name
|
|
@@ -20,6 +21,7 @@ module Safrano
|
|
|
20
21
|
@edm_type = builder.xedm_type
|
|
21
22
|
@castfunc = builder.castfunc
|
|
22
23
|
end
|
|
24
|
+
|
|
23
25
|
# wrapper to handle API
|
|
24
26
|
class Builder
|
|
25
27
|
attr_reader :xedm_type
|
|
@@ -82,9 +84,8 @@ module Safrano
|
|
|
82
84
|
end
|
|
83
85
|
|
|
84
86
|
def match(curtyp)
|
|
85
|
-
if @bui2 && (m = @bui2.match(curtyp))
|
|
86
|
-
|
|
87
|
-
elsif @bui1 && (m = @bui1.match(curtyp))
|
|
87
|
+
if (@bui2 && (m = @bui2.match(curtyp))) ||
|
|
88
|
+
(@bui1 && (m = @bui1.match(curtyp)))
|
|
88
89
|
m
|
|
89
90
|
elsif @rgx.match(curtyp)
|
|
90
91
|
type_mapping
|
|
@@ -134,6 +135,7 @@ module Safrano
|
|
|
134
135
|
RgxTypeMapping1Par.new(self)
|
|
135
136
|
end
|
|
136
137
|
end
|
|
138
|
+
|
|
137
139
|
class Builder2Par < Builder
|
|
138
140
|
def initialize(db_ty_rgx, proc)
|
|
139
141
|
@db_types_rgx = db_ty_rgx
|
|
@@ -177,6 +179,7 @@ module Safrano
|
|
|
177
179
|
@castfunc = builder.castfunc
|
|
178
180
|
end
|
|
179
181
|
end
|
|
182
|
+
|
|
180
183
|
class RgxTypeMapping2Par < RgxTypeMapping
|
|
181
184
|
def initialize(builder)
|
|
182
185
|
@edm_type = builder.xedm_type
|
data/lib/safrano/version.rb
CHANGED
|
@@ -130,7 +130,7 @@ class JoinByPathsHelper < Set
|
|
|
130
130
|
# associated model's primary key. Each current model object can be
|
|
131
131
|
# associated with many associated model objects, and each associated
|
|
132
132
|
# model object can be associated with many current model objects.
|
|
133
|
-
# TODO: testcase for :one_through_one
|
|
133
|
+
# TODO: testcase for :one_through_one
|
|
134
134
|
# when # :one_through_one :: Similar to many_to_many in terms of foreign keys, but only one object
|
|
135
135
|
# is associated to the current object through the association.
|
|
136
136
|
# Provides only getter methods, no setter or modification methods.
|
|
@@ -173,7 +173,6 @@ class JoinByPathsHelper < Set
|
|
|
173
173
|
|
|
174
174
|
next result_
|
|
175
175
|
|
|
176
|
-
|
|
177
176
|
end
|
|
178
177
|
|
|
179
178
|
lks.map! { |k| Sequel[seg.first.alias_sym][k] } unless seg.first.empty?
|
|
@@ -229,6 +228,7 @@ module Sequel
|
|
|
229
228
|
@alias_cnt = 0
|
|
230
229
|
end
|
|
231
230
|
end
|
|
231
|
+
|
|
232
232
|
module ClassMethods
|
|
233
233
|
attr_reader :aliases_sym
|
|
234
234
|
attr_reader :alias_cnt
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: safrano
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- oz
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-
|
|
11
|
+
date: 2023-11-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|
|
@@ -253,7 +253,7 @@ metadata:
|
|
|
253
253
|
changelog_uri: https://gitlab.com/dm0da/safrano/blob/master/CHANGELOG
|
|
254
254
|
source_code_uri: https://gitlab.com/dm0da/safrano/tree/master
|
|
255
255
|
documentation_uri: https://gitlab.com/dm0da/safrano/-/blob/master/README.md
|
|
256
|
-
post_install_message:
|
|
256
|
+
post_install_message:
|
|
257
257
|
rdoc_options: []
|
|
258
258
|
require_paths:
|
|
259
259
|
- lib
|
|
@@ -268,8 +268,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
268
268
|
- !ruby/object:Gem::Version
|
|
269
269
|
version: '0'
|
|
270
270
|
requirements: []
|
|
271
|
-
rubygems_version: 3.
|
|
272
|
-
signing_key:
|
|
271
|
+
rubygems_version: 3.3.15
|
|
272
|
+
signing_key:
|
|
273
273
|
specification_version: 4
|
|
274
274
|
summary: Safrano is an OData server library
|
|
275
275
|
test_files: []
|