kaiwren-wrest 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/README.rdoc +104 -0
  2. data/Rakefile +203 -0
  3. data/VERSION.yml +4 -0
  4. data/bin/wrest +22 -0
  5. data/lib/wrest.rb +41 -0
  6. data/lib/wrest/core_ext/string.rb +5 -0
  7. data/lib/wrest/core_ext/string/conversions.rb +23 -0
  8. data/lib/wrest/exceptions.rb +1 -0
  9. data/lib/wrest/exceptions/unsupported_content_type_exception.rb +15 -0
  10. data/lib/wrest/mappers.rb +21 -0
  11. data/lib/wrest/mappers/attributes_container.rb +123 -0
  12. data/lib/wrest/mappers/resource.rb +17 -0
  13. data/lib/wrest/mappers/resource/base.rb +69 -0
  14. data/lib/wrest/mappers/resource/collection.rb +12 -0
  15. data/lib/wrest/mappers/simple_resource.rb +17 -0
  16. data/lib/wrest/response.rb +38 -0
  17. data/lib/wrest/translators.rb +26 -0
  18. data/lib/wrest/translators/content_types.rb +20 -0
  19. data/lib/wrest/translators/json.rb +21 -0
  20. data/lib/wrest/translators/typed_hash.rb +4 -0
  21. data/lib/wrest/translators/xml.rb +24 -0
  22. data/lib/wrest/uri.rb +74 -0
  23. data/lib/wrest/uri_template.rb +32 -0
  24. data/lib/wrest/version.rb +22 -0
  25. data/spec/custom_matchers/custom_matchers.rb +2 -0
  26. data/spec/rcov.opts +4 -0
  27. data/spec/spec.opts +6 -0
  28. data/spec/spec_helper.rb +18 -0
  29. data/spec/wrest/mappers/attributes_container_spec.rb +184 -0
  30. data/spec/wrest/mappers/resource/base_spec.rb +158 -0
  31. data/spec/wrest/mappers/simple_resource_spec.rb +7 -0
  32. data/spec/wrest/response_spec.rb +21 -0
  33. data/spec/wrest/translators/typed_hash_spec.rb +9 -0
  34. data/spec/wrest/translators/xml_spec.rb +12 -0
  35. data/spec/wrest/translators_spec.rb +9 -0
  36. data/spec/wrest/uri_spec.rb +131 -0
  37. data/spec/wrest/uri_template_spec.rb +28 -0
  38. metadata +128 -0
@@ -0,0 +1,123 @@
1
+ # Copyright 2009 Sidu Ponnappa
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
7
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8
+ # See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ module Wrest::Mappers #:nodoc:
11
+
12
+ # Adds behaviour allowing a class to
13
+ # contain attributes and providing support
14
+ # for dynamic getters, setters and query methods.
15
+ # These methods are added at runtime, on the first
16
+ # invocation and on a per instance basis.
17
+ # <tt>respond_to?</tt> however will respond as though
18
+ # they are all already present.
19
+ # This means that two different instances of the same
20
+ # <tt>AttributesContainer</tt> could well have
21
+ # different attribute getters/setters/query methods.
22
+ #
23
+ # Note that this means the first call to a particular
24
+ # method will be slower because the method is defined
25
+ # at that point; subsequent calls will be much faster.
26
+ #
27
+ # If you're implementing your own initialize method
28
+ # remember to delegate to the default initialize
29
+ # of AttributesContainer by invoking <tt>super(attributes)</tt>
30
+ # Also keep in mind that attribute getter/setter/query methods
31
+ # will _not_ override any existing methods on the class.
32
+ #
33
+ # In situations where this is a problem, such as a client consuming Rails
34
+ # REST services where <tt>id</tt> is a common attribute and clashes with
35
+ # Object#id it is recommended to create getter/setter/query methods
36
+ # on the class (which affects all instances) using the <tt>has_attributes</tt> macro.
37
+ module AttributesContainer
38
+ def self.included(klass) #:nodoc:
39
+ klass.extend AttributesContainer::ClassMethods
40
+ klass.class_eval{ include AttributesContainer::InstanceMethods }
41
+ end
42
+
43
+ def self.build_attribute_getter(attribute_name) #:nodoc:
44
+ "def #{attribute_name};@attributes[:#{attribute_name}];end;"
45
+ end
46
+
47
+ def self.build_attribute_setter(attribute_name) #:nodoc:
48
+ "def #{attribute_name}=(value);@attributes[:#{attribute_name}] = value;end;"
49
+ end
50
+
51
+ def self.build_attribute_queryer(attribute_name) #:nodoc:
52
+ "def #{attribute_name}?;not @attributes[:#{attribute_name}].nil?;end;"
53
+ end
54
+
55
+ module ClassMethods
56
+ # This macro explicitly creates getter, setter and query methods on
57
+ # a class, overriding any exisiting methods with the same names.
58
+ # This can be used when attribute names clash with method names;
59
+ # an example would be Rails REST services which frequently make use
60
+ # an attribute named <tt>id</tt> which clashes with Object#id. Also,
61
+ # this can be used as a performance optimisation if the incoming
62
+ # attributes are known beforehand, as defining methods on the first
63
+ # invocation is no longer necessary.
64
+ def has_attributes(*attribute_names)
65
+ attribute_names.each do |attribute_name|
66
+ self.class_eval(
67
+ AttributesContainer.build_attribute_getter(attribute_name) +
68
+ AttributesContainer.build_attribute_setter(attribute_name) +
69
+ AttributesContainer.build_attribute_queryer(attribute_name)
70
+ )
71
+ end
72
+ end
73
+ end
74
+
75
+ module InstanceMethods
76
+ # Sets up any class to act like
77
+ # an attributes container by creating
78
+ # two variables, @attributes and @interface.
79
+ # Remember not to use these two variable names
80
+ # when using AttributesContainer in your
81
+ # own class.
82
+ def initialize(attributes = {})
83
+ @attributes = attributes.symbolize_keys
84
+ @interface = Module.new
85
+ self.extend @interface
86
+ end
87
+
88
+ def [](key)
89
+ @attributes[key.to_sym]
90
+ end
91
+
92
+ def []=(key, value)
93
+ @attributes[key.to_sym] = value
94
+ end
95
+
96
+ def respond_to?(method_name, include_private = false)
97
+ super(method_name, include_private) ? true : @attributes.include?(method_name.to_s.gsub(/(\?$)|(=$)/, '').to_sym)
98
+ end
99
+
100
+ # Creates getter, setter and query methods for
101
+ # attributes on the first call.
102
+ def method_missing(method_sym, *arguments)
103
+ method_name = method_sym.to_s
104
+ attribute_name = method_name.gsub(/(\?$)|(=$)/, '')
105
+
106
+ if @attributes.include?(attribute_name.to_sym) || method_name.last == '='
107
+ case method_name.last
108
+ when '='
109
+ @interface.module_eval AttributesContainer.build_attribute_setter(attribute_name)
110
+ when '?'
111
+ @interface.module_eval AttributesContainer.build_attribute_queryer(attribute_name)
112
+ else
113
+ @interface.module_eval AttributesContainer.build_attribute_getter(attribute_name)
114
+ end
115
+ send(method_sym, *arguments)
116
+ else
117
+ super(method_sym, *arguments)
118
+ end
119
+ end
120
+
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright 2009 Sidu Ponnappa
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
7
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8
+ # See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ module Wrest
11
+ module Mappers
12
+ module Resource
13
+ end
14
+ end
15
+ end
16
+
17
+ require "#{WREST_ROOT}/wrest/mappers/resource/base"
@@ -0,0 +1,69 @@
1
+ # Copyright 2009 Sidu Ponnappa
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
7
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8
+ # See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ module Wrest::Mappers::Resource #:nodoc:
11
+ # Resource::Base is the equivalent of ActiveResource::Base.
12
+ # It is a REST client targetted at Rails REST apps.
13
+ class Base
14
+ include Wrest::Mappers::AttributesContainer
15
+
16
+ has_attributes :id
17
+ attr_reader :attributes
18
+
19
+ class << self
20
+ def inherited(klass)
21
+ klass.set_resource_name klass.name
22
+ end
23
+
24
+ # Allows the resource name to be configured and creates
25
+ # a getter method for it.
26
+ # This is a useful feature when using anonymous classes like
27
+ # we often do while writing tests.
28
+ # By default, the resource name is set to the name of the class.
29
+ def set_resource_name(resource_name)
30
+ self.class_eval "def self.resource_name; '#{resource_name}';end"
31
+ end
32
+
33
+ # Allows the host url at which the resource is found to be configured
34
+ # and creates a getter method for it.
35
+ # For example in the url
36
+ # http://localhost:3000/users/1/settings
37
+ # you would set
38
+ # http://localhost:3000
39
+ # as the host url.
40
+ def set_host(host)
41
+ self.class_eval "def self.host; '#{host}';end"
42
+ end
43
+
44
+ def resource_path
45
+ @resource_path ||= "/#{resource_name.underscore.pluralize}"
46
+ end
47
+
48
+ def resource_url
49
+ "#{host}#{resource_path}"
50
+ end
51
+
52
+ def find_all
53
+ end
54
+
55
+ def find(id)
56
+ response_hash = "#{resource_url}/#{id}".to_uri.get.deserialise
57
+ resource_type = response_hash.keys.first
58
+ if(resource_type.underscore.camelize == self.name)
59
+ self.new(response_hash[resource_type].first)
60
+ else
61
+ response_hash
62
+ end
63
+ end
64
+
65
+ def objectify(hash)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,12 @@
1
+ # Copyright 2009 Sidu Ponnappa
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
7
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8
+ # See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ # Understands how to contain a collection of Wrest::Resources
11
+ class Wrest::Mappers::Resource::Collection
12
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright 2009 Sidu Ponnappa
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
7
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8
+ # See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ module Wrest
11
+ module Mappers
12
+ class SimpleResource
13
+ include Wrest::Mappers::AttributesContainer
14
+ attr_reader :attributes
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ # Copyright 2009 Sidu Ponnappa
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
7
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8
+ # See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ module Wrest #:nodoc:
11
+ # Decorates a response providing support for deserialisation.
12
+ #
13
+ # The following methods are also available (unlisted by rdoc because they're forwarded):
14
+ #
15
+ # <tt>:@http_response, :code, :message, :body, :http_version,
16
+ # :[], :content_length, :content_type, :each_header, :each_name, :each_value, :fetch,
17
+ # :get_fields, :key?, :type_params</tt>
18
+ #
19
+ # They behave exactly like their Net::HTTPResponse equivalents.
20
+ class Response
21
+ extend Forwardable
22
+ def_delegators :@http_response, :code, :message, :body, :http_version,
23
+ :[], :content_length, :content_type, :each_header, :each_name, :each_value, :fetch,
24
+ :get_fields, :key?, :type_params
25
+
26
+ def initialize(http_response)
27
+ @http_response = http_response
28
+ end
29
+
30
+ def deserialise
31
+ deserialise_using(Wrest::Translators.load(@http_response.content_type))
32
+ end
33
+
34
+ def deserialise_using(translator)
35
+ translator.call(@http_response)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ # Copyright 2009 Sidu Ponnappa
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
7
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8
+ # See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ module Wrest
11
+ # Contains strategies/lambdas which know how to deserialise
12
+ # different content types.
13
+ module Translators
14
+ # Loads the appropriate desirialisation strategy based on
15
+ # the content type
16
+ def self.load(content_type)
17
+ translator = CONTENT_TYPES[content_type]
18
+ translator || (raise UnsupportedContentTypeException.new("Unsupported content type #{content_type}"))
19
+ end
20
+ end
21
+ end
22
+
23
+ require "#{WREST_ROOT}/wrest/translators/xml"
24
+ require "#{WREST_ROOT}/wrest/translators/json"
25
+ require "#{WREST_ROOT}/wrest/translators/content_types"
26
+ require "#{WREST_ROOT}/wrest/translators/typed_hash"
@@ -0,0 +1,20 @@
1
+ # Copyright 2009 Sidu Ponnappa
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
7
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8
+ # See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ module Wrest
11
+ module Translators
12
+ # Maps content types to deserialisers
13
+ CONTENT_TYPES = {
14
+ 'application/xml' => Wrest::Translators::Xml,
15
+ 'text/xml' => Wrest::Translators::Xml,
16
+ 'application/json' => Wrest::Translators::Json,
17
+ 'text/javascript' => Wrest::Translators::Json
18
+ }
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ # Copyright 2009 Sidu Ponnappa
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
7
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8
+ # See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ require 'json'
11
+
12
+ module Wrest
13
+ module Translators
14
+ # Knows how to deserialise json.
15
+ # Depends on the json gem.
16
+ Json = lambda{|response|
17
+ JSON.parse(response.body)
18
+ }
19
+ end
20
+ end
21
+
@@ -0,0 +1,4 @@
1
+ module Wrest::Translators
2
+ class TypedHash
3
+ end
4
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright 2009 Sidu Ponnappa
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
7
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8
+ # See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ require 'xmlsimple'
11
+
12
+ module Wrest
13
+ module Translators
14
+ # Knows how to deserialise xml.
15
+ # Depends on the xmlsimple gem.
16
+ Xml = lambda{|response|
17
+ XmlSimple.xml_in(
18
+ response.body,
19
+ 'keeproot' => true
20
+ )
21
+ }
22
+ end
23
+ end
24
+
@@ -0,0 +1,74 @@
1
+ # Copyright 2009 Sidu Ponnappa
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ # Unless required by applicable law or agreed to in writing, software distributed under the License
7
+ # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8
+ # See the License for the specific language governing permissions and limitations under the License.
9
+
10
+ module Wrest #:nodoc:
11
+ # Wrest::Uri provides a simple api for
12
+ # REST calls. String#to_uri is a convenience
13
+ # method to build a Wrest::Uri from a string url.
14
+ class Uri
15
+ attr_reader :uri
16
+ def initialize(uri_string)
17
+ @uri = URI.parse(uri_string)
18
+ end
19
+
20
+ def eql?(other)
21
+ self == other
22
+ end
23
+
24
+ def ==(other)
25
+ return false if other.class != self.class
26
+ return other.uri == self.uri
27
+ end
28
+
29
+ def hash
30
+ self.uri.hash + self.class.object_id
31
+ end
32
+
33
+ # Make a HTTP get request to this URI.
34
+ # Remember to escape the parameter strings using URI.escape
35
+ def get(parameters = {}, headers = {})
36
+ do_request 'get', parameters.empty? ? @uri.request_uri : "#{@uri.request_uri}?#{parameters.to_query}", headers.stringify_keys
37
+ end
38
+
39
+ def put(body = '', headers = {})
40
+ do_request 'put', @uri.request_uri, body.to_s, headers.stringify_keys
41
+ end
42
+
43
+ def post(body = '', headers = {})
44
+ do_request 'post', @uri.request_uri, body.to_s, headers.stringify_keys
45
+ end
46
+
47
+ def delete(headers = {})
48
+ do_request 'delete', @uri.request_uri, headers.stringify_keys
49
+ end
50
+
51
+ def do_request(method, url, *args)
52
+ response = nil
53
+
54
+ Wrest.logger.info "#{method} -> #{url}"
55
+ time = Benchmark.realtime { response = Wrest::Response.new(http.send(method, url, *args)) }
56
+ Wrest.logger.info "--> %d %s (%d %.2fs)" % [response.code, response.message, response.body ? response.body.length : 0, time]
57
+
58
+ response
59
+ end
60
+
61
+ def https?
62
+ @uri.is_a?(URI::HTTPS)
63
+ end
64
+
65
+ def http
66
+ http = Net::HTTP.new(@uri.host, @uri.port)
67
+ if https?
68
+ http.use_ssl = true
69
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
70
+ end
71
+ http
72
+ end
73
+ end
74
+ end