safrano 0.5.5 → 0.6.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/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
|