safrano 0.3.2 → 0.3.3

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