safrano 0.6.1 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/core_ext/Date/format.rb +4 -0
- data/lib/core_ext/DateTime/format.rb +5 -0
- data/lib/core_ext/Time/format.rb +5 -0
- data/lib/odata/edm/primitive_types.rb +21 -0
- data/lib/odata/entity.rb +19 -5
- data/lib/odata/filter/base.rb +4 -0
- data/lib/odata/filter/parse.rb +5 -0
- data/lib/odata/filter/token.rb +46 -46
- data/lib/odata/filter/tree.rb +30 -8
- data/lib/odata/function_import.rb +3 -0
- data/lib/odata/model_ext.rb +71 -14
- data/lib/safrano/service.rb +31 -10
- data/lib/safrano/type_mapping.rb +44 -7
- data/lib/safrano/version.rb +1 -1
- data/lib/safrano.rb +1 -0
- metadata +20 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33ec32a310f05cb4118e15152f2209aec1551d6363af29eddae28b92d4abf79d
|
4
|
+
data.tar.gz: 78d6e4c243fc124280ddfc352f0e9376aae269c016bd57b9d9693caff0c7e5c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4344b9959b281e77d830fabf9d812655a50c9560258a5cca3a3364c4569efb10beee34bc7bf0f2fa1b86561fcdff499e40cb3e5a8d819a4b94013870b9a57e91
|
7
|
+
data.tar.gz: e0027ac6315f9b7ee8e1b948de9c5f1236b01f79638ef3ddcc8adad3a1972091ba5ce52f5bea060ad673581b38a7d00cb4a24ed6aa295c92558e228aabde775f
|
data/lib/core_ext/Date/format.rb
CHANGED
@@ -39,6 +39,10 @@ module Safrano
|
|
39
39
|
def to_edm_json
|
40
40
|
# no offset
|
41
41
|
# --> %Q milliseconds since epoch
|
42
|
+
# Formatting: look at
|
43
|
+
# https://services.odata.org/V2/Northwind/Northwind.svc/Employees?$format=json
|
44
|
+
# the json raw data is like this : "HireDate": "\/Date(704678400000)\/"
|
45
|
+
# --> \/\/
|
42
46
|
strftime('/Date(%Q)/')
|
43
47
|
end
|
44
48
|
end
|
@@ -36,6 +36,11 @@ module Safrano
|
|
36
36
|
# <ticks> = number of milliseconds since midnight Jan 1, 1970
|
37
37
|
# <offset> = number of minutes to add or subtract
|
38
38
|
# https://stackoverflow.com/questions/10286204/what-is-the-right-json-date-format/10286228#10286228
|
39
|
+
|
40
|
+
# Formatting: look at
|
41
|
+
# https://services.odata.org/V2/Northwind/Northwind.svc/Employees?$format=json
|
42
|
+
# the json raw data is like this : "HireDate": "\/Date(704678400000)\/"
|
43
|
+
# --> \/\/
|
39
44
|
def to_edm_json
|
40
45
|
if offset.zero?
|
41
46
|
# no offset
|
data/lib/core_ext/Time/format.rb
CHANGED
@@ -51,6 +51,11 @@ module Safrano
|
|
51
51
|
# https://docs.microsoft.com/de-de/dotnet/standard/datetime/system-text-json-support
|
52
52
|
# https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/stand-alone-json-serialization#datestimes-and-json
|
53
53
|
# https://blogs.sap.com/2017/01/05/date-and-time-in-sap-gateway-foundation/
|
54
|
+
|
55
|
+
# Formatting: look at
|
56
|
+
# https://services.odata.org/V2/Northwind/Northwind.svc/Employees?$format=json
|
57
|
+
# the json raw data is like this : "HireDate": "\/Date(704678400000)\/"
|
58
|
+
# --> \/\/
|
54
59
|
def to_edm_json
|
55
60
|
if utc? || (gmt_offset == 0)
|
56
61
|
# no offset
|
@@ -25,6 +25,7 @@ module Safrano
|
|
25
25
|
# 4 55
|
26
26
|
# 5 ,2
|
27
27
|
# 6 2
|
28
|
+
DB_TYPE_GUID_RGX = /\A\s*(uuid)\s*\z/i.freeze
|
28
29
|
|
29
30
|
DB_TYPE_FLOATP_RGX = /\A\s*(FLOAT)\s*(\(\s*(\d+)\s*\))?\s*\z/i.freeze
|
30
31
|
|
@@ -96,6 +97,13 @@ module Safrano
|
|
96
97
|
end
|
97
98
|
return if metadata[:edm_type]
|
98
99
|
|
100
|
+
# try Guid with db_type:
|
101
|
+
|
102
|
+
metadata[:edm_type] = if (DB_TYPE_GUID_RGX.match(props[:db_type]))
|
103
|
+
'Edm.Guid'
|
104
|
+
end
|
105
|
+
return if metadata[:edm_type]
|
106
|
+
|
99
107
|
# try with Sequel(ruby) type
|
100
108
|
metadata[:edm_type] = case props[:type]
|
101
109
|
when :integer
|
@@ -157,6 +165,7 @@ module Safrano
|
|
157
165
|
extend OutputClassMethods
|
158
166
|
|
159
167
|
def self.convert_from_urlparam(v)
|
168
|
+
# TODO this should use base64
|
160
169
|
Contract.valid(v.dup.force_encoding('BINARY'))
|
161
170
|
end
|
162
171
|
end
|
@@ -247,6 +256,18 @@ module Safrano
|
|
247
256
|
Contract::NOK
|
248
257
|
end
|
249
258
|
end
|
259
|
+
|
260
|
+
class Guid < UUIDTools::UUID
|
261
|
+
extend OutputClassMethods
|
262
|
+
|
263
|
+
def self.convert_from_urlparam(v)
|
264
|
+
Contract::NOK unless m = Filter::Parser::Token::GUIDRGX.match(v)
|
265
|
+
|
266
|
+
Contract.valid(UUIDTools::UUID.parse m[1])
|
267
|
+
rescue StandardError
|
268
|
+
Contract::NOK
|
269
|
+
end
|
270
|
+
end
|
250
271
|
end
|
251
272
|
end
|
252
273
|
end
|
data/lib/odata/entity.rb
CHANGED
@@ -380,9 +380,20 @@ module Safrano
|
|
380
380
|
end
|
381
381
|
end
|
382
382
|
|
383
|
-
|
384
|
-
|
385
|
-
|
383
|
+
module PKUriWithFunc
|
384
|
+
def pk_uri
|
385
|
+
self.class.pk_castfunc.call(pk)
|
386
|
+
end
|
387
|
+
|
388
|
+
def media_path_id
|
389
|
+
pk_uri.to_s
|
390
|
+
end
|
391
|
+
|
392
|
+
def media_path_ids
|
393
|
+
[pk_uri]
|
394
|
+
end
|
395
|
+
end
|
396
|
+
module PKUriWithoutFunc
|
386
397
|
def pk_uri
|
387
398
|
pk
|
388
399
|
end
|
@@ -395,6 +406,11 @@ module Safrano
|
|
395
406
|
[pk]
|
396
407
|
end
|
397
408
|
end
|
409
|
+
# for a single public key
|
410
|
+
module EntitySinglePK
|
411
|
+
include Entity
|
412
|
+
# PKUriWithoutFunc or PKUriWithFunc will be included on startup
|
413
|
+
end
|
398
414
|
|
399
415
|
# for multiple key
|
400
416
|
module EntityMultiPK
|
@@ -419,7 +435,6 @@ module Safrano
|
|
419
435
|
module EntityCreateStandardOutput
|
420
436
|
# Json formatter for a create entity POST call / Standard version; return as json object
|
421
437
|
def to_odata_create_json(request:)
|
422
|
-
# TODO: Perf: reduce method call overhead
|
423
438
|
# we added this redirection for readability and flexibility
|
424
439
|
to_odata_json(request: request)
|
425
440
|
end
|
@@ -428,7 +443,6 @@ module Safrano
|
|
428
443
|
module EntityCreateArrayOutput
|
429
444
|
# Json formatter for a create entity POST call Array version
|
430
445
|
def to_odata_create_json(request:)
|
431
|
-
# TODO: Perf: reduce method call overhead
|
432
446
|
# we added this redirection for readability and flexibility
|
433
447
|
to_odata_array_json(request: request)
|
434
448
|
end
|
data/lib/odata/filter/base.rb
CHANGED
data/lib/odata/filter/parse.rb
CHANGED
@@ -174,6 +174,11 @@ module Safrano
|
|
174
174
|
@cursor.update_state(tok, typ)
|
175
175
|
grow_at_cursor(FPNumber.new(tok))
|
176
176
|
end
|
177
|
+
when :GuidLit
|
178
|
+
with_accepted(tok, typ) do
|
179
|
+
@cursor.update_state(tok, typ)
|
180
|
+
grow_at_cursor(Guid16.new(tok))
|
181
|
+
end
|
177
182
|
when :DecimalLit
|
178
183
|
with_accepted(tok, typ) do
|
179
184
|
@cursor.update_state(tok, typ)
|
data/lib/odata/filter/token.rb
CHANGED
@@ -15,65 +15,65 @@ module Safrano
|
|
15
15
|
BINOBOOL = '[eE][qQ]|[LlgGNn][eETt]|[aA][nN][dD]|[oO][rR]'
|
16
16
|
BINOARITHM = '[aA][dD][dD]|[sS][uU][bB]|[mM][uU][lL]|[dD][iI][vV]|[mM][oO][dD]'
|
17
17
|
NOTRGX = 'not|NOT|Not'
|
18
|
-
FPRGX = '\d+(?:\.\d+)?(?:e[+-]?\d+)?[df]?'
|
19
|
-
DECIMALRGX = '\d+(?:\.\d+)[mM]'
|
18
|
+
FPRGX = '(\d+(?:\.\d+)?(?:e[+-]?\d+)?)[df]?'
|
19
|
+
DECIMALRGX = '(\d+(?:\.\d+))[mM]'
|
20
20
|
QUALITRGX = '\w+(?:\/\w+)+'
|
21
21
|
# datetime'yyyy-mm-ddThh:mm[:ss[.fffffff]]' NOTE: Spaces are not allowed between datetime and quoted portion.
|
22
22
|
# datetime is case-insensitive
|
23
23
|
DATIRGX = '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?:\:\d{2})?(?:\.\d{1,7})?'
|
24
|
-
DATETIMERGX = /datetime'#{DATIRGX}[zZ]?'/i.freeze
|
25
|
-
DATIOFFRGX = /datetimeoffset'#{DATIRGX}(?:[zZ]|[+-]\d{2}:\d{2})'
|
26
|
-
|
27
|
-
RGX = /(#{FUNCRGX})|(#{NULLRGX})|([(),])|(#{BINOBOOL})\s+|(#{BINOARITHM})|(#{NOTRGX})|#{QSTRINGRGX}
|
24
|
+
DATETIMERGX = /datetime'(#{DATIRGX}[zZ]?)'/i.freeze
|
25
|
+
DATIOFFRGX = /datetimeoffset'(#{DATIRGX}(?:[zZ]|[+-]\d{2}:\d{2}))'/i.freeze
|
26
|
+
GUIDRGX = /guid'([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})'/i.freeze
|
27
|
+
RGX = /(#{FUNCRGX})|(#{NULLRGX})|([(),])|(#{BINOBOOL})\s+|(#{BINOARITHM})|(#{NOTRGX})|#{QSTRINGRGX}|#{DECIMALRGX}|#{FPRGX}|(#{QUALITRGX})|#{DATETIMERGX}|#{DATIOFFRGX}|#{GUIDRGX}|(\w+)|(')/.freeze
|
28
28
|
|
29
29
|
def each_typed_token(inp)
|
30
30
|
typ = nil
|
31
31
|
|
32
32
|
inp.scan(RGX) do |groups|
|
33
|
-
idx = nil
|
34
|
-
found = nil
|
35
33
|
groups.each_with_index do |tok, i|
|
36
|
-
if
|
37
|
-
|
34
|
+
if tok
|
35
|
+
|
36
|
+
typ = case i
|
37
|
+
when 0
|
38
|
+
:FuncTree
|
39
|
+
when 1
|
40
|
+
:NullLiteral
|
41
|
+
when 2
|
42
|
+
case tok
|
43
|
+
when '(', ')'
|
44
|
+
:Delimiter
|
45
|
+
when ','
|
46
|
+
:Separator
|
47
|
+
end
|
48
|
+
when 3
|
49
|
+
:BinopBool
|
50
|
+
when 4
|
51
|
+
:BinopArithm
|
52
|
+
when 5
|
53
|
+
:UnopTree
|
54
|
+
when 6
|
55
|
+
:QString
|
56
|
+
when 7
|
57
|
+
:DecimalLit
|
58
|
+
when 8
|
59
|
+
:FPNumber
|
60
|
+
when 9
|
61
|
+
:Qualit
|
62
|
+
when 10
|
63
|
+
:DateTimeLit
|
64
|
+
when 11
|
65
|
+
:DateTimeOffsetLit
|
66
|
+
when 12
|
67
|
+
:GuidLit
|
68
|
+
when 13
|
69
|
+
:Literal
|
70
|
+
when 14
|
71
|
+
:unmatchedQuote
|
72
|
+
end
|
73
|
+
yield tok, typ
|
38
74
|
break
|
39
75
|
end
|
40
76
|
end
|
41
|
-
typ = case idx
|
42
|
-
when 0
|
43
|
-
:FuncTree
|
44
|
-
when 1
|
45
|
-
:NullLiteral
|
46
|
-
when 2
|
47
|
-
case found
|
48
|
-
when '(', ')'
|
49
|
-
:Delimiter
|
50
|
-
when ','
|
51
|
-
:Separator
|
52
|
-
end
|
53
|
-
when 3
|
54
|
-
:BinopBool
|
55
|
-
when 4
|
56
|
-
:BinopArithm
|
57
|
-
when 5
|
58
|
-
:UnopTree
|
59
|
-
when 6
|
60
|
-
:QString
|
61
|
-
when 7
|
62
|
-
:DecimalLit
|
63
|
-
when 8
|
64
|
-
:FPNumber
|
65
|
-
when 9
|
66
|
-
:Qualit
|
67
|
-
when 10
|
68
|
-
:DateTimeLit
|
69
|
-
when 11
|
70
|
-
:DateTimeOffsetLit
|
71
|
-
when 12
|
72
|
-
:Literal
|
73
|
-
when 13
|
74
|
-
:unmatchedQuote
|
75
|
-
end
|
76
|
-
yield found, typ
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
data/lib/odata/filter/tree.rb
CHANGED
@@ -71,7 +71,7 @@ module Safrano
|
|
71
71
|
def accept?(tok, typ)
|
72
72
|
case typ
|
73
73
|
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :ArgTree,
|
74
|
-
:UnopTree, :FPNumber, :DecimalLit, :DateTimeLit, :DateTimeOffsetLit
|
74
|
+
:UnopTree, :FPNumber, :DecimalLit, :DateTimeLit, :DateTimeOffsetLit, :GuidLit
|
75
75
|
nil
|
76
76
|
when :Delimiter
|
77
77
|
if tok == '('
|
@@ -238,7 +238,7 @@ module Safrano
|
|
238
238
|
def update_state(_tok, typ)
|
239
239
|
case typ
|
240
240
|
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :BinopBool, :BinopArithm,
|
241
|
-
:UnopTree, :FPNumber, :DecimalLit, :DateTimeLit, :DateTimeOffsetLit
|
241
|
+
:UnopTree, :FPNumber, :DecimalLit, :DateTimeLit, :DateTimeOffsetLit, :GuidLit
|
242
242
|
@state = :closed
|
243
243
|
end
|
244
244
|
end
|
@@ -304,7 +304,7 @@ module Safrano
|
|
304
304
|
when :Separator
|
305
305
|
@state = :sep
|
306
306
|
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :FPNumber, :DecimalLit,
|
307
|
-
:DateTimeLit, :DateTimeOffsetLit
|
307
|
+
:DateTimeLit, :DateTimeOffsetLit, :GuidLit
|
308
308
|
@state = :val
|
309
309
|
end
|
310
310
|
end
|
@@ -335,7 +335,7 @@ module Safrano
|
|
335
335
|
Parser::ErrorInvalidToken.new(tok, typ, self)
|
336
336
|
end
|
337
337
|
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :FPNumber, :DecimalLit,
|
338
|
-
:DateTimeLit, :DateTimeOffsetLit
|
338
|
+
:DateTimeLit, :DateTimeOffsetLit, :GuidLit
|
339
339
|
if (@state == :open) || (@state == :sep)
|
340
340
|
Parser::ErrorInvalidArity.new(tok, typ, self) if @parent.arity_full?(@children.size)
|
341
341
|
else
|
@@ -359,7 +359,9 @@ module Safrano
|
|
359
359
|
# 1.53f --> value 1.53
|
360
360
|
# 1.53d --> value 1.53
|
361
361
|
# 1.53 --> value 1.53
|
362
|
-
|
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)
|
363
365
|
end
|
364
366
|
|
365
367
|
def accept?(tok, typ)
|
@@ -376,13 +378,31 @@ module Safrano
|
|
376
378
|
:number
|
377
379
|
end
|
378
380
|
end
|
381
|
+
|
382
|
+
# Edm guid
|
383
|
+
class Guid16
|
384
|
+
def accept?(tok, typ)
|
385
|
+
case typ
|
386
|
+
when :Delimiter, :Separator, :BinopBool, :BinopArithm
|
387
|
+
nil
|
388
|
+
else
|
389
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def edm_type
|
394
|
+
:guid
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
379
398
|
class DecimalLit
|
380
399
|
def initialize(val)
|
381
400
|
# 1.53m --> value 1.53
|
382
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
|
383
403
|
# cf. DECIMALRGX in token.rb
|
384
404
|
|
385
|
-
super(val
|
405
|
+
super(val)
|
386
406
|
end
|
387
407
|
|
388
408
|
def accept?(tok, typ)
|
@@ -447,7 +467,8 @@ module Safrano
|
|
447
467
|
class DateTimeLit
|
448
468
|
def initialize(val)
|
449
469
|
# datetime'2000-12-12T12:00:53' --> value 2000-12-12T12:00:53
|
450
|
-
|
470
|
+
# Note: the tokenizer has already dropped the not usefull string parts
|
471
|
+
super(val)
|
451
472
|
end
|
452
473
|
|
453
474
|
def accept?(tok, typ)
|
@@ -467,7 +488,8 @@ module Safrano
|
|
467
488
|
class DateTimeOffsetLit
|
468
489
|
def initialize(val)
|
469
490
|
# datetimeoffset'2000-12-12T12:00:53+02:00' --> value 2000-12-12T12:00:53+02:00
|
470
|
-
|
491
|
+
# Note: the tokenizer has already dropped the not usefull string parts
|
492
|
+
super(val)
|
471
493
|
end
|
472
494
|
|
473
495
|
def accept?(tok, typ)
|
data/lib/odata/model_ext.rb
CHANGED
@@ -48,6 +48,11 @@ module Safrano
|
|
48
48
|
# typically the block should contain the publication of the associations
|
49
49
|
attr_accessor :deferred_iblock
|
50
50
|
|
51
|
+
# allows to override standard types
|
52
|
+
attr_accessor :type_mappings
|
53
|
+
|
54
|
+
attr_accessor :pk_castfunc
|
55
|
+
|
51
56
|
# convention: entityType is the namepsaced Ruby Model class --> name is just to_s
|
52
57
|
# Warning: for handling Navigation relations, we use anonymous collection classes
|
53
58
|
# dynamically subtyped from a Model class, and in such an anonymous class
|
@@ -73,7 +78,6 @@ module Safrano
|
|
73
78
|
end
|
74
79
|
|
75
80
|
def reset
|
76
|
-
# TODO: automatically reset all attributes?
|
77
81
|
@deferred_iblock = nil
|
78
82
|
@entity_set_name = nil
|
79
83
|
@uri = nil
|
@@ -82,6 +86,8 @@ module Safrano
|
|
82
86
|
@params = nil
|
83
87
|
@cx = nil
|
84
88
|
@cols_metadata = {}
|
89
|
+
@type_mappings = {}
|
90
|
+
@pk_castfunc = nil
|
85
91
|
end
|
86
92
|
|
87
93
|
def build_uri(uribase)
|
@@ -192,6 +198,7 @@ module Safrano
|
|
192
198
|
# 'Type' => Safrano.get_edm_type(db_type: prop[:db_type]) }
|
193
199
|
'Type' => metadata[:edm_type] }
|
194
200
|
attrs['Nullable'] = 'false' if prop[:allow_null] == false
|
201
|
+
attrs['Precision'] = '0' if metadata[:edm_type] == 'Edm.DateTime'
|
195
202
|
enty.add_element('Property', attrs)
|
196
203
|
end
|
197
204
|
enty
|
@@ -348,6 +355,13 @@ module Safrano
|
|
348
355
|
@nav_entity_url_regexp = @nav_entity_attribs_keys.join('|')
|
349
356
|
end
|
350
357
|
|
358
|
+
# allow to override default type settings on attribute level
|
359
|
+
# for example use Edm.Guid instead of Binary(Blob) or Edm.DateTimeOffset instead of Edm.DateTime
|
360
|
+
def with_attribute(asymb, &proc)
|
361
|
+
am = AttributeTypeMapping.builder(asymb, &proc).type_mapping
|
362
|
+
@type_mappings[asymb] = am
|
363
|
+
end
|
364
|
+
|
351
365
|
EMPTYH = {}.freeze
|
352
366
|
|
353
367
|
def build_default_template
|
@@ -359,16 +373,26 @@ module Safrano
|
|
359
373
|
# cols needed catsting before final json output
|
360
374
|
@casted_cols = {}
|
361
375
|
db_schema.each do |col, props|
|
362
|
-
# first check if we have user-defined type mapping
|
376
|
+
# first check if we have user-defined regexp based global type mapping
|
363
377
|
usermap = nil
|
364
378
|
dbtyp = props[:db_type]
|
365
379
|
metadata = @cols_metadata[col]
|
366
380
|
if service.type_mappings.values.find { |map| usermap = map.match(dbtyp) }
|
367
381
|
|
368
382
|
metadata[:edm_type] = usermap.edm_type
|
383
|
+
if usermap.castfunc
|
384
|
+
@casted_cols[col] = usermap.castfunc
|
385
|
+
next # this will override our rules below !
|
386
|
+
end
|
387
|
+
end
|
369
388
|
|
370
|
-
|
371
|
-
|
389
|
+
# attribute specific type mapping
|
390
|
+
if colmap = @type_mappings[col]
|
391
|
+
metadata[:edm_type] = colmap.edm_type
|
392
|
+
if colmap.castfunc
|
393
|
+
@casted_cols[col] = colmap.castfunc
|
394
|
+
next # this will override our rules below !
|
395
|
+
end
|
372
396
|
end
|
373
397
|
|
374
398
|
if metadata[:edm_precision] && (metadata[:edm_type] =~ /\AEdm.Decimal\(/i)
|
@@ -401,14 +425,30 @@ module Safrano
|
|
401
425
|
@casted_cols[col] = ->(x) { Base64.encode64(x) unless x.nil? } # Base64
|
402
426
|
next
|
403
427
|
end
|
428
|
+
# Odata V2 Spec:
|
429
|
+
# Literal form of Edm.Guid as used in URIs formatted as a JSON string
|
430
|
+
if metadata[:edm_type] == 'Edm.Guid'
|
431
|
+
|
432
|
+
if props[:type] == :blob # Edm.Guid but as 16 byte binary Blob on DB level, eg in Sqlite
|
433
|
+
@casted_cols[col] = ->(x) {
|
434
|
+
UUIDTools::UUID.parse_raw(x).to_s unless x.nil?
|
435
|
+
} # Base64
|
436
|
+
next
|
437
|
+
end
|
438
|
+
end
|
404
439
|
# TODO: check this more in details
|
405
440
|
# NOTE: here we use :type which is the sequel defined ruby-type
|
406
441
|
if props[:type] == :datetime || props[:type] == :date
|
407
442
|
# @casted_cols[col] = ->(x) { x&.iso8601 }
|
408
443
|
@casted_cols[col] = ->(x) { x&.to_edm_json }
|
409
444
|
end
|
445
|
+
end # db_schema.each do |col, props|
|
446
|
+
|
447
|
+
# check if key needs casting. Important for later entity-uri generation !
|
448
|
+
if primary_key.is_a? Symbol # single key field
|
449
|
+
@pk_castfunc = @casted_cols[primary_key]
|
410
450
|
end
|
411
|
-
end
|
451
|
+
end # build_casted_cols(service)
|
412
452
|
|
413
453
|
def finalize_publishing(service)
|
414
454
|
build_type_name
|
@@ -417,17 +457,17 @@ module Safrano
|
|
417
457
|
build_default_template
|
418
458
|
|
419
459
|
# add edm_types into metadata store
|
420
|
-
|
460
|
+
|
421
461
|
db_schema.each do |col, props|
|
422
462
|
metadata = @cols_metadata.key?(col) ? @cols_metadata[col] : (@cols_metadata[col] = {})
|
423
463
|
Safrano.add_edm_types(metadata, props)
|
424
464
|
end
|
425
465
|
|
426
466
|
build_casted_cols(service)
|
427
|
-
|
428
|
-
#
|
429
|
-
|
430
|
-
|
467
|
+
|
468
|
+
# build pk regexps
|
469
|
+
prepare_pk
|
470
|
+
|
431
471
|
# and finally build the path lists and allowed tr's
|
432
472
|
build_attribute_path_list
|
433
473
|
build_expand_path_list
|
@@ -440,7 +480,8 @@ module Safrano
|
|
440
480
|
finalize_media if respond_to? :finalize_media
|
441
481
|
end
|
442
482
|
|
443
|
-
KEYPRED_URL_REGEXP = /\A\(\s*'?
|
483
|
+
KEYPRED_URL_REGEXP = /\A\(\s*((?:guid)?'?[\w=,\-'\s]+'?)\s*\)(.*)/.freeze
|
484
|
+
GUIDRGX = /guid'([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})'/i.freeze
|
444
485
|
def prepare_pk
|
445
486
|
if primary_key.is_a? Array
|
446
487
|
@pk_names = []
|
@@ -479,14 +520,30 @@ module Safrano
|
|
479
520
|
else
|
480
521
|
@pk_names = [primary_key.to_s]
|
481
522
|
@pk_cast_from_string = nil
|
523
|
+
|
482
524
|
kvpredicate = case db_schema[primary_key][:type]
|
483
525
|
when :integer
|
526
|
+
# TODO harmonize this with primitive_types.rb convert_from_url
|
484
527
|
@pk_cast_from_string = ->(str) { Integer(str) }
|
485
|
-
|
528
|
+
/(\d+)/.freeze
|
486
529
|
else
|
487
|
-
|
530
|
+
metadata = @cols_metadata[primary_key]
|
531
|
+
case metadata[:edm_type]
|
532
|
+
when 'Edm.Guid'
|
533
|
+
if db_schema[primary_key][:type] == :blob # Edm.Guid but as 16byte binary Blob on DB
|
534
|
+
@pk_cast_from_string = ->(str) {
|
535
|
+
# TODO harmonize this with primitive_types.rb convert_from_url
|
536
|
+
# Sequel::SQL::Blob.new([ str.gsub('-', '') ].pack('H*')) }
|
537
|
+
Sequel::SQL::Blob.new(UUIDTools::UUID.parse(str).raw)
|
538
|
+
}
|
539
|
+
end
|
540
|
+
GUIDRGX
|
541
|
+
else
|
542
|
+
/'(\w+)'/.freeze
|
543
|
+
end
|
488
544
|
end
|
489
545
|
@iuk_rgx = /\A\s*#{kvpredicate}\s*\z/
|
546
|
+
# @entity_id_url_regexp = /\A\(\s*#{kvpredicate}\s*\)(.*)/.freeze
|
490
547
|
@entity_id_url_regexp = KEYPRED_URL_REGEXP
|
491
548
|
end
|
492
549
|
end
|
@@ -663,7 +720,7 @@ module Safrano
|
|
663
720
|
end
|
664
721
|
|
665
722
|
def pk_lookup_expr(id)
|
666
|
-
id
|
723
|
+
{ primary_key => id }
|
667
724
|
end
|
668
725
|
end
|
669
726
|
|
data/lib/safrano/service.rb
CHANGED
@@ -262,19 +262,19 @@ module Safrano
|
|
262
262
|
|
263
263
|
raise(Safrano::API::ModelNameError, modelklass) unless modelklass.is_a? Sequel::Model::ClassMethods
|
264
264
|
|
265
|
-
if modelklass.
|
266
|
-
# modules were already added previously;
|
267
|
-
# cleanup state to avoid having data from previous calls
|
268
|
-
# mostly usefull for testing (eg API)
|
269
|
-
modelklass.reset
|
270
|
-
elsif modelklass.primary_key.is_a?(Array) # first API call... (normal non-testing case)
|
265
|
+
if modelklass.primary_key.is_a?(Array) # first API call... (normal non-testing case)
|
271
266
|
modelklass.extend Safrano::EntityClassMultiPK
|
272
267
|
modelklass.include Safrano::EntityMultiPK
|
273
268
|
else
|
269
|
+
|
274
270
|
modelklass.extend Safrano::EntityClassSinglePK
|
275
271
|
modelklass.include Safrano::EntitySinglePK
|
272
|
+
|
276
273
|
end
|
277
274
|
|
275
|
+
# initialize state
|
276
|
+
modelklass.reset
|
277
|
+
|
278
278
|
# Media/Non-media
|
279
279
|
if is_media
|
280
280
|
modelklass.extend Safrano::EntityClassMedia
|
@@ -289,8 +289,12 @@ module Safrano
|
|
289
289
|
modelklass.include Safrano::NonMediaEntity
|
290
290
|
end
|
291
291
|
|
292
|
-
|
292
|
+
# this needs to be done later as it can depend on overrided attribute specific Edm type
|
293
|
+
# eg. Edm-Guid. --> moved to finalize_publishing
|
294
|
+
# modelklass.prepare_pk
|
295
|
+
|
293
296
|
modelklass.prepare_fields
|
297
|
+
|
294
298
|
esname = (entity_set_name || modelklass).to_s.freeze
|
295
299
|
serv_namespace = @xnamespace
|
296
300
|
modelklass.instance_eval do
|
@@ -317,13 +321,17 @@ module Safrano
|
|
317
321
|
modelklass.deferred_iblock = block if block_given?
|
318
322
|
end
|
319
323
|
|
324
|
+
def copy_namespace_to(klass)
|
325
|
+
serv_namespace = @xnamespace
|
326
|
+
klass.instance_eval { @namespace = serv_namespace }
|
327
|
+
end
|
328
|
+
|
320
329
|
def publish_complex_type(ctklass)
|
321
330
|
# check that the provided klass is a Safrano ComplexType
|
322
331
|
|
323
332
|
raise(Safrano::API::ComplexTypeNameError, ctklass) unless ctklass.superclass == Safrano::ComplexType
|
324
333
|
|
325
|
-
|
326
|
-
ctklass.instance_eval { @namespace = serv_namespace }
|
334
|
+
copy_namespace_to(ctklass)
|
327
335
|
|
328
336
|
@complex_types.add ctklass
|
329
337
|
end
|
@@ -337,7 +345,7 @@ module Safrano
|
|
337
345
|
end
|
338
346
|
|
339
347
|
def with_db_type(*dbtypnams, &proc)
|
340
|
-
m =
|
348
|
+
m = RgxTypeMapping.builder(*dbtypnams, &proc)
|
341
349
|
@type_mappings[m.db_types_rgx] = m
|
342
350
|
end
|
343
351
|
|
@@ -368,6 +376,8 @@ module Safrano
|
|
368
376
|
@cmap = {}
|
369
377
|
@collections.each do |klass|
|
370
378
|
@cmap[klass.entity_set_name] = klass
|
379
|
+
# set namespace needed to have qualified type name
|
380
|
+
copy_namespace_to(klass)
|
371
381
|
end
|
372
382
|
|
373
383
|
# now that we know all model klasses we can handle relationships
|
@@ -405,6 +415,17 @@ module Safrano
|
|
405
415
|
vals
|
406
416
|
end
|
407
417
|
end
|
418
|
+
# needed for example if the pk is a guid, saved as binary on DB and the DB
|
419
|
+
# does not have a real uuid type but just return binary data...
|
420
|
+
# --> we need to convert the pk similarly as for json output
|
421
|
+
unless klass.primary_key.is_a?(Array) # we do this only for single PK
|
422
|
+
# guid in multi-PK not yet supported
|
423
|
+
if klass.pk_castfunc
|
424
|
+
klass.include Safrano::PKUriWithFunc
|
425
|
+
else
|
426
|
+
klass.include Safrano::PKUriWithoutFunc
|
427
|
+
end
|
428
|
+
end
|
408
429
|
end
|
409
430
|
|
410
431
|
# build allowed transitions (requires that @collections are filled and sorted for having a
|
data/lib/safrano/type_mapping.rb
CHANGED
@@ -10,8 +10,45 @@ module Safrano
|
|
10
10
|
# Base class
|
11
11
|
class TypeMapping
|
12
12
|
attr_reader :castfunc
|
13
|
-
attr_reader :db_types_rgx
|
14
13
|
attr_reader :edm_type
|
14
|
+
end
|
15
|
+
# Model attribute (column) specific mapping
|
16
|
+
class AttributeTypeMapping < TypeMapping
|
17
|
+
attr_reader :attr_name
|
18
|
+
|
19
|
+
def initialize(builder)
|
20
|
+
@edm_type = builder.xedm_type
|
21
|
+
@castfunc = builder.castfunc
|
22
|
+
end
|
23
|
+
# wrapper to handle API
|
24
|
+
class Builder
|
25
|
+
attr_reader :xedm_type
|
26
|
+
attr_reader :castfunc
|
27
|
+
attr_reader :attr_name
|
28
|
+
|
29
|
+
def initialize(atnam)
|
30
|
+
@attr_name = atnam
|
31
|
+
end
|
32
|
+
|
33
|
+
def edm_type(input)
|
34
|
+
@xedm_type = input
|
35
|
+
end
|
36
|
+
|
37
|
+
def type_mapping
|
38
|
+
AttributeTypeMapping.new(self)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.builder(atnam, &proc)
|
43
|
+
builder = Builder.new(atnam)
|
44
|
+
builder.instance_eval(&proc)
|
45
|
+
builder
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Regexp based Global mapping
|
50
|
+
class RgxTypeMapping < TypeMapping
|
51
|
+
attr_reader :db_types_rgx
|
15
52
|
|
16
53
|
# wrapper to handle API
|
17
54
|
class Builder
|
@@ -56,7 +93,7 @@ module Safrano
|
|
56
93
|
|
57
94
|
def type_mapping
|
58
95
|
# TODO: perf; return always same object when called multiple times
|
59
|
-
|
96
|
+
RgxFixedTypeMapping.new(self)
|
60
97
|
end
|
61
98
|
end # Builder
|
62
99
|
|
@@ -94,7 +131,7 @@ module Safrano
|
|
94
131
|
p1val = @md[1]
|
95
132
|
instance_exec p1val, &@proc
|
96
133
|
|
97
|
-
|
134
|
+
RgxTypeMapping1Par.new(self)
|
98
135
|
end
|
99
136
|
end
|
100
137
|
class Builder2Par < Builder
|
@@ -121,26 +158,26 @@ module Safrano
|
|
121
158
|
p1val = @md[1]
|
122
159
|
p2val = @md[2]
|
123
160
|
instance_exec p1val, p2val, &@proc
|
124
|
-
|
161
|
+
RgxTypeMapping2Par.new(self)
|
125
162
|
end
|
126
163
|
end
|
127
164
|
end
|
128
165
|
|
129
166
|
# Fixed type (ie. without variable parts)
|
130
|
-
class
|
167
|
+
class RgxFixedTypeMapping < RgxTypeMapping
|
131
168
|
def initialize(builder)
|
132
169
|
@edm_type = builder.xedm_type
|
133
170
|
@castfunc = builder.castfunc
|
134
171
|
end
|
135
172
|
end
|
136
173
|
|
137
|
-
class
|
174
|
+
class RgxTypeMapping1Par < RgxTypeMapping
|
138
175
|
def initialize(builder)
|
139
176
|
@edm_type = builder.xedm_type
|
140
177
|
@castfunc = builder.castfunc
|
141
178
|
end
|
142
179
|
end
|
143
|
-
class
|
180
|
+
class RgxTypeMapping2Par < RgxTypeMapping
|
144
181
|
def initialize(builder)
|
145
182
|
@edm_type = builder.xedm_type
|
146
183
|
@castfunc = builder.castfunc
|
data/lib/safrano/version.rb
CHANGED
data/lib/safrano.rb
CHANGED
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.6.
|
4
|
+
version: 0.6.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- oz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -94,20 +94,34 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '1.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: uuidtools
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.2'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.2'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: rack-test
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
100
114
|
requirements:
|
101
|
-
- - "
|
115
|
+
- - "~>"
|
102
116
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
117
|
+
version: '1.0'
|
104
118
|
type: :development
|
105
119
|
prerelease: false
|
106
120
|
version_requirements: !ruby/object:Gem::Requirement
|
107
121
|
requirements:
|
108
|
-
- - "
|
122
|
+
- - "~>"
|
109
123
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
124
|
+
version: '1.0'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: rake
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|