hyperclient 0.0.8 → 0.1.0
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.
- data/Gemfile +2 -1
- data/Readme.md +15 -21
- data/examples/cyberscore.rb +28 -16
- data/examples/hal_shop.rb +15 -22
- data/hyperclient.gemspec +2 -0
- data/lib/hyperclient.rb +2 -84
- data/lib/hyperclient/attributes.rb +20 -0
- data/lib/hyperclient/collection.rb +53 -0
- data/lib/hyperclient/entry_point.rb +49 -0
- data/lib/hyperclient/http.rb +38 -37
- data/lib/hyperclient/link.rb +93 -0
- data/lib/hyperclient/link_collection.rb +24 -0
- data/lib/hyperclient/resource.rb +26 -65
- data/lib/hyperclient/resource_collection.rb +33 -0
- data/lib/hyperclient/version.rb +1 -1
- data/test/fixtures/element.json +4 -15
- data/test/hyperclient/attributes_test.rb +26 -0
- data/test/hyperclient/collection_test.rb +39 -0
- data/test/hyperclient/entry_point_test.rb +51 -0
- data/test/hyperclient/http_test.rb +37 -23
- data/test/hyperclient/link_collection_test.rb +29 -0
- data/test/hyperclient/link_test.rb +93 -0
- data/test/hyperclient/resource_collection_test.rb +34 -0
- data/test/hyperclient/resource_test.rb +36 -42
- data/test/test_helper.rb +10 -1
- metadata +55 -16
- data/lib/hyperclient/discoverer.rb +0 -84
- data/lib/hyperclient/representation.rb +0 -43
- data/lib/hyperclient/resource_factory.rb +0 -53
- data/test/hyperclient/discoverer_test.rb +0 -101
- data/test/hyperclient/representation_test.rb +0 -52
- data/test/hyperclient/resource_factory_test.rb +0 -32
- data/test/hyperclient_test.rb +0 -68
data/lib/hyperclient/http.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'httparty'
|
2
|
-
require '
|
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
|
-
#
|
19
|
-
def_delegators :@resource, :url
|
20
|
-
|
21
|
-
# Public: Initializes a HTTP agent.
|
16
|
+
# Public: Initializes the HTTP agent.
|
22
17
|
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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).
|
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
|
88
|
-
|
89
|
-
|
90
|
-
|
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.
|
107
|
-
|
108
|
-
|
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
|
data/lib/hyperclient/resource.rb
CHANGED
@@ -1,81 +1,42 @@
|
|
1
|
-
require 'hyperclient/
|
2
|
-
require 'hyperclient/
|
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
|
-
#
|
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:
|
14
|
-
|
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:
|
18
|
-
attr_reader :
|
14
|
+
# Public: Returns the links of the Resource as a LinkCollection.
|
15
|
+
attr_reader :links
|
19
16
|
|
20
|
-
# Public:
|
21
|
-
#
|
22
|
-
|
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:
|
47
|
-
|
48
|
-
|
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:
|
56
|
-
#
|
57
|
-
#
|
58
|
-
def
|
59
|
-
|
60
|
-
|
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:
|
65
|
-
#
|
66
|
-
|
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
|
data/lib/hyperclient/version.rb
CHANGED
data/test/fixtures/element.json
CHANGED
@@ -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
|