safrano 0.4.4 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
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