safrano 0.5.1 → 0.5.5
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/Numeric/convert.rb +25 -0
- data/lib/core_ext/numeric.rb +3 -0
- data/lib/odata/collection.rb +7 -5
- data/lib/odata/complex_type.rb +131 -37
- data/lib/odata/edm/primitive_types.rb +99 -20
- data/lib/odata/entity.rb +6 -31
- data/lib/odata/function_import.rb +19 -21
- data/lib/odata/model_ext.rb +83 -15
- data/lib/odata/transition.rb +25 -1
- data/lib/odata/walker.rb +19 -1
- data/lib/safrano/request.rb +1 -0
- data/lib/safrano/service.rb +40 -5
- data/lib/safrano/type_mapping.rb +149 -0
- data/lib/safrano/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb412667db93e26340fc21440c89572fbff42fe52ae36807614f0fd11aac808c
|
4
|
+
data.tar.gz: 90883acded679aa860346e49242b1149933ccdb474e3e39eb6a893da5958dde7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f66a01241685f18493d0519cb009674f0764e260ccc79b048ebc7346b5d4c64bd78deeb9983a0b4893e4fcfc2d1361f7024ca212bcbdc8071191417e793d9427
|
7
|
+
data.tar.gz: 85e2cdd6c5c65e52f882fe2e8386cdf772c239ecdf27d979d473170f51bb1c0a37d8919cb8fc7422bb04eadf50baa8adb1ad4e712ea3952d3e2c0f7d9923bd48
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# require 'bigdecimal/util'
|
4
|
+
|
5
|
+
module Safrano
|
6
|
+
module CoreIncl
|
7
|
+
module Numeric
|
8
|
+
module Convert
|
9
|
+
def toDecimalString
|
10
|
+
BigDecimal(to_s).to_s('F')
|
11
|
+
end
|
12
|
+
|
13
|
+
def toDecimalPrecisionString(precision)
|
14
|
+
p = Integer(precision)
|
15
|
+
BigDecimal(self, p).to_s('F')
|
16
|
+
end
|
17
|
+
|
18
|
+
def toDecimalPrecisionScaleString(precision, scale)
|
19
|
+
p = Integer(precision)
|
20
|
+
sprintf("%#{p + 2}.#{scale}f", self).strip
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/odata/collection.rb
CHANGED
@@ -60,7 +60,10 @@ module Safrano
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def initialize_dataset(dtset = nil)
|
63
|
-
@cx = dtset || @modelk
|
63
|
+
@cx = @cx || dtset || @modelk
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize_uparms
|
64
67
|
@uparms = UrlParameters4Coll.new(@cx, @params)
|
65
68
|
end
|
66
69
|
|
@@ -131,9 +134,9 @@ module Safrano
|
|
131
134
|
|
132
135
|
# on model class level we return the collection
|
133
136
|
def odata_get(req)
|
134
|
-
@params = req.params
|
137
|
+
@params = @params || req.params
|
135
138
|
initialize_dataset
|
136
|
-
|
139
|
+
initialize_uparms
|
137
140
|
@uparms.check_all.if_valid { |_ret|
|
138
141
|
odata_get_apply_params.if_valid { |_ret|
|
139
142
|
odata_get_output(req)
|
@@ -170,8 +173,7 @@ module Safrano
|
|
170
173
|
end
|
171
174
|
|
172
175
|
def initialize_dataset(dtset = nil)
|
173
|
-
@cx = dtset || navigated_dataset
|
174
|
-
@uparms = UrlParameters4Coll.new(@cx, @params)
|
176
|
+
@cx = @cx || dtset || navigated_dataset
|
175
177
|
end
|
176
178
|
# redefinitions of the main methods for a navigated collection
|
177
179
|
# (eg. all Books of Author[2] is Author[2].Books.all )
|
data/lib/odata/complex_type.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module Safrano
|
4
4
|
module FunctionImport
|
5
|
+
EMPTY_HASH = {}.freeze
|
5
6
|
class ResultDefinition
|
6
7
|
D = 'd'
|
7
8
|
DJ_OPEN = '{"d":'
|
@@ -12,75 +13,166 @@ module Safrano
|
|
12
13
|
RESULTSK = 'results'
|
13
14
|
COLLECTION = 'Collection'
|
14
15
|
|
15
|
-
def
|
16
|
-
|
16
|
+
def allowed_transitions
|
17
|
+
[Safrano::TransitionEnd]
|
17
18
|
end
|
18
19
|
|
19
|
-
def
|
20
|
-
|
20
|
+
def transition_end(_match_result)
|
21
|
+
Safrano::Transition::RESULT_END
|
21
22
|
end
|
22
23
|
|
23
|
-
|
24
|
+
# we will have this on class and instance level for making things simpler first
|
25
|
+
def self.klassmod
|
26
|
+
@klassmod
|
27
|
+
end
|
28
|
+
|
29
|
+
# return a subclass of ResultAsComplexType
|
30
|
+
def self.asComplexType(klassmod)
|
31
|
+
Class.new(ResultAsComplexType) do
|
32
|
+
@klassmod = klassmod
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# return a subclass of ResultAsComplexType
|
37
|
+
def self.asComplexTypeColl(klassmod)
|
38
|
+
Class.new(ResultAsComplexTypeColl) do
|
39
|
+
@klassmod = klassmod
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.asPrimitiveType(klassmod)
|
44
|
+
Class.new(ResultAsPrimitiveType) do
|
45
|
+
@klassmod = klassmod
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.asPrimitiveTypeColl(klassmod)
|
50
|
+
Class.new(ResultAsPrimitiveTypeColl) do
|
51
|
+
@klassmod = klassmod
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.asEntity(klassmod)
|
56
|
+
Class.new(ResultAsEntity) do
|
57
|
+
@klassmod = klassmod
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.asEntityColl(klassmod)
|
62
|
+
Class.new(ResultAsEntityColl) do
|
63
|
+
@klassmod = klassmod
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(value)
|
68
|
+
@value = value
|
69
|
+
end
|
70
|
+
|
71
|
+
def odata_get(req)
|
72
|
+
[200, EMPTY_HASH, [to_odata_json(req)]]
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.type_metadata
|
24
76
|
@klassmod.type_name
|
25
77
|
end
|
78
|
+
|
79
|
+
def type_metadata
|
80
|
+
self.class.type_metadata
|
81
|
+
end
|
82
|
+
|
83
|
+
# needed for ComplexType result
|
84
|
+
def to_odata_json(_req)
|
85
|
+
"#{DJ_OPEN}#{@value.odata_h.to_json}#{DJ_CLOSE}"
|
86
|
+
end
|
87
|
+
|
88
|
+
# wrapper
|
89
|
+
# for OData Entity and Collections, return them directly
|
90
|
+
# for others, ie ComplexType, Prims etc, return the ResultDefinition-subclass wrapped result
|
91
|
+
def self.do_execute_func_result(result, _req, apply_query_params = false)
|
92
|
+
self.new(result)
|
93
|
+
end
|
26
94
|
end
|
95
|
+
|
27
96
|
class ResultAsComplexType < ResultDefinition
|
97
|
+
def self.type_metadata
|
98
|
+
@klassmod.type_name
|
99
|
+
end
|
28
100
|
end
|
101
|
+
|
29
102
|
class ResultAsComplexTypeColl < ResultDefinition
|
30
|
-
def type_metadata
|
103
|
+
def self.type_metadata
|
31
104
|
"Collection(#{@klassmod.type_name})"
|
32
105
|
end
|
33
106
|
|
34
|
-
def to_odata_json(
|
35
|
-
# "#{DJ_OPEN}#{{ RESULTSK => coll.map { |c| c.odata_h } }.to_json}#{DJ_CLOSE}"
|
36
|
-
template =
|
107
|
+
def to_odata_json(req)
|
108
|
+
# "#{DJ_OPEN}#{{ RESULTSK => coll.map { |c| c.odata_h } }.to_json}#{DJ_CLOSE}"
|
109
|
+
template = self.class.klassmod.output_template
|
37
110
|
# TODO: Error handling if database contains binary BLOB data that cant be
|
38
111
|
# interpreted as UTF-8 then JSON will fail here
|
39
112
|
|
40
|
-
innerh = req.service.get_coll_odata_h(array:
|
113
|
+
innerh = req.service.get_coll_odata_h(array: @value,
|
41
114
|
template: template)
|
42
|
-
|
115
|
+
|
43
116
|
innerj = innerh.to_json
|
44
117
|
|
45
118
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
46
119
|
end
|
47
120
|
end
|
121
|
+
|
48
122
|
class ResultAsEntity < ResultDefinition
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
123
|
+
def self.type_metadata
|
124
|
+
@klassmod.type_name
|
125
|
+
end
|
126
|
+
|
127
|
+
# wrapper
|
128
|
+
# for OData Entity return them directly
|
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
|
131
|
+
# safely add request-dependant data (eg. req.params) there
|
132
|
+
apply_query_params ? result : result.inactive_query_params
|
54
133
|
end
|
55
134
|
end
|
135
|
+
|
56
136
|
class ResultAsEntityColl < ResultDefinition
|
57
|
-
def type_metadata
|
137
|
+
def self.type_metadata
|
58
138
|
"Collection(#{@klassmod.type_name})"
|
59
139
|
end
|
60
140
|
|
61
|
-
|
141
|
+
# wrapper
|
142
|
+
# for OData Entity Collection return them directly
|
143
|
+
def self.do_execute_func_result(result, req, apply_query_params = false)
|
62
144
|
coll = Safrano::OData::Collection.new(@klassmod)
|
145
|
+
# instance_exec has other instance variables; @values would be nil in the block below
|
146
|
+
# need to pass a local copy
|
147
|
+
dtset = result
|
63
148
|
coll.instance_exec do
|
64
|
-
@params = req.params
|
65
|
-
initialize_dataset(
|
149
|
+
@params = apply_query_params ? req.params : EMPTY_HASH
|
150
|
+
initialize_dataset(dtset)
|
151
|
+
initialize_uparms
|
66
152
|
end
|
67
|
-
coll
|
153
|
+
coll
|
68
154
|
end
|
69
155
|
end
|
156
|
+
|
70
157
|
class ResultAsPrimitiveType < ResultDefinition
|
71
|
-
def
|
158
|
+
def self.type_metadata
|
159
|
+
@klassmod.type_name
|
160
|
+
end
|
161
|
+
|
162
|
+
def to_odata_json(_req)
|
72
163
|
{ D => { METAK => { TYPEK => type_metadata },
|
73
|
-
VALUEK =>
|
164
|
+
VALUEK => self.class.klassmod.odata_value(@value) } }.to_json
|
74
165
|
end
|
75
166
|
end
|
167
|
+
|
76
168
|
class ResultAsPrimitiveTypeColl < ResultDefinition
|
77
|
-
def type_metadata
|
169
|
+
def self.type_metadata
|
78
170
|
"Collection(#{@klassmod.type_name})"
|
79
171
|
end
|
80
172
|
|
81
|
-
def to_odata_json(
|
82
|
-
{ D => { METAK => { TYPEK => type_metadata },
|
83
|
-
RESULTSK =>
|
173
|
+
def to_odata_json(_req)
|
174
|
+
{ D => { METAK => { TYPEK => self.class.type_metadata },
|
175
|
+
RESULTSK => self.class.klassmod.odata_collection(@value) } }.to_json
|
84
176
|
end
|
85
177
|
end
|
86
178
|
end
|
@@ -90,8 +182,9 @@ module Safrano
|
|
90
182
|
# with added OData functionality
|
91
183
|
class ComplexType
|
92
184
|
attr_reader :values
|
185
|
+
|
93
186
|
EMPTYH = {}.freeze
|
94
|
-
|
187
|
+
|
95
188
|
@namespace = nil
|
96
189
|
def self.namespace
|
97
190
|
@namespace
|
@@ -100,28 +193,28 @@ module Safrano
|
|
100
193
|
def self.props
|
101
194
|
@props
|
102
195
|
end
|
103
|
-
|
196
|
+
|
104
197
|
def type_name
|
105
198
|
self.class.type_name
|
106
199
|
end
|
107
|
-
|
200
|
+
|
108
201
|
def metadata_h
|
109
202
|
{ type: type_name }
|
110
203
|
end
|
111
|
-
|
204
|
+
|
112
205
|
def casted_values
|
113
206
|
# MVP... TODO: handle time mappings like in Entity models
|
114
207
|
values
|
115
208
|
end
|
116
|
-
|
209
|
+
|
117
210
|
# needed for nested json output
|
118
211
|
# this is a simpler version of model_ext#output_template
|
119
212
|
def self.default_template
|
120
213
|
template = {}
|
121
214
|
expand_e = {}
|
122
|
-
|
215
|
+
|
123
216
|
template[:all_values] = EMPTYH
|
124
|
-
@props.each { |prop, kl|
|
217
|
+
@props.each { |prop, kl|
|
125
218
|
if kl.respond_to? :default_template
|
126
219
|
expand_e[prop] = kl.default_template
|
127
220
|
end
|
@@ -129,10 +222,11 @@ module Safrano
|
|
129
222
|
template[:expand_e] = expand_e
|
130
223
|
template
|
131
224
|
end
|
132
|
-
|
225
|
+
|
133
226
|
def self.output_template
|
134
227
|
default_template
|
135
228
|
end
|
229
|
+
|
136
230
|
def self.type_name
|
137
231
|
@namespace ? "#{@namespace}.#{self.to_s}" : self.to_s
|
138
232
|
end
|
@@ -157,11 +251,11 @@ module Safrano
|
|
157
251
|
end
|
158
252
|
|
159
253
|
def self.return_as_collection_descriptor
|
160
|
-
FunctionImport::
|
254
|
+
FunctionImport::ResultDefinition.asComplexTypeColl(self)
|
161
255
|
end
|
162
256
|
|
163
257
|
def self.return_as_instance_descriptor
|
164
|
-
FunctionImport::
|
258
|
+
FunctionImport::ResultDefinition.asComplexType(self)
|
165
259
|
end
|
166
260
|
|
167
261
|
# add metadata xml to the passed REXML schema object
|
@@ -15,30 +15,109 @@ 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
|
-
|
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
|
+
else
|
120
|
+
end
|
42
121
|
end
|
43
122
|
|
44
123
|
# use Edm twice so that we can do include Safrano::Edm and then
|
data/lib/odata/entity.rb
CHANGED
@@ -125,7 +125,7 @@ module Safrano
|
|
125
125
|
end
|
126
126
|
|
127
127
|
def copy_request_infos(req)
|
128
|
-
@params = req.params
|
128
|
+
@params = @inactive_query_params ? EMPTY_HASH : req.params
|
129
129
|
@do_links = req.walker.do_links
|
130
130
|
@uparms = UrlParameters4Single.new(self, @params)
|
131
131
|
end
|
@@ -152,6 +152,11 @@ module Safrano
|
|
152
152
|
.tap_error { |e| return e.odata_get(req) }
|
153
153
|
end
|
154
154
|
|
155
|
+
def inactive_query_params
|
156
|
+
@inactive_query_params = true
|
157
|
+
self # chaining
|
158
|
+
end
|
159
|
+
|
155
160
|
DELETE_REL_AND_ENTY = lambda do |entity, assoc, parent|
|
156
161
|
Safrano.remove_nav_relation(assoc, parent)
|
157
162
|
entity.destroy(transaction: false)
|
@@ -306,36 +311,6 @@ module Safrano
|
|
306
311
|
end
|
307
312
|
end
|
308
313
|
|
309
|
-
module MappingBeforeOutput
|
310
|
-
# needed for proper datetime output
|
311
|
-
def casted_values(cols = nil)
|
312
|
-
vals = case cols
|
313
|
-
when nil
|
314
|
-
# we need to dup the model values as we need to change it before passing to_json,
|
315
|
-
# but we dont want to interfere with Sequel's owned data
|
316
|
-
# (eg because then in worst case it could happen that we write back changed values to DB)
|
317
|
-
values_for_odata.dup
|
318
|
-
else
|
319
|
-
selected_values_for_odata(cols)
|
320
|
-
end
|
321
|
-
self.class.time_cols.each { |tc| vals[tc] = vals[tc]&.iso8601 if vals.key?(tc) }
|
322
|
-
vals
|
323
|
-
end
|
324
|
-
end
|
325
|
-
module NoMappingBeforeOutput
|
326
|
-
# current model does not have eg. Time fields--> no special mapping, just to_json is fine
|
327
|
-
# --> we can use directly the model.values (values_for_odata) withoud dup'ing it as we dont
|
328
|
-
# need to change it, just output as is
|
329
|
-
def casted_values(cols = nil)
|
330
|
-
case cols
|
331
|
-
when nil
|
332
|
-
values_for_odata
|
333
|
-
else
|
334
|
-
selected_values_for_odata(cols)
|
335
|
-
end
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
314
|
module MediaEntity
|
340
315
|
# media entity metadata for json h
|
341
316
|
def metadata_h
|
@@ -21,7 +21,7 @@ module Safrano
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def allowed_transitions
|
24
|
-
[Safrano::
|
24
|
+
[Safrano::TransitionExecuteFunc]
|
25
25
|
end
|
26
26
|
|
27
27
|
def input(**parmtypes)
|
@@ -43,6 +43,12 @@ module Safrano
|
|
43
43
|
self
|
44
44
|
end
|
45
45
|
|
46
|
+
def auto_query_parameters
|
47
|
+
@auto_query_params = true
|
48
|
+
self # chaining
|
49
|
+
end
|
50
|
+
alias auto_query_params auto_query_parameters
|
51
|
+
|
46
52
|
def return(klassmod, &proc)
|
47
53
|
raise('Please provide a code block') unless block_given?
|
48
54
|
|
@@ -51,7 +57,7 @@ module Safrano
|
|
51
57
|
else
|
52
58
|
# if it's neither a ComplexType nor a Model-Entity
|
53
59
|
# --> assume it is a Primitive
|
54
|
-
|
60
|
+
ResultDefinition.asPrimitiveType(klassmod)
|
55
61
|
end
|
56
62
|
@proc = proc
|
57
63
|
self
|
@@ -65,7 +71,8 @@ module Safrano
|
|
65
71
|
else
|
66
72
|
# if it's neither a ComplexType nor a Modle-Entity
|
67
73
|
# --> assume it is a Primitive
|
68
|
-
ResultAsPrimitiveTypeColl.new(klassmod)
|
74
|
+
# ResultAsPrimitiveTypeColl.new(klassmod)
|
75
|
+
ResultDefinition.asPrimitiveTypeColl(klassmod)
|
69
76
|
end
|
70
77
|
@proc = proc
|
71
78
|
self
|
@@ -136,32 +143,23 @@ module Safrano
|
|
136
143
|
funky
|
137
144
|
end
|
138
145
|
|
139
|
-
def
|
146
|
+
def with_transition_validated(req)
|
140
147
|
# initialize_params
|
148
|
+
@params = req.params
|
141
149
|
return yield unless (@error = check_url_func_params)
|
142
150
|
|
143
|
-
@error
|
144
|
-
end
|
145
|
-
|
146
|
-
def to_odata_json(req)
|
147
|
-
result = @proc.call(**@funcparams)
|
148
|
-
@returning.to_odata_json(result, req)
|
151
|
+
[nil, :error, @error] if @error
|
149
152
|
end
|
150
153
|
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
def odata_get(req)
|
156
|
-
@params = req.params
|
157
|
-
|
158
|
-
with_validated_get(req) do
|
159
|
-
odata_get_output(req)
|
154
|
+
def do_execute_func(req)
|
155
|
+
with_transition_validated(req) do
|
156
|
+
result = @proc.call(**@funcparams)
|
157
|
+
[@returning.do_execute_func_result(result, req, @auto_query_params), :run]
|
160
158
|
end
|
161
159
|
end
|
162
160
|
|
163
|
-
def
|
164
|
-
|
161
|
+
def transition_execute_func(_match_result)
|
162
|
+
[self, :run_with_execute_func]
|
165
163
|
end
|
166
164
|
end
|
167
165
|
end
|
data/lib/odata/model_ext.rb
CHANGED
@@ -5,7 +5,9 @@
|
|
5
5
|
# Thus Below we have called that "EntityClass". It's meant as "Collection"
|
6
6
|
|
7
7
|
require 'json'
|
8
|
+
require 'base64'
|
8
9
|
require 'rexml/document'
|
10
|
+
require 'bigdecimal'
|
9
11
|
require_relative '../safrano/core'
|
10
12
|
require_relative 'error'
|
11
13
|
require_relative 'collection_filter'
|
@@ -31,8 +33,14 @@ module Safrano
|
|
31
33
|
attr_reader :default_template
|
32
34
|
attr_reader :uri
|
33
35
|
attr_reader :odata_upk_parts
|
34
|
-
attr_reader :
|
36
|
+
attr_reader :casted_cols
|
35
37
|
attr_reader :namespace
|
38
|
+
# store cols metata here in the model (sub)-class. Initially we stored this infos (eg. edm_types etc)
|
39
|
+
# directly into sequels db_schema hash. But this hash is on the upper Sequel::Model(table) class
|
40
|
+
# and is shared by all subclasses.
|
41
|
+
# By storing it separately here we are less dependant from Sequel, and have less issues with
|
42
|
+
# testing with multiples models class derived from same Sequel::Model(table)
|
43
|
+
attr_reader :cols_metadata
|
36
44
|
|
37
45
|
# initialising block of code to be executed at end of
|
38
46
|
# ServerApp.publish_service after all model classes have been registered
|
@@ -73,7 +81,7 @@ module Safrano
|
|
73
81
|
@uparms = nil
|
74
82
|
@params = nil
|
75
83
|
@cx = nil
|
76
|
-
|
84
|
+
@cols_metadata = {}
|
77
85
|
end
|
78
86
|
|
79
87
|
def build_uri(uribase)
|
@@ -81,11 +89,11 @@ module Safrano
|
|
81
89
|
end
|
82
90
|
|
83
91
|
def return_as_collection_descriptor
|
84
|
-
Safrano::FunctionImport::
|
92
|
+
Safrano::FunctionImport::ResultDefinition.asEntityColl(self)
|
85
93
|
end
|
86
94
|
|
87
95
|
def return_as_instance_descriptor
|
88
|
-
Safrano::FunctionImport::
|
96
|
+
Safrano::FunctionImport::ResultDefinition.asEntity(self)
|
89
97
|
end
|
90
98
|
|
91
99
|
def execute_deferred_iblock
|
@@ -94,10 +102,10 @@ module Safrano
|
|
94
102
|
|
95
103
|
# Factory json-> Model Object instance
|
96
104
|
def new_from_hson_h(hash)
|
97
|
-
#enty = new
|
98
|
-
#enty.set_fields(hash, data_fields, missing: :skip)
|
105
|
+
# enty = new
|
106
|
+
# enty.set_fields(hash, data_fields, missing: :skip)
|
99
107
|
enty = create(hash)
|
100
|
-
#enty.set(hash)
|
108
|
+
# enty.set(hash)
|
101
109
|
enty
|
102
110
|
end
|
103
111
|
|
@@ -176,13 +184,14 @@ module Safrano
|
|
176
184
|
end
|
177
185
|
# with their properties
|
178
186
|
db_schema.each do |pnam, prop|
|
187
|
+
metadata = @cols_metadata[pnam]
|
179
188
|
if prop[:primary_key] == true
|
180
189
|
enty.add_element('Key').add_element('PropertyRef',
|
181
190
|
'Name' => pnam.to_s)
|
182
191
|
end
|
183
192
|
attrs = { 'Name' => pnam.to_s,
|
184
193
|
# 'Type' => Safrano.get_edm_type(db_type: prop[:db_type]) }
|
185
|
-
'Type' =>
|
194
|
+
'Type' => metadata[:edm_type] }
|
186
195
|
attrs['Nullable'] = 'false' if prop[:allow_null] == false
|
187
196
|
enty.add_element('Property', attrs)
|
188
197
|
end
|
@@ -349,20 +358,79 @@ module Safrano
|
|
349
358
|
end
|
350
359
|
end
|
351
360
|
|
352
|
-
def
|
361
|
+
def build_casted_cols(service)
|
362
|
+
# cols needed catsting before final json output
|
363
|
+
@casted_cols = {}
|
364
|
+
db_schema.each { |col, props|
|
365
|
+
# first check if we have user-defined type mapping
|
366
|
+
usermap = nil
|
367
|
+
dbtyp = props[:db_type]
|
368
|
+
metadata = @cols_metadata[col]
|
369
|
+
if (service.type_mappings.values.find { |map| usermap = map.match(dbtyp) })
|
370
|
+
|
371
|
+
metadata[:edm_type] = usermap.edm_type
|
372
|
+
|
373
|
+
@casted_cols[col] = usermap.castfunc
|
374
|
+
next # this will override our rules below !
|
375
|
+
end
|
376
|
+
|
377
|
+
if (metadata[:edm_precision] && (metadata[:edm_type] =~ /\AEdm.Decimal\(/i))
|
378
|
+
# we save the precision and/or scale in the lambda (binding!)
|
379
|
+
|
380
|
+
@casted_cols[col] = if metadata[:edm_scale]
|
381
|
+
->(x) {
|
382
|
+
# not sure if these copies are really needed, but feels better that way
|
383
|
+
# output decimal with precision and scale
|
384
|
+
x&.toDecimalPrecisionScaleString(metadata[:edm_precision], metadata[:edm_scale])
|
385
|
+
}
|
386
|
+
else
|
387
|
+
->(x) {
|
388
|
+
# not sure if these copies are really needed, but feels better that way
|
389
|
+
# output decimal with precision only
|
390
|
+
x&.toDecimalPrecisionString(metadata[:edm_precision])
|
391
|
+
}
|
392
|
+
end
|
393
|
+
|
394
|
+
next
|
395
|
+
end
|
396
|
+
if metadata[:edm_type] == 'Edm.Decimal'
|
397
|
+
@casted_cols[col] = ->(x) { x&.toDecimalString }
|
398
|
+
next
|
399
|
+
end
|
400
|
+
# Odata V2 Spec:
|
401
|
+
# Edm.Binary Base64 encoded value of an EDM.Binary value represented as a JSON string
|
402
|
+
# See for example https://services.odata.org/V2/Northwind/Northwind.svc/Categories(1)?$format=json
|
403
|
+
if metadata[:edm_type] == 'Edm.Binary'
|
404
|
+
@casted_cols[col] = ->(x) { Base64.encode64(x) unless x.nil? } # Base64
|
405
|
+
next
|
406
|
+
end
|
407
|
+
# TODO check this more in details
|
408
|
+
# 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
|
+
|
412
|
+
end
|
413
|
+
}
|
414
|
+
end
|
415
|
+
|
416
|
+
def finalize_publishing(service)
|
353
417
|
build_type_name
|
354
418
|
|
355
419
|
# build default output template structure
|
356
420
|
build_default_template
|
357
421
|
|
358
|
-
#
|
359
|
-
@
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
props[:odata_edm_type] = Safrano.default_edm_type(ruby_type: props[:type])
|
422
|
+
# add edm_types into metadata store
|
423
|
+
@cols_metadata = {}
|
424
|
+
db_schema.each do |col, props|
|
425
|
+
metadata = @cols_metadata.key?(col) ? @cols_metadata[col] : (@cols_metadata[col] = {})
|
426
|
+
Safrano.add_edm_types(metadata, props)
|
364
427
|
end
|
365
428
|
|
429
|
+
build_casted_cols(service)
|
430
|
+
# unless @casted_cols.empty?
|
431
|
+
# require 'pry'
|
432
|
+
# binding.pry
|
433
|
+
# end
|
366
434
|
# and finally build the path lists and allowed tr's
|
367
435
|
build_attribute_path_list
|
368
436
|
build_expand_path_list
|
data/lib/odata/transition.rb
CHANGED
@@ -6,7 +6,7 @@ require_relative 'error'
|
|
6
6
|
module Safrano
|
7
7
|
# represents a state transition when navigating/parsing the url path
|
8
8
|
# from left to right
|
9
|
-
class Transition
|
9
|
+
class Transition
|
10
10
|
attr_accessor :trans
|
11
11
|
attr_accessor :match_result
|
12
12
|
attr_accessor :rgx
|
@@ -53,7 +53,31 @@ module Safrano
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
+
# Transition that does not move/change the input
|
57
|
+
class InplaceTransition < Transition
|
58
|
+
def initialize(trans:)
|
59
|
+
@trans = trans
|
60
|
+
end
|
61
|
+
|
62
|
+
def do_match(str)
|
63
|
+
@str = str
|
64
|
+
end
|
65
|
+
|
66
|
+
def path_remain
|
67
|
+
@str
|
68
|
+
end
|
69
|
+
|
70
|
+
def path_done
|
71
|
+
EMPTYSTR
|
72
|
+
end
|
73
|
+
|
74
|
+
def do_transition(ctx)
|
75
|
+
ctx.method(@trans).call(@str)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
56
79
|
TransitionEnd = Transition.new('\A(\/?)\z', trans: 'transition_end')
|
80
|
+
TransitionExecuteFunc = InplaceTransition.new(trans: 'transition_execute_func')
|
57
81
|
TransitionMetadata = Transition.new('\A(\/\$metadata)(.*)',
|
58
82
|
trans: 'transition_metadata')
|
59
83
|
TransitionBatch = Transition.new('\A(\/\$batch)(.*)',
|
data/lib/odata/walker.rb
CHANGED
@@ -30,17 +30,23 @@ module Safrano
|
|
30
30
|
# are $links requested ?
|
31
31
|
attr_reader :do_links
|
32
32
|
|
33
|
+
attr_reader :request
|
34
|
+
|
33
35
|
NIL_SERVICE_FATAL = 'Walker is called with a nil service'
|
34
36
|
EMPTYSTR = ''
|
35
37
|
SLASH = '/'
|
36
38
|
|
37
|
-
def initialize(service, path, content_id_refs = nil)
|
39
|
+
def initialize(service, path, request, content_id_refs = nil)
|
38
40
|
raise NIL_SERVICE_FATAL unless service
|
39
41
|
|
40
42
|
path = URI.decode_www_form_component(path)
|
41
43
|
@context = service
|
42
44
|
@content_id_refs = content_id_refs
|
43
45
|
|
46
|
+
# needed because for function import we need access to the url parameters (req.params)
|
47
|
+
# who contains the functions params
|
48
|
+
@request = request
|
49
|
+
|
44
50
|
@contexts = [@context]
|
45
51
|
|
46
52
|
@path_start = @path_remain = if service
|
@@ -109,6 +115,16 @@ module Safrano
|
|
109
115
|
end
|
110
116
|
end
|
111
117
|
|
118
|
+
# execute function import with request parameters
|
119
|
+
# input: @context containt the function to exectute,
|
120
|
+
# @request.params should normally contain the params
|
121
|
+
# result: validate the params for the given function, execute the function and
|
122
|
+
# return it's result back into @context,
|
123
|
+
# and finaly set status :end (or error if anyting went wrong )
|
124
|
+
def do_run_with_execute_func
|
125
|
+
@context, @status, @error = @context.do_execute_func(@request)
|
126
|
+
end
|
127
|
+
|
112
128
|
# little hacks... depending on returned state, set some attributes
|
113
129
|
def state_mappings
|
114
130
|
case @status
|
@@ -137,6 +153,8 @@ module Safrano
|
|
137
153
|
# entity reference here and place it in @context
|
138
154
|
when :run_with_content_id
|
139
155
|
do_run_with_content_id
|
156
|
+
when :run_with_execute_func
|
157
|
+
do_run_with_execute_func
|
140
158
|
end
|
141
159
|
|
142
160
|
@contexts << @context
|
data/lib/safrano/request.rb
CHANGED
data/lib/safrano/service.rb
CHANGED
@@ -5,6 +5,7 @@ require 'odata/relations'
|
|
5
5
|
require 'odata/batch'
|
6
6
|
require 'odata/complex_type'
|
7
7
|
require 'odata/function_import'
|
8
|
+
require 'safrano/type_mapping'
|
8
9
|
require 'odata/error'
|
9
10
|
require 'odata/filter/sequel'
|
10
11
|
require 'set'
|
@@ -137,6 +138,8 @@ module Safrano
|
|
137
138
|
attr_accessor :relman
|
138
139
|
attr_accessor :complex_types
|
139
140
|
attr_accessor :function_imports
|
141
|
+
attr_accessor :function_import_keys
|
142
|
+
attr_accessor :type_mappings
|
140
143
|
|
141
144
|
# Instance attributes for specialized Version specific Instances
|
142
145
|
attr_accessor :v1
|
@@ -155,7 +158,9 @@ module Safrano
|
|
155
158
|
@relman = Safrano::RelationManager.new
|
156
159
|
@complex_types = Set.new
|
157
160
|
@function_imports = {}
|
161
|
+
@function_import_keys = []
|
158
162
|
@cmap = {}
|
163
|
+
@type_mappings = {}
|
159
164
|
instance_eval(&block) if block_given?
|
160
165
|
end
|
161
166
|
|
@@ -239,6 +244,8 @@ module Safrano
|
|
239
244
|
other.batch_handler = @batch_handler
|
240
245
|
other.complex_types = @complex_types
|
241
246
|
other.function_imports = @function_imports
|
247
|
+
other.function_import_keys = @function_import_keys
|
248
|
+
other.type_mappings = @type_mappings
|
242
249
|
other
|
243
250
|
end
|
244
251
|
|
@@ -322,9 +329,16 @@ module Safrano
|
|
322
329
|
def function_import(name)
|
323
330
|
funcimp = Safrano::FunctionImport(name)
|
324
331
|
@function_imports[name] = funcimp
|
332
|
+
@function_import_keys << name
|
333
|
+
set_funcimports_sorted
|
325
334
|
funcimp
|
326
335
|
end
|
327
336
|
|
337
|
+
def with_db_type(*dbtypnams, &proc)
|
338
|
+
m = TypeMapping.builder(*dbtypnams, &proc)
|
339
|
+
@type_mappings[m.db_types_rgx] = m
|
340
|
+
end
|
341
|
+
|
328
342
|
def cmap=(imap)
|
329
343
|
@cmap = imap
|
330
344
|
set_collections_sorted(@cmap.values)
|
@@ -339,6 +353,10 @@ module Safrano
|
|
339
353
|
@collections
|
340
354
|
end
|
341
355
|
|
356
|
+
# need to be sorted by size too
|
357
|
+
def set_funcimports_sorted
|
358
|
+
@function_import_keys.sort_by! { |k| k.size }.reverse!
|
359
|
+
end
|
342
360
|
# to be called at end of publishing block to ensure we get the right names
|
343
361
|
# and additionally build the list of valid attribute path's used
|
344
362
|
# for validation of $orderby or $filter params
|
@@ -361,15 +379,30 @@ module Safrano
|
|
361
379
|
|
362
380
|
set_uribase
|
363
381
|
|
364
|
-
@collections.each(&:finalize_publishing)
|
365
|
-
|
366
382
|
# finalize the uri's and include NoMappingBeforeOutput or MappingBeforeOutput as needed
|
367
383
|
@collections.each do |klass|
|
384
|
+
klass.finalize_publishing(self)
|
385
|
+
|
368
386
|
klass.build_uri(@uribase)
|
369
|
-
klass.include(klass.time_cols.empty? ? Safrano::NoMappingBeforeOutput : Safrano::MappingBeforeOutput)
|
370
387
|
|
371
388
|
# Output create (POST) as single entity (Standard) or as array (non-standard buggy)
|
372
389
|
klass.include ( @bugfix_create_response ? Safrano::EntityCreateStandardOutput : Safrano::EntityCreateArrayOutput)
|
390
|
+
|
391
|
+
# define the most optimal casted_values method for the given model(klass)
|
392
|
+
if (klass.casted_cols.empty?)
|
393
|
+
klass.send(:define_method, :casted_values) do |cols = nil|
|
394
|
+
cols ? selected_values_for_odata(cols) : values_for_odata
|
395
|
+
end
|
396
|
+
else
|
397
|
+
klass.send(:define_method, :casted_values) do |cols = nil|
|
398
|
+
# we need to dup the model values as we need to change it before passing to_json,
|
399
|
+
# but we dont want to interfere with Sequel's owned data
|
400
|
+
# (eg because then in worst case it could happen that we write back changed values to DB)
|
401
|
+
vals = cols ? selected_values_for_odata(cols) : values_for_odata.dup
|
402
|
+
self.class.casted_cols.each { |cc, lambda| vals[cc] = lambda.call(vals[cc]) if vals.key?(cc) }
|
403
|
+
vals
|
404
|
+
end
|
405
|
+
end
|
373
406
|
end
|
374
407
|
|
375
408
|
# build allowed transitions (requires that @collections are filled and sorted for having a
|
@@ -401,7 +434,7 @@ module Safrano
|
|
401
434
|
end
|
402
435
|
|
403
436
|
def base_url_func_regexp
|
404
|
-
@
|
437
|
+
@function_import_keys.join('|')
|
405
438
|
end
|
406
439
|
|
407
440
|
def service
|
@@ -495,7 +528,9 @@ module Safrano
|
|
495
528
|
doc.add_element('edmx:Edmx', 'Version' => '1.0')
|
496
529
|
doc.root.add_namespace('xmlns:edmx', XMLNS::MSFT_ADO_2007_EDMX)
|
497
530
|
serv = doc.root.add_element('edmx:DataServices',
|
498
|
-
|
531
|
+
# TODO: export the real version (result from version negotions)
|
532
|
+
# but currently we support only v1 and v2, and most users will use v2
|
533
|
+
'm:DataServiceVersion' => '2.0')
|
499
534
|
# 'm:DataServiceVersion' => "#{self.dataServiceVersion}" )
|
500
535
|
# DataServiceVersion: This attribute MUST be in the data service
|
501
536
|
# metadata namespace
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Note: Safrano has hardcoded mapping rules for most of types.
|
4
|
+
# But
|
5
|
+
# it might not be 100% complete
|
6
|
+
# it might not always do what is expected
|
7
|
+
# The type mapping functionality here allows Safrano users to design type mapping themselves
|
8
|
+
# and fill or fix the two above issues
|
9
|
+
module Safrano
|
10
|
+
# Base class
|
11
|
+
class TypeMapping
|
12
|
+
attr_reader :castfunc
|
13
|
+
attr_reader :db_types_rgx
|
14
|
+
attr_reader :edm_type
|
15
|
+
|
16
|
+
# wrapper to handle API
|
17
|
+
class Builder
|
18
|
+
attr_reader :xedm_type
|
19
|
+
attr_reader :castfunc
|
20
|
+
attr_reader :db_types_rgx
|
21
|
+
attr_reader :bui1
|
22
|
+
attr_reader :bui2
|
23
|
+
|
24
|
+
def initialize(*dbtyps)
|
25
|
+
@db_types_rgx = dbtyps.join('|')
|
26
|
+
@rgx = /\A\s*(?:#{@db_types_rgx})\s*\z/i
|
27
|
+
end
|
28
|
+
|
29
|
+
def edm_type(input)
|
30
|
+
@xedm_type = input
|
31
|
+
end
|
32
|
+
|
33
|
+
def with_one_param(lambda = nil, &proc)
|
34
|
+
proc1 = block_given? ? proc : lambda
|
35
|
+
@bui1 = Builder1Par.new(@db_types_rgx, proc1)
|
36
|
+
end
|
37
|
+
|
38
|
+
def with_two_params(lambda = nil, &proc)
|
39
|
+
proc2 = block_given? ? proc : lambda
|
40
|
+
@bui2 = Builder2Par.new(@db_types_rgx, proc2)
|
41
|
+
end
|
42
|
+
|
43
|
+
def json_value(lambda = nil, &proc)
|
44
|
+
@castfunc = block_given? ? proc : lambda
|
45
|
+
end
|
46
|
+
|
47
|
+
def match(curtyp)
|
48
|
+
if (@bui2 && (m = @bui2.match(curtyp)))
|
49
|
+
m
|
50
|
+
elsif (@bui1 && (m = @bui1.match(curtyp)))
|
51
|
+
m
|
52
|
+
elsif @rgx.match(curtyp)
|
53
|
+
type_mapping
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def type_mapping
|
58
|
+
# TODO perf; return always same object when called multiple times
|
59
|
+
FixedTypeMapping.new(self)
|
60
|
+
end
|
61
|
+
end # Builder
|
62
|
+
|
63
|
+
def self.builder(*dbtypnams, &proc)
|
64
|
+
builder = Builder.new(*dbtypnams)
|
65
|
+
builder.instance_eval(&proc)
|
66
|
+
builder
|
67
|
+
end
|
68
|
+
|
69
|
+
class Builder1Par < Builder
|
70
|
+
def initialize(db_ty_rgx, proc)
|
71
|
+
@db_types_rgx = db_ty_rgx
|
72
|
+
@proc = proc
|
73
|
+
@rgx = /\A\s*(?:#{@db_types_rgx})\s*\(\s*(\d+)\s*\)\z/i
|
74
|
+
end
|
75
|
+
|
76
|
+
def match(curtyp)
|
77
|
+
(@md = @rgx.match(curtyp)) ? type_mapping : nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def json_value(lambda = nil, &proc)
|
81
|
+
@castfunc = block_given? ? proc : lambda
|
82
|
+
end
|
83
|
+
|
84
|
+
# this is a bit advanced/obscure programming
|
85
|
+
# the intance_exec here is required to produce correct results
|
86
|
+
# ie. it produces concrete instances of edm_type and json_val lambda
|
87
|
+
# for the later, it is kind of currying but with the *implicit* parameters
|
88
|
+
# from the calling context eg par1
|
89
|
+
|
90
|
+
# probably this is not best-practice programing as we
|
91
|
+
# have a mutating object (the builder) that
|
92
|
+
# produces different lambdas after each type_mapping(mutation) calls
|
93
|
+
def type_mapping
|
94
|
+
p1val = @md[1]
|
95
|
+
instance_exec p1val, &@proc
|
96
|
+
|
97
|
+
TypeMapping1Par.new(self)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
class Builder2Par < Builder
|
101
|
+
def initialize(db_ty_rgx, proc)
|
102
|
+
@db_types_rgx = db_ty_rgx
|
103
|
+
@proc = proc
|
104
|
+
@rgx = /\A\s*(?:#{@db_types_rgx})\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)\s*\z/i
|
105
|
+
end
|
106
|
+
|
107
|
+
def match(curtyp)
|
108
|
+
(@md = @rgx.match(curtyp)) ? type_mapping : nil
|
109
|
+
end
|
110
|
+
|
111
|
+
# this is a bit advanced/obscure programming
|
112
|
+
# the intance_exec here is required to produce correct results
|
113
|
+
# ie. it produces concrete instances of edm_type and json_val lambda
|
114
|
+
# for the later, it is kind of currying but with the *implicit* parameters
|
115
|
+
# from the calling context eg par1 and par2
|
116
|
+
|
117
|
+
# probably this is not best-practice programing as we
|
118
|
+
# have a mutating object (the builder) that
|
119
|
+
# produces different lambdas after each type_mapping(mutation) calls
|
120
|
+
def type_mapping
|
121
|
+
p1val = @md[1]
|
122
|
+
p2val = @md[2]
|
123
|
+
instance_exec p1val, p2val, &@proc
|
124
|
+
TypeMapping2Par.new(self)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Fixed type (ie. without variable parts)
|
130
|
+
class FixedTypeMapping < TypeMapping
|
131
|
+
def initialize(builder)
|
132
|
+
@edm_type = builder.xedm_type
|
133
|
+
@castfunc = builder.castfunc
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class TypeMapping1Par < TypeMapping
|
138
|
+
def initialize(builder)
|
139
|
+
@edm_type = builder.xedm_type
|
140
|
+
@castfunc = builder.castfunc
|
141
|
+
end
|
142
|
+
end
|
143
|
+
class TypeMapping2Par < TypeMapping
|
144
|
+
def initialize(builder)
|
145
|
+
@edm_type = builder.xedm_type
|
146
|
+
@castfunc = builder.castfunc
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/safrano/version.rb
CHANGED
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.5.
|
4
|
+
version: 0.5.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- oz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -117,12 +117,14 @@ files:
|
|
117
117
|
- lib/core_ext/Dir/iter.rb
|
118
118
|
- lib/core_ext/Hash/transform.rb
|
119
119
|
- lib/core_ext/Integer/edm.rb
|
120
|
+
- lib/core_ext/Numeric/convert.rb
|
120
121
|
- lib/core_ext/REXML/Document/output.rb
|
121
122
|
- lib/core_ext/String/convert.rb
|
122
123
|
- lib/core_ext/String/edm.rb
|
123
124
|
- lib/core_ext/dir.rb
|
124
125
|
- lib/core_ext/hash.rb
|
125
126
|
- lib/core_ext/integer.rb
|
127
|
+
- lib/core_ext/numeric.rb
|
126
128
|
- lib/core_ext/rexml.rb
|
127
129
|
- lib/core_ext/string.rb
|
128
130
|
- lib/odata/attribute.rb
|
@@ -164,6 +166,7 @@ files:
|
|
164
166
|
- lib/safrano/response.rb
|
165
167
|
- lib/safrano/sequel_join_by_paths.rb
|
166
168
|
- lib/safrano/service.rb
|
169
|
+
- lib/safrano/type_mapping.rb
|
167
170
|
- lib/safrano/version.rb
|
168
171
|
- lib/sequel/plugins/join_by_paths.rb
|
169
172
|
homepage: https://gitlab.com/dm0da/safrano
|