Skip to content

Commit 1a9dbc3

Browse files
committed
Extract common code from fb-auth
The goal is to be able to reuse HTTPRequest and Configuration even when authentication is not needed.
0 parents  commit 1a9dbc3

21 files changed

+498
-0
lines changed

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/.bundle/
2+
/.yardoc
3+
/Gemfile.lock
4+
/_yardoc/
5+
/coverage/
6+
/doc/
7+
/pkg/
8+
/spec/reports/
9+
/tmp/

.rspec

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--format documentation
2+
--color
3+
--fail-fast

.travis.yml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
language: ruby
2+
notifications:
3+
email: false
4+
rvm:
5+
- 2.2.2
6+
script:
7+
- bundle exec rspec
8+
- bundle exec yard stats | grep "100.00% documented"

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
For more information about changelogs, check
6+
[Keep a Changelog](http://keepachangelog.com) and
7+
[Vandamme](http://tech-angels.github.io/vandamme).
8+
9+
## 1.0.0 - 2017-03-17
10+
11+
* [FEATURE] Added Fb::Configuration and Fb.configure
12+
* [FEATURE] Added Fb::HttpRequest
13+
* [FEATURE] Added Fb::HttpError

Gemfile

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source 'https://rubygems.org'
2+
3+
# Specify your gem's dependencies in fb-support.gemspec
4+
gemspec

LICENSE.txt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017-present Fullscreen, Inc.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Common code needed by the other Fb gems
2+
=======================================
3+
4+
Fb::Support provides common functionality to all Fb gems.
5+
It is considered suitable for internal use only at this time.
6+
7+
The **source code** is available on [GitHub](https://github.com/fullscreen/fb-support) and the **documentation** on [RubyDoc](http://www.rubydoc.info/gems/fb-support/frames).
8+
9+
[![Build Status](http://img.shields.io/travis/Fullscreen/fb-support/master.svg)](https://travis-ci.org/Fullscreen/fb-support)
10+
[![Coverage Status](http://img.shields.io/coveralls/Fullscreen/fb-support/master.svg)](https://coveralls.io/r/Fullscreen/fb-support)
11+
[![Dependency Status](http://img.shields.io/gemnasium/Fullscreen/fb-support.svg)](https://gemnasium.com/Fullscreen/fb-support)
12+
[![Code Climate](http://img.shields.io/codeclimate/github/Fullscreen/fb-support.svg)](https://codeclimate.com/github/Fullscreen/fb-support)
13+
[![Online docs](http://img.shields.io/badge/docs-✓-green.svg)](http://www.rubydoc.info/gems/fb-support/frames)
14+
[![Gem Version](http://img.shields.io/gem/v/fb-support.svg)](http://rubygems.org/gems/fb-support)
15+
16+
Fb::Support provides:
17+
18+
* [Fb.configure](http://www.rubydoc.info/gems/fb-support/Fb/Config#configure-instance_method)
19+
* [Fb::Configuration](http://www.rubydoc.info/gems/fb-support/Fb/Configuration)
20+
* [Fb::HTTPRequest](http://www.rubydoc.info/gems/fb-support/Fb/HTTPRequest)
21+
* [Fb::HTTPError](http://www.rubydoc.info/gems/fb-support/Fb/HTTPError)
22+
23+
How to test
24+
===========
25+
26+
In order to run the tests you need to have one Facebook access token and set
27+
it as an environment variable:
28+
29+
export FB_TEST_ACCESS_TOKEN="5040|67b895"
30+
31+
There are many [documented ways](https://developers.facebook.com/docs/facebook-login/access-tokens#apptokens) to generate a test access token.
32+
The easiest way is probably to:
33+
34+
- create a Facebook app
35+
- copy its app Id and app Secret
36+
- join them as "app-id|app-secret"… that is a valid access token!
37+
38+
39+
How to contribute
40+
=================
41+
42+
Contribute to the code by forking the project, adding the missing code,
43+
writing the appropriate tests and submitting a pull request.
44+
45+
In order for a PR to be approved, all the tests need to pass and all the public
46+
methods need to be documented and listed in the guides. Remember:
47+
48+
- to run all tests locally: `bundle exec rspec`
49+
- to generate the docs locally: `bundle exec yard`
50+
- to list undocumented methods: `bundle exec yard stats --list-undoc`

Rakefile

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require 'bundler/gem_tasks'
2+
3+
require "rspec/core/rake_task"
4+
require "rspec/core/version"
5+
6+
desc "Run all examples"
7+
RSpec::Core::RakeTask.new :spec
8+
9+
task default: [:spec]

bin/console

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env ruby
2+
3+
require "bundler/setup"
4+
require "fb/support"
5+
6+
# You can add fixtures and/or initialization code here to make experimenting
7+
# with your gem easier. You can also use a different console, if you like.
8+
9+
# (If you use this, don't forget to add pry to your Gemfile!)
10+
# require "pry"
11+
# Pry.start
12+
13+
require "irb"
14+
IRB.start(__FILE__)

bin/setup

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
IFS=$'\n\t'
4+
set -vx
5+
6+
bundle install
7+
8+
# Do any other automated setup that you need to do here

fb-support.gemspec

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# coding: utf-8
2+
lib = File.expand_path('../lib', __FILE__)
3+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4+
require 'fb/support/version'
5+
6+
Gem::Specification.new do |spec|
7+
spec.name = 'fb-support'
8+
spec.version = Fb::Support::VERSION
9+
spec.authors = ['Claudio Baccigalupo']
10+
spec.email = ['[email protected]']
11+
12+
spec.summary = %q{Support utilities for Fb gems}
13+
spec.description = %q{Fb::Support provides common functionality to Fb,
14+
Fb::Auth. It is considered suitable for internal use only at this time.}
15+
spec.homepage = 'https://github.com/fullscreen/fb-support'
16+
spec.license = 'MIT'
17+
18+
spec.required_ruby_version = '>= 2.2.2'
19+
20+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
21+
f.match(%r{^(test|spec|features)/})
22+
end
23+
spec.bindir = 'exe'
24+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25+
spec.require_paths = ['lib']
26+
27+
spec.add_development_dependency 'bundler', '~> 1.14'
28+
spec.add_development_dependency 'rspec', '~> 3.5'
29+
spec.add_development_dependency 'rake', '~> 12.0'
30+
spec.add_development_dependency 'coveralls', '~> 0.8.20'
31+
spec.add_development_dependency 'pry-nav', '~> 0.2.4'
32+
spec.add_development_dependency 'yard', '~> 0.9.8'
33+
end

lib/fb/config.rb

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require 'fb/configuration'
2+
3+
# An object-oriented Ruby client for Facebook.
4+
# @see http://www.rubydoc.info/gems/fb-core/
5+
module Fb
6+
# Provides methods to read and write global configuration settings.
7+
#
8+
# A typical usage is to set the API keys for a Facebook app.
9+
#
10+
# @example Set the API client id/secret for a Facebook app:
11+
# Fb.configure do |config|
12+
# config.client_id = 'ABCDEFGHIJ1234567890'
13+
# config.client_secret = 'ABCDEFGHIJ1234567890'
14+
# end
15+
#
16+
# Note that Fb.configure has precedence over values through with
17+
# environment variables (see {Fb::Models::Configuration}).
18+
#
19+
module Config
20+
# Yields the global configuration to the given block.
21+
#
22+
# @example
23+
# Fb.configure do |config|
24+
# config.client_id = 'ABCDEFGHIJ1234567890'
25+
# end
26+
#
27+
# @yield [Fb::Models::Configuration] The global configuration.
28+
def configure
29+
yield configuration if block_given?
30+
end
31+
32+
# Returns the global {Fb::Models::Configuration} object.
33+
#
34+
# While this method _can_ be used to read and write configuration settings,
35+
# it is easier to use {Fb::Config#configure} Fb.configure}.
36+
#
37+
# @example
38+
# Fb.configuration.client_id = 'ABCDEFGHIJ1234567890'
39+
#
40+
# @return [Fb::Models::Configuration] The global configuration.
41+
def configuration
42+
@configuration ||= Configuration.new
43+
end
44+
end
45+
46+
# @note Config is the only module auto-loaded in the Fb module,
47+
# in order to have a syntax as easy as Fb.configure
48+
extend Config
49+
end

lib/fb/configuration.rb

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
module Fb
2+
# Provides an object to store global configuration settings.
3+
#
4+
# This class is typically not used directly, but by calling
5+
# {Fb::Config#configure Fb.configure}, which creates and updates a single
6+
# instance of {Fb::Models::Configuration}.
7+
#
8+
# @example Set the API client id/secret for a web-client Facebook app:
9+
# Fb.configure do |config|
10+
# config.client_id = 'ABCDEFGHIJ1234567890'
11+
# config.client_secret = 'ABCDEFGHIJ1234567890'
12+
# end
13+
#
14+
# @see Fb::Config for more examples.
15+
#
16+
# An alternative way to set global configuration settings is by storing
17+
# them in the following environment variables:
18+
#
19+
# * +FB_CLIENT_ID+ to store the Client ID for a Facebook app
20+
# * +FB_CLIENT_SECRET+ to store the Client Secret for a Facebook app
21+
#
22+
# In case both methods are used together,
23+
# {Fb::Config#configure Fb.configure} takes precedence.
24+
#
25+
# @example Set the API client id/secret for a Facebook app:
26+
# ENV['FB_CLIENT_ID'] = 'ABCDEFGHIJ1234567890'
27+
# ENV['FB_CLIENT_SECRET'] = 'ABCDEFGHIJ1234567890'
28+
#
29+
class Configuration
30+
# @return [String] the Client ID for Facebook applications.
31+
attr_accessor :client_id
32+
33+
# @return [String] the Client Secret for web/device Facebook applications.
34+
attr_accessor :client_secret
35+
36+
# @return [String] the level of output to print for debugging purposes.
37+
attr_accessor :log_level
38+
39+
# Initialize the global configuration settings, using the values of
40+
# the specified following environment variables by default.
41+
def initialize
42+
@client_id = ENV['FB_CLIENT_ID']
43+
@client_secret = ENV['FB_CLIENT_SECRET']
44+
@log_level = ENV['FB_LOG_LEVEL']
45+
end
46+
47+
# @return [Boolean] whether the logging output is extra-verbose.
48+
# Useful when developing (e.g., to print the curl of every request).
49+
def developing?
50+
%w(devel).include? log_level.to_s
51+
end
52+
end
53+
end

lib/fb/http_error.rb

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module Fb
2+
# A wrapper around StandardError.
3+
class HTTPError < StandardError
4+
attr_reader :response
5+
6+
def initialize(msg, response:)
7+
super msg
8+
@response = response
9+
end
10+
end
11+
end

lib/fb/http_request.rb

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
require 'net/http'
2+
require 'json'
3+
require 'fb/http_error'
4+
5+
module Fb
6+
# A wrapper around +Net::HTTP+ to send HTTP requests to any web API and
7+
# return their result or raise an error if the result is unexpected.
8+
# The basic way to use Request is by calling +run+ on an instance.
9+
# @example Get information about Fullscreen’s Facebook page
10+
# path = '/v2.10/221406534569729'
11+
# params = {fields: 'id,link,about', access_token: access_token}
12+
# response = Fb::HTTPRequest.new(path: path, params: params).run
13+
# response.body
14+
# @api private
15+
class HTTPRequest
16+
# Initializes a Request object.
17+
# @param [Hash] options the options for the request.
18+
# @option options [Class] :expected_response (Net::HTTPSuccess) The class
19+
# of response that the request should obtain when run.
20+
# @option options [String] :host The host of the request URI.
21+
# @option options [String] :path The path of the request URI.
22+
# @option options [Hash] :params ({}) The params to use as the query
23+
# component of the request URI, for instance the Hash +{a: 1, b: 2}+
24+
# corresponds to the query parameters +"a=1&b=2"+.
25+
# @option options [#size] :body The body of the request.
26+
# @option options [Proc] :error_message The block that will be invoked
27+
# when a request fails.
28+
def initialize(options = {})
29+
@expected_response = options.fetch :expected_response, Net::HTTPSuccess
30+
@host = options.fetch :host, 'graph.facebook.com'
31+
@path = options[:path]
32+
@params = options.fetch :params, {}
33+
end
34+
35+
# Sends the request and returns the response with the body parsed from JSON.
36+
# @return [Net::HTTPResponse] if the request succeeds.
37+
# @raise [Fb::HTTPError] if the request fails.
38+
def run
39+
if response.is_a? @expected_response
40+
response.tap do
41+
parse_response!
42+
end
43+
else
44+
raise HTTPError.new(error_message, response: response)
45+
end
46+
end
47+
48+
# @return [String] the request URL.
49+
def url
50+
uri.to_s
51+
end
52+
53+
private
54+
55+
# @return [URI::HTTPS] the (memoized) URI of the request.
56+
def uri
57+
@uri ||= URI::HTTPS.build host: @host, path: @path, query: query
58+
end
59+
60+
def query
61+
URI.encode_www_form @params
62+
end
63+
64+
# @return [Net::HTTPRequest] the full HTTP request object.
65+
def http_request
66+
@http_request ||= Net::HTTP::Get.new uri.request_uri
67+
end
68+
69+
def as_curl
70+
'curl'.tap do |curl|
71+
http_request.each_header{|k, v| curl << %Q{ -H "#{k}: #{v}"}}
72+
curl << %Q{ "#{url}"}
73+
end
74+
end
75+
76+
# Run the request and memoize the response or the server error received.
77+
def response
78+
@response ||= Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
79+
curl_request = as_curl
80+
p curl_request if Fb.configuration.developing?
81+
http.request http_request
82+
end
83+
end
84+
85+
# Replaces the body of the response with the parsed version of the body,
86+
# according to the format specified in the HTTPRequest.
87+
def parse_response!
88+
if response.body
89+
response.body = JSON response.body
90+
end
91+
end
92+
93+
def error_message
94+
JSON(response.body)['error']['message']
95+
end
96+
end
97+
end

0 commit comments

Comments
 (0)