safrano 0.1.0 → 0.2.0

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: 34ab64bf4fd2bbde8453e754091a3bd5618f41e6d51d9d15c9c678efcb38d544
4
- data.tar.gz: d0ee4590389199a0e3530aee4a7aa244dde9355de8506a1091c5e0215ad40d78
3
+ metadata.gz: 6894a03a0224ba8beedc0a05fba7700ad4e06b34d79858478c5e4fe998ed8eef
4
+ data.tar.gz: '059f1dc5d7d3c4537f279be2b170b5c67b2c61fdbe27199f511e8f1e894776ad'
5
5
  SHA512:
6
- metadata.gz: b9531e9de93d7d117ea1b58c1a90967706b102078513752f0bfef2447dc9c5988438d7ee1e9053827da0e58ac2aad07a6450a7c27b1fa5833ba329fe6c2bd4a8
7
- data.tar.gz: 9e102e3c98ce0d47c0dd1dda33c8b2b76e0b7a3b9503d3af86c2ae08404773000b87442711ca230875d715148f90d870e9e63b9aae5c866677d4b0e8ca687ddd
6
+ metadata.gz: d41f73aa8bdd9084eaa150ee06d6c9b05e91ff18dd24f25daae75f25572cf0500c0c5669ade95275b65891655fae4e0e0c53fbb2712e414fe8daf6df91efb8cb
7
+ data.tar.gz: 11e49d68d0fc005be501f069ab8fcbaad40050449435f4369938b10f1cb90c71cc86b361b4bbaa7541ac7e274ec34de1f6925a1a63d481bcd5bb5690bfb84649
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env ruby
2
-
3
1
  CRLF = "\r\n".freeze
4
2
  LF = "\n".freeze
5
3
 
@@ -293,6 +291,7 @@ module MIME
293
291
  def initialize(boundary)
294
292
  @boundary = boundary
295
293
  @hd = {}
294
+ @content_id_references = nil
296
295
  @parser = Parser.new(self)
297
296
  end
298
297
 
@@ -300,6 +299,37 @@ module MIME
300
299
  (@boundary == other.boundary) && (@content == other.content)
301
300
  end
302
301
 
302
+ def each_changeset
303
+ return unless @level.zero?
304
+
305
+ @content.each do |part|
306
+ yield part if part.is_a? MIME::Content::Multipart::Base
307
+ end
308
+ end
309
+
310
+ def prepare_content_id_refs
311
+ if @level.zero?
312
+ each_changeset(&:prepare_content_id_refs)
313
+ elsif @level == 1
314
+
315
+ @content_id_references = {}
316
+
317
+ @content.each do |part|
318
+ next unless part.is_a? MIME::Content::Application::Http
319
+
320
+ case part.content.http_method
321
+ when 'POST', 'PUT'
322
+ if (ctid = part.hd['content-id'])
323
+ part.content.content_id = ctid
324
+ @content_id_references[ctid] = nil
325
+ end
326
+ end
327
+ part.content.content_id_references = @content_id_references
328
+ end
329
+
330
+ end
331
+ end
332
+
303
333
  def set_multipart_header
304
334
  @hd['content-type'] = "multipart/mixed; boundary=#{@boundary}"
305
335
  end
@@ -313,29 +343,41 @@ module MIME
313
343
  @response = self.class.new(::SecureRandom.uuid)
314
344
  @response.set_multipart_header
315
345
  if @level == 1 # changeset need their own global transaction
316
- # the change requests that are part of if have @level==2
346
+ # the change requests that are part of it have @level==2
317
347
  # and will be flagged with in_changeset=true
318
348
  # and this will finally be used to skip the transaction
319
349
  # of the changes
320
350
  batcha.db.transaction do
321
351
  begin
322
- @response.content = @content.map { |part| part.get_response(batcha) }
352
+ @response.content = @content.map { |part|
353
+ part.get_response(batcha)
354
+ }
323
355
  rescue Sequel::Rollback => e
324
356
  # one of the changes of the changeset has failed
325
357
  # --> provide a dummy empty response for the change-parts
326
358
  # then transmit the Rollback to Sequel
327
- @response.content = @content.map { |_part|
328
- MIME::Content::Application::HttpResp.new
329
- }
359
+ get_failed_changeset_response(e)
330
360
  raise
331
361
  end
332
362
  end
333
363
  else
334
- @response.content = @content.map { |part| part.get_response(batcha) }
364
+ @response.content = @content.map { |part|
365
+ part.get_response(batcha)
366
+ }
335
367
  end
336
368
  @response
337
369
  end
338
370
 
371
+ def get_failed_changeset_response(_xeption)
372
+ @response = MIME::Content::Application::HttpResp.new
373
+ @response.status = '400'
374
+ @response.content = [{ 'odata.error' =>
375
+ { 'message' =>
376
+ 'Bad Request: Failed changeset ' } }.to_json]
377
+ @response.hd = { 'Content-Type' => 'application/json;charset=utf-8' }
378
+ @response
379
+ end
380
+
339
381
  def unparse(bodyonly = false)
340
382
  b = ''
341
383
  unless bodyonly
@@ -356,6 +398,10 @@ module MIME
356
398
  attr_accessor :http_method
357
399
  attr_accessor :uri
358
400
 
401
+ # for changeset content-id refs
402
+ attr_accessor :content_id
403
+ attr_accessor :content_id_references
404
+
359
405
  def initialize
360
406
  @hd = {}
361
407
  @content = ''
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env ruby
2
-
3
1
  require 'rack_app.rb'
4
2
  require 'safrano_core.rb'
5
3
 
@@ -44,7 +42,11 @@ module OData
44
42
  @request = OData::Request.new(env)
45
43
  @response = OData::Response.new
46
44
 
47
- @request.in_changeset = true if part_req.level == 2
45
+ if part_req.level == 2
46
+ @request.in_changeset = true
47
+ @request.content_id = part_req.content_id
48
+ @request.content_id_references = part_req.content_id_references
49
+ end
48
50
 
49
51
  before
50
52
  dispatch
@@ -114,6 +116,8 @@ module OData
114
116
 
115
117
  batcha = @request.create_batch_app
116
118
  @mult_request = @request.parse_multipart
119
+
120
+ @mult_request.prepare_content_id_refs
117
121
  @mult_response = OData::Response.new
118
122
 
119
123
  resp_hdrs, @mult_response.body = @mult_request.get_http_resp(batcha)
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env ruby
2
1
  # Design: Collections are nothing more as Sequel based model classes that have
3
2
  # somehow the character of an array (Enumerable)
4
3
  # Thus Below we have called that "EntityClass". It's meant as "Collection"
@@ -179,7 +178,7 @@ module OData
179
178
  return unless @params['$skip']
180
179
 
181
180
  iskip = number_or_nil(@params['$skip'])
182
- return BadRequestError if iskip.nil? || (iskip < 0)
181
+ return BadRequestError if iskip.nil? || iskip.negative?
183
182
  end
184
183
 
185
184
  def check_u_p_inlinecount
@@ -303,9 +302,9 @@ module OData
303
302
  end
304
303
 
305
304
  def odata_post(req)
306
- # TODO: check Request body format...
307
305
  # TODO: this is for v2 only...
308
306
  on_error = (proc { raise Sequel::Rollback } if req.in_changeset)
307
+
309
308
  req.with_parsed_data(on_error: on_error) do |data|
310
309
  data.delete('__metadata')
311
310
  # validate payload column names
@@ -315,9 +314,14 @@ module OData
315
314
  end
316
315
 
317
316
  if req.accept?('application/json')
318
- [201, { 'Content-Type' => 'application/json;charset=utf-8' },
319
- new_from_hson_h(data, in_changeset: req.in_changeset)
320
- .to_odata_post_json(service: req.service)]
317
+
318
+ new_entity = new_from_hson_h(data, in_changeset: req.in_changeset)
319
+ req.register_content_id_ref(new_entity)
320
+ new_entity.copy_request_infos(req)
321
+
322
+ [201,
323
+ { 'Content-Type' => 'application/json;charset=utf-8' },
324
+ new_entity.to_odata_post_json(service: req.service)]
321
325
  else # TODO: other formats
322
326
  415
323
327
  end
@@ -18,7 +18,7 @@ module OData
18
18
  attr_reader :assoc
19
19
  attr_reader :assocs
20
20
  attr_reader :oarg
21
- def initialize(ostr, _dt)
21
+ def initialize(ostr, _dtset)
22
22
  ostr.strip!
23
23
  @orderp = ostr
24
24
  @assocs = Set.new
@@ -101,7 +101,7 @@ module OData
101
101
  def to_odata_json(service:)
102
102
  { 'd' => service.get_entity_odata_h(entity: self,
103
103
  expand: @params['$expand'],
104
- # links: @do_links,
104
+ # links: @do_links,
105
105
  uribase: @uribase) }.to_json
106
106
  end
107
107
 
@@ -121,11 +121,16 @@ module OData
121
121
  type: type_name }
122
122
  end
123
123
 
124
- # Finally Process REST verbs...
125
- def odata_get(req)
124
+ def copy_request_infos(req)
126
125
  @params = req.params
127
126
  @uribase = req.uribase
128
127
  @do_links = req.walker.do_links
128
+ end
129
+
130
+ # Finally Process REST verbs...
131
+ def odata_get(req)
132
+ copy_request_infos(req)
133
+
129
134
  if req.accept?('application/json')
130
135
  [200, { 'Content-Type' => 'application/json;charset=utf-8' },
131
136
  to_odata_json(service: req.service)]
@@ -43,6 +43,12 @@ module OData
43
43
  HTTP_CODE = 400
44
44
  @msg = 'Bad Request Error'
45
45
  end
46
+ # Generic failed changeset
47
+ class BadRequestFailedChangeSet < BadRequestError
48
+ HTTP_CODE = 400
49
+ @msg = 'Bad Request: Failed changeset '
50
+ end
51
+
46
52
  # for Syntax error in Filtering
47
53
  class BadRequestFilterParseError < BadRequestError
48
54
  HTTP_CODE = 400
@@ -27,11 +27,13 @@ module OData
27
27
  # are $links requested ?
28
28
  attr_reader :do_links
29
29
 
30
- def initialize(service, path)
30
+ def initialize(service, path, content_id_refs = nil)
31
31
  raise 'Walker is called with a nil service' unless service
32
32
 
33
33
  path = URI.decode_www_form_component(path)
34
34
  @context = service
35
+ @content_id_refs = content_id_refs
36
+
35
37
  @contexts = [@context]
36
38
  @path_start = @path_remain = if service
37
39
  unprefixed(service.xpath_prefix, path)
@@ -82,17 +84,42 @@ module OData
82
84
  get_next_transition
83
85
  if @tr_next
84
86
  @context, @status, @error = @tr_next.do_transition(@context)
87
+ # little hack's
88
+ case @status
89
+ # we dont have the content-id references data on service level
90
+ # but we have it here, so in case of a $content-id transition
91
+ # the returned context is just the id, and we get the final result
92
+ # entity reference here and place it in @context
93
+ when :run_with_content_id
94
+ if @content_id_refs.is_a? Hash
95
+ if (@context = @content_id_refs[@context.to_s])
96
+ @status = :run
97
+ else
98
+ @context = nil
99
+ @status = :error
100
+ # TODO: more appropriate error handling
101
+ @error = OData::ErrorNotFound
102
+ end
103
+ else
104
+ @context = nil
105
+ @status = :error
106
+ # TODO: more appropriate error handling
107
+ @error = OData::ErrorNotFound
108
+ end
109
+ end
110
+
85
111
  @contexts << @context
86
112
  @path_remain = @tr_next.path_remain
87
113
  @path_done << @tr_next.path_done
114
+
88
115
  # little hack's
89
116
  case @status
90
117
  when :end_with_count
91
118
  @do_count = true
92
- @status == :end
119
+ @status = :end
93
120
  when :end_with_value
94
121
  @raw_value = true
95
- @status == :end
122
+ @status = :end
96
123
  when :run_with_links
97
124
  @do_links = true
98
125
  @status = :run
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env ruby
2
-
3
1
  require 'rack'
4
2
  require 'rack/cors'
5
3
 
@@ -23,18 +23,15 @@ module OData
23
23
  end
24
24
 
25
25
  def odata_error
26
- if @walker.error.nil?
26
+ return @walker.error.odata_get(@request) unless @walker.error.nil?
27
27
 
28
- # this is too critical; raise a real Exception
29
- # begin
30
- raise 'Walker construction failed with a unknown Error '
28
+ # this is too critical; raise a real Exception
29
+ # begin
30
+ raise 'Walker construction failed with a unknown Error '
31
31
  # rescue StandardError
32
32
  # binding.pry
33
33
  # end
34
34
  # [500, {}, 'Server Error']
35
- else
36
- @walker.error.odata_get(@request)
37
- end
38
35
  end
39
36
 
40
37
  def odata_delete
@@ -13,8 +13,6 @@ module OData
13
13
  HEADER_VAL_RAW = '(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*'.freeze
14
14
  HEADER_VAL_WITH_PAR = /(?:#{HEADER_VAL_RAW})\s*(?:;#{HEADER_PARAM})*/.freeze
15
15
 
16
- attr_accessor :in_changeset
17
-
18
16
  # borowed from Sinatra
19
17
  class AcceptEntry
20
18
  attr_accessor :params
@@ -73,13 +71,32 @@ module OData
73
71
  def parseable_data?
74
72
  false
75
73
  end
74
+
76
75
  # OData extension
77
76
  attr_accessor :service_base
78
77
  attr_accessor :service
79
78
  attr_accessor :walker
80
79
 
80
+ # request is part of a $batch changeset
81
+ attr_accessor :in_changeset
82
+
83
+ # content_id of request in a $batch changeset
84
+ attr_accessor :content_id
85
+
86
+ # content-id references map
87
+ attr_accessor :content_id_references
88
+
89
+ # stores the newly created entity for the current content-id of
90
+ # the processed request
91
+ def register_content_id_ref(new_entity)
92
+ return unless @in_changeset
93
+ return unless @content_id
94
+
95
+ @content_id_references[@content_id] = new_entity
96
+ end
97
+
81
98
  def create_odata_walker
82
- @walker = Walker.new(@service, path_info)
99
+ @walker = Walker.new(@service, path_info, @content_id_references)
83
100
  end
84
101
 
85
102
  def accept
@@ -81,6 +81,8 @@ module Safrano
81
81
  trans: 'transition_metadata')
82
82
  TransitionBatch = Transition.new('\A(\/\$batch)(.*)',
83
83
  trans: 'transition_batch')
84
+ TransitionContentId = Transition.new('\A(\/\$\d+)(.*)',
85
+ trans: 'transition_content_id')
84
86
  TransitionCount = Transition.new('(\A\/\$count)(.*)\z',
85
87
  trans: 'transition_count')
86
88
  TransitionValue = Transition.new('(\A\/\$value)(.*)\z',
@@ -77,7 +77,7 @@ module OData
77
77
  # handle $links ... Note: $expand seems to be ignored when $links
78
78
  # are requested
79
79
  def get_entity_odata_link_h(entity:, uribase:)
80
- hres = { uri: entity.uri(uribase) }
80
+ { uri: entity.uri(uribase) }
81
81
  end
82
82
 
83
83
  def get_entity_odata_h(entity:, expand: nil, uribase:)
@@ -470,6 +470,7 @@ module OData
470
470
  Safrano::TransitionEnd,
471
471
  Safrano::TransitionMetadata,
472
472
  Safrano::TransitionBatch,
473
+ Safrano::TransitionContentId,
473
474
  Safrano::Transition.new(%r{\A/(#{base_url_regexp})(.*)},
474
475
  trans: 'transition_collection')
475
476
  ]
@@ -483,6 +484,11 @@ module OData
483
484
  [@batch_handler, :run]
484
485
  end
485
486
 
487
+ def transition_content_id(match_result)
488
+ # TODO: a nicer way of extracting the content-id number?
489
+ [match_result[1].sub!(Regexp.new('\A\/\$'), ''), :run_with_content_id]
490
+ end
491
+
486
492
  def transition_metadata(_match_result)
487
493
  [@meta, :run]
488
494
  end
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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - D.M.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-07 00:00:00.000000000 Z
11
+ date: 2019-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -140,7 +140,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
140
  - !ruby/object:Gem::Version
141
141
  version: '0'
142
142
  requirements: []
143
- rubygems_version: 3.0.3
143
+ rubyforge_project:
144
+ rubygems_version: 2.7.6.2
144
145
  signing_key:
145
146
  specification_version: 4
146
147
  summary: Safrano is a Ruby OData provider based on Sequel and Rack