safrano 0.8.0 → 0.8.1

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