safrano 0.6.7 → 0.7.0

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.
@@ -38,6 +38,28 @@ module Safrano
38
38
 
39
39
  # type mappings are hard, especially between "Standards" like SQL and OData V2 (might be a bit better in V4 ?)
40
40
  # this is all best effort/try to make it work logic
41
+
42
+ RUBY_TY_EDM_TY_MAP = { integer: 'Edm.Int32',
43
+ string: 'Edm.String',
44
+ date: 'Edm.DateTime',
45
+ datetime: 'Edm.DateTime',
46
+ time: 'Edm.Time',
47
+ boolean: 'Edm.Boolean',
48
+ float: 'Edm.Double',
49
+ decimal: 'Edm.Decimal',
50
+ blob: 'Edm.Binary' }.freeze
51
+ DB_TY_EDM_TY_MAP = { 'smallint' => 'Edm.Int16',
52
+ 'int2' => 'Edm.Int16',
53
+ 'smallserial' => 'Edm.Int16',
54
+ 'int' => 'Edm.Int32',
55
+ 'integer' => 'Edm.Int32',
56
+ 'serial' => 'Edm.Int32',
57
+ 'mediumint' => 'Edm.Int32',
58
+ 'int4' => 'Edm.Int32',
59
+ 'bigint' => 'Edm.Int64',
60
+ 'bigserial' => 'Edm.Int64',
61
+ 'int8' => 'Edm.Int64',
62
+ 'tinyint' => 'Edm.Byte' }.freeze
41
63
  def self.add_edm_types(metadata, props)
42
64
  # try num/dec with db_type:
43
65
  metadata[:edm_type] = if (md = DB_TYPE_NUMDEC_RGX.match(props[:db_type]))
@@ -80,51 +102,24 @@ module Safrano
80
102
 
81
103
  # try int-like with db_type:
82
104
  # smallint|int|integer|bigint|serial|bigserial
83
- metadata[:edm_type] = if (md = DB_TYPE_INTLIKE_RGX.match(props[:db_type]))
84
105
 
106
+ metadata[:edm_type] = if (md = DB_TYPE_INTLIKE_RGX.match(props[:db_type]))
85
107
  if (itype = md[1])
86
- case itype.downcase
87
- when 'smallint', 'int2', 'smallserial'
88
- 'Edm.Int16'
89
- when 'int', 'integer', 'serial', 'mediumint', 'int4'
90
- 'Edm.Int32'
91
- when 'bigint', 'bigserial', 'int8'
92
- 'Edm.Int64'
93
- when 'tinyint'
94
- 'Edm.Byte'
95
- end
108
+ DB_TY_EDM_TY_MAP[itype.downcase]
96
109
  end
97
110
  end
98
111
  return if metadata[:edm_type]
99
112
 
100
113
  # try Guid with db_type:
101
114
 
102
- metadata[:edm_type] = if (DB_TYPE_GUID_RGX.match(props[:db_type]))
115
+ metadata[:edm_type] = if DB_TYPE_GUID_RGX.match(props[:db_type])
103
116
  'Edm.Guid'
104
117
  end
118
+
105
119
  return if metadata[:edm_type]
106
120
 
107
121
  # try with Sequel(ruby) type
108
- metadata[:edm_type] = case props[:type]
109
- when :integer
110
- 'Edm.Int32'
111
- when :string
112
- 'Edm.String'
113
- when :date
114
- 'Edm.DateTime'
115
- when :datetime
116
- 'Edm.DateTime'
117
- when :time
118
- 'Edm.Time'
119
- when :boolean
120
- 'Edm.Boolean'
121
- when :float
122
- 'Edm.Double'
123
- when :decimal
124
- 'Edm.Decimal'
125
- when :blob
126
- 'Edm.Binary'
127
- end
122
+ metadata[:edm_type] = RUBY_TY_EDM_TY_MAP[props[:type]]
128
123
  end
129
124
 
130
125
  # use Edm twice so that we can do include Safrano::Edm and then
@@ -153,8 +148,8 @@ module Safrano
153
148
  nil
154
149
  end
155
150
 
156
- def self.convert_from_urlparam(v)
157
- return Contract::NOK unless v == 'null'
151
+ def self.convert_from_urlparam(val)
152
+ return Contract::NOK unless val == 'null'
158
153
 
159
154
  Contract.valid(nil)
160
155
  end
@@ -164,9 +159,9 @@ module Safrano
164
159
  class Binary < String
165
160
  extend OutputClassMethods
166
161
 
167
- def self.convert_from_urlparam(v)
168
- # TODO this should use base64
169
- Contract.valid(v.dup.force_encoding('BINARY'))
162
+ def self.convert_from_urlparam(val)
163
+ # TODO: this should use base64
164
+ Contract.valid(val.dup.force_encoding('BINARY'))
170
165
  end
171
166
  end
172
167
 
@@ -180,13 +175,13 @@ module Safrano
180
175
  end
181
176
 
182
177
  def self.odata_collection(array)
183
- array.map { |v| odata_value(v) }
178
+ array.map { |val| odata_value(val) }
184
179
  end
185
180
 
186
- def self.convert_from_urlparam(v)
187
- return Contract::NOK unless %w[true false].include?(v)
181
+ def self.convert_from_urlparam(val)
182
+ return Contract::NOK unless %w[true false].include?(val)
188
183
 
189
- Contract.valid(v == 'true')
184
+ Contract.valid(val == 'true')
190
185
  end
191
186
  end
192
187
 
@@ -195,8 +190,8 @@ module Safrano
195
190
  class Byte < Integer
196
191
  extend OutputClassMethods
197
192
 
198
- def self.convert_from_urlparam(v)
199
- return Contract::NOK unless (bytev = v.to_i) < 256
193
+ def self.convert_from_urlparam(val)
194
+ return Contract::NOK unless (bytev = val.to_i) < 256
200
195
 
201
196
  Contract.valid(bytev)
202
197
  end
@@ -209,29 +204,29 @@ module Safrano
209
204
  end
210
205
 
211
206
  def self.odata_collection(array)
212
- array.map { |v| odata_value(v) }
207
+ array.map { |val| odata_value(val) }
213
208
  end
214
209
 
215
- def self.convert_from_urlparam(v)
216
- Contract.valid(DateTime.parse(v))
210
+ def self.convert_from_urlparam(val)
211
+ Contract.valid(DateTime.parse(val))
217
212
  rescue StandardError
218
- convertion_error(v)
213
+ convertion_error(val)
219
214
  end
220
215
  end
221
216
 
222
217
  class String < ::String
223
218
  extend OutputClassMethods
224
219
 
225
- def self.convert_from_urlparam(v)
226
- Contract.valid(v)
220
+ def self.convert_from_urlparam(val)
221
+ Contract.valid(val)
227
222
  end
228
223
  end
229
224
 
230
225
  class Int32 < Integer
231
226
  extend OutputClassMethods
232
227
 
233
- def self.convert_from_urlparam(v)
234
- return Contract::NOK unless (ret = number_or_nil(v))
228
+ def self.convert_from_urlparam(val)
229
+ return Contract::NOK unless (ret = number_or_nil(val))
235
230
 
236
231
  Contract.valid(ret)
237
232
  end
@@ -240,8 +235,8 @@ module Safrano
240
235
  class Int64 < Integer
241
236
  extend OutputClassMethods
242
237
 
243
- def self.convert_from_urlparam(v)
244
- return Contract::NOK unless (ret = number_or_nil(v))
238
+ def self.convert_from_urlparam(val)
239
+ return Contract::NOK unless (ret = number_or_nil(val))
245
240
 
246
241
  Contract.valid(ret)
247
242
  end
@@ -250,8 +245,8 @@ module Safrano
250
245
  class Double < Float
251
246
  extend OutputClassMethods
252
247
 
253
- def self.convert_from_urlparam(v)
254
- Contract.valid(v.to_f)
248
+ def self.convert_from_urlparam(val)
249
+ Contract.valid(val.to_f)
255
250
  rescue StandardError
256
251
  Contract::NOK
257
252
  end
@@ -260,10 +255,10 @@ module Safrano
260
255
  class Guid < UUIDTools::UUID
261
256
  extend OutputClassMethods
262
257
 
263
- def self.convert_from_urlparam(v)
264
- Contract::NOK unless m = Filter::Parser::Token::GUIDRGX.match(v)
258
+ def self.convert_from_urlparam(val)
259
+ Contract::NOK unless (m = Filter::Parser::Token::GUIDRGX.match(val))
265
260
 
266
- Contract.valid(UUIDTools::UUID.parse m[1])
261
+ Contract.valid(UUIDTools::UUID.parse(m[1]))
267
262
  rescue StandardError
268
263
  Contract::NOK
269
264
  end
@@ -271,9 +266,3 @@ module Safrano
271
266
  end
272
267
  end
273
268
  end
274
-
275
- # include Safrano
276
-
277
- # x = Edm::String.new('xxx')
278
-
279
- # pp x
data/lib/odata/entity.rb CHANGED
@@ -372,8 +372,7 @@ module Safrano
372
372
 
373
373
  end
374
374
  model.media_handler.replace_file(data: data,
375
- entity: self,
376
- filename: filename)
375
+ entity: self)
377
376
 
378
377
  ARY_204_EMPTY_HASH_ARY
379
378
  end
data/lib/odata/error.rb CHANGED
@@ -50,9 +50,9 @@ module Safrano
50
50
 
51
51
  # used in function import error handling. cf. func import / do_execute_func
52
52
  def self.with_error(result)
53
- if result.respond_to?(:error) && (err = result.error)
54
- yield err
55
- end
53
+ return unless result.respond_to?(:error) && (err = result.error)
54
+
55
+ yield err
56
56
  end
57
57
 
58
58
  # base module for HTTP errors, when used as a Error Class
data/lib/odata/expand.rb CHANGED
@@ -14,9 +14,6 @@ module Safrano
14
14
  expandstr.nil? ? EmptyExpand : MultiExpand.new(expandstr, model)
15
15
  end
16
16
 
17
- # output template
18
- attr_reader :template
19
-
20
17
  def apply_to_dataset(dtcx)
21
18
  Contract.valid(dtcx)
22
19
  end
@@ -196,7 +196,7 @@ module Safrano
196
196
  unless (@error = check_url_func_params)
197
197
  begin
198
198
  return yield
199
- rescue LocalJumpError => e
199
+ rescue LocalJumpError
200
200
  @error = Safrano::ServiceOperationReturnError.new
201
201
  end
202
202
  end
@@ -212,13 +212,15 @@ module Safrano
212
212
 
213
213
  # Note: Sequel::Error exceptions are already
214
214
  # handled on rack app level (cf. the call methode )
215
- Safrano::with_error(result) do |error|
215
+ Safrano.with_error(result) do |error|
216
216
  @error = error
217
217
  return [nil, :error, @error] # this is return from do_execute_func !
218
218
  end
219
219
 
220
220
  # non error case
221
- [@returning.do_execute_func_result(result, req, @auto_query_params), :run]
221
+ [@returning.do_execute_func_result(result, req,
222
+ apply_query_params: @auto_query_params),
223
+ :run]
222
224
  end
223
225
  end
224
226
 
@@ -142,11 +142,11 @@ module Safrano
142
142
  @attribute_path_list = attribute_path_list
143
143
  end
144
144
 
145
- MAX_DEPTH = 6
145
+ MAX_DEPTH = 4
146
146
  def attribute_path_list(depth = 0)
147
147
  ret = @columns_str.dup
148
148
  # break circles
149
- return ret if depth > MAX_DEPTH
149
+ return ret if depth >= MAX_DEPTH
150
150
 
151
151
  depth += 1
152
152
 
@@ -166,7 +166,7 @@ module Safrano
166
166
  ret.concat(@nav_collection_attribs_keys) if @nav_collection_attribs
167
167
 
168
168
  # break circles
169
- return ret if depth > MAX_DEPTH
169
+ return ret if depth >= MAX_DEPTH
170
170
 
171
171
  depth += 1
172
172
 
@@ -387,7 +387,7 @@ module Safrano
387
387
  end
388
388
 
389
389
  # attribute specific type mapping
390
- if colmap = @type_mappings[col]
390
+ if (colmap = @type_mappings[col])
391
391
  metadata[:edm_type] = colmap.edm_type
392
392
  if colmap.castfunc
393
393
  @casted_cols[col] = colmap.castfunc
@@ -430,7 +430,7 @@ module Safrano
430
430
  if metadata[:edm_type] == 'Edm.Guid'
431
431
 
432
432
  if props[:type] == :blob # Edm.Guid but as 16 byte binary Blob on DB level, eg in Sqlite
433
- @casted_cols[col] = ->(x) {
433
+ @casted_cols[col] = lambda { |x|
434
434
  UUIDTools::UUID.parse_raw(x).to_s unless x.nil?
435
435
  } # Base64
436
436
  next
@@ -445,9 +445,9 @@ module Safrano
445
445
  end # db_schema.each do |col, props|
446
446
 
447
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]
450
- end
448
+ return unless primary_key.is_a? Symbol # single key field
449
+
450
+ @pk_castfunc = @casted_cols[primary_key]
451
451
  end # build_casted_cols(service)
452
452
 
453
453
  def finalize_publishing(service)
@@ -516,14 +516,13 @@ module Safrano
516
516
 
517
517
  @iuk_rgx_parts.transform_values! { |v| /\A#{v}\z/ }
518
518
 
519
- @entity_id_url_regexp = KEYPRED_URL_REGEXP
520
519
  else
521
520
  @pk_names = [primary_key.to_s]
522
521
  @pk_cast_from_string = nil
523
522
 
524
523
  kvpredicate = case db_schema[primary_key][:type]
525
524
  when :integer
526
- # TODO harmonize this with primitive_types.rb convert_from_url
525
+ # TODO: Harmonize this with primitive_types.rb convert_from_url
527
526
  @pk_cast_from_string = ->(str) { Integer(str) }
528
527
  /(\d+)/.freeze
529
528
  else
@@ -531,9 +530,7 @@ module Safrano
531
530
  case metadata[:edm_type]
532
531
  when 'Edm.Guid'
533
532
  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*')) }
533
+ @pk_cast_from_string = lambda { |str|
537
534
  Sequel::SQL::Blob.new(UUIDTools::UUID.parse(str).raw)
538
535
  }
539
536
  end
@@ -543,9 +540,9 @@ module Safrano
543
540
  end
544
541
  end
545
542
  @iuk_rgx = /\A\s*#{kvpredicate}\s*\z/
546
- # @entity_id_url_regexp = /\A\(\s*#{kvpredicate}\s*\)(.*)/.freeze
547
- @entity_id_url_regexp = KEYPRED_URL_REGEXP
548
543
  end
544
+ # @entity_id_url_regexp = /\A\(\s*#{kvpredicate}\s*\)(.*)/.freeze
545
+ @entity_id_url_regexp = KEYPRED_URL_REGEXP
549
546
  end
550
547
 
551
548
  def prepare_fields
@@ -673,21 +670,19 @@ module Safrano
673
670
 
674
671
  mid.split(/\s*,\s*/).each do |midpart|
675
672
  mval = nil
676
- mpk, mrgx = scan_rgx_parts.find do |_pk, rgx|
673
+ mpk, _mrgx = scan_rgx_parts.find do |_pk, rgx|
677
674
  if (md = rgx.match(midpart))
678
675
  mval = md[1]
679
676
  end
680
677
  end
681
- if mpk && mval
682
- mdch[mpk] = if (pk_cast = @pk_cast_from_string[mpk])
683
- pk_cast.call(mval)
684
- else
685
- mval # no cast needed, eg for string
686
- end
687
- scan_rgx_parts.delete(mpk)
688
- else
689
- return Contract::NOK
690
- end
678
+ return Contract::NOK unless mpk && mval
679
+
680
+ mdch[mpk] = if (pk_cast = @pk_cast_from_string[mpk])
681
+ pk_cast.call(mval)
682
+ else
683
+ mval # no cast needed, eg for string
684
+ end
685
+ scan_rgx_parts.delete(mpk)
691
686
  end
692
687
  # normally arriving here we have mdch filled with key values pairs,
693
688
  # but not in the model key ordering. lets just re-order the values
data/lib/odata/walker.rb CHANGED
@@ -66,34 +66,27 @@ module Safrano
66
66
  path
67
67
  else
68
68
  # path.sub!(/\A#{prefix}/, '')
69
- # TODO check
69
+ # TODO: check
70
70
  path.sub(/\A#{prefix}/, EMPTYSTR)
71
71
  end
72
72
  end
73
73
 
74
74
  def get_next_transition
75
- # this does not work if there are multiple valid transitions
75
+ # handle multiple valid transitions
76
76
  # like when we have attributes that are substring of each other
77
77
  # --> instead of using detect (ie take first transition)
78
78
  # we need to use select and then find the longest match
79
- # tr_next = @context.allowed_transitions.detect do |t|
80
- # t.do_match(@path_remain)
81
- # end
82
79
 
83
- valid_tr = @context.allowed_transitions.select do |t|
80
+ valid_tr = @context.allowed_transitions.map(&:dup).select do |t|
84
81
  t.do_match(@path_remain)
85
82
  end
86
83
 
87
- # this is a very fragile and obscure but required hack (wanted: a
88
- # better one) to make attributes that are substrings of each other
84
+ # HACK: (wanted: a better one) to make attributes that are substrings of each other
89
85
  # work well
90
- @tr_next = if valid_tr
91
- if valid_tr.size == 1
92
- valid_tr.first
93
- elsif valid_tr.size > 1
94
- valid_tr.max_by { |t| t.match_result[1].size }
95
- end
96
- end
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 }
97
90
  end
98
91
 
99
92
  # perform a content-id ($batch changeset ref) transition
@@ -45,9 +45,9 @@ module MIME
45
45
  end
46
46
 
47
47
  def parse_next_part(line)
48
- if @target.parser.next_part(line)
48
+ return if @target.parser.next_part(line)
49
49
 
50
- elsif @target.parser.last_part(line)
50
+ if @target.parser.last_part(line)
51
51
  @state = :end
52
52
  else
53
53
  @target.parser.addline(line)
@@ -113,8 +113,8 @@ module MIME
113
113
  MPS = 'multipart/'
114
114
  MP_RGX1 = %r{^(digest|mixed);\s*boundary="(.*)"}.freeze
115
115
  MP_RGX2 = %r{^(digest|mixed);\s*boundary=(.*)}.freeze
116
- # APP_HTTP_RGX = %r{^application/http}.freeze
117
116
  APP_HTTP = 'application/http'
117
+
118
118
  def new_content
119
119
  @target =
120
120
  if @target_ct.start_with?(MPS) &&
@@ -138,18 +138,17 @@ module MIME
138
138
 
139
139
  def parse_str(inpstr, level: 0)
140
140
  # we need to keep the line separators --> use io.readlines
141
- if inpstr.respond_to?(:readlines)
142
- @lines = inpstr.readlines(@sep)
143
- else
144
- # rack input wrapper only has gets but not readlines
145
- # BUT the rack SPEC says it only supports gets without argument!
146
- # --> finally we end up using read and split into lines...
147
- # normally should be ok for $batch POST payloads
148
-
149
- # inpstr.read should be a String
150
- @lines = inpstr.read.lines(@sep)
151
-
152
- end
141
+ @lines = if inpstr.respond_to?(:readlines)
142
+ inpstr.readlines(@sep)
143
+ else
144
+ # rack input wrapper only has gets but not readlines
145
+ # BUT the rack SPEC says it only supports gets without argument!
146
+ # --> finally we end up using read and split into lines...
147
+ # normally should be ok for $batch POST payloads
148
+
149
+ # inpstr.read should be a String
150
+ inpstr.read.lines(@sep)
151
+ end
153
152
  # tmp hack for test-tools that convert CRLF in payload to LF :-(
154
153
  if @lines.size == 1
155
154
  @sep = LF
@@ -319,7 +318,7 @@ module MIME
319
318
  def addline(line)
320
319
  @body_lines << line
321
320
  end
322
- end
321
+ end # Parser
323
322
 
324
323
  attr_reader :boundary
325
324
 
@@ -369,12 +368,12 @@ module MIME
369
368
  @hd[CTT_TYPE_LC] = "#{Safrano::MP_MIXED}; boundary=#{@boundary}"
370
369
  end
371
370
 
372
- def get_http_resp(batcha)
373
- get_response(batcha)
374
- [@response.hd, @response.unparse(true)]
371
+ def get_mult_resp(full_req)
372
+ get_response(full_req)
373
+ [202, @response.hd, @response.unparse_bodyonly]
375
374
  end
376
375
 
377
- def get_response(batcha)
376
+ def get_response(full_req)
378
377
  @response = self.class.new(::SecureRandom.uuid)
379
378
  @response.set_multipart_header
380
379
  if @level == 1 # changeset need their own global transaction
@@ -382,9 +381,10 @@ module MIME
382
381
  # and will be flagged with in_changeset=true
383
382
  # and this will finally be used to skip the transaction
384
383
  # of the changes
385
- batcha.db.transaction do
384
+
385
+ full_req.db.transaction do
386
386
  begin
387
- @response.content = @content.map { |part| part.get_response(batcha) }
387
+ @response.content = @content.map { |part| part.get_response(full_req) }
388
388
  rescue Sequel::Rollback => e
389
389
  # one of the changes of the changeset has failed
390
390
  # --> provide a dummy empty response for the change-parts
@@ -394,7 +394,7 @@ module MIME
394
394
  end
395
395
  end
396
396
  else
397
- @response.content = @content.map { |prt| prt.get_response(batcha) }
397
+ @response.content = @content.map { |part| part.get_response(full_req) }
398
398
  end
399
399
  @response
400
400
  end
@@ -409,10 +409,20 @@ module MIME
409
409
  @response
410
410
  end
411
411
 
412
- def unparse(bodyonly = false)
412
+ def unparse
413
413
  b = +String.new
414
- b << "#{Safrano::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}" unless bodyonly
414
+ b << "#{Safrano::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}"
415
+
416
+ # warning: duplicated code with below
417
+ b << crbdcr = "#{CRLF}--#{@boundary}#{CRLF}"
418
+ b << @content.map(&:unparse).join(crbdcr)
419
+ b << "#{CRLF}--#{@boundary}--#{CRLF}"
420
+ b
421
+ end
415
422
 
423
+ def unparse_bodyonly
424
+ b = +String.new
425
+ # warning: duplicated code with above
416
426
  b << crbdcr = "#{CRLF}--#{@boundary}#{CRLF}"
417
427
  b << @content.map(&:unparse).join(crbdcr)
418
428
  b << "#{CRLF}--#{@boundary}--#{CRLF}"
@@ -450,6 +460,7 @@ module MIME
450
460
  class HttpResp < Media
451
461
  attr_accessor :status
452
462
  attr_accessor :content
463
+ attr_accessor :rack_resp
453
464
 
454
465
  APPLICATION_HTTP_11 = ['Content-Type: application/http',
455
466
  "Content-Transfer-Encoding: binary#{CRLF}",
@@ -558,9 +569,9 @@ module MIME
558
569
  @content = other.content
559
570
  end
560
571
 
561
- def get_response(batchapp)
562
- # self.content should be the request
563
- rack_resp = batchapp.batch_call(@content)
572
+ def get_response(full_req)
573
+ # self.content should be the part-request
574
+ rack_resp = full_req.batch_call(@content)
564
575
  @response = MIME::Content::Application::HttpResp.new
565
576
  @response.status = rack_resp[0]
566
577
  @response.hd = rack_resp[1]