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 +4 -4
- data/lib/multipart.rb +54 -8
- data/lib/odata/batch.rb +7 -3
- data/lib/odata/collection.rb +10 -6
- data/lib/odata/collection_order.rb +1 -1
- data/lib/odata/entity.rb +8 -3
- data/lib/odata/error.rb +6 -0
- data/lib/odata/walker.rb +30 -3
- data/lib/odata_rack_builder.rb +0 -2
- data/lib/rack_app.rb +4 -7
- data/lib/request.rb +20 -3
- data/lib/safrano_core.rb +2 -0
- data/lib/service.rb +7 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6894a03a0224ba8beedc0a05fba7700ad4e06b34d79858478c5e4fe998ed8eef
|
4
|
+
data.tar.gz: '059f1dc5d7d3c4537f279be2b170b5c67b2c61fdbe27199f511e8f1e894776ad'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d41f73aa8bdd9084eaa150ee06d6c9b05e91ff18dd24f25daae75f25572cf0500c0c5669ade95275b65891655fae4e0e0c53fbb2712e414fe8daf6df91efb8cb
|
7
|
+
data.tar.gz: 11e49d68d0fc005be501f069ab8fcbaad40050449435f4369938b10f1cb90c71cc86b361b4bbaa7541ac7e274ec34de1f6925a1a63d481bcd5bb5690bfb84649
|
data/lib/multipart.rb
CHANGED
@@ -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
|
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|
|
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
|
-
|
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|
|
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 = ''
|
data/lib/odata/batch.rb
CHANGED
@@ -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
|
-
|
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)
|
data/lib/odata/collection.rb
CHANGED
@@ -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? ||
|
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
|
-
|
319
|
-
|
320
|
-
|
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
|
data/lib/odata/entity.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
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)]
|
data/lib/odata/error.rb
CHANGED
@@ -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
|
data/lib/odata/walker.rb
CHANGED
@@ -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
|
119
|
+
@status = :end
|
93
120
|
when :end_with_value
|
94
121
|
@raw_value = true
|
95
|
-
@status
|
122
|
+
@status = :end
|
96
123
|
when :run_with_links
|
97
124
|
@do_links = true
|
98
125
|
@status = :run
|
data/lib/odata_rack_builder.rb
CHANGED
data/lib/rack_app.rb
CHANGED
@@ -23,18 +23,15 @@ module OData
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def odata_error
|
26
|
-
|
26
|
+
return @walker.error.odata_get(@request) unless @walker.error.nil?
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
data/lib/request.rb
CHANGED
@@ -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
|
data/lib/safrano_core.rb
CHANGED
@@ -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',
|
data/lib/service.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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
|
-
|
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
|