safrano 0.3.2 → 0.3.3

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: '048c3efb69194f00a6ed5593c23300ae7b9d46f7d98a546d443bd5d9c5d2c6c8'
4
- data.tar.gz: 825698055233240072faee6e038e531884e23d938fc1ed9708e98d27cf0db3c6
3
+ metadata.gz: b25412d7e053724965ba296bdd43a6f0b3bbd8a62cee2f45a14edf18303ed6c1
4
+ data.tar.gz: 1e9ad5ce553d13199e4ce79cecf7209ce1bb2695f5c476d8f5e5e16bd57bd4f9
5
5
  SHA512:
6
- metadata.gz: d2697db38029b2701a418a620b74e7b8080d368cd34461073de46065d7057cb3b852cc62795cabb1bbbad82d10607bff96b899beb483a3c0f03e028cf8cb8a31
7
- data.tar.gz: 12a8674563e67e2f3c4e475d5867f8965e0095a0ff0afea80079f6e7e59c56921abc506d33b07ca483f5f2c16024176a6e3679db26b09e979a9f9fb6fac8d755
6
+ metadata.gz: ad1361bb963f77870f784ed5322318b2567bac06e6ef915dcda62f57df767a0893a988672e4ace8dbd64cdda0cbff8d78de2aa712d4ebceb8821ed109018c909
7
+ data.tar.gz: d4f5f16fa548883e697479f313aa824c0798c38d71ead64172552ddfaec79fb53652063bf6dbfe36362cca5c30a80534665e8a12895fb2f343f1857a0dba8c1c
data/lib/multipart.rb CHANGED
@@ -10,6 +10,10 @@ module MIME
10
10
  class Media
11
11
  # Parser for MIME::Media
12
12
  class Parser
13
+ HMD_RGX = /^([\w-]+)\s*:\s*(.*)/.freeze
14
+
15
+ CRLF_LINE_RGX = /^#{CRLF}$/.freeze
16
+
13
17
  attr_accessor :lines
14
18
  attr_accessor :target
15
19
  def initialize
@@ -47,10 +51,11 @@ module MIME
47
51
  end
48
52
 
49
53
  def parse_head(line)
50
- if (hmd = /^([\w-]+)\s*:\s*(.*)/.match(line))
54
+ if (hmd = HMD_RGX.match(line))
51
55
  @target_hd[hmd[1].downcase] = hmd[2].strip
52
56
 
53
- elsif /^#{CRLF}$/ =~ line
57
+ # elsif CRLF_LINE_RGX =~ line
58
+ elsif CRLF == line
54
59
  @target_ct = @target_hd['content-type'] || 'text/plain'
55
60
  @state = new_content
56
61
 
@@ -102,14 +107,19 @@ module MIME
102
107
  @target.ct = @target_ct
103
108
  @state = :bmp
104
109
  end
105
- MP_RGX1 = %r{^multipart/(digest|mixed);\s*boundary=\"(.*)\"}.freeze
106
- MP_RGX2 = %r{^multipart/(digest|mixed);\s*boundary=(.*)}.freeze
110
+ MPS = 'multipart/'.freeze
111
+ MP_RGX1 = %r{^(digest|mixed);\s*boundary=\"(.*)\"}.freeze
112
+ MP_RGX2 = %r{^(digest|mixed);\s*boundary=(.*)}.freeze
113
+ # APP_HTTP_RGX = %r{^application/http}.freeze
114
+ APP_HTTP = 'application/http'.freeze
107
115
  def new_content
108
116
  @target =
109
- if (md = MP_RGX1.match(@target_ct)) ||
110
- (md = MP_RGX2.match(@target_ct))
117
+ if @target_ct.start_with?(MPS) and
118
+ (md = ((MP_RGX1.match(@target_ct[10..-1])) ||
119
+ (MP_RGX2.match(@target_ct[10..-1])))
120
+ )
111
121
  multipart_content(md[2].strip)
112
- elsif %r{^application/http} =~ @target_ct
122
+ elsif @target_ct.start_with?(APP_HTTP)
113
123
  MIME::Content::Application::Http.new
114
124
  else
115
125
  MIME::Content::Text::Plain.new
@@ -187,6 +197,8 @@ module MIME
187
197
  class Plain < Media
188
198
  # Parser for Text::Plain
189
199
  class Parser
200
+ HMD_RGX = /^([\w-]+)\s*:\s*(.*)/.freeze
201
+ CRLF_LINE_RGX = /^#{CRLF}$/.freeze
190
202
  def initialize(target)
191
203
  @state = :h
192
204
  @lines = []
@@ -198,9 +210,10 @@ module MIME
198
210
  end
199
211
 
200
212
  def parse_head(line)
201
- if (hmd = /^([\w-]+)\s*:\s*(.*)/.match(line))
213
+ if (hmd = HMD_RGX.match(line))
202
214
  @target.hd[hmd[1].downcase] = hmd[2].strip
203
- elsif /^#{CRLF}$/ =~ line
215
+ # elsif CRLF_LINE_RGX =~ line
216
+ elsif CRLF == line
204
217
  @state = :b
205
218
  else
206
219
  @target.content << line
@@ -251,6 +264,7 @@ module MIME
251
264
  class Base < Media
252
265
  # Parser for Multipart Base class
253
266
  class Parser
267
+ CRLF_ENDING_RGX = /#{CRLF}$/.freeze
254
268
  def initialize(target)
255
269
  @body_lines = []
256
270
  @target = target
@@ -295,7 +309,8 @@ module MIME
295
309
  # to remove it from the end of the last body line
296
310
  return unless @body_lines
297
311
 
298
- @body_lines.last.sub!(/#{CRLF}$/, '')
312
+ # @body_lines.last.sub!(CRLF_ENDING_RGX, '')
313
+ @body_lines.last.chomp!(CRLF)
299
314
  @parts << @body_lines
300
315
  end
301
316
 
@@ -349,7 +364,7 @@ module MIME
349
364
  end
350
365
 
351
366
  def set_multipart_header
352
- @hd['content-type'] = "multipart/mixed; boundary=#{@boundary}"
367
+ @hd['content-type'] = "#{OData::MP_MIXED}; boundary=#{@boundary}"
353
368
  end
354
369
 
355
370
  def get_http_resp(batcha)
@@ -390,14 +405,15 @@ module MIME
390
405
  @response.content = [{ 'odata.error' =>
391
406
  { 'message' =>
392
407
  'Bad Request: Failed changeset ' } }.to_json]
393
- @response.hd = { 'Content-Type' => 'application/json;charset=utf-8' }
408
+ @response.hd = OData::CT_JSON
394
409
  @response
395
410
  end
396
411
 
397
412
  def unparse(bodyonly = false)
398
413
  b = ''
399
414
  unless bodyonly
400
- b << 'Content-Type' << ': ' << @hd['content-type'] << CRLF
415
+ # b << OData::CONTENT_TYPE << ': ' << @hd[OData::CTT_TYPE_LC] << CRLF
416
+ b << "#{OData::CONTENT_TYPE}: #{@hd[OData::CTT_TYPE_LC]}#{CRLF}"
401
417
  end
402
418
  b << "#{CRLF}--#{@boundary}#{CRLF}"
403
419
  b << @content.map(&:unparse).join("#{CRLF}--#{@boundary}#{CRLF}")
@@ -425,7 +441,8 @@ module MIME
425
441
 
426
442
  def unparse
427
443
  b = "#{@http_method} #{@uri} HTTP/1.1#{CRLF}"
428
- @hd.each { |k, v| b << k.to_s << ': ' << v.to_s << CRLF }
444
+ @hd.each { |k, v| b << "#{k}: #{v}#{CRLF}" }
445
+ # @hd.each { |k, v| b << k.to_s << ': ' << v.to_s << CRLF }
429
446
  b << CRLF
430
447
  b << @content if @content != ''
431
448
  b
@@ -455,7 +472,8 @@ module MIME
455
472
  def unparse
456
473
  b = String.new(APPLICATION_HTTP_11)
457
474
  b << "#{@status} #{StatusMessage[@status]} #{CRLF}"
458
- @hd.each { |k, v| b << k.to_s << ': ' << v.to_s << CRLF }
475
+ @hd.each { |k, v| b << "#{k}: #{v}#{CRLF}" }
476
+ # @hd.each { |k, v| b << k.to_s << ': ' << v.to_s << CRLF }
459
477
  b << CRLF
460
478
  b << @content.join if @content
461
479
  b
@@ -471,6 +489,9 @@ module MIME
471
489
 
472
490
  # Parser for Http Media
473
491
  class Parser
492
+ HMD_RGX = /^([\w-]+)\s*:\s*(.*)/.freeze
493
+ CRLF_LINE_RGX = /^#{CRLF}$/.freeze
494
+
474
495
  def initialize(target)
475
496
  @state = :http
476
497
  @lines = []
@@ -483,7 +504,7 @@ module MIME
483
504
  end
484
505
 
485
506
  def parse_http(line)
486
- if (hmd = /^([\w-]+)\s*:\s*(.*)/.match(line))
507
+ if (hmd = HMD_RGX.match(line))
487
508
  @target.hd[hmd[1].downcase] = hmd[2].strip
488
509
  elsif (mdht = HTTP_R_RGX.match(line))
489
510
  @state = :hd
@@ -499,9 +520,10 @@ module MIME
499
520
  end
500
521
 
501
522
  def parse_head(line)
502
- if (hmd = /^([\w-]+)\s*:\s*(.*)/.match(line))
523
+ if (hmd = HMD_RGX.match(line))
503
524
  @target.content.hd[hmd[1].downcase] = hmd[2].strip
504
- elsif /^#{CRLF}$/ =~ line
525
+ elsif CRLF == line
526
+ # elsif CRLF_LINE_RGX =~ line
505
527
  @state = :b
506
528
  else
507
529
  @body_lines << line
data/lib/odata/batch.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'rack_app.rb'
2
2
  require 'safrano_core.rb'
3
+ require 'rack/body_proxy'
4
+ require_relative './common_logger.rb'
3
5
 
4
6
  module OData
5
7
  # Support for OData multipart $batch Requests
@@ -38,7 +40,7 @@ module OData
38
40
  def batch_call(part_req)
39
41
  env = batch_env(part_req)
40
42
  env['HTTP_HOST'] = @full_req.env['HTTP_HOST']
41
-
43
+ began_at = Rack::Utils.clock_time
42
44
  @request = OData::Request.new(env)
43
45
  @response = OData::Response.new
44
46
 
@@ -51,7 +53,16 @@ module OData
51
53
  before
52
54
  dispatch
53
55
 
54
- @response.finish
56
+ status, header, body = @response.finish
57
+ # Logging of sub-requests with ODataCommonLogger.
58
+ # A bit hacky but working
59
+ # TODO: test ?
60
+ if (logga = @full_req.env['safrano.logger_mw'])
61
+ logga.batch_log(env, status, header, began_at)
62
+ # TODO check why/if we need Rack::Utils::HeaderHash.new(header)
63
+ # and Rack::BodyProxy.new(body) ?
64
+ end
65
+ [status, header, body]
55
66
  end
56
67
 
57
68
  # shamelessely copied from Rack::TEST:Session
@@ -71,7 +82,10 @@ module OData
71
82
  @env = ::Rack::MockRequest.env_for(mime_req.uri,
72
83
  method: mime_req.http_method,
73
84
  input: mime_req.content)
85
+ # Logging of sub-requests
86
+ @env[Rack::RACK_ERRORS] = @full_req.env[Rack::RACK_ERRORS]
74
87
  @env.merge! headers_for_env(mime_req.hd)
88
+
75
89
  @env
76
90
  end
77
91
  end
@@ -112,7 +126,7 @@ module OData
112
126
  def odata_post(req)
113
127
  @request = req
114
128
 
115
- if @request.media_type == 'multipart/mixed'
129
+ if @request.media_type == OData::MP_MIXED
116
130
 
117
131
  batcha = @request.create_batch_app
118
132
  @mult_request = @request.parse_multipart
@@ -9,6 +9,7 @@ require 'odata/error.rb'
9
9
  require 'odata/collection_filter.rb'
10
10
  require 'odata/collection_order.rb'
11
11
  require 'odata/url_parameters.rb'
12
+ require 'odata/collection_media.rb'
12
13
 
13
14
  # small helper method
14
15
  # http://stackoverflow.com/
@@ -52,6 +53,9 @@ module OData
52
53
  attr_reader :nav_entity_attribs
53
54
  attr_reader :data_fields
54
55
  attr_reader :inlinecount
56
+ # set to parent entity in case the collection is a nav.collection
57
+ # nil otherwise
58
+ attr_reader :nav_parent
55
59
 
56
60
  attr_accessor :namespace
57
61
 
@@ -75,13 +79,27 @@ module OData
75
79
  attr_accessor :deferred_iblock
76
80
 
77
81
  # convention: entityType is the Ruby Model class --> name is just to_s
82
+ # Warning: for handling Navigation relations, we use anonymous collection classes
83
+ # dynamically subtyped from a Model class, and in such an anonymous class
84
+ # the class-name is not the OData Type. In these subclass we redefine "type_name"
85
+ # thus when we need the Odata type name, we shall use this method instead of just the collection class name
78
86
  def type_name
79
87
  to_s
80
88
  end
81
89
 
82
- # convention: default for entity_set_name is the model table name
90
+ # convention: default for entity_set_name is the type name
83
91
  def entity_set_name
84
- @entity_set_name = (@entity_set_name || table_name.to_s)
92
+ @entity_set_name = (@entity_set_name || type_name)
93
+ end
94
+
95
+ def reset
96
+ # TODO: automatically reset all attributes?
97
+ @deferred_iblock = nil
98
+ @entity_set_name = nil
99
+ @uparms = nil
100
+ @params = nil
101
+ @ax = nil
102
+ @cx = nil
85
103
  end
86
104
 
87
105
  def execute_deferred_iblock
@@ -89,19 +107,37 @@ module OData
89
107
  end
90
108
 
91
109
  # Factory json-> Model Object instance
92
- def new_from_hson_h(hash, in_changeset: false)
110
+ def new_from_hson_h(hash)
93
111
  enty = new
94
- hash.delete('__metadata')
95
- # DONE: move this somewhere else where it's evaluated only once at setup
96
- # data_fields = db_schema.map do |col, cattr|
97
- # cattr[:primary_key] ? nil : col
98
- # end.select { |col| col }
99
112
  enty.set_fields(hash, @data_fields, missing: :skip)
100
- # in-changeset requests get their own transaction
101
- enty.save(transaction: !in_changeset)
102
113
  enty
103
114
  end
104
115
 
116
+ CREATE_AND_SAVE_ENTY_AND_REL = lambda do |new_entity, assoc, parent|
117
+ # in-changeset requests get their own transaction
118
+ case assoc[:type]
119
+ when :one_to_many, :one_to_one
120
+ OData.create_nav_relation(new_entity, assoc, parent)
121
+ new_entity.save(transaction: false)
122
+ when :many_to_one
123
+ new_entity.save(transaction: false)
124
+ OData.create_nav_relation(new_entity, assoc, parent)
125
+ parent.save(transaction: false)
126
+ else
127
+ # not supported
128
+ end
129
+ end
130
+ def odata_create_save_entity_and_rel(req, new_entity, assoc, parent)
131
+ if req.in_changeset
132
+ # in-changeset requests get their own transaction
133
+ CREATE_AND_SAVE_ENTY_AND_REL.call(new_entity, assoc, parent)
134
+ else
135
+ db.transaction do
136
+ CREATE_AND_SAVE_ENTY_AND_REL.call(new_entity, assoc, parent)
137
+ end
138
+ end
139
+ end
140
+
105
141
  def odata_get_inlinecount_w_sequel
106
142
  return unless (icp = @params['$inlinecount'])
107
143
 
@@ -114,10 +150,6 @@ module OData
114
150
  end
115
151
  end
116
152
 
117
- def navigated_coll
118
- false
119
- end
120
-
121
153
  def attrib_path_valid?(path)
122
154
  @attribute_path_list.include? path
123
155
  end
@@ -209,7 +241,7 @@ module OData
209
241
  def initialize_dataset
210
242
  @cx = self
211
243
  @ax = nil
212
- @cx = navigated_dataset if @cx.navigated_coll
244
+ @cx = navigated_dataset if @cx.nav_parent
213
245
  @model = if @cx.respond_to? :model
214
246
  @cx.model
215
247
  else
@@ -227,9 +259,9 @@ module OData
227
259
  [200, CT_TEXT, @cx.count.to_s]
228
260
  elsif req.accept?(APPJSON)
229
261
  if req.walker.do_links
230
- [200, CT_JSON, to_odata_links_json(service: req.service)]
262
+ [200, CT_JSON, [to_odata_links_json(service: req.service)]]
231
263
  else
232
- [200, CT_JSON, to_odata_json(service: req.service)]
264
+ [200, CT_JSON, [to_odata_json(service: req.service)]]
233
265
  end
234
266
  else # TODO: other formats
235
267
  406
@@ -250,6 +282,10 @@ module OData
250
282
  end
251
283
  end
252
284
 
285
+ def odata_post(req)
286
+ odata_create_entity_and_relation(req, @navattr_reflection, @nav_parent)
287
+ end
288
+
253
289
  # add metadata xml to the passed REXML schema object
254
290
  def add_metadata_rexml(schema)
255
291
  enty = schema.add_element('EntityType', 'Name' => to_s)
@@ -289,48 +325,28 @@ module OData
289
325
  end
290
326
  end
291
327
 
328
+ D = 'd'.freeze
329
+ DJopen = '{"d":'.freeze
330
+ DJclose = '}'.freeze
292
331
  def to_odata_links_json(service:)
293
- { 'd' => service.get_coll_odata_links_h(array: get_a,
332
+ innerj = service.get_coll_odata_links_h(array: get_a,
294
333
  uribase: @uribase,
295
- icount: @inlinecount) }.to_json
334
+ icount: @inlinecount).to_json
335
+ "#{DJopen}#{innerj}#{DJclose}"
296
336
  end
297
337
 
298
338
  def to_odata_json(service:)
299
- { 'd' => service.get_coll_odata_h(array: get_a,
339
+ innerj = service.get_coll_odata_h(array: get_a,
300
340
  expand: @params['$expand'],
301
341
  uribase: @uribase,
302
- icount: @inlinecount) }.to_json
342
+ icount: @inlinecount).to_json
343
+ "#{DJopen}#{innerj}#{DJclose}"
303
344
  end
304
345
 
305
346
  def get_a
306
347
  @ax.nil? ? @cx.to_a : @ax
307
348
  end
308
349
 
309
- def odata_post(req)
310
- # TODO: this is for v2 only...
311
- on_error = (proc { raise Sequel::Rollback } if req.in_changeset)
312
-
313
- req.with_parsed_data(on_error: on_error) do |data|
314
- data.delete('__metadata')
315
- # validate payload column names
316
- if (invalid = invalid_hash_data?(data))
317
- on_error.call if on_error
318
- return [422, {}, ['Invalid attribute name: ', invalid.to_s]]
319
- end
320
-
321
- if req.accept?(APPJSON)
322
-
323
- new_entity = new_from_hson_h(data, in_changeset: req.in_changeset)
324
- req.register_content_id_ref(new_entity)
325
- new_entity.copy_request_infos(req)
326
-
327
- [201, CT_JSON, new_entity.to_odata_post_json(service: req.service)]
328
- else # TODO: other formats
329
- 415
330
- end
331
- end
332
- end
333
-
334
350
  # this functionally similar to the Sequel Rels (many_to_one etc)
335
351
  # We need to base this on the Sequel rels, or extend them
336
352
  def add_nav_prop_collection(assoc_symb, attr_name_str = nil)
@@ -471,6 +487,7 @@ module OData
471
487
  end
472
488
  include Transitions
473
489
  end
490
+
474
491
  # special handling for composite key
475
492
  module EntityClassMultiPK
476
493
  include EntityClassBase
@@ -508,4 +525,38 @@ module OData
508
525
  check_odata_val_type(id, db_schema[primary_key][:type])
509
526
  end
510
527
  end
528
+
529
+ # normal handling for non-media entity
530
+ module EntityClassNonMedia
531
+ # POST for non-media entity collection -->
532
+ # 1. Create and add entity from payload
533
+ # 2. Create relationship if needed
534
+ def odata_create_entity_and_relation(req, assoc, parent)
535
+ # TODO: this is for v2 only...
536
+ req.with_parsed_data do |data|
537
+ data.delete('__metadata')
538
+ # validate payload column names
539
+ if (invalid = invalid_hash_data?(data))
540
+ ::OData::Request::ON_CGST_ERROR.call(req)
541
+ return [422, {}, ['Invalid attribute name: ', invalid.to_s]]
542
+ end
543
+
544
+ if req.accept?(APPJSON)
545
+ new_entity = new_from_hson_h(data)
546
+ if parent
547
+ odata_create_save_entity_and_rel(req, new_entity, assoc, parent)
548
+ else
549
+ # in-changeset requests get their own transaction
550
+ new_entity.save(transaction: !req.in_changeset)
551
+ end
552
+ req.register_content_id_ref(new_entity)
553
+ new_entity.copy_request_infos(req)
554
+
555
+ [201, CT_JSON, new_entity.to_odata_post_json(service: req.service)]
556
+ else # TODO: other formats
557
+ 415
558
+ end
559
+ end
560
+ end
561
+ end
511
562
  end