safrano 0.6.1 → 0.6.3
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 +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
|