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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64699787371f5e9f696461faac80382cf2d53fe78d387742d8650e5a4b2c54df
4
- data.tar.gz: 530e7619b4b27a1a50a63f832d4f3a8894850c6cea36e638769cdffbc6b7d942
3
+ metadata.gz: 26b1634021c6ab2117b0b2fe4fb8c49242d4a045b6afafec604ef61e26c3bc48
4
+ data.tar.gz: 86adac4fb2ab8ad02d570f373325b7e1c4945941a01e2328fc50ff10c9460abd
5
5
  SHA512:
6
- metadata.gz: e5d118f15a3a3ba78f5385867c82370e36612585b7f83329343babb2e3a8a7220d1059ad766fbe7c9e1f7f74a57945c086e60624d0301259c1427f0053b59052
7
- data.tar.gz: 52b283be3e8902226f14864a74eae30bc62c65908be748ea37c5a150f574f3130c8957f26251e1958e298eba221eeada9f0155fcc73ed2591034e97466f1e629
6
+ metadata.gz: c19b30f23c22127742d8c557a23feec6de75a91d20b1b8b0033fb85e4f1549e92de300e17cb3d80564f92ed0dcd81096b5b4aca1a268eba803b6bbd6a4c0a75c
7
+ data.tar.gz: ee3a9d73e9e1d5862cb1a699786e9f81c5a58d7937678b5a138eecae08ccc10e49248f70066ede55e270fb361744978ca4b5b9a75bbd6e62a2c4685e6fc2ae46
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Safrano
4
+ module CoreExt
5
+ module Date
6
+ module Format
7
+ # Date is a DateTime with time 00:00:00 thus the milliseconds are ALWAYS 000
8
+ # excepted when we get Date(0) = Epoch date
9
+ REGEX = /\/Date\((?:(-?\d+)000|0+)\)\//.freeze
10
+ # Input: /Date(86400000)/
11
+ # Match groups
12
+ # 1. 86400
13
+ # Input: /Date(0)/
14
+ # Match groups
15
+ # 1.
16
+
17
+ def from_edm_json(instr)
18
+ return unless (md = instr.match(REGEX))
19
+
20
+ if md[1].nil? # no offset relative to Epoch
21
+ ::Date.new(1970, 1, 1).freeze
22
+ else
23
+ # parse as seconds since epoch
24
+ ::Date.strptime(md[1], '%s')
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ module CoreIncl
31
+ module Date
32
+ module Format
33
+ # https://www.odata.org/documentation/odata-version-2-0/json-format/
34
+ # Edm.DateTime
35
+ # "/Date(<ticks>["+" | "-" <offset>)/"
36
+ # <ticks> = number of milliseconds since midnight Jan 1, 1970
37
+ # <offset> = number of minutes to add or subtract
38
+ # https://stackoverflow.com/questions/10286204/what-is-the-right-json-date-format/10286228#10286228
39
+ def to_edm_json
40
+ # no offset
41
+ # --> %Q milliseconds since epoch
42
+ strftime('/Date(%Q)/')
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Safrano
4
+ module CoreExt
5
+ module DateTime
6
+ module Format
7
+ REGEX = /\/Date\((-?\d+)(?:([-+])(\d+))?\)\//.freeze
8
+ # Input: /Date(120000+150)/
9
+ # Match groups
10
+ # 1. 120000
11
+ # 2. +
12
+ # 3. 150
13
+
14
+ def from_edm_json(instr)
15
+ return unless (md = instr.match(REGEX))
16
+
17
+ if md[3].nil? # no offset
18
+ ::DateTime.strptime(md[1], '%Q')
19
+ else
20
+ # offset in hours / mins
21
+ off_h, off_m = md[3].to_i.divmod(60)
22
+ # DateTime.strptime("120000+2:30", '%Q%z') milliseconds since epoch with a z-offset
23
+ # --> #<DateTime: 1970-01-01T02:32:00+02:30 ((2440588j,120s,0n),+9000s,2299161j)>
24
+ ::DateTime.strptime("#{md[1]}#{md[2]}#{off_h}:#{off_m}", '%Q%z')
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ module CoreIncl
31
+ module DateTime
32
+ module Format
33
+ # https://www.odata.org/documentation/odata-version-2-0/json-format/
34
+ # Edm.DateTime
35
+ # "/Date(<ticks>["+" | "-" <offset>)/"
36
+ # <ticks> = number of milliseconds since midnight Jan 1, 1970
37
+ # <offset> = number of minutes to add or subtract
38
+ # https://stackoverflow.com/questions/10286204/what-is-the-right-json-date-format/10286228#10286228
39
+ def to_edm_json
40
+ if offset.zero?
41
+ # no offset
42
+ # --> %Q milliseconds since epoch
43
+ strftime('/Date(%Q)/')
44
+ else
45
+ # same as above with GMT offset in minutes
46
+ # DateTime offset is Rational ; fraction of hours per Day --> *24*60
47
+ min_off_s = (min_off = (offset * 60 * 24).to_i) > 0 ? "+#{min_off}" : min_off.to_s
48
+ "/Date(#{strftime('%Q')}#{min_off_s})/"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -5,13 +5,15 @@ module Safrano
5
5
  module CoreExt
6
6
  module Dir
7
7
  module Iter
8
- def each_child(dir)
9
- ::Dir.foreach(dir) do |x|
10
- next if (x == '.') || (x == '..')
8
+ unless ::Dir.respond_to? :each_child
9
+ def each_child(dir)
10
+ ::Dir.foreach(dir) do |x|
11
+ next if (x == '.') || (x == '..')
11
12
 
12
- yield x
13
+ yield x
14
+ end
13
15
  end
14
- end unless ::Dir.respond_to? :each_child
16
+ end
15
17
  end
16
18
  end
17
19
  end
@@ -5,15 +5,23 @@ module Safrano
5
5
  module CoreIncl
6
6
  module Hash
7
7
  module Transform
8
- def transform_keys!
9
- keys.each do |key|
10
- self[yield(key)] = delete(key)
8
+ unless method_defined? :transform_keys!
9
+ def transform_keys!
10
+ keys.each do |key|
11
+ self[yield(key)] = delete(key)
12
+ end
13
+ self
11
14
  end
12
- self
13
- end unless method_defined? :transform_keys!
15
+ end
14
16
 
15
17
  def symbolize_keys!
16
- transform_keys! { |key| key.to_sym rescue key }
18
+ transform_keys! do |key|
19
+ begin
20
+ key.to_sym
21
+ rescue StandardError
22
+ key
23
+ end
24
+ end
17
25
  end
18
26
  end
19
27
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'bigdecimal/util'
4
+
5
+ module Safrano
6
+ module CoreIncl
7
+ module Numeric
8
+ module Convert
9
+ def toDecimalString
10
+ BigDecimal(to_s).to_s('F')
11
+ end
12
+
13
+ def toDecimalPrecisionString(precision)
14
+ p = Integer(precision)
15
+ BigDecimal(self, p).to_s('F')
16
+ end
17
+
18
+ def toDecimalPrecisionScaleString(precision, scale)
19
+ p = Integer(precision)
20
+ format("%#{p + 2}.#{scale}f", self).strip
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Safrano
4
+ module CoreExt
5
+ module Time
6
+ module Format
7
+ REGEX = /\/Date\((-?\d+)(?:([-+])(\d+))?\)\//.freeze
8
+ # Input: /Date(120000+150)/
9
+ # Match groups
10
+ # 1. 120000
11
+ # 2. +
12
+ # 3. 150
13
+
14
+ def from_edm_json(instr)
15
+ return unless (md = instr.match(REGEX))
16
+
17
+ sec, milli = md[1].to_i.divmod(1000)
18
+ secm = milli.zero? ? sec : sec + Float(milli) / 1000
19
+ if md[3].nil? # no offset
20
+ # ::Time.at(sec, milli, :millisecond) # not supported in ruby 2.4
21
+ ::Time.gm(1970, 1, 1) + secm
22
+ else
23
+ # offset in hours / mins
24
+ off_h, off_m = md[3].to_i.divmod(60)
25
+ # add leading 0 because Time.at is expecting "+HH:MM", "-HH:MM"
26
+ off_h = off_h < 10 ? "0#{off_h}" : off_h.to_s
27
+ off_m = off_m < 10 ? "0#{off_m}" : off_m.to_s
28
+ # Time.strptime("120000+2:30", '%Q%z') milliseconds since epoch with a z-offset
29
+ # --> #<DateTime: 1970-01-01T02:32:00+02:30 ((2440588j,120s,0n),+9000s,2299161j)>
30
+ # ::Time.at(sec, milli, :millisecond, in: "#{md[2]}#{off_h}:#{off_m}") # not supported in ruby 2.4
31
+ offset_str = "#{md[2]}#{off_h}:#{off_m}"
32
+ ::Time.new(1970, 1, 1, 0, 0, 0, offset_str) + secm + ::Time.zone_offset(offset_str)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ module CoreIncl
40
+ module Time
41
+ module Format
42
+ # Welcome to Hell
43
+ # https://www.odata.org/documentation/odata-version-2-0/json-format/
44
+ # Edm.DateTime
45
+ # "/Date(<ticks>["+" | "-" <offset>)/"
46
+ # <ticks> = number of milliseconds since midnight Jan 1, 1970
47
+ # <offset> = number of minutes to add or subtract
48
+ # https://stackoverflow.com/questions/10286204/what-is-the-right-json-date-format/10286228#10286228
49
+ # also https://www.hanselman.com/blog/on-the-nightmare-that-is-json-dates-plus-jsonnet-and-aspnet-web-api
50
+ # and https://itecnote.com/tecnote/c-use-json-net-to-parse-json-date-of-format-dateepochtime-offset/
51
+ # https://docs.microsoft.com/de-de/dotnet/standard/datetime/system-text-json-support
52
+ # https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/stand-alone-json-serialization#datestimes-and-json
53
+ # https://blogs.sap.com/2017/01/05/date-and-time-in-sap-gateway-foundation/
54
+ def to_edm_json
55
+ if utc? || (gmt_offset == 0)
56
+ # no offset
57
+ # %s : seconds since unix epoch
58
+ # %L : milliseconds 000-999
59
+ # --> %S%L milliseconds since epoch
60
+ strftime('/Date(%s%L)/')
61
+ else
62
+ # same as above with GMT offset in minutes
63
+
64
+ min_off_s = (min_off = gmt_offset / 60) > 0 ? "+#{min_off}" : min_off.to_s
65
+ "/Date(#{strftime('%s%L')}#{min_off_s})/"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'Date/format'
2
+ require 'date'
3
+
4
+ Date.include Safrano::CoreIncl::Date::Format
5
+ Date.extend Safrano::CoreExt::Date::Format
@@ -0,0 +1,5 @@
1
+ require_relative 'DateTime/format'
2
+ require 'date'
3
+
4
+ DateTime.include Safrano::CoreIncl::DateTime::Format
5
+ DateTime.extend Safrano::CoreExt::DateTime::Format
@@ -0,0 +1,3 @@
1
+ require_relative 'Numeric/convert'
2
+
3
+ Numeric.include Safrano::CoreIncl::Numeric::Convert
@@ -0,0 +1,5 @@
1
+ require_relative 'Time/format'
2
+ require 'time'
3
+
4
+ Time.include Safrano::CoreIncl::Time::Format
5
+ Time.extend Safrano::CoreExt::Time::Format
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
- require_relative '../safrano/core.rb'
5
- require_relative './entity.rb'
4
+ require_relative '../safrano/core'
5
+ require_relative './entity'
6
6
 
7
7
  module Safrano
8
8
  # Represents a named and valued attribute of an Entity
@@ -18,12 +18,14 @@ module Safrano
18
18
  def value
19
19
  # WARNING ... this require more work to handle the timezones topci
20
20
  # currently it is just set to make some minimal testcase work
21
+ # See also model_ext.rb
21
22
  case (v = @entity.values[@name.to_sym])
23
+ when Date
24
+ v.to_edm_json
22
25
  when Time
23
- # try to get back the database time zone and value
24
- # (v + v.gmt_offset).utc.to_datetime
25
- v.iso8601
26
-
26
+ v.to_edm_json
27
+ when DateTime
28
+ v.to_edm_json
27
29
  else
28
30
  v
29
31
  end
data/lib/odata/batch.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../safrano/rack_app.rb'
4
- require_relative '../safrano/core.rb'
3
+ require_relative '../safrano/rack_app'
4
+ require_relative '../safrano/core'
5
5
  require 'rack/body_proxy'
6
- require_relative './common_logger.rb'
6
+ require_relative './common_logger'
7
7
 
8
8
  module Safrano
9
9
  # Support for OData multipart $batch Requests
@@ -60,13 +60,13 @@ module Safrano
60
60
  end
61
61
 
62
62
  def initialize_dataset(dtset = nil)
63
- @cx = @cx || dtset || @modelk
63
+ @cx = @cx || dtset || @modelk
64
64
  end
65
-
65
+
66
66
  def initialize_uparms
67
67
  @uparms = UrlParameters4Coll.new(@cx, @params)
68
68
  end
69
-
69
+
70
70
  def odata_get_apply_params
71
71
  @uparms.apply_to_dataset(@cx).map_result! do |dataset|
72
72
  @cx = dataset
@@ -134,14 +134,14 @@ module Safrano
134
134
 
135
135
  # on model class level we return the collection
136
136
  def odata_get(req)
137
- @params = @params || req.params
138
- initialize_dataset
137
+ @params ||= req.params
138
+ initialize_dataset
139
139
  initialize_uparms
140
- @uparms.check_all.if_valid { |_ret|
141
- odata_get_apply_params.if_valid { |_ret|
140
+ @uparms.check_all.if_valid do |_ret|
141
+ odata_get_apply_params.if_valid do |_ret|
142
142
  odata_get_output(req)
143
- }
144
- }.tap_error { |e| return e.odata_get(req) }.result
143
+ end
144
+ end.tap_error { |e| return e.odata_get(req) }.result
145
145
  end
146
146
 
147
147
  def odata_post(req)
@@ -20,7 +20,9 @@ module Safrano
20
20
  end
21
21
 
22
22
  # finalize
23
- def finalize(_jh) Contract::OK end
23
+ def finalize(_jh)
24
+ Contract::OK
25
+ end
24
26
 
25
27
  def empty?
26
28
  true
@@ -143,8 +143,8 @@ module Safrano
143
143
  # with directory Tree structure
144
144
 
145
145
  class StaticTree < Static
146
- SEP = '/00/'.freeze
147
- VERS = '/v'.freeze
146
+ SEP = '/00/'
147
+ VERS = '/v'
148
148
 
149
149
  def self.path_builder(ids)
150
150
  ids.map { |id| id.to_s.chars.join('/') }.join(SEP) << VERS
@@ -276,7 +276,7 @@ module Safrano
276
276
 
277
277
  media_handler.check_before_create(data: data,
278
278
  entity: new_entity,
279
- filename: filename).if_valid { |_ret|
279
+ filename: filename).if_valid do |_ret|
280
280
  # to_one rels are create with FK data set on the parent entity
281
281
  if parent
282
282
  odata_create_save_entity_and_rel(req, new_entity, assoc, parent)
@@ -302,7 +302,7 @@ module Safrano
302
302
  # Contract.valid([201, EMPTY_HASH, new_entity.to_odata_post_json(service: req.service)])
303
303
  # TODO quirks array mode !
304
304
  Contract.valid([201, EMPTY_HASH, new_entity.to_odata_create_json(request: req)])
305
- }.tap_error { |e| return e.odata_get(req) }.result
305
+ end.tap_error { |e| return e.odata_get(req) }.result
306
306
 
307
307
  else # TODO: other formats
308
308
  415
@@ -311,26 +311,3 @@ module Safrano
311
311
  end
312
312
  end
313
313
  end
314
-
315
- # deprecated
316
- # REMOVE 0.6
317
- module OData
318
- module Media
319
- class Static < ::Safrano::Media::Static
320
- def initialize(root: nil, mediaklass:)
321
- ::Safrano::Deprecation.deprecate('OData::Media::Static',
322
- 'Use Safrano::Media::Static instead')
323
-
324
- super
325
- end
326
- end
327
- class StaticTree < ::Safrano::Media::StaticTree
328
- def initialize(root: nil, mediaklass:)
329
- ::Safrano::Deprecation.deprecate('OData::Media::StaticTree',
330
- 'Use Safrano::Media::StaticTree instead')
331
-
332
- super
333
- end
334
- end
335
- end
336
- end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'odata/error.rb'
3
+ require 'odata/error'
4
4
 
5
5
  # all ordering related classes in our OData module
6
6
  module Safrano
@@ -10,36 +10,14 @@ module Rack
10
10
  # Handle https://github.com/rack/rack/pull/1526
11
11
  # new in Rack 2.2.2 : Format has now 11 placeholders instead of 10
12
12
 
13
- MSG_FUNC = if FORMAT.count('%') == 10
13
+ MSG_FUNC = case FORMAT.count('%')
14
+ when 10
14
15
  lambda { |env, length, status, began_at|
15
- FORMAT % [
16
- env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_ADDR'] || '-',
17
- env['REMOTE_USER'] || '-',
18
- Time.now.strftime('%d/%b/%Y:%H:%M:%S %z'),
19
- env[REQUEST_METHOD],
20
- env[SCRIPT_NAME] + env[PATH_INFO],
21
- env[QUERY_STRING].empty? ? '' : "?#{env[QUERY_STRING]}",
22
- env[SERVER_PROTOCOL],
23
- status.to_s[0..3],
24
- length,
25
- Utils.clock_time - began_at
26
- ]
16
+ format(FORMAT, env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_ADDR'] || '-', env['REMOTE_USER'] || '-', Time.now.strftime('%d/%b/%Y:%H:%M:%S %z'), env[REQUEST_METHOD], env[SCRIPT_NAME] + env[PATH_INFO], env[QUERY_STRING].empty? ? '' : "?#{env[QUERY_STRING]}", env[SERVER_PROTOCOL], status.to_s[0..3], length, Utils.clock_time - began_at)
27
17
  }
28
- elsif FORMAT.count('%') == 11
18
+ when 11
29
19
  lambda { |env, length, status, began_at|
30
- FORMAT % [
31
- env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_ADDR'] || '-',
32
- env['REMOTE_USER'] || '-',
33
- Time.now.strftime('%d/%b/%Y:%H:%M:%S %z'),
34
- env[REQUEST_METHOD],
35
- env[SCRIPT_NAME],
36
- env[PATH_INFO],
37
- env[QUERY_STRING].empty? ? '' : "?#{env[QUERY_STRING]}",
38
- env[SERVER_PROTOCOL],
39
- status.to_s[0..3],
40
- length,
41
- Utils.clock_time - began_at
42
- ]
20
+ format(FORMAT, env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_ADDR'] || '-', env['REMOTE_USER'] || '-', Time.now.strftime('%d/%b/%Y:%H:%M:%S %z'), env[REQUEST_METHOD], env[SCRIPT_NAME], env[PATH_INFO], env[QUERY_STRING].empty? ? '' : "?#{env[QUERY_STRING]}", env[SERVER_PROTOCOL], status.to_s[0..3], length, Utils.clock_time - began_at)
43
21
  }
44
22
  end
45
23