dialers 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.
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "dialers/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dialers"
8
+ spec.version = Dialers::VERSION
9
+ spec.authors = ["juliogarciag"]
10
+ spec.email = ["julioggonz@gmail.com"]
11
+
12
+ spec.summary = "Api Wrappers for Ruby"
13
+ spec.description = "Api Wrappers for Ruby"
14
+ spec.homepage = "https://github.com/platanus/dialers"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency "faraday", "~> 0.9"
25
+ spec.add_dependency "faraday_middleware", "~> 0.9"
26
+ spec.add_dependency "faraday-conductivity", "~> 0.3"
27
+ spec.add_development_dependency "bundler", "~> 1.10"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "rspec", "~> 3.3"
30
+ spec.add_development_dependency "pry", "~> 0.10"
31
+ spec.add_development_dependency "guard", "~> 2.13"
32
+ spec.add_development_dependency "guard-rspec", "~> 4.6"
33
+ spec.add_development_dependency "rspec-nc", "~> 0.2"
34
+ spec.add_development_dependency "rspec-legacy_formatters", "~> 1.0"
35
+ spec.add_development_dependency "simple_oauth", "~> 0.3"
36
+ spec.add_development_dependency "patron", "~> 0.4"
37
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "./api_caller"
2
+
3
+ module Github
4
+ class Repository
5
+ attr_accessor :id, :name, :description, :language
6
+
7
+ def to_s
8
+ "#{id} : #{name} : #{description} : #{language}"
9
+ end
10
+ end
11
+
12
+ class Api < Dialers::Wrapper
13
+ api_caller { ApiCaller.new }
14
+
15
+ def user_repos(username)
16
+ api_caller.get("users/#{username}/repos").transform_to_many(Repository)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ require "dialers"
2
+
3
+ module Github
4
+ class ApiCaller < Dialers::Caller
5
+ TIMEOUT_IN_SECONDS = 5
6
+ GITHUB_API_URL = "https://api.github.com"
7
+
8
+ setup_api(url: GITHUB_API_URL) do |faraday|
9
+ faraday.request :json
10
+ faraday.request :request_headers, accept: "application/vnd.github.v3+json"
11
+ faraday.response :json
12
+ faraday.adapter :net_http
13
+ faraday.options.timeout = TIMEOUT_IN_SECONDS
14
+ faraday.options.open_timeout = TIMEOUT_IN_SECONDS
15
+ end
16
+
17
+ short_circuits.add(
18
+ if: -> (response) { Dialers::Status.new(response.status).server_error? },
19
+ do: -> (response) { fail Dialers::ServerError.new(response) }
20
+ )
21
+
22
+ short_circuits.add(
23
+ if: -> (response) { Dialers::Status.new(response.status).is?(404) },
24
+ do: -> (response) { fail Dialers::NotFoundError.new(response) }
25
+ )
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ require_relative "./api"
2
+
3
+ username = ARGV.first
4
+ github = Github::Api.new
5
+
6
+ begin
7
+ github.user_repos(username).each do |repository|
8
+ puts repository.to_s
9
+ end
10
+ rescue Dialers::NotFoundError
11
+ puts "Not Found Username #{username}."
12
+ end
@@ -0,0 +1,27 @@
1
+ require_relative "./api_caller"
2
+
3
+ module Twitter
4
+ class Tweet
5
+ attr_accessor :created_at, :text
6
+ end
7
+
8
+ class User
9
+ attr_accessor :screen_name, :profile_image_url
10
+ end
11
+
12
+ class Api < Dialers::Wrapper
13
+ api_caller { ApiCaller.new }
14
+
15
+ def get_user_timeline
16
+ api_caller.get("statuses/user_timeline.json").transform_to_many(Tweet)
17
+ end
18
+
19
+ def get_user
20
+ api_caller.get("account/verify_credentials.json").transform_to_one(User)
21
+ end
22
+
23
+ def search(query)
24
+ api_caller.get("search/tweets.json", q: query).transform_to_many(Tweet, root: "statuses")
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,43 @@
1
+ require "dialers"
2
+
3
+ module Twitter
4
+ class ApiCaller < Dialers::Caller
5
+ BASE_URL = "https://api.twitter.com/1.1/"
6
+ CONSUMER_KEY = ENV["TWITTER_CONSUMER_KEY"]
7
+ CONSUMER_SECRET = ENV["TWITTER_CONSUMER_SECRET"]
8
+ TOKEN = ENV["TWITTER_TOKEN"]
9
+ TOKEN_SECRET = ENV["TWITTER_TOKEN_SECRET"]
10
+
11
+ setup_api(url: BASE_URL, ssl: { verify: true }) do |faraday|
12
+ faraday.request :json
13
+ faraday.request :oauth,
14
+ consumer_key: CONSUMER_KEY,
15
+ consumer_secret: CONSUMER_SECRET,
16
+ token: TOKEN,
17
+ token_secret: TOKEN_SECRET
18
+ faraday.request :request_headers, accept: "*/*"
19
+ faraday.response :json
20
+ faraday.adapter :patron
21
+ end
22
+
23
+ short_circuits.add(
24
+ if: -> (response) { Dialers::Status.new(response.status).server_error? },
25
+ do: -> (response) { fail Dialers::ServerError.new(response) }
26
+ )
27
+
28
+ short_circuits.add(
29
+ if: -> (response) { Dialers::Status.new(response.status).is?(400) },
30
+ do: -> (response) { fail Dialers::ResponseError.new(response) }
31
+ )
32
+
33
+ short_circuits.add(
34
+ if: -> (response) { Dialers::Status.new(response.status).is?(401) },
35
+ do: -> (response) { fail Dialers::UnauthorizedError.new(response) }
36
+ )
37
+
38
+ short_circuits.add(
39
+ if: -> (response) { Dialers::Status.new(response.status).is?(404) },
40
+ do: -> (response) { fail Dialers::NotFoundError.new(response) }
41
+ )
42
+ end
43
+ end
@@ -0,0 +1,16 @@
1
+ require_relative "./api"
2
+ require "simple_oauth"
3
+
4
+ api = Twitter::Api.new
5
+ keyword = ARGV.first
6
+
7
+ ruby_tweet = api.get_user_timeline.find do |tweet|
8
+ tweet.text.include?(keyword)
9
+ end || api.search(keyword).first
10
+
11
+ if ruby_tweet
12
+ puts "Gotcha!"
13
+ puts ruby_tweet.text
14
+ else
15
+ puts "We searched in your timeline and in the world for #{keyword} and we didn't find nothing :("
16
+ end
@@ -0,0 +1,13 @@
1
+ require "dialers/version"
2
+ require "faraday"
3
+ require "faraday_middleware"
4
+ require "faraday/conductivity"
5
+ require "dialers/assign_attributes"
6
+ require "dialers/errors"
7
+ require "dialers/request_options"
8
+ require "dialers/transformable"
9
+ require "dialers/short_circuit"
10
+ require "dialers/short_circuits_collection"
11
+ require "dialers/status"
12
+ require "dialers/caller"
13
+ require "dialers/wrapper"
@@ -0,0 +1,20 @@
1
+ module Dialers
2
+ module AssignAttributes
3
+ # Assign the attributes hash into the object calling attribute writers of the object
4
+ # if the object can respond to them.
5
+ #
6
+ # @param object [Object] any object with some or no attribute writers.
7
+ # @param attributes [Hash<Symbol, Object>] the attributes using symbols and objects.
8
+ #
9
+ # @return [Object] the same object passed as parameter.
10
+ def self.call(object, attributes)
11
+ attributes.each do |key, value|
12
+ writer = "#{key}=".to_sym
13
+ if object.respond_to?(writer)
14
+ object.public_send(writer, value)
15
+ end
16
+ end
17
+ object
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,128 @@
1
+ module Dialers
2
+ class Caller
3
+ IDEMPOTENT_AND_SAFE_METHODS = [:get, :head, :options]
4
+ MAX_RETRIES = 5
5
+
6
+ class << self
7
+ # Setups a connection using {https://github.com/lostisland/faraday Faraday}.
8
+ #
9
+ # @param [Array] Arguments to pass to the faraday connection.
10
+ # @yield A block to pass to the faraday connection
11
+ #
12
+ # @return [Faraday::Connection] a connection
13
+ def setup_api(*args, &block)
14
+ api = Faraday.new(*args) { |faraday| block.call(faraday) }
15
+ const_set "API", api
16
+ end
17
+
18
+ # @return [ShortCircuitsCollection] a collection of short circuits that can stop the process.
19
+ def short_circuits
20
+ @short_circuits ||= Dialers::ShortCircuitsCollection.new
21
+ end
22
+
23
+ private
24
+
25
+ # @!macro [attach] query_holder_request_method
26
+ # @method $1
27
+ # Make a $1 request.
28
+ #
29
+ # @param url [String] The path for the request.
30
+ # @param params [Hash] The query params to attach to the url.
31
+ # @param headers [Hash] The headers.
32
+ #
33
+ # @return [Transformable] a transformable object
34
+ def query_holder_request_method(http_method)
35
+ define_method(http_method) do |url, params = {}, headers = {}|
36
+ options = RequestOptions.new
37
+ options.url = url
38
+ options.http_method = http_method
39
+ options.query_params = params
40
+ options.headers = headers
41
+
42
+ transform(http_call(options))
43
+ end
44
+ end
45
+
46
+ # @!macro [attach] body_holder_request_method
47
+ # @method $1
48
+ # Make a $1 request.
49
+ #
50
+ # @param url [String] The path for the request.
51
+ # @param payload [Hash] The request body.
52
+ # @param headers [Hash] The headers.
53
+ #
54
+ # @return [Transformable] a transformable object
55
+ def body_holder_request_method(http_method)
56
+ define_method(http_method) do |url, payload = {}, headers = {}|
57
+ options = RequestOptions.new
58
+ options.url = url
59
+ options.http_method = http_method
60
+ options.payload = payload
61
+ options.headers = headers
62
+
63
+ transform(http_call(options))
64
+ end
65
+ end
66
+ end
67
+
68
+ public
69
+
70
+ query_holder_request_method :get
71
+ query_holder_request_method :head
72
+ query_holder_request_method :delete
73
+ query_holder_request_method :options
74
+ body_holder_request_method :post
75
+ body_holder_request_method :put
76
+ body_holder_request_method :patch
77
+
78
+ private
79
+
80
+ def transform(response)
81
+ self.class.short_circuits.search_for_stops(response)
82
+ Dialers::Transformable.new(response)
83
+ end
84
+
85
+ def http_call(request_options, current_retries = 0)
86
+ call_api(request_options)
87
+ rescue Faraday::ParsingError => _exception
88
+ raise Dialers::ParsingError.new(exception)
89
+ rescue Faraday::ConnectionFailed => exception
90
+ raise Dialers::UnreachableError.new(exception)
91
+ rescue Faraday::TimeoutError => exception
92
+ retry_call(request_options, exception, current_retries)
93
+ end
94
+
95
+ def retry_call(request_options, exception, current_retries)
96
+ if idempotent_and_safe_method?(request_options.http_method) && current_retries <= MAX_RETRIES
97
+ http_call(request_options, current_retries + 1)
98
+ else
99
+ fail Dialers::UnreachableError.new(exception)
100
+ end
101
+ end
102
+
103
+ def call_api(request_options)
104
+ api.public_send(
105
+ request_options.http_method, request_options.url, request_options.query_params || {}
106
+ ) do |request|
107
+ request.body = request_options.payload
108
+ (request_options.headers || {}).each do |key, value|
109
+ request.headers[key] = value
110
+ end
111
+ end
112
+ end
113
+
114
+ def api
115
+ @api ||= get_api
116
+ end
117
+
118
+ def get_api
119
+ self.class::API
120
+ rescue NameError
121
+ raise Dialers::InexistentApiError.new(self.class)
122
+ end
123
+
124
+ def idempotent_and_safe_method?(http_method)
125
+ IDEMPOTENT_AND_SAFE_METHODS.include?(http_method)
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,77 @@
1
+ module Dialers
2
+ class ErrorAsErrorProxy < StandardError
3
+ def initialize(error = nil)
4
+ self.error = error
5
+ end
6
+
7
+ def message
8
+ error ? error.message : super
9
+ end
10
+
11
+ private
12
+
13
+ attr_accessor :error
14
+ end
15
+
16
+ class ErrorWithResponse < StandardError
17
+ def initialize(response = nil)
18
+ self.response = response
19
+ end
20
+
21
+ def message
22
+ if response.nil?
23
+ super
24
+ else
25
+ "\n
26
+ STATUS: #{response.status}
27
+ URL: #{response.env.url}
28
+ REQUEST HEADERS: #{response.env.request_headers}
29
+ HEADERS: #{response.env.response_headers}
30
+ BODY: #{response.body}\n\n"
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ attr_accessor :response
37
+ end
38
+
39
+ class UnreachableError < ErrorAsErrorProxy
40
+ end
41
+
42
+ class ParsingError < ErrorAsErrorProxy
43
+ end
44
+
45
+ class ResponseError < ErrorWithResponse
46
+ end
47
+
48
+ class InexistentApiError < StandardError
49
+ def initialize(searched_class)
50
+ self.searched_class = searched_class
51
+ end
52
+
53
+ def message
54
+ "\n\nSEARCHED CLASS: #{searched_class}\n\n"
55
+ end
56
+
57
+ private
58
+
59
+ attr_accessor :searched_class
60
+ end
61
+
62
+ class ServerError < ErrorWithResponse
63
+ end
64
+
65
+ class NotFoundError < ErrorWithResponse
66
+ end
67
+
68
+ class UnauthorizedError < ErrorWithResponse
69
+ end
70
+
71
+ class ImpossibleTranformationError < ErrorWithResponse
72
+ end
73
+
74
+ ERRORS = [
75
+ UnreachableError, ParsingError, ResponseError, InexistentApiError
76
+ ]
77
+ end
@@ -0,0 +1,5 @@
1
+ module Dialers
2
+ class RequestOptions
3
+ attr_accessor :url, :http_method, :query_params, :payload, :headers
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ module Dialers
2
+ class ShortCircuit
3
+ def initialize(condition, action)
4
+ self.condition = condition
5
+ self.action = action
6
+ end
7
+
8
+ def can_stop?(response)
9
+ condition.call(response)
10
+ end
11
+
12
+ def stop(response)
13
+ action.call(response)
14
+ end
15
+
16
+ attr_accessor :condition, :action
17
+ end
18
+ end