edh 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +12 -0
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/.travis.yml +13 -0
- data/.yardopts +3 -0
- data/Gemfile +23 -0
- data/Guardfile +6 -0
- data/LICENSE +22 -0
- data/Manifest +25 -0
- data/Rakefile +15 -0
- data/autotest/discover.rb +1 -0
- data/changelog.md +4 -0
- data/edh.gemspec +28 -0
- data/lib/edh.rb +47 -0
- data/lib/edh/api.rb +93 -0
- data/lib/edh/api/rest_api.rb +58 -0
- data/lib/edh/errors.rb +78 -0
- data/lib/edh/http_service.rb +142 -0
- data/lib/edh/http_service/multipart_request.rb +40 -0
- data/lib/edh/http_service/response.rb +18 -0
- data/lib/edh/test_users.rb +188 -0
- data/lib/edh/utils.rb +20 -0
- data/lib/edh/version.rb +3 -0
- data/readme.md +42 -0
- data/spec/cases/api_spec.rb +143 -0
- data/spec/cases/edh_spec.rb +64 -0
- data/spec/cases/edh_test_spec.rb +5 -0
- data/spec/cases/error_spec.rb +104 -0
- data/spec/cases/http_service_spec.rb +324 -0
- data/spec/cases/multipart_request_spec.rb +66 -0
- data/spec/cases/utils_spec.rb +24 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/custom_matchers.rb +28 -0
- data/spec/support/edh_test.rb +185 -0
- data/spec/support/mock_http_service.rb +124 -0
- data/spec/support/ordered_hash.rb +201 -0
- data/spec/support/rest_api_shared_examples.rb +114 -0
- metadata +182 -0
data/.autotest
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --order rand
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem "yard"
|
5
|
+
end
|
6
|
+
|
7
|
+
group :development, :test do
|
8
|
+
gem "typhoeus" unless defined? JRUBY_VERSION
|
9
|
+
|
10
|
+
# Testing infrastructure
|
11
|
+
gem 'guard'
|
12
|
+
gem 'guard-rspec'
|
13
|
+
|
14
|
+
if RUBY_PLATFORM =~ /darwin/
|
15
|
+
# OS X integration
|
16
|
+
gem "ruby_gntp"
|
17
|
+
gem "rb-fsevent"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
gem "jruby-openssl" if defined? JRUBY_VERSION
|
22
|
+
|
23
|
+
gemspec
|
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2010-2011 Alex Koppel, originally Koala Gem
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
LICENSE
|
3
|
+
Manifest
|
4
|
+
Rakefile
|
5
|
+
init.rb
|
6
|
+
edh.gemspec
|
7
|
+
lib/edh.rb
|
8
|
+
lib/edh/http_services.rb
|
9
|
+
lib/edh/rest_api.rb
|
10
|
+
lib/edh/test_users.rb
|
11
|
+
readme.md
|
12
|
+
spec/edh/api_base_tests.rb
|
13
|
+
spec/edh/assets/beach.jpg
|
14
|
+
spec/edh/http_services/http_service_tests.rb
|
15
|
+
spec/edh/http_services/net_http_service_tests.rb
|
16
|
+
spec/edh/http_services/typhoeus_service_tests.rb
|
17
|
+
spec/edh/live_testing_data_helper.rb
|
18
|
+
spec/edh/rest_api/rest_api_no_access_token_tests.rb
|
19
|
+
spec/edh/rest_api/rest_api_tests.rb
|
20
|
+
spec/edh/rest_api/rest_api_with_access_token_tests.rb
|
21
|
+
spec/edh/test_users/test_users_tests.rb
|
22
|
+
spec/edh_spec.rb
|
23
|
+
spec/edh_spec_helper.rb
|
24
|
+
spec/edh_spec_without_mocks.rb
|
25
|
+
spec/mock_http_service.rb
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
rescue LoadError
|
7
|
+
puts 'although not required, bundler is recommened for running the tests'
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :spec
|
11
|
+
|
12
|
+
require 'rspec/core/rake_task'
|
13
|
+
RSpec::Core::RakeTask.new do |t|
|
14
|
+
t.rspec_opts = ["--color", '--format doc']
|
15
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Autotest.add_discovery { "rspec2" }
|
data/changelog.md
ADDED
data/edh.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
3
|
+
require 'edh/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "edh"
|
7
|
+
gem.summary = "A lightweight, flexible library for EDH Passport"
|
8
|
+
gem.description = "A lightweight, flexible library for EDH Passport"
|
9
|
+
gem.homepage = "http://github.com/everydayhero/edh"
|
10
|
+
gem.version = EDH::VERSION
|
11
|
+
|
12
|
+
gem.authors = ["Alex Koppel", "Joel Richards"]
|
13
|
+
gem.email = "joelr@everydayhero.com.au"
|
14
|
+
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.files = `git ls-files`.split("\n")
|
17
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
|
20
|
+
gem.extra_rdoc_files = ["readme.md", "changelog.md"]
|
21
|
+
gem.rdoc_options = ["--line-numbers", "--inline-source", "--title", "EDH"]
|
22
|
+
|
23
|
+
gem.add_runtime_dependency("multi_json")
|
24
|
+
gem.add_runtime_dependency("faraday")
|
25
|
+
gem.add_runtime_dependency("addressable")
|
26
|
+
gem.add_development_dependency("rspec")
|
27
|
+
gem.add_development_dependency("rake")
|
28
|
+
end
|
data/lib/edh.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# useful tools
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'multi_json'
|
4
|
+
|
5
|
+
# include edh modules
|
6
|
+
require 'edh/errors'
|
7
|
+
require 'edh/api'
|
8
|
+
require 'edh/test_users'
|
9
|
+
|
10
|
+
# HTTP module so we can communicate with Passport
|
11
|
+
require 'edh/http_service'
|
12
|
+
|
13
|
+
# miscellaneous
|
14
|
+
require 'edh/utils'
|
15
|
+
require 'edh/version'
|
16
|
+
|
17
|
+
module EDH
|
18
|
+
# A Ruby client library for the Passport Platform.
|
19
|
+
|
20
|
+
# Making HTTP requests
|
21
|
+
class << self
|
22
|
+
# Control which HTTP service framework EDH uses.
|
23
|
+
attr_accessor :http_service
|
24
|
+
end
|
25
|
+
|
26
|
+
# @private
|
27
|
+
# For current HTTPServices, switch the service as expected.
|
28
|
+
def self.http_service=(service)
|
29
|
+
if service.respond_to?(:deprecated_interface)
|
30
|
+
# if this is a deprecated module, support the old interface
|
31
|
+
# by changing the default adapter so the right library is used
|
32
|
+
# we continue to use the single HTTPService module for everything
|
33
|
+
service.deprecated_interface
|
34
|
+
else
|
35
|
+
# if it's a real http_service, use it
|
36
|
+
@http_service = service
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# An convenenient alias to EDH.http_service.make_request.
|
41
|
+
def self.make_request(path, args, verb, options = {})
|
42
|
+
http_service.make_request(path, args, verb, options)
|
43
|
+
end
|
44
|
+
|
45
|
+
# we use Faraday as our main service, with mock as the other main one
|
46
|
+
self.http_service = HTTPService
|
47
|
+
end
|
data/lib/edh/api.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# graph_batch_api and legacy are required at the bottom, since they depend on API being defined
|
2
|
+
require 'edh/api/rest_api'
|
3
|
+
|
4
|
+
module EDH
|
5
|
+
module Passport
|
6
|
+
class API
|
7
|
+
# Creates a new API client.
|
8
|
+
# @param [String] access_token access token
|
9
|
+
# @note If no access token is provided, you can only access some public information.
|
10
|
+
# @return [EDH::Passport::API] the API client
|
11
|
+
def initialize(access_token = nil, server = nil)
|
12
|
+
@access_token = access_token
|
13
|
+
@server = server
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :access_token
|
17
|
+
|
18
|
+
include RestAPIMethods
|
19
|
+
|
20
|
+
# Makes a request to the appropriate Passport API.
|
21
|
+
# @note You'll rarely need to call this method directly.
|
22
|
+
#
|
23
|
+
# @see RestAPIMethods#rest_call
|
24
|
+
#
|
25
|
+
# @param path the server path for this request (leading / is prepended if not present)
|
26
|
+
# @param args arguments to be sent to Passport
|
27
|
+
# @param verb the HTTP method to use
|
28
|
+
# @param options request-related options for EDH and Faraday.
|
29
|
+
# @option options [Symbol] :http_component which part of the response (headers, body, or status) to return
|
30
|
+
# @option options [Boolean] :use_ssl force SSL for this request, even if it's tokenless.
|
31
|
+
# (All API requests with access tokens use SSL.)
|
32
|
+
# @param error_checking_block a block to evaluate the response status for additional JSON-encoded errors
|
33
|
+
#
|
34
|
+
# @yield The response for evaluation
|
35
|
+
#
|
36
|
+
# @raise [EDH::Passport::ServerError] if Passport returns an error (response status >= 500)
|
37
|
+
#
|
38
|
+
# @return the body of the response from Passport (unless another http_component is requested)
|
39
|
+
def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
|
40
|
+
# If a access token is explicitly provided, use that
|
41
|
+
# This is explicitly needed in batch requests so GraphCollection
|
42
|
+
# results preserve any specific access tokens provided
|
43
|
+
args["access_token"] ||= @access_token || @app_access_token if @access_token || @app_access_token
|
44
|
+
options.merge!({:server => @server}) unless @server.nil?
|
45
|
+
|
46
|
+
# Translate any arrays in the params into comma-separated strings
|
47
|
+
args = sanitize_request_parameters(args)
|
48
|
+
|
49
|
+
# add a leading /
|
50
|
+
path = "/#{path}" unless path =~ /^\//
|
51
|
+
|
52
|
+
# make the request via the provided service
|
53
|
+
result = EDH.make_request(path, args, verb, options)
|
54
|
+
|
55
|
+
if result.status.to_i >= 500
|
56
|
+
raise EDH::Passport::ServerError.new(result.status.to_i, result.body)
|
57
|
+
end
|
58
|
+
|
59
|
+
yield result if error_checking_block
|
60
|
+
|
61
|
+
# if we want a component other than the body (e.g. redirect header for images), return that
|
62
|
+
if component = options[:http_component]
|
63
|
+
component == :response ? result : result.send(options[:http_component])
|
64
|
+
else
|
65
|
+
# parse the body as JSON and run it through the error checker (if provided)
|
66
|
+
# Note: Passport sometimes sends results like "true" and "false", which aren't strictly objects
|
67
|
+
# and cause MultiJson.load to fail -- so we account for that by wrapping the result in []
|
68
|
+
MultiJson.load("[#{result.body.to_s}]")[0]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Sanitizes Ruby objects into Passport-compatible string values.
|
75
|
+
#
|
76
|
+
# @param parameters a hash of parameters.
|
77
|
+
#
|
78
|
+
# Returns a hash in which values that are arrays of non-enumerable values
|
79
|
+
# (Strings, Symbols, Numbers, etc.) are turned into comma-separated strings.
|
80
|
+
def sanitize_request_parameters(parameters)
|
81
|
+
parameters.reduce({}) do |result, (key, value)|
|
82
|
+
# if the parameter is an array that contains non-enumerable values,
|
83
|
+
# turn it into a comma-separated list
|
84
|
+
# in Ruby 1.8.7, strings are enumerable, but we don't care
|
85
|
+
if value.is_a?(Array) && value.none? {|entry| entry.is_a?(Enumerable) && !entry.is_a?(String)}
|
86
|
+
value = value.join(",")
|
87
|
+
end
|
88
|
+
result.merge(key => value)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module EDH
|
2
|
+
module Passport
|
3
|
+
REST_SERVER = "passport.everydayhero.com/api/v1"
|
4
|
+
|
5
|
+
# Methods used to interact with Passport's REST API.
|
6
|
+
|
7
|
+
module RestAPIMethods
|
8
|
+
#convenience methods
|
9
|
+
def create pp_method, args = {}, options = {}
|
10
|
+
rest_call("me/#{pp_method}", args, options, :post)
|
11
|
+
end
|
12
|
+
|
13
|
+
def delete pp_action_uid, args = {}, options = {}
|
14
|
+
rest_call("actions/#{pp_action_uid}", args, options, :delete)
|
15
|
+
end
|
16
|
+
|
17
|
+
def update pp_action_uid, args = {}, options = {}
|
18
|
+
rest_call("actions/#{pp_action_uid}", args, options, :put)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Make a call to the REST API.
|
22
|
+
#
|
23
|
+
# @note The order of the last two arguments is non-standard (for historical reasons). Sorry.
|
24
|
+
#
|
25
|
+
# @param pp_method the API call you want to make
|
26
|
+
#
|
27
|
+
# @raise [EDH::Passport::APIError] if Passport returns an error
|
28
|
+
#
|
29
|
+
# @return the result from Passport
|
30
|
+
def rest_call(pp_method, args = {}, options = {}, verb = "get")
|
31
|
+
args = MultiJson.load(args) if args.is_a?(String)
|
32
|
+
api("#{pp_method}", args.merge('format' => 'json'), verb, options) do |response|
|
33
|
+
# check for REST API-specific errors
|
34
|
+
if response.status >= 400
|
35
|
+
begin
|
36
|
+
response_hash = MultiJson.load(response.body)
|
37
|
+
rescue MultiJson::DecodeError
|
38
|
+
response_hash = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
error_info = {
|
42
|
+
'code' => response_hash['error_code'],
|
43
|
+
'error_subcode' => response_hash['error_subcode'],
|
44
|
+
'message' => response_hash['error_msg']
|
45
|
+
}
|
46
|
+
|
47
|
+
if response.status >= 500
|
48
|
+
raise ServerError.new(response.status, response.body, error_info)
|
49
|
+
else
|
50
|
+
raise ClientError.new(response.status, response.body, error_info)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end # module Passport
|
58
|
+
end # module EDH
|
data/lib/edh/errors.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module EDH
|
2
|
+
|
3
|
+
class EDHError < StandardError; end
|
4
|
+
|
5
|
+
module Passport
|
6
|
+
|
7
|
+
class APIError < ::EDH::EDHError
|
8
|
+
attr_accessor :pp_error_type, :pp_error_code, :pp_error_subcode, :pp_error_message,
|
9
|
+
:http_status, :response_body
|
10
|
+
|
11
|
+
# Create a new API Error
|
12
|
+
#
|
13
|
+
# @param http_status [Integer] The HTTP status code of the response
|
14
|
+
# @param response_body [String] The response body
|
15
|
+
# @param error_info One of the following:
|
16
|
+
# [Hash] The error information extracted from the request
|
17
|
+
# ("type", "code", "error_subcode", "message")
|
18
|
+
# [String] The error description
|
19
|
+
# If error_info is nil or not provided, the method will attempt to extract
|
20
|
+
# the error info from the response_body
|
21
|
+
#
|
22
|
+
# @return the newly created APIError
|
23
|
+
def initialize(http_status, response_body, error_info = nil)
|
24
|
+
if response_body
|
25
|
+
self.response_body = response_body.strip
|
26
|
+
else
|
27
|
+
self.response_body = ''
|
28
|
+
end
|
29
|
+
self.http_status = http_status
|
30
|
+
|
31
|
+
if error_info && error_info.is_a?(String)
|
32
|
+
message = error_info
|
33
|
+
else
|
34
|
+
unless error_info
|
35
|
+
begin
|
36
|
+
error_info = MultiJson.load(response_body)['error'] if response_body
|
37
|
+
rescue
|
38
|
+
end
|
39
|
+
error_info ||= {}
|
40
|
+
end
|
41
|
+
|
42
|
+
self.pp_error_type = error_info["type"]
|
43
|
+
self.pp_error_code = error_info["code"]
|
44
|
+
self.pp_error_subcode = error_info["error_subcode"]
|
45
|
+
self.pp_error_message = error_info["message"]
|
46
|
+
|
47
|
+
error_array = []
|
48
|
+
%w(type code error_subcode message).each do |key|
|
49
|
+
error_array << "#{key}: #{error_info[key]}" if error_info[key]
|
50
|
+
end
|
51
|
+
|
52
|
+
if error_array.empty?
|
53
|
+
message = self.response_body
|
54
|
+
else
|
55
|
+
message = error_array.join(', ')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
message += " [HTTP #{http_status}]" if http_status
|
59
|
+
|
60
|
+
super(message)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Passport returned an invalid response body
|
65
|
+
class BadPassportResponse < APIError; end
|
66
|
+
|
67
|
+
# Any error with a 5xx HTTP status code
|
68
|
+
class ServerError < APIError; end
|
69
|
+
|
70
|
+
# Any error with a 4xx HTTP status code
|
71
|
+
class ClientError < APIError; end
|
72
|
+
|
73
|
+
# All graph API authentication failures.
|
74
|
+
class AuthenticationError < ClientError; end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|