safrano 0.5.3 → 0.6.0

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.
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: []