safrano 0.5.3 → 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 +25 -0
- 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/numeric.rb +3 -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 +9 -9
- 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 +61 -67
- data/lib/odata/edm/primitive_types.rb +110 -42
- data/lib/odata/entity.rb +14 -47
- 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 +19 -18
- data/lib/odata/model_ext.rb +96 -38
- data/lib/odata/request/json.rb +171 -0
- data/lib/odata/transition.rb +13 -9
- data/lib/odata/url_parameters.rb +3 -3
- data/lib/odata/walker.rb +9 -9
- 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 +43 -12
- data/lib/safrano/type_mapping.rb +149 -0
- data/lib/safrano/version.rb +1 -2
- data/lib/safrano.rb +3 -0
- metadata +54 -15
data/lib/odata/complex_type.rb
CHANGED
@@ -12,153 +12,148 @@ module Safrano
|
|
12
12
|
VALUEK = 'value'
|
13
13
|
RESULTSK = 'results'
|
14
14
|
COLLECTION = 'Collection'
|
15
|
-
|
15
|
+
|
16
16
|
def allowed_transitions
|
17
17
|
[Safrano::TransitionEnd]
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def transition_end(_match_result)
|
21
21
|
Safrano::Transition::RESULT_END
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
# we will have this on class and instance level for making things simpler first
|
25
|
-
|
26
|
-
|
25
|
+
class << self
|
26
|
+
attr_reader :klassmod
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
# return a subclass of ResultAsComplexType
|
30
30
|
def self.asComplexType(klassmod)
|
31
31
|
Class.new(ResultAsComplexType) do
|
32
32
|
@klassmod = klassmod
|
33
33
|
end
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
# return a subclass of ResultAsComplexType
|
37
37
|
def self.asComplexTypeColl(klassmod)
|
38
38
|
Class.new(ResultAsComplexTypeColl) do
|
39
39
|
@klassmod = klassmod
|
40
40
|
end
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
def self.asPrimitiveType(klassmod)
|
44
44
|
Class.new(ResultAsPrimitiveType) do
|
45
45
|
@klassmod = klassmod
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
def self.asPrimitiveTypeColl(klassmod)
|
50
50
|
Class.new(ResultAsPrimitiveTypeColl) do
|
51
51
|
@klassmod = klassmod
|
52
52
|
end
|
53
|
-
end
|
54
|
-
|
53
|
+
end
|
54
|
+
|
55
55
|
def self.asEntity(klassmod)
|
56
56
|
Class.new(ResultAsEntity) do
|
57
57
|
@klassmod = klassmod
|
58
58
|
end
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
def self.asEntityColl(klassmod)
|
62
62
|
Class.new(ResultAsEntityColl) do
|
63
63
|
@klassmod = klassmod
|
64
64
|
end
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
def initialize(value)
|
68
68
|
@value = value
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
def odata_get(req)
|
72
72
|
[200, EMPTY_HASH, [to_odata_json(req)]]
|
73
73
|
end
|
74
|
+
|
74
75
|
def self.type_metadata
|
75
76
|
@klassmod.type_name
|
76
77
|
end
|
78
|
+
|
77
79
|
def type_metadata
|
78
80
|
self.class.type_metadata
|
79
81
|
end
|
80
|
-
|
82
|
+
|
81
83
|
# needed for ComplexType result
|
82
84
|
def to_odata_json(_req)
|
83
85
|
"#{DJ_OPEN}#{@value.odata_h.to_json}#{DJ_CLOSE}"
|
84
|
-
end
|
85
|
-
|
86
|
-
# wrapper
|
86
|
+
end
|
87
|
+
|
88
|
+
# wrapper
|
87
89
|
# for OData Entity and Collections, return them directly
|
88
90
|
# for others, ie ComplexType, Prims etc, return the ResultDefinition-subclass wrapped result
|
89
|
-
def self.do_execute_func_result(result, _req,
|
90
|
-
|
91
|
+
def self.do_execute_func_result(result, _req, _apply_query_params = false)
|
92
|
+
new(result)
|
91
93
|
end
|
92
|
-
|
93
94
|
end
|
94
|
-
|
95
|
+
|
95
96
|
class ResultAsComplexType < ResultDefinition
|
96
97
|
def self.type_metadata
|
97
98
|
@klassmod.type_name
|
98
99
|
end
|
99
100
|
end
|
100
|
-
|
101
|
+
|
101
102
|
class ResultAsComplexTypeColl < ResultDefinition
|
102
103
|
def self.type_metadata
|
103
104
|
"Collection(#{@klassmod.type_name})"
|
104
105
|
end
|
105
106
|
|
106
107
|
def to_odata_json(req)
|
107
|
-
# "#{DJ_OPEN}#{{ RESULTSK => coll.map { |c| c.odata_h } }.to_json}#{DJ_CLOSE}"
|
108
|
+
# "#{DJ_OPEN}#{{ RESULTSK => coll.map { |c| c.odata_h } }.to_json}#{DJ_CLOSE}"
|
108
109
|
template = self.class.klassmod.output_template
|
109
110
|
# TODO: Error handling if database contains binary BLOB data that cant be
|
110
111
|
# interpreted as UTF-8 then JSON will fail here
|
111
112
|
|
112
113
|
innerh = req.service.get_coll_odata_h(array: @value,
|
113
114
|
template: template)
|
114
|
-
|
115
|
+
|
115
116
|
innerj = innerh.to_json
|
116
117
|
|
117
118
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
118
119
|
end
|
119
120
|
end
|
120
|
-
|
121
|
+
|
121
122
|
class ResultAsEntity < ResultDefinition
|
122
|
-
|
123
123
|
def self.type_metadata
|
124
124
|
@klassmod.type_name
|
125
125
|
end
|
126
126
|
|
127
|
-
|
128
|
-
# wrapper
|
127
|
+
# wrapper
|
129
128
|
# for OData Entity return them directly
|
130
|
-
def self.do_execute_func_result(result, _req, apply_query_params=false)
|
131
|
-
# note: Sequel entities instances seem to be thread safe, so we can
|
129
|
+
def self.do_execute_func_result(result, _req, apply_query_params = false)
|
130
|
+
# note: Sequel entities instances seem to be thread safe, so we can
|
132
131
|
# safely add request-dependant data (eg. req.params) there
|
133
132
|
apply_query_params ? result : result.inactive_query_params
|
134
133
|
end
|
135
|
-
|
136
134
|
end
|
137
|
-
|
135
|
+
|
138
136
|
class ResultAsEntityColl < ResultDefinition
|
139
|
-
|
140
137
|
def self.type_metadata
|
141
138
|
"Collection(#{@klassmod.type_name})"
|
142
139
|
end
|
143
|
-
|
144
|
-
# wrapper
|
140
|
+
|
141
|
+
# wrapper
|
145
142
|
# for OData Entity Collection return them directly
|
146
|
-
def self.do_execute_func_result(result, req, apply_query_params=false)
|
143
|
+
def self.do_execute_func_result(result, req, apply_query_params = false)
|
147
144
|
coll = Safrano::OData::Collection.new(@klassmod)
|
148
145
|
# instance_exec has other instance variables; @values would be nil in the block below
|
149
146
|
# need to pass a local copy
|
150
147
|
dtset = result
|
151
148
|
coll.instance_exec do
|
152
|
-
|
153
|
-
@params = apply_query_params ? req.params : EMPTY_HASH
|
149
|
+
@params = apply_query_params ? req.params : EMPTY_HASH
|
154
150
|
initialize_dataset(dtset)
|
155
151
|
initialize_uparms
|
156
152
|
end
|
157
153
|
coll
|
158
154
|
end
|
159
|
-
|
160
155
|
end
|
161
|
-
|
156
|
+
|
162
157
|
class ResultAsPrimitiveType < ResultDefinition
|
163
158
|
def self.type_metadata
|
164
159
|
@klassmod.type_name
|
@@ -169,7 +164,7 @@ module Safrano
|
|
169
164
|
VALUEK => self.class.klassmod.odata_value(@value) } }.to_json
|
170
165
|
end
|
171
166
|
end
|
172
|
-
|
167
|
+
|
173
168
|
class ResultAsPrimitiveTypeColl < ResultDefinition
|
174
169
|
def self.type_metadata
|
175
170
|
"Collection(#{@klassmod.type_name})"
|
@@ -179,7 +174,6 @@ module Safrano
|
|
179
174
|
{ D => { METAK => { TYPEK => self.class.type_metadata },
|
180
175
|
RESULTSK => self.class.klassmod.odata_collection(@value) } }.to_json
|
181
176
|
end
|
182
|
-
|
183
177
|
end
|
184
178
|
end
|
185
179
|
|
@@ -188,51 +182,51 @@ module Safrano
|
|
188
182
|
# with added OData functionality
|
189
183
|
class ComplexType
|
190
184
|
attr_reader :values
|
185
|
+
|
191
186
|
EMPTYH = {}.freeze
|
192
|
-
|
187
|
+
|
193
188
|
@namespace = nil
|
194
|
-
|
195
|
-
|
189
|
+
class << self
|
190
|
+
attr_reader :namespace
|
196
191
|
end
|
197
192
|
|
198
|
-
|
199
|
-
|
193
|
+
class << self
|
194
|
+
attr_reader :props
|
200
195
|
end
|
201
|
-
|
196
|
+
|
202
197
|
def type_name
|
203
198
|
self.class.type_name
|
204
199
|
end
|
205
|
-
|
200
|
+
|
206
201
|
def metadata_h
|
207
202
|
{ type: type_name }
|
208
203
|
end
|
209
|
-
|
204
|
+
|
210
205
|
def casted_values
|
211
206
|
# MVP... TODO: handle time mappings like in Entity models
|
212
207
|
values
|
213
208
|
end
|
214
|
-
|
209
|
+
|
215
210
|
# needed for nested json output
|
216
211
|
# this is a simpler version of model_ext#output_template
|
217
212
|
def self.default_template
|
218
213
|
template = {}
|
219
214
|
expand_e = {}
|
220
|
-
|
215
|
+
|
221
216
|
template[:all_values] = EMPTYH
|
222
|
-
@props.each
|
223
|
-
if kl.respond_to? :default_template
|
224
|
-
|
225
|
-
end
|
226
|
-
}
|
217
|
+
@props.each do |prop, kl|
|
218
|
+
expand_e[prop] = kl.default_template if kl.respond_to? :default_template
|
219
|
+
end
|
227
220
|
template[:expand_e] = expand_e
|
228
221
|
template
|
229
222
|
end
|
230
|
-
|
223
|
+
|
231
224
|
def self.output_template
|
232
225
|
default_template
|
233
226
|
end
|
227
|
+
|
234
228
|
def self.type_name
|
235
|
-
@namespace ? "#{@namespace}.#{self
|
229
|
+
@namespace ? "#{@namespace}.#{self}" : to_s
|
236
230
|
end
|
237
231
|
|
238
232
|
def initialize
|
@@ -244,13 +238,13 @@ module Safrano
|
|
244
238
|
def odata_h
|
245
239
|
ret = { METAK => { TYPEK => self.class.type_name } }
|
246
240
|
|
247
|
-
@values.each
|
241
|
+
@values.each do |k, v|
|
248
242
|
ret[k] = if v.respond_to? :odata_h
|
249
243
|
v.odata_h
|
250
244
|
else
|
251
245
|
v
|
252
246
|
end
|
253
|
-
|
247
|
+
end
|
254
248
|
ret
|
255
249
|
end
|
256
250
|
|
@@ -276,14 +270,14 @@ module Safrano
|
|
276
270
|
end
|
277
271
|
end
|
278
272
|
|
279
|
-
def
|
273
|
+
def self.ComplexType(**props)
|
280
274
|
Class.new(Safrano::ComplexType) do
|
281
275
|
@props = props
|
282
|
-
props.each
|
276
|
+
props.each do |a, _klassmod|
|
283
277
|
asym = a.to_sym
|
284
|
-
define_method(asym)
|
285
|
-
define_method("#{a}=")
|
286
|
-
|
278
|
+
define_method(asym) { @values[asym] }
|
279
|
+
define_method("#{a}=") { |val| @values[asym] = val }
|
280
|
+
end
|
287
281
|
define_method :initialize do |*p, **kwvals|
|
288
282
|
super()
|
289
283
|
p.zip(props.keys).each { |val, a| @values[a] = val } if p
|
@@ -15,36 +15,108 @@ module Safrano
|
|
15
15
|
# Classes specifying generic types that Sequel will convert to
|
16
16
|
# database-specific types.
|
17
17
|
DB_TYPE_STRING_RGX = /\ACHAR\s*\(\d+\)\z/.freeze
|
18
|
-
|
18
|
+
DB_TYPE_NUMDEC_RGX = /\A(NUMERIC|DECIMAL)\s*(\(\s*((\d+)\s*(,\s*(\d+))?)\s*\))?\s*\z/i.freeze
|
19
|
+
# thank you rubular
|
20
|
+
# Test String: DECIMAL (55,2 )
|
21
|
+
# Match groups
|
22
|
+
# 1 DECIMAL
|
23
|
+
# 2 (55,2 )
|
24
|
+
# 3 55,2
|
25
|
+
# 4 55
|
26
|
+
# 5 ,2
|
27
|
+
# 6 2
|
28
|
+
|
29
|
+
DB_TYPE_FLOATP_RGX = /\A\s*(FLOAT)\s*(\(\s*(\d+)\s*\))?\s*\z/i.freeze
|
30
|
+
|
31
|
+
# Note: "char" (quoted!) is postgresql's byte type
|
32
|
+
DB_TYPE_INTLIKE_RGX = /\A\s*(smallserial|smallint|integer|int2|int4|int8|int|mediumint|bigint|serial|bigserial|tinyint)\s*/i.freeze
|
19
33
|
# used in $metadata
|
20
34
|
# cf. Sequel Database column_schema_default_to_ruby_value
|
21
35
|
# schema_column_type
|
22
36
|
# https://www.odata.org/documentation/odata-version-2-0/overview/
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
37
|
+
|
38
|
+
# type mappings are hard, especially between "Standards" like SQL and OData V2 (might be a bit better in V4 ?)
|
39
|
+
# this is all best effort/try to make it work logic
|
40
|
+
def self.add_edm_types(metadata, props)
|
41
|
+
# try num/dec with db_type:
|
42
|
+
metadata[:edm_type] = if (md = DB_TYPE_NUMDEC_RGX.match(props[:db_type]))
|
43
|
+
prec = md[4]
|
44
|
+
scale = md[6]
|
45
|
+
if scale && prec
|
46
|
+
if scale == '0' # dont force default scale to 0 like SQL standard
|
47
|
+
metadata[:edm_precision] = prec
|
48
|
+
"Edm.Decimal(#{prec})"
|
49
|
+
else
|
50
|
+
# we have precision and scale
|
51
|
+
metadata[:edm_scale] = scale
|
52
|
+
metadata[:edm_precision] = prec
|
53
|
+
"Edm.Decimal(#{prec},#{scale})"
|
54
|
+
end
|
55
|
+
elsif prec
|
56
|
+
# we have precision only
|
57
|
+
metadata[:edm_precision] = prec
|
58
|
+
"Edm.Decimal(#{prec})"
|
59
|
+
else
|
60
|
+
'Edm.Decimal'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
return if metadata[:edm_type]
|
64
|
+
|
65
|
+
# try float(prec) with db_type:
|
66
|
+
metadata[:edm_type] = if (md = DB_TYPE_FLOATP_RGX.match(props[:db_type]))
|
67
|
+
# FLOAT( 22) match groups
|
68
|
+
# 1 FLOAT
|
69
|
+
# 2 (22 )
|
70
|
+
# 3 22
|
71
|
+
|
72
|
+
if (prec = md[3])
|
73
|
+
# we have precision only
|
74
|
+
metadata[:edm_precision] = prec
|
75
|
+
'Edm.Double'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
return if metadata[:edm_type]
|
79
|
+
|
80
|
+
# try int-like with db_type:
|
81
|
+
# smallint|int|integer|bigint|serial|bigserial
|
82
|
+
metadata[:edm_type] = if (md = DB_TYPE_INTLIKE_RGX.match(props[:db_type]))
|
83
|
+
|
84
|
+
if (itype = md[1])
|
85
|
+
case itype.downcase
|
86
|
+
when 'smallint', 'int2', 'smallserial'
|
87
|
+
'Edm.Int16'
|
88
|
+
when 'int', 'integer', 'serial', 'mediumint', 'int4'
|
89
|
+
'Edm.Int32'
|
90
|
+
when 'bigint', 'bigserial', 'int8'
|
91
|
+
'Edm.Int64'
|
92
|
+
when 'tinyint'
|
93
|
+
'Edm.Byte'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
return if metadata[:edm_type]
|
98
|
+
|
99
|
+
# try with Sequel(ruby) type
|
100
|
+
metadata[:edm_type] = case props[:type]
|
101
|
+
when :integer
|
102
|
+
'Edm.Int32'
|
103
|
+
when :string
|
104
|
+
'Edm.String'
|
105
|
+
when :date
|
106
|
+
'Edm.DateTime'
|
107
|
+
when :datetime
|
108
|
+
'Edm.DateTime'
|
109
|
+
when :time
|
110
|
+
'Edm.Time'
|
111
|
+
when :boolean
|
112
|
+
'Edm.Boolean'
|
113
|
+
when :float
|
114
|
+
'Edm.Double'
|
115
|
+
when :decimal
|
116
|
+
'Edm.Decimal'
|
117
|
+
when :blob
|
118
|
+
'Edm.Binary'
|
119
|
+
end
|
48
120
|
end
|
49
121
|
|
50
122
|
# use Edm twice so that we can do include Safrano::Edm and then
|
@@ -69,12 +141,12 @@ module Safrano
|
|
69
141
|
class Null < NilClass
|
70
142
|
extend OutputClassMethods
|
71
143
|
# nil --> null convertion is done by to_json
|
72
|
-
def self.odata_value(
|
144
|
+
def self.odata_value(_instance)
|
73
145
|
nil
|
74
146
|
end
|
75
147
|
|
76
148
|
def self.convert_from_urlparam(v)
|
77
|
-
return Contract::NOK unless
|
149
|
+
return Contract::NOK unless v == 'null'
|
78
150
|
|
79
151
|
Contract.valid(nil)
|
80
152
|
end
|
@@ -94,7 +166,7 @@ module Safrano
|
|
94
166
|
# or false([nil, false])
|
95
167
|
class Boolean < Object
|
96
168
|
extend OutputClassMethods
|
97
|
-
def
|
169
|
+
def self.odata_value(instance)
|
98
170
|
instance ? true : false
|
99
171
|
end
|
100
172
|
|
@@ -103,7 +175,7 @@ module Safrano
|
|
103
175
|
end
|
104
176
|
|
105
177
|
def self.convert_from_urlparam(v)
|
106
|
-
return Contract::NOK unless [
|
178
|
+
return Contract::NOK unless %w[true false].include?(v)
|
107
179
|
|
108
180
|
Contract.valid(v == 'true')
|
109
181
|
end
|
@@ -115,7 +187,7 @@ module Safrano
|
|
115
187
|
extend OutputClassMethods
|
116
188
|
|
117
189
|
def self.convert_from_urlparam(v)
|
118
|
-
return Contract::NOK unless (
|
190
|
+
return Contract::NOK unless (bytev = v.to_i) < 256
|
119
191
|
|
120
192
|
Contract.valid(bytev)
|
121
193
|
end
|
@@ -123,7 +195,7 @@ module Safrano
|
|
123
195
|
|
124
196
|
class DateTime < ::DateTime
|
125
197
|
extend OutputClassMethods
|
126
|
-
def
|
198
|
+
def self.odata_value(instance)
|
127
199
|
instance.to_datetime
|
128
200
|
end
|
129
201
|
|
@@ -132,11 +204,9 @@ module Safrano
|
|
132
204
|
end
|
133
205
|
|
134
206
|
def self.convert_from_urlparam(v)
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
return convertion_error(v)
|
139
|
-
end
|
207
|
+
Contract.valid(DateTime.parse(v))
|
208
|
+
rescue StandardError
|
209
|
+
convertion_error(v)
|
140
210
|
end
|
141
211
|
end
|
142
212
|
|
@@ -172,11 +242,9 @@ module Safrano
|
|
172
242
|
extend OutputClassMethods
|
173
243
|
|
174
244
|
def self.convert_from_urlparam(v)
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
return Contract::NOK
|
179
|
-
end
|
245
|
+
Contract.valid(v.to_f)
|
246
|
+
rescue StandardError
|
247
|
+
Contract::NOK
|
180
248
|
end
|
181
249
|
end
|
182
250
|
end
|
data/lib/odata/entity.rb
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
require 'rexml/document'
|
5
|
-
require 'safrano
|
6
|
-
require 'odata/model_ext
|
5
|
+
require 'safrano'
|
6
|
+
require 'odata/model_ext' # required for self.class.entity_type_name ??
|
7
7
|
require_relative 'navigation_attribute'
|
8
8
|
|
9
9
|
module Safrano
|
@@ -86,9 +86,9 @@ module Safrano
|
|
86
86
|
"#{self.class.uri}#{@odata_pk}"
|
87
87
|
end
|
88
88
|
|
89
|
-
D = 'd'
|
90
|
-
DJ_OPEN = '{"d":'
|
91
|
-
DJ_CLOSE = '}'
|
89
|
+
D = 'd'
|
90
|
+
DJ_OPEN = '{"d":'
|
91
|
+
DJ_CLOSE = '}'
|
92
92
|
|
93
93
|
# Json formatter for a single entity (probably OData V1/V2 like)
|
94
94
|
def to_odata_json(request:)
|
@@ -151,11 +151,12 @@ module Safrano
|
|
151
151
|
@uparms.check_all.tap_valid { return odata_get_output(req) }
|
152
152
|
.tap_error { |e| return e.odata_get(req) }
|
153
153
|
end
|
154
|
+
|
154
155
|
def inactive_query_params
|
155
156
|
@inactive_query_params = true
|
156
157
|
self # chaining
|
157
158
|
end
|
158
|
-
|
159
|
+
|
159
160
|
DELETE_REL_AND_ENTY = lambda do |entity, assoc, parent|
|
160
161
|
Safrano.remove_nav_relation(assoc, parent)
|
161
162
|
entity.destroy(transaction: false)
|
@@ -175,7 +176,7 @@ module Safrano
|
|
175
176
|
destroy(transaction: false)
|
176
177
|
end
|
177
178
|
rescue StandardError => e
|
178
|
-
raise SequelAdapterError
|
179
|
+
raise SequelAdapterError, e
|
179
180
|
end
|
180
181
|
|
181
182
|
# TODO: differentiate between POST/PUT/PATCH/MERGE
|
@@ -202,7 +203,7 @@ module Safrano
|
|
202
203
|
if req.walker.media_value
|
203
204
|
odata_media_value_put(req)
|
204
205
|
elsif req.accept?(APPJSON)
|
205
|
-
data = JSON.
|
206
|
+
data = Safrano::OData::JSON.parse_one(req.body.read, self.class)
|
206
207
|
data.delete('__metadata')
|
207
208
|
|
208
209
|
if req.in_changeset
|
@@ -219,7 +220,7 @@ module Safrano
|
|
219
220
|
end
|
220
221
|
|
221
222
|
def odata_patch(req)
|
222
|
-
req.with_parsed_data do |data|
|
223
|
+
req.with_parsed_data(self.class) do |data|
|
223
224
|
data.delete('__metadata')
|
224
225
|
|
225
226
|
# validate payload column names
|
@@ -310,40 +311,6 @@ module Safrano
|
|
310
311
|
end
|
311
312
|
end
|
312
313
|
|
313
|
-
module MappingBeforeOutput
|
314
|
-
# needed for proper datetime or Decimal output
|
315
|
-
def casted_values(cols = nil)
|
316
|
-
vals = case cols
|
317
|
-
when nil
|
318
|
-
# we need to dup the model values as we need to change it before passing to_json,
|
319
|
-
# but we dont want to interfere with Sequel's owned data
|
320
|
-
# (eg because then in worst case it could happen that we write back changed values to DB)
|
321
|
-
values_for_odata.dup
|
322
|
-
else
|
323
|
-
selected_values_for_odata(cols)
|
324
|
-
end
|
325
|
-
# TODO better design (perf/ do more during startup and less during request runtime )
|
326
|
-
# TODO replace the quick and dirty BigDecimal hack with something better
|
327
|
-
self.class.decimal_cols.each { |dc| vals[dc] = BigDecimal(vals[dc].to_s).to_s('F') if vals.key?(dc) }
|
328
|
-
|
329
|
-
self.class.time_cols.each { |tc| vals[tc] = vals[tc]&.iso8601 if vals.key?(tc) }
|
330
|
-
vals
|
331
|
-
end
|
332
|
-
end
|
333
|
-
module NoMappingBeforeOutput
|
334
|
-
# current model does not have eg. Time fields--> no special mapping, just to_json is fine
|
335
|
-
# --> we can use directly the model.values (values_for_odata) withoud dup'ing it as we dont
|
336
|
-
# need to change it, just output as is
|
337
|
-
def casted_values(cols = nil)
|
338
|
-
case cols
|
339
|
-
when nil
|
340
|
-
values_for_odata
|
341
|
-
else
|
342
|
-
selected_values_for_odata(cols)
|
343
|
-
end
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
314
|
module MediaEntity
|
348
315
|
# media entity metadata for json h
|
349
316
|
def metadata_h
|
@@ -434,9 +401,9 @@ module Safrano
|
|
434
401
|
include Entity
|
435
402
|
def pk_uri
|
436
403
|
pku = +''
|
437
|
-
self.class.odata_upk_parts.each_with_index
|
404
|
+
self.class.odata_upk_parts.each_with_index do |upart, i|
|
438
405
|
pku = "#{pku}#{upart}#{pk[i]}"
|
439
|
-
|
406
|
+
end
|
440
407
|
pku
|
441
408
|
end
|
442
409
|
|
@@ -452,7 +419,7 @@ module Safrano
|
|
452
419
|
module EntityCreateStandardOutput
|
453
420
|
# Json formatter for a create entity POST call / Standard version; return as json object
|
454
421
|
def to_odata_create_json(request:)
|
455
|
-
# TODO Perf: reduce method call overhead
|
422
|
+
# TODO: Perf: reduce method call overhead
|
456
423
|
# we added this redirection for readability and flexibility
|
457
424
|
to_odata_json(request: request)
|
458
425
|
end
|
@@ -461,7 +428,7 @@ module Safrano
|
|
461
428
|
module EntityCreateArrayOutput
|
462
429
|
# Json formatter for a create entity POST call Array version
|
463
430
|
def to_odata_create_json(request:)
|
464
|
-
# TODO Perf: reduce method call overhead
|
431
|
+
# TODO: Perf: reduce method call overhead
|
465
432
|
# we added this redirection for readability and flexibility
|
466
433
|
to_odata_array_json(request: request)
|
467
434
|
end
|