wrest 4.0.0-universal-java-18

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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +169 -0
  3. data/LICENCE +7 -0
  4. data/README.md +436 -0
  5. data/bin/wrest +4 -0
  6. data/bin/wrest_shell.rb +23 -0
  7. data/lib/wrest/async_request/event_machine_backend.rb +32 -0
  8. data/lib/wrest/async_request/thread_backend.rb +34 -0
  9. data/lib/wrest/async_request/thread_pool.rb +29 -0
  10. data/lib/wrest/async_request.rb +51 -0
  11. data/lib/wrest/cache_proxy.rb +119 -0
  12. data/lib/wrest/caching/memcached.rb +37 -0
  13. data/lib/wrest/caching/redis.rb +38 -0
  14. data/lib/wrest/caching.rb +57 -0
  15. data/lib/wrest/callback.rb +70 -0
  16. data/lib/wrest/components/container/alias_accessors.rb +70 -0
  17. data/lib/wrest/components/container/typecaster.rb +178 -0
  18. data/lib/wrest/components/container.rb +204 -0
  19. data/lib/wrest/components/mutators/base.rb +65 -0
  20. data/lib/wrest/components/mutators/camel_to_snake_case.rb +26 -0
  21. data/lib/wrest/components/mutators/xml_type_caster.rb +56 -0
  22. data/lib/wrest/components/mutators.rb +42 -0
  23. data/lib/wrest/components/translators/content_types.rb +25 -0
  24. data/lib/wrest/components/translators/json.rb +36 -0
  25. data/lib/wrest/components/translators/txt.rb +35 -0
  26. data/lib/wrest/components/translators/xml/conversions.rb +56 -0
  27. data/lib/wrest/components/translators/xml.rb +77 -0
  28. data/lib/wrest/components/translators.rb +30 -0
  29. data/lib/wrest/components.rb +22 -0
  30. data/lib/wrest/core_ext/hash/conversions.rb +45 -0
  31. data/lib/wrest/core_ext/hash.rb +7 -0
  32. data/lib/wrest/core_ext/string/conversions.rb +38 -0
  33. data/lib/wrest/core_ext/string.rb +7 -0
  34. data/lib/wrest/exceptions.rb +38 -0
  35. data/lib/wrest/hash_with_case_insensitive_access.rb +52 -0
  36. data/lib/wrest/hash_with_indifferent_access.rb +442 -0
  37. data/lib/wrest/http_codes.rb +83 -0
  38. data/lib/wrest/http_shared/headers.rb +345 -0
  39. data/lib/wrest/http_shared/standard_headers.rb +22 -0
  40. data/lib/wrest/http_shared/standard_tokens.rb +21 -0
  41. data/lib/wrest/http_shared.rb +25 -0
  42. data/lib/wrest/multipart.rb +84 -0
  43. data/lib/wrest/native/connection_factory.rb +28 -0
  44. data/lib/wrest/native/delete.rb +27 -0
  45. data/lib/wrest/native/get.rb +83 -0
  46. data/lib/wrest/native/options.rb +27 -0
  47. data/lib/wrest/native/patch.rb +27 -0
  48. data/lib/wrest/native/post.rb +27 -0
  49. data/lib/wrest/native/post_multipart.rb +36 -0
  50. data/lib/wrest/native/put.rb +27 -0
  51. data/lib/wrest/native/put_multipart.rb +36 -0
  52. data/lib/wrest/native/redirection.rb +39 -0
  53. data/lib/wrest/native/request.rb +161 -0
  54. data/lib/wrest/native/response.rb +278 -0
  55. data/lib/wrest/native/session.rb +66 -0
  56. data/lib/wrest/native.rb +36 -0
  57. data/lib/wrest/test/request_patches.rb +12 -0
  58. data/lib/wrest/test.rb +3 -0
  59. data/lib/wrest/uri/builders.rb +48 -0
  60. data/lib/wrest/uri.rb +312 -0
  61. data/lib/wrest/uri_template.rb +63 -0
  62. data/lib/wrest/utils.rb +129 -0
  63. data/lib/wrest/version.rb +14 -0
  64. data/lib/wrest.rb +77 -0
  65. data/lib/wrest_no_ext.rb +7 -0
  66. metadata +286 -0
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2009 Sidu Ponnappa
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at native://www.apache.org/licenses/LICENSE-2.0
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
9
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and limitations under the License.
11
+
12
+ module Wrest
13
+ module AsyncRequest
14
+ # Uses a pool of Threads to make requests.
15
+ # Only recommended for production use on JRuby.
16
+ class ThreadBackend
17
+ attr_reader :thread_pool
18
+
19
+ def initialize(number_of_threads = 5)
20
+ @thread_pool = ThreadPool.new(number_of_threads)
21
+ end
22
+
23
+ def execute(request)
24
+ @thread_pool.execute_eventually(request)
25
+ end
26
+
27
+ # Uses Thread#join to wait until all
28
+ # background requests are completed.
29
+ def wait_for_thread_pool!
30
+ @thread_pool.join_pool_threads!
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2009-2016 Sidu Ponnappa
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at native://www.apache.org/licenses/LICENSE-2.0
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
9
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and limitations under the License.
11
+
12
+ module Wrest
13
+ module AsyncRequest
14
+ class ThreadPool
15
+ def initialize(number_of_threads)
16
+ @pool = Concurrent::FixedThreadPool.new(number_of_threads)
17
+ end
18
+
19
+ def execute_eventually(request)
20
+ @pool.post { request.invoke }
21
+ nil
22
+ end
23
+
24
+ def join_pool_threads!
25
+ @pool.wait_for_termination
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2009 Sidu Ponnappa
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at native://www.apache.org/licenses/LICENSE-2.0
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
9
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and limitations under the License.
11
+
12
+ module Wrest
13
+ module AsyncRequest
14
+ # Loads Wrest eventmachine backend alongwith eventmachine gem
15
+ def self.enable_em
16
+ require 'wrest/async_request/event_machine_backend'
17
+ end
18
+
19
+ # Assign default backend to be used for asynchronous request. Default is to use threads
20
+ def self.default_backend=(backend)
21
+ @default_backend = backend
22
+ end
23
+
24
+ # Assign default backend for asynchronous request to using eventmachine.
25
+ def self.default_to_em!
26
+ enable_em
27
+ self.default_backend = Wrest::AsyncRequest::EventMachineBackend.new
28
+ end
29
+
30
+ # Assign default backend for asynchronous request to using threads.
31
+ def self.default_to_threads!(number_of_threads = 5)
32
+ self.default_backend = Wrest::AsyncRequest::ThreadBackend.new(number_of_threads)
33
+ end
34
+
35
+ # Returns the default backend, which is the ThreadBackend
36
+ def self.default_backend
37
+ @default_backend || default_to_threads!
38
+ end
39
+
40
+ # Uses Thread#join to wait until all background requests
41
+ # are completed.
42
+ #
43
+ # Use this as the last instruction in a script to prevent it from
44
+ # exiting before background threads have completed running.
45
+ #
46
+ # Needs Wrest.default_backend to be an instance of ThreadBackend.
47
+ def self.wait_for_thread_pool!
48
+ default_backend.wait_for_thread_pool!
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wrest
4
+ class CacheProxy
5
+ class << self
6
+ def new(get, cache_store)
7
+ if cache_store
8
+ DefaultCacheProxy.new(get, cache_store)
9
+ else
10
+ NullCacheProxy.new(get)
11
+ end
12
+ end
13
+ end
14
+
15
+ class NullCacheProxy
16
+ def initialize(get)
17
+ @get = get
18
+ end
19
+
20
+ def get
21
+ @get.invoke_without_cache_check
22
+ end
23
+ end
24
+
25
+ class DefaultCacheProxy
26
+ HOP_BY_HOP_HEADERS = %w[connection
27
+ keep-alive
28
+ proxy-authenticate
29
+ proxy-authorization
30
+ te
31
+ trailers
32
+ transfer-encoding
33
+ upgrade].freeze
34
+
35
+ def initialize(get, cache_store)
36
+ @get = get
37
+ @cache_store = cache_store
38
+ end
39
+
40
+ def log_cached_response
41
+ Wrest.logger.debug "<*> (GET #{@get.hash}) #{@get.uri.protocol}://#{@get.uri.host}:#{@get.uri.port}#{@get.http_request.path}"
42
+ end
43
+
44
+ def get
45
+ cached_response = @cache_store[@get.full_uri_string]
46
+ return fresh_get_response if cached_response.nil?
47
+
48
+ if cached_response.expired?
49
+ expired_cached_response(cached_response)
50
+ else
51
+ log_cached_response
52
+ cached_response
53
+ end
54
+ end
55
+
56
+ def update_cache_headers_for(cached_response, new_response)
57
+ # RFC 2616 13.5.3 (Combining Headers)
58
+ cached_response.headers.merge!(new_response.headers.reject do |key, _value|
59
+ (HOP_BY_HOP_HEADERS.include? key.downcase)
60
+ end)
61
+ end
62
+
63
+ def cache(response)
64
+ @cache_store[@get.full_uri_string] = response.clone if response&.cacheable?
65
+ end
66
+
67
+ # :nodoc:
68
+ def fresh_get_response
69
+ @cache_store.delete @get.full_uri_string
70
+
71
+ response = @get.invoke_without_cache_check
72
+
73
+ cache(response)
74
+
75
+ response
76
+ end
77
+
78
+ # :nodoc:
79
+ def get_validated_response_for(cached_response)
80
+ new_response = send_validation_request_for(cached_response)
81
+ if new_response.code == '304'
82
+ update_cache_headers_for(cached_response, new_response)
83
+ log_cached_response
84
+ cached_response
85
+ else
86
+ cache(new_response)
87
+ new_response
88
+ end
89
+ end
90
+
91
+ # :nodoc:
92
+ # Send a cache-validation request to the server. This would be the actual Get request with extra cache-validation headers.
93
+ # If a 304 (Not Modified) is received, Wrest would use the cached_response itself. Otherwise the new response is cached and used.
94
+ def send_validation_request_for(cached_response)
95
+ last_modified = cached_response.last_modified
96
+ etag = cached_response.headers['etag']
97
+
98
+ cache_validation_headers = {}
99
+ cache_validation_headers['if-modified-since'] = last_modified unless last_modified.nil?
100
+ cache_validation_headers['if-none-match'] = etag unless etag.nil?
101
+
102
+ new_request = @get.build_request_without_cache_store(cache_validation_headers)
103
+
104
+ new_request.invoke
105
+ end
106
+
107
+ private
108
+
109
+ # :nodoc:
110
+ def expired_cached_response(cached_response)
111
+ if cached_response.can_be_validated?
112
+ get_validated_response_for(cached_response)
113
+ else
114
+ fresh_get_response
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ gem 'dalli', '~> 2'
5
+ rescue Gem::LoadError => e
6
+ Wrest.logger.debug 'Dalli ~> 2 not found. The Dalli gem is necessary to use the memcached caching back-end.'
7
+ raise e
8
+ end
9
+
10
+ require 'dalli'
11
+
12
+ module Wrest
13
+ module Caching
14
+ class Memcached
15
+ def initialize(server_urls = nil, options = {})
16
+ @memcached = Dalli::Client.new(server_urls, options)
17
+ end
18
+
19
+ def [](key)
20
+ @memcached.get(key)
21
+ end
22
+
23
+ def []=(key, value)
24
+ @memcached.set(key, value)
25
+ end
26
+
27
+ # should be compatible with Hash - return value of the deleted element.
28
+ def delete(key)
29
+ value = self[key]
30
+
31
+ @memcached.delete key
32
+
33
+ value
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ gem 'redis', '~> 3'
5
+ rescue Gem::LoadError => e
6
+ Wrest.logger.debug 'Redis ~> 3 not found. The Redis gem is necessary to use redis as a caching back-end.'
7
+ raise e
8
+ end
9
+
10
+ require 'redis'
11
+ require 'yaml'
12
+
13
+ module Wrest
14
+ module Caching
15
+ class Redis
16
+ def initialize(redis_options = {})
17
+ @redis = ::Redis.new(redis_options)
18
+ end
19
+
20
+ def [](key)
21
+ value = @redis.get(key)
22
+ value.nil? ? nil : YAML.unsafe_load(value)
23
+ end
24
+
25
+ def []=(key, response)
26
+ marshalled_response = YAML.dump(response)
27
+ @redis.set(key, marshalled_response)
28
+ @redis.expire(key, response.freshness_lifetime) unless response.expired?
29
+ end
30
+
31
+ def delete(key)
32
+ value = self[key]
33
+ @redis.del(key)
34
+ value
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2009 Sidu Ponnappa
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at native://www.apache.org/licenses/LICENSE-2.0
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
9
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and limitations under the License.
11
+
12
+ module Wrest
13
+ module Caching
14
+ # Loads the Memcached caching back-end and the Dalli gem
15
+ def self.enable_memcached
16
+ require 'wrest/caching/memcached'
17
+ end
18
+
19
+ # Loads the Redis caching back-end and the Redis gem
20
+ def self.enable_redis
21
+ require 'wrest/caching/redis'
22
+ end
23
+
24
+ # Configures Wrest to cache all requests. This will use a Ruby Hash.
25
+ # WARNING: This should NEVER be used in a real environment. The Hash will
26
+ # keep growing since Wrest does not limit the size of a cache store.
27
+ #
28
+ # Please switch to the memcached or redis back-end for production use.
29
+ def self.default_to_hash!
30
+ self.default_store = ({})
31
+ end
32
+
33
+ # Default Wrest to using memcached for caching requests.
34
+ def self.default_to_memcached!
35
+ enable_memcached
36
+ self.default_store = Wrest::Caching::Memcached.new
37
+ end
38
+
39
+ # Default Wrest to using redis for caching requests.
40
+ #
41
+ # Options to configuring the redis gem can be passed as arguments.
42
+ def self.default_to_redis!(redis_options = {})
43
+ enable_redis
44
+ self.default_store = Wrest::Caching::Redis.new(redis_options)
45
+ end
46
+
47
+ # Assign the default cache store to be used. Default is none.
48
+ def self.default_store=(store)
49
+ @default_store = store
50
+ end
51
+
52
+ # Returns the default store for caching, if any is set.
53
+ def self.default_store
54
+ @default_store
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2009 Sidu Ponnappa
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at native://www.apache.org/licenses/LICENSE-2.0
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
9
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and limitations under the License.
11
+ #
12
+ module Wrest
13
+ class Callback
14
+ attr_reader :callback_hash
15
+
16
+ def initialize(callable)
17
+ case callable
18
+ when Hash
19
+ @callback_hash = Callback.ensure_values_are_collections(callable)
20
+ when Proc
21
+ @callback_hash = {}
22
+ callable.call(self)
23
+ when Callback
24
+ @callback_hash = callable.callback_hash.dup
25
+ end
26
+ end
27
+
28
+ def merge(callback)
29
+ merged_callback_hash = callback_hash.clone
30
+ other_callback_hash = callback.callback_hash
31
+ other_callback_hash.each do |code, callback_blocks|
32
+ merged_callback_hash[code] ||= []
33
+ merged_callback_hash[code] += callback_blocks
34
+ end
35
+ Callback.new(merged_callback_hash)
36
+ end
37
+
38
+ def execute(response)
39
+ callback_hash.each do |code, callback_list|
40
+ callback_list.each { |callback| callback.call(response) } if case code
41
+ when Range
42
+ code.include?(response.code.to_i)
43
+ when Integer
44
+ code == response.code.to_i
45
+ end
46
+ end
47
+ end
48
+
49
+ def on(code, &block)
50
+ @callback_hash[code] ? @callback_hash[code] << block : @callback_hash[code] = [block]
51
+ end
52
+
53
+ { 200 => 'ok', 201 => 'created', 202 => 'accepted', 204 => 'no_content', 301 => 'moved_permanently', 302 => 'found', 303 => 'see_other', 304 => 'not_modified',
54
+ 307 => 'temporary_redirect', 400 => 'bad_request', 401 => 'unauthorized', 403 => 'forbidden', 404 => 'not_found', 405 => 'method_not_allowed',
55
+ 406 => 'not_acceptable', 422 => 'unprocessable_entity', 500 => 'internal_server_error' }.each do |code, method|
56
+ method_name = "on_#{method}".to_sym
57
+ define_method method_name do |&block|
58
+ (@callback_hash[code] ? @callback_hash[code] << block : @callback_hash[code] = [block]) if block
59
+ end
60
+ end
61
+
62
+ def self.ensure_values_are_collections(hash)
63
+ result = {}
64
+ hash.each do |code, block|
65
+ result[code] = block.is_a?(Array) ? block : [block]
66
+ end
67
+ result
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2009 Sidu Ponnappa
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
9
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and limitations under the License.
11
+
12
+ module Wrest
13
+ module Components
14
+ module Container
15
+ module AliasAccessors
16
+ def self.included(klass) # :nodoc:
17
+ klass.extend AliasAccessors::ClassMethods
18
+ end
19
+
20
+ def self.build_aliased_attribute_getter(attribute_name, alias_name) # :nodoc:
21
+ "def #{alias_name};#{attribute_name};end;"
22
+ end
23
+
24
+ def self.build_aliased_attribute_setter(attribute_name, alias_name) # :nodoc:
25
+ "def #{alias_name}=(value);self.#{attribute_name}=value;end;"
26
+ end
27
+
28
+ def self.build_aliased_attribute_queryer(attribute_name, alias_name) # :nodoc:
29
+ "def #{alias_name}?;self.#{attribute_name}?;end;"
30
+ end
31
+
32
+ module ClassMethods
33
+ # Creates an alias set of getter, setter and query methods for
34
+ # attributes that aren't quite the way you'd like them to be; this
35
+ # is especially useful when you have no control over the source web
36
+ # sevice/resource.
37
+ #
38
+ # For example, lets say that a particular resource exposes a
39
+ # User's age as 'a' and sex as 's'. Typically, you'd have to access it as
40
+ # user.a and user.s whereas you's like to access it as user.age and user.sex.
41
+ # This is where alias_accessors comes into the picture. Your User class would
42
+ # look somethig like this:
43
+ #
44
+ # class User
45
+ # include Wrest::Components::Container
46
+ #
47
+ # alias_accessors :a => :age,
48
+ # :s => :sex
49
+ # end
50
+ # This would create the methods user.age, user.age= and user.age? which delegates
51
+ # to user.a, user.a= and user.a? respectively.
52
+ #
53
+ # See examples/wow_realm_status.rb for a working example.
54
+ #
55
+ # WARNING: If you try to create an alias with the same name as the attribute,
56
+ # and then use it, you _will_ cause an infinite loop.
57
+ def alias_accessors(alias_map)
58
+ alias_map.each do |attribute_name, alias_name|
59
+ class_eval(
60
+ AliasAccessors.build_aliased_attribute_getter(attribute_name, alias_name) +
61
+ AliasAccessors.build_aliased_attribute_setter(attribute_name, alias_name) +
62
+ AliasAccessors.build_aliased_attribute_queryer(attribute_name, alias_name)
63
+ )
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2009 Sidu Ponnappa
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
9
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and limitations under the License.
11
+
12
+ module Wrest
13
+ module Components
14
+ module Container
15
+ # An extension to Container that adds support for specifying
16
+ # how the values associated with certain attribute keys
17
+ # should be typecast.
18
+ #
19
+ # This extension can be used in situations where the attributes
20
+ # hash consists of just strings with no associated tup information.
21
+ # For example, params recieved from a web browser may contain
22
+ # attributes like
23
+ # 'id' => '4', 'dateofbirth' => '1984-04-05'
24
+ # and we'd like to have these cast to an integer and a date
25
+ # respectively, rather than have to deal with them as strings.
26
+ module Typecaster
27
+ PARSING = {
28
+ 'symbol' => proc { |symbol| symbol.to_s.to_sym },
29
+ 'date' => proc { |date| ::Date.parse(date) },
30
+ 'datetime' => proc { |time|
31
+ begin
32
+ Time.xmlschema(time).utc
33
+ rescue StandardError
34
+ ::DateTime.parse(time).utc
35
+ end
36
+ },
37
+ 'integer' => proc { |integer| integer.to_i },
38
+ 'float' => proc { |float| float.to_f },
39
+ 'decimal' => proc do |number|
40
+ if number.is_a?(String)
41
+ number.to_d
42
+ else
43
+ BigDecimal(number)
44
+ end
45
+ end,
46
+ 'boolean' => proc { |boolean| %w[1 true].include?(boolean.to_s.strip) },
47
+ 'string' => proc { |string| string.to_s },
48
+ 'yaml' => proc { |yaml|
49
+ begin
50
+ YAML.safe_load(yaml)
51
+ rescue StandardError
52
+ yaml
53
+ end
54
+ },
55
+ 'base64Binary' => proc { |bin| ::Base64.decode64(bin) },
56
+ 'binary' => proc { |bin, entity| _parse_binary(bin, entity) },
57
+ 'file' => proc { |file, entity| _parse_file(file, entity) },
58
+ 'double' => proc { |float| float.to_f },
59
+ 'dateTime' => proc { |time|
60
+ begin
61
+ Time.xmlschema(time).utc
62
+ rescue StandardError
63
+ ::DateTime.parse(time).utc
64
+ end
65
+ }
66
+ }.freeze
67
+
68
+ def self.included(klass)
69
+ # :nodoc:
70
+ klass.extend Typecaster::ClassMethods
71
+ klass.class_eval { include Typecaster::InstanceMethods }
72
+ klass.send(:alias_method, :initialize_without_typecasting, :initialize)
73
+ klass.send(:alias_method, :initialize, :initialize_with_typecasting)
74
+ end
75
+
76
+ module Helpers
77
+ def as_base64_binary
78
+ PARSING['base64Binary']
79
+ end
80
+
81
+ def as_boolean
82
+ PARSING['boolean']
83
+ end
84
+
85
+ def as_decimal
86
+ PARSING['decimal']
87
+ end
88
+
89
+ def as_date
90
+ PARSING['date']
91
+ end
92
+
93
+ def as_datetime
94
+ PARSING['datetime']
95
+ end
96
+
97
+ def as_float
98
+ PARSING['float']
99
+ end
100
+
101
+ def as_integer
102
+ PARSING['integer']
103
+ end
104
+
105
+ def as_symbol
106
+ PARSING['symbol']
107
+ end
108
+
109
+ def as_yaml
110
+ PARSING['yaml']
111
+ end
112
+ end
113
+
114
+ module ClassMethods
115
+ # Accepts a set of attribute-name/lambda pairs which are used
116
+ # to typecast string values injected through the constructor.
117
+ # Typically needed when populating an +Container+
118
+ # directly from request params. Typecasting kicks in for
119
+ # a given value _only_ if it is a String, Hash or Array, the
120
+ # three classes that deserilisation can produce.
121
+ #
122
+ # Typecast information is inherited by subclasses; however be
123
+ # aware that explicitly invoking +typecast+ in a subclass will
124
+ # discard inherited typecast information leaving only the casts
125
+ # defined in the subclass.
126
+ #
127
+ # Note that this _will_ increase the time needed to initialize
128
+ # instances.
129
+ #
130
+ # Common typecasts such as integer, float, datetime etc. are
131
+ # available through predefined helpers. See TypecastHelpers
132
+ # for a full list.
133
+ #
134
+ # Example:
135
+ #
136
+ # class Demon
137
+ # include Wrest::Components::Container
138
+ # include Wrest::Components::Container::Typecaster
139
+ #
140
+ # typecast :age => as_integer,
141
+ # :chi => lambda{|chi| Chi.new(chi)}
142
+ # end
143
+ #
144
+ # kai_wren = Demon.new('age' => '1500', 'chi' => '1024')
145
+ # kai_wren.age # => 1500
146
+ # kai_wren.chi # => #<Chi:0x113af8c @count="1024">
147
+ def typecast(cast_map)
148
+ @typecast_map = @typecast_map ? @typecast_map.merge(cast_map.transform_keys(&:to_sym)) : cast_map.transform_keys(&:to_sym)
149
+ end
150
+
151
+ def typecast_map # :nodoc:
152
+ if defined?(@typecast_map)
153
+ @typecast_map
154
+ elsif superclass != Object && superclass.respond_to?(:typecast_map)
155
+ superclass.typecast_map
156
+ else
157
+ {}
158
+ end
159
+ end
160
+ end
161
+
162
+ module InstanceMethods # :nodoc:
163
+ def initialize_with_typecasting(attributes = {})
164
+ # :nodoc:
165
+ initialize_without_typecasting(attributes)
166
+ self.class.typecast_map.each do |key, typecaster|
167
+ value = @attributes[key]
168
+ if value.is_a?(String) || value.is_a?(Hash) || value.is_a?(Array)
169
+ @attributes[key] =
170
+ typecaster.call(value)
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end