hyperclient 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  require 'httparty'
2
- require 'forwardable'
2
+ require 'json'
3
3
 
4
4
  # Public: A parser for HTTParty that understand the mime application/hal+json.
5
5
  class JSONHalParser < HTTParty::Parser
@@ -10,30 +10,45 @@ module Hyperclient
10
10
  # Internal: This class wrapps HTTParty and performs the HTTP requests for a
11
11
  # resource.
12
12
  class HTTP
13
- extend Forwardable
14
13
  include HTTParty
15
-
16
14
  parser JSONHalParser
17
15
 
18
- # Private: Delegate the url to the resource.
19
- def_delegators :@resource, :url
20
-
21
- # Public: Initializes a HTTP agent.
16
+ # Public: Initializes the HTTP agent.
22
17
  #
23
- # resource - A Resource instance. A Resource is given instead of the url
24
- # since the resource url could change during its live.
25
- def initialize(resource, options = {})
26
- @resource = resource
27
- authenticate(options[:auth]) if options && options.include?(:auth)
28
- headers(options[:headers]) if options && options.include?(:headers)
29
- enable_debug(options[:debug]) if options && options.include?(:debug)
18
+ # url - A String to send the HTTP requests.
19
+ # config - A Hash with the configuration of the HTTP connection.
20
+ # :headers - The Hash with the headers of the connection.
21
+ # :auth - The Hash with the authentication options:
22
+ # :type - A String or Symbol to set the authentication type.
23
+ # Allowed values are :digest or :basic.
24
+ # :user - A String with the user.
25
+ # :password - A String with the user.
26
+ # :debug - The flag (true/false) to debug the HTTP connections.
27
+ #
28
+ def initialize(url, config)
29
+ @url = url
30
+ @config = config
31
+ @base_uri = config.fetch(:base_uri)
32
+
33
+ authenticate!
34
+ toggle_debug! if @config[:debug]
35
+
36
+ self.class.headers(@config[:headers]) if @config.include?(:headers)
37
+ end
38
+
39
+ def url
40
+ begin
41
+ URI.parse(@base_uri).merge(@url).to_s
42
+ rescue URI::InvalidURIError
43
+ @url
44
+ end
30
45
  end
31
46
 
32
47
  # Public: Sends a GET request the the resource url.
33
48
  #
34
49
  # Returns: The parsed response.
35
50
  def get
36
- self.class.get(url).parsed_response
51
+ JSON.parse(self.class.get(url).response.body)
37
52
  end
38
53
 
39
54
  # Public: Sends a POST request the the resource url.
@@ -79,34 +94,20 @@ module Hyperclient
79
94
  # Internal: Sets the authentication method for HTTParty.
80
95
  #
81
96
  # options - An options Hash to set the authentication options.
82
- # :type - A String or Symbol to set the authentication type.
83
- # Can be either :digest or :basic.
84
- # :credentials - An Array of Strings with the user and password.
85
97
  #
86
98
  # Returns nothing.
87
- def authenticate(options)
88
- auth_method = options[:type].to_s + '_auth'
89
- self.class.send(auth_method, *options[:credentials])
90
- end
91
-
92
- # Internal: Adds default headers for all the requests.
93
- #
94
- # headers - A Hash with the header.
95
- #
96
- # Example:
97
- # headers({'accept-encoding' => 'deflate, gzip'})
98
- #
99
- # Returns nothing.
100
- def headers(headers)
101
- self.class.send(:headers, headers)
99
+ def authenticate!
100
+ if (options = @config[:auth])
101
+ auth_method = options.delete(:type).to_s + '_auth'
102
+ self.class.send(auth_method, options[:user], options[:password])
103
+ end
102
104
  end
103
105
 
104
106
  # Internal: Enables HTTP debugging.
105
107
  #
106
- # stream - An object to stream the HTTP out to or just a truthy value. If
107
- # it's truthy it will output to $stderr.
108
- def enable_debug(stream)
109
- return unless stream
108
+ # stream - An object to stream the HTTP out to or just a truthy value.
109
+ def toggle_debug!
110
+ stream = @config[:debug]
110
111
 
111
112
  if stream.respond_to?(:<<)
112
113
  self.class.debug_output(stream)
@@ -0,0 +1,93 @@
1
+ require 'hyperclient/http'
2
+ require 'hyperclient/resource'
3
+ require 'uri_template'
4
+
5
+ module Hyperclient
6
+ # Internal: The Link is used to let a Resource interact with the API.
7
+ #
8
+ class Link
9
+ extend Forwardable
10
+ # Public: Delegate all HTTP methods (get, post, put, delete, options and
11
+ # head) to the http connection.
12
+ def_delegators :http, :get, :post, :put, :delete, :options, :head
13
+
14
+ # Public: Initializes a new Link.
15
+ #
16
+ # link - The String with the URI of the link.
17
+ # entry_point - The EntryPoint object to inject the cofnigutation.
18
+ # uri_variables - The optional Hash with the variables to expand the link
19
+ # if it is templated.
20
+ def initialize(link, entry_point, uri_variables = nil)
21
+ @link = link
22
+ @entry_point = entry_point
23
+ @uri_variables = uri_variables
24
+ end
25
+
26
+ # Public: Returns the Resource which the Link is pointing to.
27
+ def resource
28
+ @resource ||=Resource.new(http.get, @entry_point)
29
+ end
30
+
31
+ # Public: Indicates if the link is an URITemplate or a regular URI.
32
+ #
33
+ # Returns true if it is templated.
34
+ # Returns false if it nos templated.
35
+ def templated?
36
+ !!@link['templated']
37
+ end
38
+
39
+ # Public: Expands the Link when is templated with the given variables.
40
+ #
41
+ # uri_variables - The Hash with the variables to expand the URITemplate.
42
+ #
43
+ # Returns a new Link with the expanded variables.
44
+ def expand(uri_variables)
45
+ self.class.new(@link, @entry_point, uri_variables)
46
+ end
47
+
48
+ # Public: Returns the url of the Link.
49
+ #
50
+ # Raises MissingURITemplateVariables if the Link is templated but there are
51
+ # no uri variables to expand it.
52
+ def url
53
+ return @link['href'] unless templated?
54
+ raise MissingURITemplateVariablesException if @uri_variables == nil
55
+
56
+ @url ||= URITemplate.new(@link['href']).expand(@uri_variables)
57
+ end
58
+
59
+ private
60
+ # Internal: Returns the HTTP client used to interact with the API.
61
+ def http
62
+ @http ||= HTTP.new(url, @entry_point.config)
63
+ end
64
+
65
+ # Internal: Delegate the method to the API if it exists.
66
+ #
67
+ # This allows `api.links.posts.embedded` instead of
68
+ # `api.links.posts.resource.embedded`
69
+ def method_missing(method, *args, &block)
70
+ if resource.respond_to?(method)
71
+ resource.send(method, *args, &block)
72
+ else
73
+ super
74
+ end
75
+ end
76
+
77
+ # Internal: Accessory method to allow the link respond to the
78
+ # methods that will hit method_missing.
79
+ def respond_to_missing?(method, include_private = false)
80
+ resource.respond_to?(method.to_s)
81
+ end
82
+ end
83
+
84
+ # Public: Exception that is raised when building a templated Link without uri
85
+ # variables.
86
+ class MissingURITemplateVariablesException < StandardError
87
+
88
+ # Public: Returns a String with the exception message.
89
+ def message
90
+ "The URL to this links is templated, but no variables where given."
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,24 @@
1
+ require 'hyperclient/collection'
2
+ require 'hyperclient/link'
3
+
4
+ module Hyperclient
5
+ # Public: A wrapper class to easily acces the links in a Resource.
6
+ #
7
+ # Examples
8
+ #
9
+ # resource.links['author']
10
+ # resource.links.author
11
+ #
12
+ class LinkCollection < Collection
13
+ # Public: Initializes a LinkCollection.
14
+ #
15
+ # collection - The Hash with the links.
16
+ # entry_point - The EntryPoint object to inject the cofnigutation.
17
+ #
18
+ def initialize(collection, entry_point)
19
+ @collection = (collection || {}).inject({}) do |hash, (name, link)|
20
+ hash.update(name => Link.new(link, entry_point))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,81 +1,42 @@
1
- require 'hyperclient/representation'
2
- require 'hyperclient/http'
1
+ require 'hyperclient/attributes'
2
+ require 'hyperclient/link_collection'
3
+ require 'hyperclient/resource_collection'
3
4
 
4
5
  module Hyperclient
5
6
  # Public: Represents a resource from your API. Its responsability is to
6
- # perform HTTP requests against itself and ease the way you access the
7
- # resource's attributes, links and embedded resources.
7
+ # ease the way you access its attributes, links and embedded resources.
8
8
  class Resource
9
9
  extend Forwardable
10
- # Public: Delegate attributes and resources to the representation.
11
- def_delegators :representation, :attributes, :embedded, :links
12
10
 
13
- # Public: Delegate all HTTP methods (get, post, put, delete, options and
14
- # head) to Hyperclient::HTTP.
15
- def_delegators :@http, :get, :post, :put, :delete, :options, :head
11
+ # Public: Returns the attributes of the Resource as Attributes.
12
+ attr_reader :attributes
16
13
 
17
- # Public: A String representing the Resource name.
18
- attr_reader :name
14
+ # Public: Returns the links of the Resource as a LinkCollection.
15
+ attr_reader :links
19
16
 
20
- # Public: Initializes a Resource.
21
- #
22
- # url - A String with the url of the resource. Can be either absolute or
23
- # relative.
24
- #
25
- # options - An options Hash to initialize different values:
26
- # :name - The String name of the resource.
27
- # :representation - An optional Hash representation of the resource's
28
- # HTTP representation.
29
- # :http - An optional Hash to pass to the HTTP class.
30
- def initialize(url, options = {})
31
- @url = url
32
- @name = options[:name]
33
- @http = HTTP.new(self, options[:http])
34
- initialize_representation(options[:representation])
35
- end
36
-
37
- # Public: Sets the entry point for all the resources in your API client.
38
- #
39
- # url - A String with the URL of your API entry point.
40
- #
41
- # Returns nothing.
42
- def self.entry_point=(url)
43
- @@entry_point = URI(url)
44
- end
17
+ # Public: Returns the embedded resource of the Resource as a
18
+ # ResourceCollection.
19
+ attr_reader :embedded
45
20
 
46
- # Public: Returns A String representing the resource url.
47
- def url
48
- begin
49
- @@entry_point.merge(@url).to_s
50
- rescue URI::InvalidURIError
51
- @url
52
- end
53
- end
21
+ # Public: Delegate all HTTP methods (get, post, put, delete, options and
22
+ # head) to its Link.
23
+ def_delegators :self_link, :get, :post, :put, :delete, :options, :head
54
24
 
55
- # Public: Gets a fresh representation from the resource representation.
56
- #
57
- # Returns itself (this way you can chain method calls).
58
- def reload
59
- initialize_representation(get)
60
- self
25
+ # Public: Initializes a Resource.
26
+ # representation - The hash with the HAL representation of the Resource.
27
+ # entry_point - The EntryPoint object to inject the cofnigutation.
28
+ def initialize(representation, entry_point)
29
+ @links = LinkCollection.new(representation['_links'], entry_point)
30
+ @embedded = ResourceCollection.new(representation['_embedded'], entry_point)
31
+ @attributes = Attributes.new(representation)
32
+ @entry_point = entry_point
61
33
  end
62
34
 
63
35
  private
64
- # Internal: Initializes a Representation
65
- #
66
- # raw_representation - A Hash representing the HTTP representation for the resource.
67
- #
68
- # Return nothing.
69
- def initialize_representation(raw_representation)
70
- if raw_representation && !raw_representation.empty?
71
- @representation = Representation.new(raw_representation)
72
- end
73
- end
74
-
75
- # Internal: Returns the resource representation.
76
- def representation
77
- reload unless @representation
78
- @representation
36
+ # Internal: Returns the self Link of the Resource. Used to handle the HTTP
37
+ # connections.
38
+ def self_link
39
+ @links['self']
79
40
  end
80
41
  end
81
42
  end
@@ -0,0 +1,33 @@
1
+ require 'hyperclient/collection'
2
+ require 'hyperclient/resource'
3
+
4
+ module Hyperclient
5
+ # Public: A wrapper class to easily acces the embedded resources in a
6
+ # Resource.
7
+ #
8
+ # Examples
9
+ #
10
+ # resource.embedded['comments']
11
+ # resource.embedded.comments
12
+ #
13
+ class ResourceCollection < Collection
14
+ # Public: Initializes a ResourceCollection.
15
+ #
16
+ # collection - The Hash with the embedded resources.
17
+ # entry_point - The EntryPoint object to inject the cofnigutation.
18
+ #
19
+ def initialize(collection, entry_point)
20
+ @entry_point = entry_point
21
+ @collection = (collection || {}).inject({}) do |hash, (name, resource)|
22
+ hash.update(name => build_resource(resource))
23
+ end
24
+ end
25
+
26
+ private
27
+ def build_resource(representation)
28
+ return representation.map(&method(:build_resource)) if representation.is_a?(Array)
29
+
30
+ Resource.new(representation, @entry_point)
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module Hyperclient
2
- VERSION = "0.0.8"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -4,7 +4,8 @@
4
4
  "href": "/productions/1"
5
5
  },
6
6
  "filter": {
7
- "href": "/productions/1?categories="
7
+ "href": "/productions/1?categories={filter}",
8
+ "templated": true
8
9
  }
9
10
  },
10
11
  "title": "Real World ASP.NET MVC3",
@@ -25,16 +26,10 @@
25
26
  "self": {
26
27
  "href": "/episodes/1"
27
28
  },
28
- "media": [
29
- {
29
+ "media": {
30
30
  "type": "video/webm; codecs='vp8.0, vorbis'",
31
31
  "href": "/media/1"
32
- },
33
- {
34
- "type": "video/ogg; codecs='theora, vorbis'",
35
- "href": "/media/2"
36
32
  }
37
- ]
38
33
  },
39
34
  "title": "Foundations",
40
35
  "description": "In this episode we talk about what it is we're doing: building our startup and getting ourselves off the ground. We take..",
@@ -45,16 +40,10 @@
45
40
  "self": {
46
41
  "href": "/episodes/2"
47
42
  },
48
- "media": [
49
- {
50
- "type": "video/webm; codecs='vp8.0, vorbis'",
51
- "href": "/media/3"
52
- },
53
- {
43
+ "media": {
54
44
  "type": "video/ogg; codecs='theora, vorbis'",
55
45
  "href": "/media/4"
56
46
  }
57
- ]
58
47
  },
59
48
  "title": "Membership",
60
49
  "description": "In this episode Rob hooks up testing in an effort to deal with ASP.NET Membership. The team has decided..",
@@ -0,0 +1,26 @@
1
+ require_relative '../test_helper'
2
+ require 'hyperclient/attributes'
3
+
4
+ module Hyperclient
5
+ describe Attributes do
6
+ let(:representation) do
7
+ JSON.parse( File.read('test/fixtures/element.json'))
8
+ end
9
+
10
+ let(:attributes) do
11
+ Attributes.new(representation)
12
+ end
13
+
14
+ it 'does not set _links as an attribute' do
15
+ attributes.wont_respond_to :_links
16
+ end
17
+
18
+ it 'does not set _embedded as an attribute' do
19
+ attributes.wont_respond_to :_embedded
20
+ end
21
+
22
+ it 'is a collection' do
23
+ Attributes.ancestors.must_include Collection
24
+ end
25
+ end
26
+ end