safrano 0.6.0 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26b1634021c6ab2117b0b2fe4fb8c49242d4a045b6afafec604ef61e26c3bc48
4
- data.tar.gz: 86adac4fb2ab8ad02d570f373325b7e1c4945941a01e2328fc50ff10c9460abd
3
+ metadata.gz: a14d6778d6391597537f6747dc2cdd650413bd45d8351af3ba427d349c90e887
4
+ data.tar.gz: f8f899d1d1cffd48701539e85240f7368eec5f85c6d0d1647f0448f55bdfde58
5
5
  SHA512:
6
- metadata.gz: c19b30f23c22127742d8c557a23feec6de75a91d20b1b8b0033fb85e4f1549e92de300e17cb3d80564f92ed0dcd81096b5b4aca1a268eba803b6bbd6a4c0a75c
7
- data.tar.gz: ee3a9d73e9e1d5862cb1a699786e9f81c5a58d7937678b5a138eecae08ccc10e49248f70066ede55e270fb361744978ca4b5b9a75bbd6e62a2c4685e6fc2ae46
6
+ metadata.gz: dbe8f715a880590d6077f392408a50082e99cfd5f23b9648fd63c64a543c361a155cba6144a63da75a4c1e48f0e2ccb1da95d19729d49cb881b82100bc122b3c
7
+ data.tar.gz: 199026ff4cf9abc042cd547232f8aa9c251c6849f9e954fe6351da2b416b1cc87d3ad974d09bbf7c12122d1bff65ca1699556bffcaae563d329984ffacb5ea53
@@ -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)
@@ -348,6 +354,13 @@ module Safrano
348
354
  @nav_entity_url_regexp = @nav_entity_attribs_keys.join('|')
349
355
  end
350
356
 
357
+ # allow to override default type settings on attribute level
358
+ # for example use Edm.Guid instead of Binary(Blob) or Edm.DateTimeOffset instead of Edm.DateTime
359
+ def with_attribute(asymb, &proc)
360
+ am = AttributeTypeMapping.builder(asymb, &proc).type_mapping
361
+ @type_mappings[asymb] = am
362
+ end
363
+
351
364
  EMPTYH = {}.freeze
352
365
 
353
366
  def build_default_template
@@ -359,16 +372,26 @@ module Safrano
359
372
  # cols needed catsting before final json output
360
373
  @casted_cols = {}
361
374
  db_schema.each do |col, props|
362
- # first check if we have user-defined type mapping
375
+ # first check if we have user-defined regexp based global type mapping
363
376
  usermap = nil
364
377
  dbtyp = props[:db_type]
365
378
  metadata = @cols_metadata[col]
366
379
  if service.type_mappings.values.find { |map| usermap = map.match(dbtyp) }
367
380
 
368
381
  metadata[:edm_type] = usermap.edm_type
382
+ if usermap.castfunc
383
+ @casted_cols[col] = usermap.castfunc
384
+ next # this will override our rules below !
385
+ end
386
+ end
369
387
 
370
- @casted_cols[col] = usermap.castfunc
371
- next # this will override our rules below !
388
+ # attribute specific type mapping
389
+ if colmap = @type_mappings[col]
390
+ metadata[:edm_type] = colmap.edm_type
391
+ if colmap.castfunc
392
+ @casted_cols[col] = colmap.castfunc
393
+ next # this will override our rules below !
394
+ end
372
395
  end
373
396
 
374
397
  if metadata[:edm_precision] && (metadata[:edm_type] =~ /\AEdm.Decimal\(/i)
@@ -401,14 +424,30 @@ module Safrano
401
424
  @casted_cols[col] = ->(x) { Base64.encode64(x) unless x.nil? } # Base64
402
425
  next
403
426
  end
427
+ # Odata V2 Spec:
428
+ # Literal form of Edm.Guid as used in URIs formatted as a JSON string
429
+ if metadata[:edm_type] == 'Edm.Guid'
430
+
431
+ if props[:type] == :blob # Edm.Guid but as 16 byte binary Blob on DB level, eg in Sqlite
432
+ @casted_cols[col] = ->(x) {
433
+ UUIDTools::UUID.parse_raw(x).to_s unless x.nil?
434
+ } # Base64
435
+ next
436
+ end
437
+ end
404
438
  # TODO: check this more in details
405
439
  # NOTE: here we use :type which is the sequel defined ruby-type
406
440
  if props[:type] == :datetime || props[:type] == :date
407
441
  # @casted_cols[col] = ->(x) { x&.iso8601 }
408
442
  @casted_cols[col] = ->(x) { x&.to_edm_json }
409
443
  end
444
+ end # db_schema.each do |col, props|
445
+
446
+ # check if key needs casting. Important for later entity-uri generation !
447
+ if primary_key.is_a? Symbol # single key field
448
+ @pk_castfunc = @casted_cols[primary_key]
410
449
  end
411
- end
450
+ end # build_casted_cols(service)
412
451
 
413
452
  def finalize_publishing(service)
414
453
  build_type_name
@@ -417,17 +456,17 @@ module Safrano
417
456
  build_default_template
418
457
 
419
458
  # add edm_types into metadata store
420
- @cols_metadata = {}
459
+
421
460
  db_schema.each do |col, props|
422
461
  metadata = @cols_metadata.key?(col) ? @cols_metadata[col] : (@cols_metadata[col] = {})
423
462
  Safrano.add_edm_types(metadata, props)
424
463
  end
425
464
 
426
465
  build_casted_cols(service)
427
- # unless @casted_cols.empty?
428
- # require 'pry'
429
- # binding.pry
430
- # end
466
+
467
+ # build pk regexps
468
+ prepare_pk
469
+
431
470
  # and finally build the path lists and allowed tr's
432
471
  build_attribute_path_list
433
472
  build_expand_path_list
@@ -440,7 +479,8 @@ module Safrano
440
479
  finalize_media if respond_to? :finalize_media
441
480
  end
442
481
 
443
- KEYPRED_URL_REGEXP = /\A\(\s*'?([\w=,'\s]+)'?\s*\)(.*)/.freeze
482
+ KEYPRED_URL_REGEXP = /\A\(\s*((?:guid)?'?[\w=,\-'\s]+'?)\s*\)(.*)/.freeze
483
+ 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
484
  def prepare_pk
445
485
  if primary_key.is_a? Array
446
486
  @pk_names = []
@@ -479,14 +519,30 @@ module Safrano
479
519
  else
480
520
  @pk_names = [primary_key.to_s]
481
521
  @pk_cast_from_string = nil
522
+
482
523
  kvpredicate = case db_schema[primary_key][:type]
483
524
  when :integer
525
+ # TODO harmonize this with primitive_types.rb convert_from_url
484
526
  @pk_cast_from_string = ->(str) { Integer(str) }
485
- '(\\d+)'
527
+ /(\d+)/.freeze
486
528
  else
487
- "'(\\w+)'"
529
+ metadata = @cols_metadata[primary_key]
530
+ case metadata[:edm_type]
531
+ when 'Edm.Guid'
532
+ if db_schema[primary_key][:type] == :blob # Edm.Guid but as 16byte binary Blob on DB
533
+ @pk_cast_from_string = ->(str) {
534
+ # TODO harmonize this with primitive_types.rb convert_from_url
535
+ # Sequel::SQL::Blob.new([ str.gsub('-', '') ].pack('H*')) }
536
+ Sequel::SQL::Blob.new(UUIDTools::UUID.parse(str).raw)
537
+ }
538
+ end
539
+ GUIDRGX
540
+ else
541
+ /'(\w+)'/.freeze
542
+ end
488
543
  end
489
544
  @iuk_rgx = /\A\s*#{kvpredicate}\s*\z/
545
+ # @entity_id_url_regexp = /\A\(\s*#{kvpredicate}\s*\)(.*)/.freeze
490
546
  @entity_id_url_regexp = KEYPRED_URL_REGEXP
491
547
  end
492
548
  end
@@ -663,7 +719,7 @@ module Safrano
663
719
  end
664
720
 
665
721
  def pk_lookup_expr(id)
666
- id
722
+ { primary_key => id }
667
723
  end
668
724
  end
669
725
 
@@ -696,7 +752,7 @@ module Safrano
696
752
  # json is default content type so we dont need to specify it here again
697
753
  # TODO quirks array mode !
698
754
  # [201, EMPTY_HASH, new_entity.to_odata_post_json(service: req.service)]
699
- [201, EMPTY_HASH, new_entity.to_odata_create_json(request: req)]
755
+ [201, { 'Location' => new_entity.uri }, new_entity.to_odata_create_json(request: req)]
700
756
  else # TODO: other formats
701
757
  415
702
758
  end
data/lib/safrano/core.rb CHANGED
@@ -14,6 +14,8 @@ module Safrano
14
14
  # some prominent constants... probably already defined elsewhere eg in Rack
15
15
  # but lets KISS
16
16
  CONTENT_TYPE = 'Content-Type'
17
+ CONTENT_LENGTH = 'Content-Length'
18
+ LOCATION = 'Location'
17
19
  CTT_TYPE_LC = 'content-type'
18
20
  TEXTPLAIN_UTF8 = 'text/plain;charset=utf-8'
19
21
  APPJSON = 'application/json'
@@ -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
@@ -337,7 +341,7 @@ module Safrano
337
341
  end
338
342
 
339
343
  def with_db_type(*dbtypnams, &proc)
340
- m = TypeMapping.builder(*dbtypnams, &proc)
344
+ m = RgxTypeMapping.builder(*dbtypnams, &proc)
341
345
  @type_mappings[m.db_types_rgx] = m
342
346
  end
343
347
 
@@ -405,6 +409,17 @@ module Safrano
405
409
  vals
406
410
  end
407
411
  end
412
+ # needed for example if the pk is a guid, saved as binary on DB and the DB
413
+ # does not have a real uuid type but just return binary data...
414
+ # --> we need to convert the pk similarly as for json output
415
+ unless klass.primary_key.is_a?(Array) # we do this only for single PK
416
+ # guid in multi-PK not yet supported
417
+ if klass.pk_castfunc
418
+ klass.include Safrano::PKUriWithFunc
419
+ else
420
+ klass.include Safrano::PKUriWithoutFunc
421
+ end
422
+ end
408
423
  end
409
424
 
410
425
  # 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,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Safrano
3
- VERSION = '0.6.0'
4
+ VERSION = '0.6.2'
4
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.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - oz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-30 00:00:00.000000000 Z
11
+ date: 2022-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -70,30 +70,44 @@ dependencies:
70
70
  name: tzinfo
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '2.0'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '2.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: tzinfo-data
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '1.0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
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