dialers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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