safrano 0.5.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/core_ext/Date/format.rb +47 -0
- data/lib/core_ext/DateTime/format.rb +54 -0
- data/lib/core_ext/Dir/iter.rb +7 -5
- data/lib/core_ext/Hash/transform.rb +14 -6
- data/lib/core_ext/Numeric/convert.rb +1 -1
- data/lib/core_ext/Time/format.rb +71 -0
- data/lib/core_ext/date.rb +5 -0
- data/lib/core_ext/datetime.rb +5 -0
- data/lib/core_ext/time.rb +5 -0
- data/lib/odata/attribute.rb +8 -6
- data/lib/odata/batch.rb +3 -3
- data/lib/odata/collection.rb +5 -5
- data/lib/odata/collection_filter.rb +3 -1
- data/lib/odata/collection_media.rb +4 -27
- data/lib/odata/collection_order.rb +1 -1
- data/lib/odata/common_logger.rb +5 -27
- data/lib/odata/complex_type.rb +19 -21
- data/lib/odata/edm/primitive_types.rb +14 -19
- data/lib/odata/entity.rb +12 -12
- data/lib/odata/error.rb +7 -7
- data/lib/odata/expand.rb +2 -2
- data/lib/odata/filter/base.rb +10 -1
- data/lib/odata/filter/error.rb +2 -2
- data/lib/odata/filter/parse.rb +16 -2
- data/lib/odata/filter/sequel.rb +31 -4
- data/lib/odata/filter/sequel_datetime_adapter.rb +21 -0
- data/lib/odata/filter/token.rb +18 -5
- data/lib/odata/filter/tree.rb +83 -9
- data/lib/odata/function_import.rb +11 -9
- data/lib/odata/model_ext.rb +26 -29
- data/lib/odata/request/json.rb +171 -0
- data/lib/odata/transition.rb +2 -2
- data/lib/odata/url_parameters.rb +3 -3
- data/lib/odata/walker.rb +1 -1
- data/lib/safrano/multipart.rb +1 -3
- data/lib/safrano/rack_app.rb +2 -14
- data/lib/safrano/rack_builder.rb +0 -15
- data/lib/safrano/request.rb +3 -3
- data/lib/safrano/response.rb +3 -3
- data/lib/safrano/service.rb +13 -5
- data/lib/safrano/type_mapping.rb +4 -4
- data/lib/safrano/version.rb +1 -2
- data/lib/safrano.rb +3 -0
- metadata +51 -15
data/lib/odata/model_ext.rb
CHANGED
@@ -104,9 +104,8 @@ module Safrano
|
|
104
104
|
def new_from_hson_h(hash)
|
105
105
|
# enty = new
|
106
106
|
# enty.set_fields(hash, data_fields, missing: :skip)
|
107
|
-
|
107
|
+
create(hash)
|
108
108
|
# enty.set(hash)
|
109
|
-
enty
|
110
109
|
end
|
111
110
|
|
112
111
|
def attrib_path_valid?(path)
|
@@ -353,20 +352,18 @@ module Safrano
|
|
353
352
|
|
354
353
|
def build_default_template
|
355
354
|
@default_template = { all_values: EMPTYH }
|
356
|
-
if @nav_entity_attribs || @nav_collection_attribs
|
357
|
-
@default_template[:deferr] = (@nav_entity_attribs&.keys || []) + (@nav_collection_attribs&.keys || EMPTY_ARRAY)
|
358
|
-
end
|
355
|
+
@default_template[:deferr] = (@nav_entity_attribs&.keys || []) + (@nav_collection_attribs&.keys || EMPTY_ARRAY) if @nav_entity_attribs || @nav_collection_attribs
|
359
356
|
end
|
360
357
|
|
361
358
|
def build_casted_cols(service)
|
362
359
|
# cols needed catsting before final json output
|
363
360
|
@casted_cols = {}
|
364
|
-
db_schema.each
|
361
|
+
db_schema.each do |col, props|
|
365
362
|
# first check if we have user-defined type mapping
|
366
363
|
usermap = nil
|
367
364
|
dbtyp = props[:db_type]
|
368
365
|
metadata = @cols_metadata[col]
|
369
|
-
if
|
366
|
+
if service.type_mappings.values.find { |map| usermap = map.match(dbtyp) }
|
370
367
|
|
371
368
|
metadata[:edm_type] = usermap.edm_type
|
372
369
|
|
@@ -374,17 +371,17 @@ module Safrano
|
|
374
371
|
next # this will override our rules below !
|
375
372
|
end
|
376
373
|
|
377
|
-
if
|
374
|
+
if metadata[:edm_precision] && (metadata[:edm_type] =~ /\AEdm.Decimal\(/i)
|
378
375
|
# we save the precision and/or scale in the lambda (binding!)
|
379
376
|
|
380
377
|
@casted_cols[col] = if metadata[:edm_scale]
|
381
|
-
|
378
|
+
lambda { |x|
|
382
379
|
# not sure if these copies are really needed, but feels better that way
|
383
380
|
# output decimal with precision and scale
|
384
381
|
x&.toDecimalPrecisionScaleString(metadata[:edm_precision], metadata[:edm_scale])
|
385
382
|
}
|
386
383
|
else
|
387
|
-
|
384
|
+
lambda { |x|
|
388
385
|
# not sure if these copies are really needed, but feels better that way
|
389
386
|
# output decimal with precision only
|
390
387
|
x&.toDecimalPrecisionString(metadata[:edm_precision])
|
@@ -404,13 +401,13 @@ module Safrano
|
|
404
401
|
@casted_cols[col] = ->(x) { Base64.encode64(x) unless x.nil? } # Base64
|
405
402
|
next
|
406
403
|
end
|
407
|
-
# TODO check this more in details
|
404
|
+
# TODO: check this more in details
|
408
405
|
# NOTE: here we use :type which is the sequel defined ruby-type
|
409
|
-
if props[:type] == :datetime
|
410
|
-
@casted_cols[col] = ->(x) { x&.iso8601 }
|
411
|
-
|
406
|
+
if props[:type] == :datetime || props[:type] == :date
|
407
|
+
# @casted_cols[col] = ->(x) { x&.iso8601 }
|
408
|
+
@casted_cols[col] = ->(x) { x&.to_edm_json }
|
412
409
|
end
|
413
|
-
|
410
|
+
end
|
414
411
|
end
|
415
412
|
|
416
413
|
def finalize_publishing(service)
|
@@ -440,7 +437,7 @@ module Safrano
|
|
440
437
|
build_entity_allowed_transitions
|
441
438
|
|
442
439
|
# for media
|
443
|
-
finalize_media if
|
440
|
+
finalize_media if respond_to? :finalize_media
|
444
441
|
end
|
445
442
|
|
446
443
|
KEYPRED_URL_REGEXP = /\A\(\s*'?([\w=,'\s]+)'?\s*\)(.*)/.freeze
|
@@ -449,7 +446,7 @@ module Safrano
|
|
449
446
|
@pk_names = []
|
450
447
|
@pk_cast_from_string = {}
|
451
448
|
odata_upk_build = []
|
452
|
-
primary_key.each
|
449
|
+
primary_key.each do |pk|
|
453
450
|
@pk_names << pk.to_s
|
454
451
|
kvpredicate = case db_schema[pk][:type]
|
455
452
|
when :integer
|
@@ -459,19 +456,19 @@ module Safrano
|
|
459
456
|
"'?'"
|
460
457
|
end
|
461
458
|
odata_upk_build << "#{pk}=#{kvpredicate}"
|
462
|
-
|
459
|
+
end
|
463
460
|
@odata_upk_parts = odata_upk_build.join(',').split('?')
|
464
461
|
|
465
462
|
# regex parts for unordered matching
|
466
|
-
@iuk_rgx_parts = primary_key.map
|
463
|
+
@iuk_rgx_parts = primary_key.map do |pk|
|
467
464
|
kvpredicate = case db_schema[pk][:type]
|
468
465
|
when :integer
|
469
|
-
|
466
|
+
'(\\d+)'
|
470
467
|
else
|
471
468
|
"'(\\w+)'"
|
472
469
|
end
|
473
470
|
[pk, "#{pk}=#{kvpredicate}"]
|
474
|
-
|
471
|
+
end.to_h
|
475
472
|
|
476
473
|
# single regex assuming the key fields are ordered !
|
477
474
|
@iuk_rgx = /\A#{@iuk_rgx_parts.values.join(',\s*')}\z/
|
@@ -485,7 +482,7 @@ module Safrano
|
|
485
482
|
kvpredicate = case db_schema[primary_key][:type]
|
486
483
|
when :integer
|
487
484
|
@pk_cast_from_string = ->(str) { Integer(str) }
|
488
|
-
|
485
|
+
'(\\d+)'
|
489
486
|
else
|
490
487
|
"'(\\w+)'"
|
491
488
|
end
|
@@ -617,14 +614,14 @@ module Safrano
|
|
617
614
|
scan_rgx_parts = @iuk_rgx_parts.dup
|
618
615
|
mdch = {}
|
619
616
|
|
620
|
-
mid.split(/\s*,\s*/).each
|
617
|
+
mid.split(/\s*,\s*/).each do |midpart|
|
621
618
|
mval = nil
|
622
|
-
mpk, mrgx = scan_rgx_parts.find
|
619
|
+
mpk, mrgx = scan_rgx_parts.find do |_pk, rgx|
|
623
620
|
if (md = rgx.match(midpart))
|
624
621
|
mval = md[1]
|
625
622
|
end
|
626
|
-
|
627
|
-
if mpk
|
623
|
+
end
|
624
|
+
if mpk && mval
|
628
625
|
mdch[mpk] = if (pk_cast = @pk_cast_from_string[mpk])
|
629
626
|
pk_cast.call(mval)
|
630
627
|
else
|
@@ -634,7 +631,7 @@ module Safrano
|
|
634
631
|
else
|
635
632
|
return Contract::NOK
|
636
633
|
end
|
637
|
-
|
634
|
+
end
|
638
635
|
# normally arriving here we have mdch filled with key values pairs,
|
639
636
|
# but not in the model key ordering. lets just re-order the values
|
640
637
|
mdc = @iuk_rgx_parts.keys.map { |pk| mdch[pk] }
|
@@ -653,7 +650,7 @@ module Safrano
|
|
653
650
|
|
654
651
|
def parse_odata_key(rawid)
|
655
652
|
if (md = @iuk_rgx.match(rawid))
|
656
|
-
if
|
653
|
+
if @pk_cast_from_string
|
657
654
|
Contract.valid(@pk_cast_from_string.call(md[1]))
|
658
655
|
else
|
659
656
|
Contract.valid(md[1]) # no cast needed, eg for string
|
@@ -677,7 +674,7 @@ module Safrano
|
|
677
674
|
# 2. Create relationship if needed
|
678
675
|
def odata_create_entity_and_relation(req, assoc = nil, parent = nil)
|
679
676
|
# TODO: this is for v2 only...
|
680
|
-
req.with_parsed_data do |data|
|
677
|
+
req.with_parsed_data(self) do |data|
|
681
678
|
data.delete('__metadata')
|
682
679
|
|
683
680
|
# validate payload column names
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
# client parsing functionality to ease testing
|
5
|
+
|
6
|
+
module Safrano
|
7
|
+
module OData
|
8
|
+
# this is used to parse inbound json payload on POST / PUT & co
|
9
|
+
# it does not do symbolize but proper (hopefully) type casting when needed
|
10
|
+
module JSON
|
11
|
+
# def self.parse(*args)
|
12
|
+
# ::JSON.parse(*args)
|
13
|
+
# end
|
14
|
+
|
15
|
+
def self.cast_values_in(resd, typ)
|
16
|
+
typ.db_schema.each do |f, props|
|
17
|
+
metadata = typ.cols_metadata[f]
|
18
|
+
|
19
|
+
case props[:type]
|
20
|
+
when :datetime, :date
|
21
|
+
fstr = f.to_s
|
22
|
+
# resd[fstr] = Time.parse(resd[f]) if resd[fstr]
|
23
|
+
resd[fstr] = Sequel.datetime_class.from_edm_json(resd[fstr]) if resd[fstr]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
resd
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse_one(*args, type)
|
31
|
+
cast_values_in(::JSON.parse(*args), type)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
# This is used from the Test-suite code !
|
36
|
+
# it does recursive / deep symbolize additionally to inbound casting
|
37
|
+
module XJSON
|
38
|
+
def self.get_class_from_meta(meta)
|
39
|
+
# type is normally namespaced ! --> split.last is the class
|
40
|
+
meta[:type].split('.').last.constantize
|
41
|
+
end
|
42
|
+
|
43
|
+
# symbolise keys and cast/parse values back to the proper ruby type;
|
44
|
+
# proper type meaning the one that Sequel chooses when loading data from the DB
|
45
|
+
# apply recursively to nested navigation attributes sub-structures
|
46
|
+
def self.cast_values_in(resd)
|
47
|
+
resd.symbolize_keys!
|
48
|
+
|
49
|
+
if (defered = resd[:__deferred])
|
50
|
+
defered.symbolize_keys!
|
51
|
+
elsif meta = resd[:__metadata]
|
52
|
+
meta.symbolize_keys!
|
53
|
+
|
54
|
+
# type is normally namespaced ! --> split.last is the class
|
55
|
+
typ = get_class_from_meta(meta)
|
56
|
+
|
57
|
+
typ.db_schema.each do |f, props|
|
58
|
+
metadata = typ.cols_metadata[f]
|
59
|
+
case props[:type]
|
60
|
+
when :datetime
|
61
|
+
# resd[f] = Time.parse(resd[f]) if resd[f]
|
62
|
+
# resd[f] = DateTime.strptime(resd[f], '/Date(%Q)').to_time if resd[f]
|
63
|
+
resd[f] = Sequel.datetime_class.from_edm_json(resd[f]) if resd[f]
|
64
|
+
when :decimal
|
65
|
+
resd[f] = BigDecimal(resd[f])
|
66
|
+
when :float
|
67
|
+
resd[f] = Float(resd[f])
|
68
|
+
else
|
69
|
+
# TODO: better typ-system
|
70
|
+
# Currently Sequel loads Numeric(x,y) as Float
|
71
|
+
resd[f] = Float(resd[f]) if metadata[:edm_type] =~ /\A\s*Edm.Decimal/
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
if typ.nav_entity_attribs
|
76
|
+
typ.nav_entity_attribs.each_key do |nattr|
|
77
|
+
cast_values_in(resd[nattr.to_sym]) if resd[nattr.to_sym]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if typ.nav_collection_attribs
|
82
|
+
typ.nav_collection_attribs.each_key do |ncattr|
|
83
|
+
if (resd_attr = resd[ncattr.to_sym])
|
84
|
+
if (defered = resd_attr['__deferred'])
|
85
|
+
defered.symbolize_keys! if defered
|
86
|
+
else
|
87
|
+
resd_attr.symbolize_keys! # 'results' --> :results
|
88
|
+
resd_attr[:results].each { |enty| cast_values_in(enty) }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
resd
|
95
|
+
end
|
96
|
+
|
97
|
+
# symbolise attrib names (h-keys)
|
98
|
+
# apply recursively to nested navigation attributes sub-structures
|
99
|
+
def self.symbolize_attribs_in(resd)
|
100
|
+
resd.symbolize_keys!
|
101
|
+
|
102
|
+
if (defered = resd[:__deferred])
|
103
|
+
defered.symbolize_keys!
|
104
|
+
elsif meta = resd[:__metadata]
|
105
|
+
meta.symbolize_keys!
|
106
|
+
|
107
|
+
typ = get_class_from_meta(meta)
|
108
|
+
|
109
|
+
if typ.nav_entity_attribs
|
110
|
+
typ.nav_entity_attribs.each_key do |nattr|
|
111
|
+
symbolize_attribs_in(resd[nattr.to_sym])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
if typ.nav_collection_attribs
|
116
|
+
typ.nav_collection_attribs.each_key do |ncattr|
|
117
|
+
if (defered = resd[ncattr.to_sym]['__deferred'])
|
118
|
+
defered.symbolize_keys!
|
119
|
+
else
|
120
|
+
resd[ncattr.to_sym].each { |enty| symbolize_attribs_in(enty) }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
resd
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.parse_one_nocast(*args)
|
130
|
+
resh = ::JSON.parse(*args)
|
131
|
+
symbolize_attribs_in(resh['d'])
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.parse_one(*args)
|
135
|
+
resh = ::JSON.parse(*args)
|
136
|
+
cast_values_in(resh['d'])
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.v1_parse_coll(*args)
|
140
|
+
resh = ::JSON.parse(*args)
|
141
|
+
|
142
|
+
resh['d'].map! { |currd| cast_values_in(currd) }
|
143
|
+
|
144
|
+
resh['d']
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.parse_coll(*args)
|
148
|
+
resh = ::JSON.parse(*args)
|
149
|
+
|
150
|
+
resh['d']['results'].map! { |currd| cast_values_in(currd) }
|
151
|
+
|
152
|
+
resh['d']
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.v1_parse_coll_nocast(*args)
|
156
|
+
resh = ::JSON.parse(*args)
|
157
|
+
|
158
|
+
resh['d'].map! { |currd| symbolize_attribs_in(currd) }
|
159
|
+
|
160
|
+
resh['d']
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.parse_coll_nocast(*args)
|
164
|
+
resh = ::JSON.parse(*args)
|
165
|
+
|
166
|
+
resh['d']['results'].map! { |currd| symbolize_attribs_in(currd) }
|
167
|
+
|
168
|
+
resh['d']
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/lib/odata/transition.rb
CHANGED
@@ -12,8 +12,8 @@ module Safrano
|
|
12
12
|
attr_accessor :rgx
|
13
13
|
attr_reader :remain_idx
|
14
14
|
|
15
|
-
EMPTYSTR = ''
|
16
|
-
SLASH = '/'
|
15
|
+
EMPTYSTR = ''
|
16
|
+
SLASH = '/'
|
17
17
|
|
18
18
|
RESULT_BAD_REQ_ERR = [nil, :error, ::Safrano::BadRequestError].freeze
|
19
19
|
RESULT_NOT_FOUND_ERR = [nil, :error, ::Safrano::ErrorNotFound].freeze
|
data/lib/odata/url_parameters.rb
CHANGED
@@ -68,20 +68,20 @@ module Safrano
|
|
68
68
|
return Contract::OK unless @params['$top']
|
69
69
|
|
70
70
|
itop = number_or_nil(@params['$top'])
|
71
|
-
|
71
|
+
itop.nil? || itop.zero? ? BadRequestError : Contract::OK
|
72
72
|
end
|
73
73
|
|
74
74
|
def check_skip
|
75
75
|
return Contract::OK unless @params['$skip']
|
76
76
|
|
77
77
|
iskip = number_or_nil(@params['$skip'])
|
78
|
-
|
78
|
+
iskip.nil? || iskip.negative? ? BadRequestError : Contract::OK
|
79
79
|
end
|
80
80
|
|
81
81
|
def check_inlinecount
|
82
82
|
return Contract::OK unless (icp = @params['$inlinecount'])
|
83
83
|
|
84
|
-
(
|
84
|
+
(icp == 'allpages') || (icp == 'none') ? Contract::OK : BadRequestInlineCountParamError
|
85
85
|
end
|
86
86
|
|
87
87
|
def check_filter
|
data/lib/odata/walker.rb
CHANGED
data/lib/safrano/multipart.rb
CHANGED
@@ -411,9 +411,7 @@ module MIME
|
|
411
411
|
|
412
412
|
def unparse(bodyonly = false)
|
413
413
|
b = +String.new
|
414
|
-
unless bodyonly
|
415
|
-
b << "#{Safrano::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}"
|
416
|
-
end
|
414
|
+
b << "#{Safrano::CONTENT_TYPE}: #{@hd[CTT_TYPE_LC]}#{CRLF}" unless bodyonly
|
417
415
|
|
418
416
|
b << crbdcr = "#{CRLF}--#{@boundary}#{CRLF}"
|
419
417
|
b << @content.map(&:unparse).join(crbdcr)
|
data/lib/safrano/rack_app.rb
CHANGED
@@ -10,7 +10,7 @@ module Safrano
|
|
10
10
|
module MethodHandlers
|
11
11
|
def odata_options
|
12
12
|
@walker.finalize.tap_error { |err| return err.odata_get(@request) }
|
13
|
-
.if_valid do |
|
13
|
+
.if_valid do |_context|
|
14
14
|
# cf. stackoverflow.com/questions/22924678/sinatra-delete-response-headers
|
15
15
|
headers.delete('Content-Type')
|
16
16
|
@response.headers.delete('Content-Type')
|
@@ -119,7 +119,7 @@ module Safrano
|
|
119
119
|
@response = Safrano::Response.new
|
120
120
|
|
121
121
|
before.tap_error { |err| dispatch_error(err) }
|
122
|
-
.tap_valid { |
|
122
|
+
.tap_valid { |_res| dispatch }
|
123
123
|
|
124
124
|
# handle remaining Sequel errors that we couldnt prevent with our
|
125
125
|
# own pre-checks
|
@@ -161,15 +161,3 @@ module Safrano
|
|
161
161
|
end
|
162
162
|
end
|
163
163
|
end
|
164
|
-
|
165
|
-
# deprecated
|
166
|
-
# REMOVE 0.6
|
167
|
-
module OData
|
168
|
-
class ServerApp < Safrano::ServerApp
|
169
|
-
def self.publish_service(&block)
|
170
|
-
::Safrano::Deprecation.deprecate('OData::ServerApp',
|
171
|
-
'Use Safrano::ServerApp instead')
|
172
|
-
super
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
data/lib/safrano/rack_builder.rb
CHANGED
@@ -17,18 +17,3 @@ module Rack
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
21
|
-
# deprecated
|
22
|
-
# REMOVE 0.6
|
23
|
-
module Rack
|
24
|
-
module OData
|
25
|
-
class Builder < ::Rack::Safrano::Builder
|
26
|
-
def initialize(default_app = nil, &block)
|
27
|
-
::Safrano::Deprecation.deprecate('Rack::OData::Builder',
|
28
|
-
'Use Rack::Safrano::Builder instead')
|
29
|
-
|
30
|
-
super
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
data/lib/safrano/request.rb
CHANGED
@@ -144,11 +144,11 @@ module Safrano
|
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
147
|
-
def with_parsed_data
|
147
|
+
def with_parsed_data(type)
|
148
148
|
if content_type == APPJSON
|
149
149
|
# Parse json payload
|
150
150
|
begin
|
151
|
-
data = JSON.
|
151
|
+
data = Safrano::OData::JSON.parse_one(body.read, type)
|
152
152
|
rescue JSON::ParserError => e
|
153
153
|
ON_CGST_ERROR.call(self)
|
154
154
|
return [400, EMPTY_HASH, ['JSON Parser Error while parsing payload : ',
|
@@ -239,7 +239,7 @@ module Safrano
|
|
239
239
|
get_minversion.if_valid do |minv|
|
240
240
|
return MAX_LT_MIN_DTSV_ERROR if minv > maxv
|
241
241
|
|
242
|
-
get_version.if_valid do |
|
242
|
+
get_version.if_valid do |_v|
|
243
243
|
@service = nil
|
244
244
|
@service = case maxv
|
245
245
|
when '1'
|
data/lib/safrano/response.rb
CHANGED
@@ -18,8 +18,8 @@ module Safrano
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def body=(value)
|
21
|
-
value = value.body while ::Rack::Response
|
22
|
-
@body = String
|
21
|
+
value = value.body while value.is_a?(::Rack::Response)
|
22
|
+
@body = value.is_a?(String) ? [value.to_str] : value
|
23
23
|
end
|
24
24
|
|
25
25
|
def each
|
@@ -51,7 +51,7 @@ module Safrano
|
|
51
51
|
private
|
52
52
|
|
53
53
|
def calculate_content_length?
|
54
|
-
headers['Content-Type'] && !headers['Content-Length'] && (Array
|
54
|
+
headers['Content-Type'] && !headers['Content-Length'] && body.is_a?(Array)
|
55
55
|
end
|
56
56
|
|
57
57
|
def calculated_content_length
|
data/lib/safrano/service.rb
CHANGED
@@ -161,6 +161,8 @@ module Safrano
|
|
161
161
|
@function_import_keys = []
|
162
162
|
@cmap = {}
|
163
163
|
@type_mappings = {}
|
164
|
+
# enabled per default starting from 0.6
|
165
|
+
@bugfix_create_response = true
|
164
166
|
instance_eval(&block) if block_given?
|
165
167
|
end
|
166
168
|
|
@@ -209,9 +211,8 @@ module Safrano
|
|
209
211
|
(@v2.xserver_url = @xserver_url) if @v2
|
210
212
|
end
|
211
213
|
|
212
|
-
# keep the bug active for now, but allow to activate the fix
|
213
|
-
|
214
|
-
def bugfix_create_response(bool = false)
|
214
|
+
# keep the bug active for now, but allow to de-activate the fix
|
215
|
+
def bugfix_create_response(bool)
|
215
216
|
@bugfix_create_response = bool
|
216
217
|
end
|
217
218
|
|
@@ -246,6 +247,7 @@ module Safrano
|
|
246
247
|
other.function_imports = @function_imports
|
247
248
|
other.function_import_keys = @function_import_keys
|
248
249
|
other.type_mappings = @type_mappings
|
250
|
+
other.bugfix_create_response(@bugfix_create_response)
|
249
251
|
other
|
250
252
|
end
|
251
253
|
|
@@ -386,10 +388,10 @@ module Safrano
|
|
386
388
|
klass.build_uri(@uribase)
|
387
389
|
|
388
390
|
# Output create (POST) as single entity (Standard) or as array (non-standard buggy)
|
389
|
-
klass.include
|
391
|
+
klass.include(@bugfix_create_response ? Safrano::EntityCreateStandardOutput : Safrano::EntityCreateArrayOutput)
|
390
392
|
|
391
393
|
# define the most optimal casted_values method for the given model(klass)
|
392
|
-
if
|
394
|
+
if klass.casted_cols.empty?
|
393
395
|
klass.send(:define_method, :casted_values) do |cols = nil|
|
394
396
|
cols ? selected_values_for_odata(cols) : values_for_odata
|
395
397
|
end
|
@@ -413,10 +415,16 @@ module Safrano
|
|
413
415
|
case Sequel::Model.db.adapter_scheme
|
414
416
|
when :postgres
|
415
417
|
Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreePostgres
|
418
|
+
Safrano::Filter::DateTimeLit.include Safrano::Filter::DateTimeDefault
|
419
|
+
Safrano::Filter::DateTimeOffsetLit.include Safrano::Filter::DateTimeDefault
|
416
420
|
when :sqlite
|
417
421
|
Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreeSqlite
|
422
|
+
Safrano::Filter::DateTimeLit.include Safrano::Filter::DateTimeSqlite
|
423
|
+
Safrano::Filter::DateTimeOffsetLit.include Safrano::Filter::DateTimeSqlite
|
418
424
|
else
|
419
425
|
Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreeDefault
|
426
|
+
Safrano::Filter::DateTimeLit.include Safrano::Filter::DateTimeDefault
|
427
|
+
Safrano::Filter::DateTimeOffsetLit.include Safrano::Filter::DateTimeDefault
|
420
428
|
end
|
421
429
|
end
|
422
430
|
|
data/lib/safrano/type_mapping.rb
CHANGED
@@ -45,17 +45,17 @@ module Safrano
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def match(curtyp)
|
48
|
-
if
|
48
|
+
if @bui2 && (m = @bui2.match(curtyp))
|
49
49
|
m
|
50
|
-
elsif
|
50
|
+
elsif @bui1 && (m = @bui1.match(curtyp))
|
51
51
|
m
|
52
|
-
elsif
|
52
|
+
elsif @rgx.match(curtyp)
|
53
53
|
type_mapping
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
57
|
def type_mapping
|
58
|
-
# TODO perf; return always same object when called multiple times
|
58
|
+
# TODO: perf; return always same object when called multiple times
|
59
59
|
FixedTypeMapping.new(self)
|
60
60
|
end
|
61
61
|
end # Builder
|
data/lib/safrano/version.rb
CHANGED
data/lib/safrano.rb
CHANGED
@@ -14,9 +14,12 @@ require_relative 'odata/entity'
|
|
14
14
|
require_relative 'odata/attribute'
|
15
15
|
require_relative 'odata/navigation_attribute'
|
16
16
|
require_relative 'odata/model_ext'
|
17
|
+
require_relative 'odata/request/json'
|
17
18
|
require_relative 'safrano/service'
|
18
19
|
require_relative 'odata/walker'
|
19
20
|
require 'sequel'
|
20
21
|
require_relative 'safrano/sequel_join_by_paths'
|
21
22
|
require_relative 'safrano/rack_app'
|
22
23
|
require_relative 'safrano/rack_builder'
|
24
|
+
|
25
|
+
Sequel.extension :named_timezones
|