safrano 0.4.4 → 0.5.2
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/odata/attribute.rb +2 -2
- data/lib/odata/collection.rb +15 -10
- data/lib/odata/collection_filter.rb +5 -5
- data/lib/odata/collection_media.rb +63 -25
- data/lib/odata/complex_type.rb +185 -43
- data/lib/odata/entity.rb +31 -8
- data/lib/odata/error.rb +28 -0
- data/lib/odata/filter/base.rb +5 -0
- data/lib/odata/filter/parse.rb +6 -0
- data/lib/odata/filter/sequel.rb +15 -0
- data/lib/odata/filter/token.rb +13 -10
- data/lib/odata/filter/tree.rb +4 -4
- data/lib/odata/function_import.rb +35 -34
- data/lib/odata/model_ext.rb +28 -5
- data/lib/odata/transition.rb +22 -2
- data/lib/odata/walker.rb +23 -5
- data/lib/safrano/contract.rb +7 -9
- data/lib/safrano/core.rb +12 -12
- data/lib/safrano/deprecation.rb +5 -5
- data/lib/safrano/multipart.rb +4 -4
- data/lib/safrano/rack_app.rb +1 -1
- data/lib/safrano/request.rb +3 -2
- data/lib/safrano/service.rb +38 -21
- data/lib/safrano/version.rb +1 -1
- metadata +9 -9
data/lib/odata/entity.rb
CHANGED
@@ -112,10 +112,11 @@ module Safrano
|
|
112
112
|
selvals
|
113
113
|
end
|
114
114
|
|
115
|
-
# post
|
116
|
-
|
117
|
-
|
118
|
-
|
115
|
+
# some clients wrongly expect post payload with the new entity in an array
|
116
|
+
# TODO quirks array mode !
|
117
|
+
def to_odata_array_json(request:)
|
118
|
+
innerj = request.service.get_coll_odata_h(array: [self],
|
119
|
+
template: self.class.default_template).to_json
|
119
120
|
"#{DJ_OPEN}#{innerj}#{DJ_CLOSE}"
|
120
121
|
end
|
121
122
|
|
@@ -124,7 +125,7 @@ module Safrano
|
|
124
125
|
end
|
125
126
|
|
126
127
|
def copy_request_infos(req)
|
127
|
-
@params = req.params
|
128
|
+
@params = @inactive_query_params ? EMPTY_HASH : req.params
|
128
129
|
@do_links = req.walker.do_links
|
129
130
|
@uparms = UrlParameters4Single.new(self, @params)
|
130
131
|
end
|
@@ -150,7 +151,11 @@ module Safrano
|
|
150
151
|
@uparms.check_all.tap_valid { return odata_get_output(req) }
|
151
152
|
.tap_error { |e| return e.odata_get(req) }
|
152
153
|
end
|
153
|
-
|
154
|
+
def inactive_query_params
|
155
|
+
@inactive_query_params = true
|
156
|
+
self # chaining
|
157
|
+
end
|
158
|
+
|
154
159
|
DELETE_REL_AND_ENTY = lambda do |entity, assoc, parent|
|
155
160
|
Safrano.remove_nav_relation(assoc, parent)
|
156
161
|
entity.destroy(transaction: false)
|
@@ -263,6 +268,7 @@ module Safrano
|
|
263
268
|
ret
|
264
269
|
end
|
265
270
|
end
|
271
|
+
|
266
272
|
# end of module SafranoEntity
|
267
273
|
module Entity
|
268
274
|
include EntityBase
|
@@ -438,5 +444,22 @@ module Safrano
|
|
438
444
|
pk_hash.values
|
439
445
|
end
|
440
446
|
end
|
441
|
-
|
442
|
-
|
447
|
+
|
448
|
+
module EntityCreateStandardOutput
|
449
|
+
# Json formatter for a create entity POST call / Standard version; return as json object
|
450
|
+
def to_odata_create_json(request:)
|
451
|
+
# TODO Perf: reduce method call overhead
|
452
|
+
# we added this redirection for readability and flexibility
|
453
|
+
to_odata_json(request: request)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
module EntityCreateArrayOutput
|
458
|
+
# Json formatter for a create entity POST call Array version
|
459
|
+
def to_odata_create_json(request:)
|
460
|
+
# TODO Perf: reduce method call overhead
|
461
|
+
# we added this redirection for readability and flexibility
|
462
|
+
to_odata_array_json(request: request)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end # end of Module OData
|
data/lib/odata/error.rb
CHANGED
@@ -37,6 +37,15 @@ module Safrano
|
|
37
37
|
super(msg, symbname)
|
38
38
|
end
|
39
39
|
end
|
40
|
+
|
41
|
+
# duplicate attribute name
|
42
|
+
class ModelDuplicateAttributeError < NameError
|
43
|
+
def initialize(klass, symb)
|
44
|
+
symbname = symb.to_s
|
45
|
+
msg = "There is already an attribute :#{symbname} defined in class #{klass}"
|
46
|
+
super(msg, symbname)
|
47
|
+
end
|
48
|
+
end
|
40
49
|
end
|
41
50
|
|
42
51
|
# base module for HTTP errors, when used as a Error Class
|
@@ -105,6 +114,18 @@ module Safrano
|
|
105
114
|
end
|
106
115
|
end
|
107
116
|
|
117
|
+
# http Unprocessable Entity for example when trying to
|
118
|
+
# upload duplicated media ressource
|
119
|
+
class UnprocessableEntityError
|
120
|
+
extend ErrorClass
|
121
|
+
include ErrorInstance
|
122
|
+
HTTP_CODE = 422
|
123
|
+
@msg = 'Unprocessable Entity'
|
124
|
+
def initialize(reason)
|
125
|
+
@msg = reason
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
108
129
|
# http Bad Req.
|
109
130
|
class BadRequestError
|
110
131
|
extend ErrorClass
|
@@ -116,6 +137,13 @@ module Safrano
|
|
116
137
|
end
|
117
138
|
end
|
118
139
|
|
140
|
+
# for upload empty media
|
141
|
+
class BadRequestEmptyMediaUpload < BadRequestError
|
142
|
+
include ErrorInstance
|
143
|
+
def initialize(path)
|
144
|
+
@msg = "Bad Request: empty media file #{path}"
|
145
|
+
end
|
146
|
+
end
|
119
147
|
# Generic failed changeset
|
120
148
|
class BadRequestFailedChangeSet < BadRequestError
|
121
149
|
@msg = 'Bad Request: Failed changeset '
|
data/lib/odata/filter/base.rb
CHANGED
@@ -57,6 +57,11 @@ module Safrano
|
|
57
57
|
class Literal < Leave
|
58
58
|
end
|
59
59
|
|
60
|
+
# Null Literal is unquoted null word
|
61
|
+
class NullLiteral < Literal
|
62
|
+
LEUQES = nil
|
63
|
+
end
|
64
|
+
|
60
65
|
# Qualit (qualified lits) are words separated by /
|
61
66
|
# path/path/path/attrib
|
62
67
|
class Qualit < Literal
|
data/lib/odata/filter/parse.rb
CHANGED
@@ -151,6 +151,12 @@ module Safrano
|
|
151
151
|
grow_at_cursor(Literal.new(tok))
|
152
152
|
end
|
153
153
|
|
154
|
+
when :NullLiteral
|
155
|
+
with_accepted(tok, typ) do
|
156
|
+
@cursor.update_state(tok, typ)
|
157
|
+
grow_at_cursor(NullLiteral.new(tok))
|
158
|
+
end
|
159
|
+
|
154
160
|
when :Qualit
|
155
161
|
with_accepted(tok, typ) do
|
156
162
|
@cursor.update_state(tok, typ)
|
data/lib/odata/filter/sequel.rb
CHANGED
@@ -183,6 +183,13 @@ module Safrano
|
|
183
183
|
end
|
184
184
|
Contract.collect_result!(@children[0].leuqes(jh),
|
185
185
|
@children[1].leuqes(jh)) do |c0, c1|
|
186
|
+
if c1 == NullLiteral::LEUQES
|
187
|
+
if @value == :eq
|
188
|
+
leuqes_op = :IS
|
189
|
+
elsif @value == :ne
|
190
|
+
leuqes_op = :'IS NOT'
|
191
|
+
end
|
192
|
+
end
|
186
193
|
Sequel::SQL::BooleanExpression.new(leuqes_op, c0, c1)
|
187
194
|
end
|
188
195
|
end
|
@@ -262,6 +269,14 @@ module Safrano
|
|
262
269
|
end
|
263
270
|
end
|
264
271
|
|
272
|
+
# Null
|
273
|
+
class NullLiteral
|
274
|
+
def leuqes(jh)
|
275
|
+
# Sequel's representation of NULL
|
276
|
+
success LEUQES
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
265
280
|
# Qualit (qualified lits) are words separated by /
|
266
281
|
class Qualit
|
267
282
|
def leuqes(jh)
|
data/lib/odata/filter/token.rb
CHANGED
@@ -10,13 +10,14 @@ module Safrano
|
|
10
10
|
day hour minute month second year
|
11
11
|
round floor ceiling].freeze
|
12
12
|
FUNCRGX = FUNCNAMES.join('|')
|
13
|
+
NULLRGX = 'null|NULL|Null'
|
13
14
|
QSTRINGRGX = /'((?:[^']|(?:'{2}))*)'/.freeze
|
14
15
|
BINOBOOL = '[eE][qQ]|[LlgGNn][eETt]|[aA][nN][dD]|[oO][rR]'
|
15
16
|
BINOARITHM = '[aA][dD][dD]|[sS][uU][bB]|[mM][uU][lL]|[dD][iI][vV]|[mM][oO][dD]'
|
16
|
-
NOTRGX = 'not|NOT'
|
17
|
+
NOTRGX = 'not|NOT|Not'
|
17
18
|
FPRGX = '\d+(?:\.\d+)?(?:e[+-]?\d+)?'
|
18
19
|
QUALITRGX = '\w+(?:\/\w+)+'
|
19
|
-
RGX = /(#{FUNCRGX})|([\(\),])|(#{BINOBOOL})\s+|(#{BINOARITHM})|(#{NOTRGX})|#{QSTRINGRGX}|(#{FPRGX})|(#{QUALITRGX})|(\w+)|(')/.freeze
|
20
|
+
RGX = /(#{FUNCRGX})|(#{NULLRGX})|([\(\),])|(#{BINOBOOL})\s+|(#{BINOARITHM})|(#{NOTRGX})|#{QSTRINGRGX}|(#{FPRGX})|(#{QUALITRGX})|(\w+)|(')/.freeze
|
20
21
|
|
21
22
|
def each_typed_token(inp)
|
22
23
|
typ = nil
|
@@ -34,27 +35,29 @@ module Safrano
|
|
34
35
|
when 0
|
35
36
|
:FuncTree
|
36
37
|
when 1
|
38
|
+
:NullLiteral
|
39
|
+
when 2
|
37
40
|
case found
|
38
41
|
when '(', ')'
|
39
42
|
:Delimiter
|
40
43
|
when ','
|
41
44
|
:Separator
|
42
45
|
end
|
43
|
-
when 2
|
44
|
-
:BinopBool
|
45
46
|
when 3
|
46
|
-
:
|
47
|
+
:BinopBool
|
47
48
|
when 4
|
48
|
-
:
|
49
|
+
:BinopArithm
|
49
50
|
when 5
|
50
|
-
:
|
51
|
+
:UnopTree
|
51
52
|
when 6
|
52
|
-
:
|
53
|
+
:QString
|
53
54
|
when 7
|
54
|
-
:
|
55
|
+
:FPNumber
|
55
56
|
when 8
|
56
|
-
:
|
57
|
+
:Qualit
|
57
58
|
when 9
|
59
|
+
:Literal
|
60
|
+
when 10
|
58
61
|
:unmatchedQuote
|
59
62
|
end
|
60
63
|
yield found, typ
|
data/lib/odata/filter/tree.rb
CHANGED
@@ -65,7 +65,7 @@ module Safrano
|
|
65
65
|
# nil is considered as accepted, otherwise non-nil=the error
|
66
66
|
def accept?(tok, typ)
|
67
67
|
case typ
|
68
|
-
when :Literal, :Qualit, :QString, :FuncTree, :ArgTree,
|
68
|
+
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :ArgTree,
|
69
69
|
:UnopTree, :FPNumber
|
70
70
|
nil
|
71
71
|
when :Delimiter
|
@@ -232,7 +232,7 @@ module Safrano
|
|
232
232
|
|
233
233
|
def update_state(_tok, typ)
|
234
234
|
case typ
|
235
|
-
when :Literal, :Qualit, :QString, :FuncTree, :BinopBool, :BinopArithm, :UnopTree, :FPNumber
|
235
|
+
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :BinopBool, :BinopArithm, :UnopTree, :FPNumber
|
236
236
|
@state = :closed
|
237
237
|
end
|
238
238
|
end
|
@@ -297,7 +297,7 @@ module Safrano
|
|
297
297
|
@state = :closed
|
298
298
|
when :Separator
|
299
299
|
@state = :sep
|
300
|
-
when :Literal, :Qualit, :QString, :FuncTree, :FPNumber
|
300
|
+
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :FPNumber
|
301
301
|
@state = :val
|
302
302
|
end
|
303
303
|
end
|
@@ -327,7 +327,7 @@ module Safrano
|
|
327
327
|
elsif @state == :sep
|
328
328
|
Parser::ErrorInvalidToken.new(tok, typ, self)
|
329
329
|
end
|
330
|
-
when :Literal, :Qualit, :QString, :FuncTree, :FPNumber
|
330
|
+
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :FPNumber
|
331
331
|
if (@state == :open) || (@state == :sep)
|
332
332
|
if @parent.arity_full?(@children.size)
|
333
333
|
Parser::ErrorInvalidArity.new(tok, typ, self)
|
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
require_relative 'complex_type'
|
4
4
|
require_relative 'edm/primitive_types'
|
5
|
+
require_relative 'transition'
|
6
|
+
|
5
7
|
module Safrano
|
6
|
-
def
|
8
|
+
def self.FunctionImport(name)
|
7
9
|
FunctionImport::Function.new(name)
|
8
10
|
end
|
9
11
|
|
@@ -19,12 +21,12 @@ module Safrano
|
|
19
21
|
end
|
20
22
|
|
21
23
|
def allowed_transitions
|
22
|
-
[Safrano::
|
24
|
+
[Safrano::TransitionExecuteFunc]
|
23
25
|
end
|
24
26
|
|
25
27
|
def input(**parmtypes)
|
26
28
|
@input = {}
|
27
|
-
parmtypes.each
|
29
|
+
parmtypes.each do |k, t|
|
28
30
|
@input[k] = case t.name
|
29
31
|
when 'Integer'
|
30
32
|
Safrano::Edm::Edm::Int32
|
@@ -37,10 +39,16 @@ module Safrano
|
|
37
39
|
else
|
38
40
|
t
|
39
41
|
end
|
40
|
-
|
42
|
+
end
|
41
43
|
self
|
42
44
|
end
|
43
|
-
|
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
|
+
|
44
52
|
def return(klassmod, &proc)
|
45
53
|
raise('Please provide a code block') unless block_given?
|
46
54
|
|
@@ -49,7 +57,7 @@ module Safrano
|
|
49
57
|
else
|
50
58
|
# if it's neither a ComplexType nor a Model-Entity
|
51
59
|
# --> assume it is a Primitive
|
52
|
-
|
60
|
+
ResultDefinition.asPrimitiveType(klassmod)
|
53
61
|
end
|
54
62
|
@proc = proc
|
55
63
|
self
|
@@ -63,7 +71,8 @@ module Safrano
|
|
63
71
|
else
|
64
72
|
# if it's neither a ComplexType nor a Modle-Entity
|
65
73
|
# --> assume it is a Primitive
|
66
|
-
ResultAsPrimitiveTypeColl.new(klassmod)
|
74
|
+
# ResultAsPrimitiveTypeColl.new(klassmod)
|
75
|
+
ResultDefinition.asPrimitiveTypeColl(klassmod)
|
67
76
|
end
|
68
77
|
@proc = proc
|
69
78
|
self
|
@@ -91,20 +100,20 @@ module Safrano
|
|
91
100
|
return nil unless @input # anything to check ?
|
92
101
|
|
93
102
|
# do we have all parameters provided ?
|
94
|
-
check_missing_params.tap_error
|
103
|
+
check_missing_params.tap_error { |error| return error }
|
95
104
|
# ==> all params were provided
|
96
105
|
|
97
106
|
# now we shall check the content and type of the parameters
|
98
|
-
@input.each
|
107
|
+
@input.each do |ksym, typ|
|
99
108
|
typ.convert_from_urlparam(v = @params[ksym.to_s])
|
100
109
|
.tap_valid do |retval|
|
101
110
|
@funcparams[ksym] = retval
|
102
111
|
end
|
103
|
-
.tap_error do
|
112
|
+
.tap_error do
|
104
113
|
# return is really needed here, or we end up returning nil below
|
105
114
|
return parameter_convertion_error(ksym, typ, v)
|
106
115
|
end
|
107
|
-
|
116
|
+
end
|
108
117
|
nil
|
109
118
|
end
|
110
119
|
|
@@ -125,41 +134,33 @@ module Safrano
|
|
125
134
|
# EntitySet= @entity_set ,
|
126
135
|
'ReturnType' => @returning.type_metadata,
|
127
136
|
'm:HttpMethod' => @http_method)
|
128
|
-
@input.each
|
137
|
+
@input.each do |iname, type|
|
129
138
|
funky.add_element('Parameter',
|
130
139
|
'Name' => iname.to_s,
|
131
140
|
'Type' => type.type_name,
|
132
141
|
'Mode' => 'In')
|
133
|
-
|
142
|
+
end if @input
|
134
143
|
funky
|
135
144
|
end
|
136
|
-
|
137
|
-
def
|
145
|
+
|
146
|
+
def with_transition_validated(req)
|
138
147
|
# initialize_params
|
148
|
+
@params = req.params
|
139
149
|
return yield unless (@error = check_url_func_params)
|
140
150
|
|
141
|
-
@error
|
151
|
+
[nil, :error, @error] if @error
|
142
152
|
end
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
def odata_get_output(req)
|
150
|
-
[200, EMPTY_HASH, [to_odata_json(req)]]
|
151
|
-
end
|
152
|
-
|
153
|
-
def odata_get(req)
|
154
|
-
@params = req.params
|
155
|
-
|
156
|
-
with_validated_get(req) do
|
157
|
-
odata_get_output(req)
|
153
|
+
|
154
|
+
|
155
|
+
def do_execute_func(req)
|
156
|
+
with_transition_validated(req) do
|
157
|
+
result = @proc.call(**@funcparams)
|
158
|
+
[@returning.do_execute_func_result(result, req, @auto_query_params), :run]
|
158
159
|
end
|
159
160
|
end
|
160
|
-
|
161
|
-
def
|
162
|
-
[
|
161
|
+
|
162
|
+
def transition_execute_func(_match_result)
|
163
|
+
[self, :run_with_execute_func]
|
163
164
|
end
|
164
165
|
end
|
165
166
|
end
|
data/lib/odata/model_ext.rb
CHANGED
@@ -81,11 +81,11 @@ module Safrano
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def return_as_collection_descriptor
|
84
|
-
Safrano::FunctionImport::
|
84
|
+
Safrano::FunctionImport::ResultDefinition.asEntityColl(self)
|
85
85
|
end
|
86
86
|
|
87
87
|
def return_as_instance_descriptor
|
88
|
-
Safrano::FunctionImport::
|
88
|
+
Safrano::FunctionImport::ResultDefinition.asEntity(self)
|
89
89
|
end
|
90
90
|
|
91
91
|
def execute_deferred_iblock
|
@@ -94,8 +94,10 @@ module Safrano
|
|
94
94
|
|
95
95
|
# Factory json-> Model Object instance
|
96
96
|
def new_from_hson_h(hash)
|
97
|
-
enty = new
|
98
|
-
enty.set_fields(hash, data_fields, missing: :skip)
|
97
|
+
#enty = new
|
98
|
+
#enty.set_fields(hash, data_fields, missing: :skip)
|
99
|
+
enty = create(hash)
|
100
|
+
#enty.set(hash)
|
99
101
|
enty
|
100
102
|
end
|
101
103
|
|
@@ -299,6 +301,14 @@ module Safrano
|
|
299
301
|
|
300
302
|
attr_class = assoc[:class_name].constantize
|
301
303
|
lattr_name_str = (attr_name_str || assoc_symb.to_s)
|
304
|
+
|
305
|
+
# check duplicate attributes names
|
306
|
+
raise Safrano::API::ModelDuplicateAttributeError.new(self, lattr_name_str) if @columns.include? lattr_name_str.to_sym
|
307
|
+
|
308
|
+
if @nav_entity_attribs_keys
|
309
|
+
raise Safrano::API::ModelDuplicateAttributeError.new(self, lattr_name_str) if @nav_entity_attribs_keys.include? lattr_name_str
|
310
|
+
end
|
311
|
+
|
302
312
|
@nav_collection_attribs[lattr_name_str] = attr_class
|
303
313
|
@nav_collection_attribs_keys << lattr_name_str
|
304
314
|
@nav_collection_url_regexp = @nav_collection_attribs_keys.join('|')
|
@@ -317,6 +327,14 @@ module Safrano
|
|
317
327
|
|
318
328
|
attr_class = assoc[:class_name].constantize
|
319
329
|
lattr_name_str = (attr_name_str || assoc_symb.to_s)
|
330
|
+
|
331
|
+
# check duplicate attributes names
|
332
|
+
raise Safrano::API::ModelDuplicateAttributeError.new(self, lattr_name_str) if @columns.include? lattr_name_str.to_sym
|
333
|
+
|
334
|
+
if @nav_collection_attribs_keys
|
335
|
+
raise Safrano::API::ModelDuplicateAttributeError.new(self, lattr_name_str) if @nav_collection_attribs_keys.include? lattr_name_str
|
336
|
+
end
|
337
|
+
|
320
338
|
@nav_entity_attribs[lattr_name_str] = attr_class
|
321
339
|
@nav_entity_attribs_keys << lattr_name_str
|
322
340
|
@nav_entity_url_regexp = @nav_entity_attribs_keys.join('|')
|
@@ -352,6 +370,9 @@ module Safrano
|
|
352
370
|
|
353
371
|
build_allowed_transitions
|
354
372
|
build_entity_allowed_transitions
|
373
|
+
|
374
|
+
# for media
|
375
|
+
finalize_media if self.respond_to? :finalize_media
|
355
376
|
end
|
356
377
|
|
357
378
|
KEYPRED_URL_REGEXP = /\A\(\s*'?([\w=,'\s]+)'?\s*\)(.*)/.freeze
|
@@ -608,7 +629,9 @@ module Safrano
|
|
608
629
|
req.register_content_id_ref(new_entity)
|
609
630
|
new_entity.copy_request_infos(req)
|
610
631
|
# json is default content type so we dont need to specify it here again
|
611
|
-
|
632
|
+
# TODO quirks array mode !
|
633
|
+
# [201, EMPTY_HASH, new_entity.to_odata_post_json(service: req.service)]
|
634
|
+
[201, EMPTY_HASH, new_entity.to_odata_create_json(request: req)]
|
612
635
|
else # TODO: other formats
|
613
636
|
415
|
614
637
|
end
|