safrano 0.1.0 → 0.2.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.
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