wrest 4.0.0-universal-java-18

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