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.
data/lib/odata/entity.rb CHANGED
@@ -112,10 +112,11 @@ module Safrano
112
112
  selvals
113
113
  end
114
114
 
115
- # post paylod expects the new entity in an array
116
- def to_odata_post_json(service:)
117
- innerj = service.get_coll_odata_h(array: [self],
118
- template: self.class.default_template).to_json
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
- end
442
- # end of Module OData
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 '
@@ -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
@@ -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)
@@ -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)
@@ -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
- :BinopArithm
47
+ :BinopBool
47
48
  when 4
48
- :UnopTree
49
+ :BinopArithm
49
50
  when 5
50
- :QString
51
+ :UnopTree
51
52
  when 6
52
- :FPNumber
53
+ :QString
53
54
  when 7
54
- :Qualit
55
+ :FPNumber
55
56
  when 8
56
- :Literal
57
+ :Qualit
57
58
  when 9
59
+ :Literal
60
+ when 10
58
61
  :unmatchedQuote
59
62
  end
60
63
  yield found, typ
@@ -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 Safrano.FunctionImport(name)
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::TransitionEnd]
24
+ [Safrano::TransitionExecuteFunc]
23
25
  end
24
26
 
25
27
  def input(**parmtypes)
26
28
  @input = {}
27
- parmtypes.each { |k, t|
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
- ResultAsPrimitiveType.new(klassmod)
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 do |error| return error end
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 { |ksym, typ|
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 |error|
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 { |iname, type|
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
- } if @input
142
+ end if @input
134
143
  funky
135
144
  end
136
-
137
- def with_validated_get(req)
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.odata_get(req) if @error
151
+ [nil, :error, @error] if @error
142
152
  end
143
-
144
- def to_odata_json(req)
145
- result = @proc.call(**@funcparams)
146
- @returning.to_odata_json(result, req)
147
- end
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 transition_end(_match_result)
162
- [nil, :end]
161
+
162
+ def transition_execute_func(_match_result)
163
+ [self, :run_with_execute_func]
163
164
  end
164
165
  end
165
166
  end
@@ -81,11 +81,11 @@ module Safrano
81
81
  end
82
82
 
83
83
  def return_as_collection_descriptor
84
- Safrano::FunctionImport::ResultAsEntityColl.new(self)
84
+ Safrano::FunctionImport::ResultDefinition.asEntityColl(self)
85
85
  end
86
86
 
87
87
  def return_as_instance_descriptor
88
- Safrano::FunctionImport::ResultAsEntity.new(self)
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
- [201, EMPTY_HASH, new_entity.to_odata_post_json(service: req.service)]
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