safrano 0.6.7 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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]