safrano 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/core_ext/Date/format.rb +47 -0
  3. data/lib/core_ext/DateTime/format.rb +54 -0
  4. data/lib/core_ext/Dir/iter.rb +7 -5
  5. data/lib/core_ext/Hash/transform.rb +14 -6
  6. data/lib/core_ext/Numeric/convert.rb +25 -0
  7. data/lib/core_ext/Time/format.rb +71 -0
  8. data/lib/core_ext/date.rb +5 -0
  9. data/lib/core_ext/datetime.rb +5 -0
  10. data/lib/core_ext/numeric.rb +3 -0
  11. data/lib/core_ext/time.rb +5 -0
  12. data/lib/odata/attribute.rb +8 -6
  13. data/lib/odata/batch.rb +3 -3
  14. data/lib/odata/collection.rb +9 -9
  15. data/lib/odata/collection_filter.rb +3 -1
  16. data/lib/odata/collection_media.rb +4 -27
  17. data/lib/odata/collection_order.rb +1 -1
  18. data/lib/odata/common_logger.rb +5 -27
  19. data/lib/odata/complex_type.rb +61 -67
  20. data/lib/odata/edm/primitive_types.rb +110 -42
  21. data/lib/odata/entity.rb +14 -47
  22. data/lib/odata/error.rb +7 -7
  23. data/lib/odata/expand.rb +2 -2
  24. data/lib/odata/filter/base.rb +10 -1
  25. data/lib/odata/filter/error.rb +2 -2
  26. data/lib/odata/filter/parse.rb +16 -2
  27. data/lib/odata/filter/sequel.rb +31 -4
  28. data/lib/odata/filter/sequel_datetime_adapter.rb +21 -0
  29. data/lib/odata/filter/token.rb +18 -5
  30. data/lib/odata/filter/tree.rb +83 -9
  31. data/lib/odata/function_import.rb +19 -18
  32. data/lib/odata/model_ext.rb +96 -38
  33. data/lib/odata/request/json.rb +171 -0
  34. data/lib/odata/transition.rb +13 -9
  35. data/lib/odata/url_parameters.rb +3 -3
  36. data/lib/odata/walker.rb +9 -9
  37. data/lib/safrano/multipart.rb +1 -3
  38. data/lib/safrano/rack_app.rb +2 -14
  39. data/lib/safrano/rack_builder.rb +0 -15
  40. data/lib/safrano/request.rb +3 -3
  41. data/lib/safrano/response.rb +3 -3
  42. data/lib/safrano/service.rb +43 -12
  43. data/lib/safrano/type_mapping.rb +149 -0
  44. data/lib/safrano/version.rb +1 -2
  45. data/lib/safrano.rb +3 -0
  46. metadata +54 -15
@@ -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'
@@ -138,6 +139,7 @@ module Safrano
138
139
  attr_accessor :complex_types
139
140
  attr_accessor :function_imports
140
141
  attr_accessor :function_import_keys
142
+ attr_accessor :type_mappings
141
143
 
142
144
  # Instance attributes for specialized Version specific Instances
143
145
  attr_accessor :v1
@@ -158,6 +160,9 @@ module Safrano
158
160
  @function_imports = {}
159
161
  @function_import_keys = []
160
162
  @cmap = {}
163
+ @type_mappings = {}
164
+ # enabled per default starting from 0.6
165
+ @bugfix_create_response = true
161
166
  instance_eval(&block) if block_given?
162
167
  end
163
168
 
@@ -206,9 +211,8 @@ module Safrano
206
211
  (@v2.xserver_url = @xserver_url) if @v2
207
212
  end
208
213
 
209
- # keep the bug active for now, but allow to activate the fix,
210
- # later we will change the default to be fixed
211
- def bugfix_create_response(bool = false)
214
+ # keep the bug active for now, but allow to de-activate the fix
215
+ def bugfix_create_response(bool)
212
216
  @bugfix_create_response = bool
213
217
  end
214
218
 
@@ -242,6 +246,8 @@ module Safrano
242
246
  other.complex_types = @complex_types
243
247
  other.function_imports = @function_imports
244
248
  other.function_import_keys = @function_import_keys
249
+ other.type_mappings = @type_mappings
250
+ other.bugfix_create_response(@bugfix_create_response)
245
251
  other
246
252
  end
247
253
 
@@ -330,6 +336,11 @@ module Safrano
330
336
  funcimp
331
337
  end
332
338
 
339
+ def with_db_type(*dbtypnams, &proc)
340
+ m = TypeMapping.builder(*dbtypnams, &proc)
341
+ @type_mappings[m.db_types_rgx] = m
342
+ end
343
+
333
344
  def cmap=(imap)
334
345
  @cmap = imap
335
346
  set_collections_sorted(@cmap.values)
@@ -343,10 +354,10 @@ module Safrano
343
354
  @collections.sort_by! { |klass| klass.entity_set_name.size }.reverse! if @collections
344
355
  @collections
345
356
  end
346
-
357
+
347
358
  # need to be sorted by size too
348
359
  def set_funcimports_sorted
349
- @function_import_keys.sort_by!{|k| k.size}.reverse!
360
+ @function_import_keys.sort_by! { |k| k.size }.reverse!
350
361
  end
351
362
  # to be called at end of publishing block to ensure we get the right names
352
363
  # and additionally build the list of valid attribute path's used
@@ -370,16 +381,30 @@ module Safrano
370
381
 
371
382
  set_uribase
372
383
 
373
- @collections.each(&:finalize_publishing)
374
-
375
384
  # finalize the uri's and include NoMappingBeforeOutput or MappingBeforeOutput as needed
376
385
  @collections.each do |klass|
386
+ klass.finalize_publishing(self)
387
+
377
388
  klass.build_uri(@uribase)
378
- # TODO perf
379
- klass.include( (klass.time_cols.empty? && klass.decimal_cols.empty?) ? Safrano::NoMappingBeforeOutput : Safrano::MappingBeforeOutput)
380
389
 
381
390
  # Output create (POST) as single entity (Standard) or as array (non-standard buggy)
382
- klass.include ( @bugfix_create_response ? Safrano::EntityCreateStandardOutput : Safrano::EntityCreateArrayOutput)
391
+ klass.include(@bugfix_create_response ? Safrano::EntityCreateStandardOutput : Safrano::EntityCreateArrayOutput)
392
+
393
+ # define the most optimal casted_values method for the given model(klass)
394
+ if klass.casted_cols.empty?
395
+ klass.send(:define_method, :casted_values) do |cols = nil|
396
+ cols ? selected_values_for_odata(cols) : values_for_odata
397
+ end
398
+ else
399
+ klass.send(:define_method, :casted_values) do |cols = nil|
400
+ # we need to dup the model values as we need to change it before passing to_json,
401
+ # but we dont want to interfere with Sequel's owned data
402
+ # (eg because then in worst case it could happen that we write back changed values to DB)
403
+ vals = cols ? selected_values_for_odata(cols) : values_for_odata.dup
404
+ self.class.casted_cols.each { |cc, lambda| vals[cc] = lambda.call(vals[cc]) if vals.key?(cc) }
405
+ vals
406
+ end
407
+ end
383
408
  end
384
409
 
385
410
  # build allowed transitions (requires that @collections are filled and sorted for having a
@@ -390,10 +415,16 @@ module Safrano
390
415
  case Sequel::Model.db.adapter_scheme
391
416
  when :postgres
392
417
  Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreePostgres
418
+ Safrano::Filter::DateTimeLit.include Safrano::Filter::DateTimeDefault
419
+ Safrano::Filter::DateTimeOffsetLit.include Safrano::Filter::DateTimeDefault
393
420
  when :sqlite
394
421
  Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreeSqlite
422
+ Safrano::Filter::DateTimeLit.include Safrano::Filter::DateTimeSqlite
423
+ Safrano::Filter::DateTimeOffsetLit.include Safrano::Filter::DateTimeSqlite
395
424
  else
396
425
  Safrano::Filter::FuncTree.include Safrano::Filter::FuncTreeDefault
426
+ Safrano::Filter::DateTimeLit.include Safrano::Filter::DateTimeDefault
427
+ Safrano::Filter::DateTimeOffsetLit.include Safrano::Filter::DateTimeDefault
397
428
  end
398
429
  end
399
430
 
@@ -505,8 +536,8 @@ module Safrano
505
536
  doc.add_element('edmx:Edmx', 'Version' => '1.0')
506
537
  doc.root.add_namespace('xmlns:edmx', XMLNS::MSFT_ADO_2007_EDMX)
507
538
  serv = doc.root.add_element('edmx:DataServices',
508
- # TODO: export the real version (result from version negotions)
509
- # but currently we support only v1 and v2, and most users will use v2
539
+ # TODO: export the real version (result from version negotions)
540
+ # but currently we support only v1 and v2, and most users will use v2
510
541
  'm:DataServiceVersion' => '2.0')
511
542
  # 'm:DataServiceVersion' => "#{self.dataServiceVersion}" )
512
543
  # DataServiceVersion: This attribute MUST be in the data service
@@ -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
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
-
3
2
  module Safrano
4
- VERSION = '0.5.3'
3
+ VERSION = '0.6.0'
5
4
  end
data/lib/safrano.rb CHANGED
@@ -14,9 +14,12 @@ require_relative 'odata/entity'
14
14
  require_relative 'odata/attribute'
15
15
  require_relative 'odata/navigation_attribute'
16
16
  require_relative 'odata/model_ext'
17
+ require_relative 'odata/request/json'
17
18
  require_relative 'safrano/service'
18
19
  require_relative 'odata/walker'
19
20
  require 'sequel'
20
21
  require_relative 'safrano/sequel_join_by_paths'
21
22
  require_relative 'safrano/rack_app'
22
23
  require_relative 'safrano/rack_builder'
24
+
25
+ Sequel.extension :named_timezones
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.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - oz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-10 00:00:00.000000000 Z
11
+ date: 2022-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rfc2047
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.3'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: sequel
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -53,33 +67,33 @@ dependencies:
53
67
  - !ruby/object:Gem::Version
54
68
  version: '5.15'
55
69
  - !ruby/object:Gem::Dependency
56
- name: rfc2047
70
+ name: tzinfo
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
- - - "~>"
73
+ - - ">="
60
74
  - !ruby/object:Gem::Version
61
- version: '0.3'
75
+ version: '0'
62
76
  type: :runtime
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - "~>"
80
+ - - ">="
67
81
  - !ruby/object:Gem::Version
68
- version: '0.3'
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
- name: rake
84
+ name: tzinfo-data
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - "~>"
87
+ - - ">="
74
88
  - !ruby/object:Gem::Version
75
- version: '12.3'
76
- type: :development
89
+ version: '0'
90
+ type: :runtime
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - "~>"
94
+ - - ">="
81
95
  - !ruby/object:Gem::Version
82
- version: '12.3'
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rack-test
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +108,20 @@ dependencies:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '12.3'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '12.3'
97
125
  - !ruby/object:Gem::Dependency
98
126
  name: rubocop
99
127
  requirement: !ruby/object:Gem::Requirement
@@ -108,23 +136,31 @@ dependencies:
108
136
  - - "~>"
109
137
  - !ruby/object:Gem::Version
110
138
  version: '0.51'
111
- description: Safrano is an OData server library based on Ruby Sequel and Rack.
139
+ description: 'Safrano is an OData server library '
112
140
  email: dev@aithscel.eu
113
141
  executables: []
114
142
  extensions: []
115
143
  extra_rdoc_files: []
116
144
  files:
145
+ - lib/core_ext/Date/format.rb
146
+ - lib/core_ext/DateTime/format.rb
117
147
  - lib/core_ext/Dir/iter.rb
118
148
  - lib/core_ext/Hash/transform.rb
119
149
  - lib/core_ext/Integer/edm.rb
150
+ - lib/core_ext/Numeric/convert.rb
120
151
  - lib/core_ext/REXML/Document/output.rb
121
152
  - lib/core_ext/String/convert.rb
122
153
  - lib/core_ext/String/edm.rb
154
+ - lib/core_ext/Time/format.rb
155
+ - lib/core_ext/date.rb
156
+ - lib/core_ext/datetime.rb
123
157
  - lib/core_ext/dir.rb
124
158
  - lib/core_ext/hash.rb
125
159
  - lib/core_ext/integer.rb
160
+ - lib/core_ext/numeric.rb
126
161
  - lib/core_ext/rexml.rb
127
162
  - lib/core_ext/string.rb
163
+ - lib/core_ext/time.rb
128
164
  - lib/odata/attribute.rb
129
165
  - lib/odata/batch.rb
130
166
  - lib/odata/collection.rb
@@ -141,6 +177,7 @@ files:
141
177
  - lib/odata/filter/error.rb
142
178
  - lib/odata/filter/parse.rb
143
179
  - lib/odata/filter/sequel.rb
180
+ - lib/odata/filter/sequel_datetime_adapter.rb
144
181
  - lib/odata/filter/sequel_function_adapter.rb
145
182
  - lib/odata/filter/token.rb
146
183
  - lib/odata/filter/tree.rb
@@ -148,6 +185,7 @@ files:
148
185
  - lib/odata/model_ext.rb
149
186
  - lib/odata/navigation_attribute.rb
150
187
  - lib/odata/relations.rb
188
+ - lib/odata/request/json.rb
151
189
  - lib/odata/select.rb
152
190
  - lib/odata/transition.rb
153
191
  - lib/odata/url_parameters.rb
@@ -164,6 +202,7 @@ files:
164
202
  - lib/safrano/response.rb
165
203
  - lib/safrano/sequel_join_by_paths.rb
166
204
  - lib/safrano/service.rb
205
+ - lib/safrano/type_mapping.rb
167
206
  - lib/safrano/version.rb
168
207
  - lib/sequel/plugins/join_by_paths.rb
169
208
  homepage: https://gitlab.com/dm0da/safrano
@@ -192,5 +231,5 @@ requirements: []
192
231
  rubygems_version: 3.2.5
193
232
  signing_key:
194
233
  specification_version: 4
195
- summary: Safrano is an OData server library based on Ruby Sequel and Rack
234
+ summary: Safrano is an OData server library
196
235
  test_files: []