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.
- checksums.yaml +7 -0
- data/CHANGELOG +169 -0
- data/LICENCE +7 -0
- data/README.md +436 -0
- data/bin/wrest +4 -0
- data/bin/wrest_shell.rb +23 -0
- data/lib/wrest/async_request/event_machine_backend.rb +32 -0
- data/lib/wrest/async_request/thread_backend.rb +34 -0
- data/lib/wrest/async_request/thread_pool.rb +29 -0
- data/lib/wrest/async_request.rb +51 -0
- data/lib/wrest/cache_proxy.rb +119 -0
- data/lib/wrest/caching/memcached.rb +37 -0
- data/lib/wrest/caching/redis.rb +38 -0
- data/lib/wrest/caching.rb +57 -0
- data/lib/wrest/callback.rb +70 -0
- data/lib/wrest/components/container/alias_accessors.rb +70 -0
- data/lib/wrest/components/container/typecaster.rb +178 -0
- data/lib/wrest/components/container.rb +204 -0
- data/lib/wrest/components/mutators/base.rb +65 -0
- data/lib/wrest/components/mutators/camel_to_snake_case.rb +26 -0
- data/lib/wrest/components/mutators/xml_type_caster.rb +56 -0
- data/lib/wrest/components/mutators.rb +42 -0
- data/lib/wrest/components/translators/content_types.rb +25 -0
- data/lib/wrest/components/translators/json.rb +36 -0
- data/lib/wrest/components/translators/txt.rb +35 -0
- data/lib/wrest/components/translators/xml/conversions.rb +56 -0
- data/lib/wrest/components/translators/xml.rb +77 -0
- data/lib/wrest/components/translators.rb +30 -0
- data/lib/wrest/components.rb +22 -0
- data/lib/wrest/core_ext/hash/conversions.rb +45 -0
- data/lib/wrest/core_ext/hash.rb +7 -0
- data/lib/wrest/core_ext/string/conversions.rb +38 -0
- data/lib/wrest/core_ext/string.rb +7 -0
- data/lib/wrest/exceptions.rb +38 -0
- data/lib/wrest/hash_with_case_insensitive_access.rb +52 -0
- data/lib/wrest/hash_with_indifferent_access.rb +442 -0
- data/lib/wrest/http_codes.rb +83 -0
- data/lib/wrest/http_shared/headers.rb +345 -0
- data/lib/wrest/http_shared/standard_headers.rb +22 -0
- data/lib/wrest/http_shared/standard_tokens.rb +21 -0
- data/lib/wrest/http_shared.rb +25 -0
- data/lib/wrest/multipart.rb +84 -0
- data/lib/wrest/native/connection_factory.rb +28 -0
- data/lib/wrest/native/delete.rb +27 -0
- data/lib/wrest/native/get.rb +83 -0
- data/lib/wrest/native/options.rb +27 -0
- data/lib/wrest/native/patch.rb +27 -0
- data/lib/wrest/native/post.rb +27 -0
- data/lib/wrest/native/post_multipart.rb +36 -0
- data/lib/wrest/native/put.rb +27 -0
- data/lib/wrest/native/put_multipart.rb +36 -0
- data/lib/wrest/native/redirection.rb +39 -0
- data/lib/wrest/native/request.rb +161 -0
- data/lib/wrest/native/response.rb +278 -0
- data/lib/wrest/native/session.rb +66 -0
- data/lib/wrest/native.rb +36 -0
- data/lib/wrest/test/request_patches.rb +12 -0
- data/lib/wrest/test.rb +3 -0
- data/lib/wrest/uri/builders.rb +48 -0
- data/lib/wrest/uri.rb +312 -0
- data/lib/wrest/uri_template.rb +63 -0
- data/lib/wrest/utils.rb +129 -0
- data/lib/wrest/version.rb +14 -0
- data/lib/wrest.rb +77 -0
- data/lib/wrest_no_ext.rb +7 -0
- 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
|