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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcb0478a7adf2f62f2530ede1bfe1ebee5b0261379a71d4aec54f3007094429f
4
- data.tar.gz: 95a5adbb64d6311d30dad9e4c6c5a2b5a54c4ab0dd788c00743d36a4dbee5cd4
3
+ metadata.gz: 33ec32a310f05cb4118e15152f2209aec1551d6363af29eddae28b92d4abf79d
4
+ data.tar.gz: 78d6e4c243fc124280ddfc352f0e9376aae269c016bd57b9d9693caff0c7e5c2
5
5
  SHA512:
6
- metadata.gz: aa50d7338736540e2ef60a2cded37b2993b8a485d4f760c7f06ddf0ff26ba4cbbf1aaaca6a618f6812c6c85cd8ac05f8d93b514799f311997639e1025e7c4da1
7
- data.tar.gz: 967000423228b25f80f070b53ccc2aafdad2e96ab3f6c96bfe17d6845e5d7bfef412dd3c5cd81a7b46c419769a3d0f743e2a74d1e656af983a4ad372e95c05f8
6
+ metadata.gz: 4344b9959b281e77d830fabf9d812655a50c9560258a5cca3a3364c4569efb10beee34bc7bf0f2fa1b86561fcdff499e40cb3e5a8d819a4b94013870b9a57e91
7
+ data.tar.gz: e0027ac6315f9b7ee8e1b948de9c5f1236b01f79638ef3ddcc8adad3a1972091ba5ce52f5bea060ad673581b38a7d00cb4a24ed6aa295c92558e228aabde775f
@@ -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
@@ -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
- # for a single public key
384
- module EntitySinglePK
385
- include Entity
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
@@ -79,5 +79,9 @@ module Safrano
79
79
  end
80
80
  class DateTimeOffsetLit < Leave
81
81
  end
82
+
83
+ # Edm Guid 16 bytes
84
+ class Guid16 < Leave
85
+ end
82
86
  end
83
87
  end
@@ -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)
@@ -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})'/.freeze
26
-
27
- RGX = /(#{FUNCRGX})|(#{NULLRGX})|([(),])|(#{BINOBOOL})\s+|(#{BINOARITHM})|(#{NOTRGX})|#{QSTRINGRGX}|(#{DECIMALRGX})|(#{FPRGX})|(#{QUALITRGX})|(#{DATETIMERGX})|(#{DATIOFFRGX})|(\w+)|(')/.freeze
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 (found = tok)
37
- idx = i
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
@@ -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
- val[-1] =~ /[fd]/i ? super(val[0..-2]) : super(val)
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[0..-2])
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
- super(val[9..-2])
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
- super(val[15..-2])
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)
@@ -36,6 +36,9 @@ module Safrano
36
36
  Safrano::Edm::Edm::Double
37
37
  when 'DateTime'
38
38
  Safrano::Edm::Edm::DateTime
39
+ # UUID with uuidtools
40
+ when 'UUIDTools::UUID'
41
+ Safrano::Edm::Edm::Guid
39
42
  else
40
43
  t
41
44
  end
@@ -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
- @casted_cols[col] = usermap.castfunc
371
- next # this will override our rules below !
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
- @cols_metadata = {}
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
- # unless @casted_cols.empty?
428
- # require 'pry'
429
- # binding.pry
430
- # end
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*'?([\w=,'\s]+)'?\s*\)(.*)/.freeze
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
- '(\\d+)'
528
+ /(\d+)/.freeze
486
529
  else
487
- "'(\\w+)'"
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
 
@@ -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.ancestors.include? Safrano::Entity
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
- modelklass.prepare_pk
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
- serv_namespace = @xnamespace
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 = TypeMapping.builder(*dbtypnams, &proc)
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
@@ -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
- FixedTypeMapping.new(self)
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
- TypeMapping1Par.new(self)
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
- TypeMapping2Par.new(self)
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 FixedTypeMapping < TypeMapping
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 TypeMapping1Par < TypeMapping
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 TypeMapping2Par < TypeMapping
180
+ class RgxTypeMapping2Par < RgxTypeMapping
144
181
  def initialize(builder)
145
182
  @edm_type = builder.xedm_type
146
183
  @castfunc = builder.castfunc
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Safrano
4
- VERSION = '0.6.1'
4
+ VERSION = '0.6.3'
5
5
  end
data/lib/safrano.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'json'
4
4
  require 'rexml/document'
5
+ require 'uuidtools'
5
6
  require_relative 'safrano/version'
6
7
  require_relative 'safrano/deprecation'
7
8
  require_relative 'safrano/core_ext'
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.1
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-08-07 00:00:00.000000000 Z
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