finapps 0.0.14.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ require 'pp'
2
+
3
+ module FinApps
4
+ module REST
5
+ module Connection
6
+ include FinApps::REST::Defaults
7
+
8
+ # @param [Hash] company_credentials
9
+ # @param [Hash] config
10
+ # @return [Faraday::Connection]
11
+ def set_up_connection(company_credentials, config)
12
+ logger.debug "##{__method__.to_s} => Started"
13
+
14
+ company_credentials.validate_required_strings!
15
+ logger.debug "##{__method__.to_s} => company_credentials passed validation."
16
+
17
+ host = config[:host]
18
+ validate_host_url! host
19
+
20
+ base_url = "#{host}/v#{API_VERSION}"
21
+ logger.debug " base_url: #{base_url}"
22
+
23
+ timeout = config[:timeout].blank? ? DEFAULTS[:timeout] : config[:timeout]
24
+ logger.debug " timeout: #{timeout}"
25
+
26
+ user_identifier = config[:user_identifier]
27
+ logger.debug " user_identifier: #{user_identifier}" if user_identifier.present?
28
+
29
+ user_token = config[:user_token]
30
+ logger.debug ' user_token: [REDACTED]' if user_token.present?
31
+
32
+ connection = Faraday.new(:url => base_url,
33
+ :request => {
34
+ :open_timeout => timeout,
35
+ :timeout => timeout},
36
+ :headers => {
37
+ :accept => HEADERS[:accept],
38
+ :user_agent => HEADERS[:user_agent]}) do |conn|
39
+
40
+ # Request Middleware
41
+ conn.use FinApps::Middleware::ApiToken, company_credentials
42
+ conn.request :json
43
+ conn.request :retry
44
+ conn.request :multipart
45
+ conn.request :url_encoded
46
+ if user_identifier.blank? || user_token.blank?
47
+ logger.debug "##{__method__.to_s} => User credentials were not provided. Authentication header not set."
48
+ else
49
+ conn.request :basic_auth, user_identifier, user_token
50
+ logger.debug "##{__method__.to_s} => Basic Authentication header set for provided user credentials."
51
+ end
52
+
53
+ # Response Middleware
54
+ conn.use FinApps::Middleware::RaiseHttpExceptions
55
+ conn.response :rashify
56
+ conn.response :json, :content_type => /\bjson$/
57
+ conn.use FinApps::Middleware::ResponseLogger
58
+
59
+ # Adapter (ensure that the adapter is always last.)
60
+ conn.adapter :typhoeus
61
+ end
62
+
63
+ logger.debug "##{__method__.to_s} => Completed"
64
+ connection
65
+ end
66
+
67
+ private
68
+ def validate_host_url!(host_url)
69
+ raise MissingArgumentsError.new 'Missing argument: host_url.' if host_url.blank?
70
+ raise InvalidArgumentsError.new 'Invalid argument: host_url does not specify a valid protocol (http/https).' unless host_url.start_with?('http://', 'https://')
71
+
72
+ logger.debug "##{__method__.to_s} => host [#{host_url}] passed validation."
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,36 @@
1
+ module FinApps
2
+ module REST
3
+ module Defaults
4
+
5
+ API_VERSION = '1'
6
+
7
+ HEADERS = {
8
+ :accept => 'application/json',
9
+ :user_agent => "finapps-ruby/#{FinApps::VERSION} (#{RUBY_ENGINE}/#{RUBY_PLATFORM} #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})"
10
+ }
11
+
12
+ # noinspection SpellCheckingInspection
13
+ DEFAULTS = {
14
+ :host => 'https://www.financialapps.com',
15
+ :timeout => 30,
16
+ :proxy_addr => nil,
17
+ :proxy_port => nil,
18
+ :proxy_user => nil,
19
+ :proxy_pass => nil,
20
+ :retry_limit => 1,
21
+ :log_level => Logger::INFO
22
+ }
23
+
24
+
25
+ END_POINTS = {
26
+ :users_create => 'users/new',
27
+ :users_login => 'users/login',
28
+ :users_delete => 'users/:public_id/delete',
29
+ :institutions_search => 'institutions/:search_term/search',
30
+ :institutions_form => 'institutions/:site_id/form',
31
+ :institutions_add => 'institutions/:site_id/add',
32
+ }
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,110 @@
1
+ module FinApps
2
+ module REST
3
+
4
+ # Custom error class for rescuing from all FinApps errors
5
+ class Error < StandardError;
6
+ attr_reader :response
7
+
8
+ def initialize(ex, response = nil)
9
+ @wrapped_exception = nil
10
+ @response = response
11
+
12
+ if ex.respond_to?(:backtrace)
13
+ super(ex.message)
14
+ @wrapped_exception = ex
15
+ elsif ex.respond_to?(:each_key)
16
+ super("the server responded with status #{ex[:status]}")
17
+ @response = ex
18
+ else
19
+ super(ex.to_s)
20
+ end
21
+ end
22
+
23
+ def backtrace
24
+ if @wrapped_exception
25
+ @wrapped_exception.backtrace
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def inspect
32
+ %(#<#{self.class}>)
33
+ end
34
+
35
+ # @return [Array<String>]
36
+ def error_messages
37
+ @response.present? ? @response[:error_messages] : []
38
+ end
39
+
40
+ end
41
+
42
+ # Raised when required arguments are missing
43
+ class MissingArgumentsError < Error
44
+ def initialize(message)
45
+ super(message)
46
+ end
47
+ end
48
+
49
+ # Raised when invalid arguments are detected
50
+ class InvalidArgumentsError < Error
51
+ def initialize(message)
52
+ super(message)
53
+ end
54
+ end
55
+
56
+
57
+ # Client Error 4xx
58
+ # The 4xx class of status code is intended for cases in which the client seems to have erred.
59
+ class ClientError < Error
60
+ end
61
+
62
+ class BadRequest < ClientError
63
+ end
64
+
65
+ class Unauthorized < ClientError
66
+ end
67
+
68
+ class Forbidden < ClientError
69
+ end
70
+
71
+ class NotFound < ClientError
72
+ end
73
+
74
+ class MethodNotAllowed < ClientError
75
+ end
76
+
77
+ class NotAcceptable < ClientError
78
+ end
79
+
80
+ class ConnectionFailed < ClientError
81
+ end
82
+
83
+ class Conflict < ClientError
84
+ end
85
+
86
+
87
+ # Server Error 5xx
88
+ #
89
+ # Response status codes beginning with the digit "5" indicate cases in which the server is aware
90
+ # that it has erred or is incapable of performing the request.
91
+ class ServerError < Error
92
+ end
93
+
94
+ class InternalServerError < ServerError
95
+ end
96
+
97
+ class BadGateway < ServerError
98
+ end
99
+
100
+ class ServiceUnavailable < ServerError
101
+ end
102
+
103
+ class GatewayTimeout < ServerError
104
+ end
105
+
106
+ class VersionNotSupported < ServerError
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,32 @@
1
+ module FinApps
2
+ module REST
3
+
4
+ class Institutions < FinApps::REST::Resources
5
+
6
+ # @param [String] term
7
+ # @return [Array<FinApps::REST::Institution>, Array<String>]
8
+ def search(term)
9
+ logger.debug "##{__method__.to_s} => Started"
10
+
11
+ raise MissingArgumentsError.new 'Missing argument: term.' if term.blank?
12
+ logger.debug "##{__method__.to_s} => term: #{term}"
13
+
14
+ path = END_POINTS[:institutions_search].sub! ':search_term', term.to_s
15
+
16
+ institutions, error_messages = @client.get(path) do |r|
17
+ r.body.each { |i| Institution.new(i) }
18
+ end
19
+
20
+ logger.debug "##{__method__.to_s} => Completed"
21
+ return institutions, error_messages
22
+ end
23
+
24
+
25
+ end
26
+
27
+ class Institution < FinApps::REST::Resource
28
+ attr_accessor :base_url, :display_name, :site_id, :org_display_name
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ module FinApps
2
+ module REST
3
+ class Resource
4
+
5
+ # @param [Hash] hash
6
+ def initialize(hash)
7
+ hash.each { |k, v| instance_variable_set("@#{k}", v) unless v.nil? } if hash.present?
8
+ self
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module FinApps
2
+ module REST
3
+ class Resources
4
+
5
+ include FinApps::REST::Defaults
6
+ include FinApps::Logging
7
+
8
+ # @param [FinApps::REST::Client] client
9
+ # @return [FinApps::REST::Resources]
10
+ def initialize(client)
11
+ @client = client
12
+ logger.debug "##{__method__.to_s} => #{self.class.name} initialized."
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ module FinApps
2
+ module REST
3
+
4
+ class Users < FinApps::REST::Resources
5
+ include FinApps::Logging
6
+
7
+ # @param [Hash] params
8
+ # @return [FinApps::REST::User, Array<String>]
9
+ def create(params = {})
10
+ logger.debug "##{__method__.to_s} => Started"
11
+ user, error_messages = @client.post(END_POINTS[:users_create], params) { |r| User.new(r.body) }
12
+ logger.debug "##{__method__.to_s} => Completed"
13
+
14
+ return user, error_messages
15
+ end
16
+
17
+ # @param [Hash] params
18
+ # @return [FinApps::REST::User, Array<String>]
19
+ def login(params = {})
20
+ logger.debug "##{__method__.to_s} => Started"
21
+ user, error_messages = @client.post(END_POINTS[:users_login], params) { |r| User.new(r.body) }
22
+ logger.debug "##{__method__.to_s} => Completed"
23
+
24
+ return user, error_messages
25
+ end
26
+
27
+ # @param [String] public_id
28
+ # @return [Array<String>]
29
+ def delete(public_id)
30
+ logger.debug "##{__method__.to_s} => Started"
31
+ raise MissingArgumentsError.new 'Missing argument: public_id.' if public_id.blank?
32
+ _, error_messages = @client.delete(END_POINTS[:users_delete].sub! ':public_id', public_id.to_s)
33
+ logger.debug "##{__method__.to_s} => Completed"
34
+
35
+ error_messages
36
+ end
37
+
38
+ end
39
+
40
+ class User < FinApps::REST::Resource
41
+ attr_accessor :public_id, :token, :email, :first_name, :last_name, :postal_code
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,78 @@
1
+ module FinApps
2
+ module Logging
3
+
4
+ SEVERITY_LABEL = %w(DEBUG INFO WARN ERROR FATAL UNKNOWN)
5
+ PROTECTED_KEYS = [:password, :password_confirm]
6
+
7
+ class << self;
8
+ attr_accessor :tag;
9
+ end
10
+
11
+ def logger=(logger)
12
+ @logger = logger
13
+ end
14
+
15
+ # noinspection SpellCheckingInspection
16
+ def logger
17
+
18
+ @logger ||= begin
19
+ require 'logger' unless defined?(::Logger)
20
+ ::Logger.new(STDOUT).tap do |log|
21
+ log.progname = "#{self.class.to_s}"
22
+ log.formatter = proc do |severity, time, progname, msg|
23
+ Logging.tag.present? ?
24
+ "[%s#%d] %5s -- %s: %s %s\n" % [format_datetime(time), $$, severity, progname, Logging.tag.to_s, msg2str(msg)] :
25
+ "[%s#%d] %5s -- %s: %s\n" % [format_datetime(time), $$, severity, progname, msg2str(msg)]
26
+
27
+ end
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ def set_up_logger_level(logger_level)
34
+ unless logger_level.blank? || @logger.level == logger_level
35
+ @logger.info "##{__method__.to_s} => Setting logger level to #{SEVERITY_LABEL[logger_level]}"
36
+ @logger.level = logger_level
37
+ end
38
+ end
39
+
40
+ # noinspection SpellCheckingInspection
41
+ def set_up_logger_session_params(uuid, session_id)
42
+ if uuid.present? || session_id.present?
43
+ uuid ||= '-'
44
+ session_id ||= '-'
45
+ logger.formatter = proc do |severity, time, progname, msg|
46
+ "[%s#%d] %5s -- %s: [#{uuid}] [#{session_id}] %s\n" % [format_datetime(time), $$, severity, progname, msg2str(msg)]
47
+ end
48
+ end
49
+ end
50
+
51
+ # @param [Hash] hash
52
+ def skip_sensitive_data(hash)
53
+ if hash.is_a? Hash
54
+ redacted = hash.clone
55
+ redacted.update(redacted) { |key, v1| (PROTECTED_KEYS.include? key) ? '[REDACTED]' : v1 }
56
+ else
57
+ hash
58
+ end
59
+ end
60
+
61
+ private
62
+ def format_datetime(time)
63
+ time.strftime('%Y-%m-%dT%H:%M:%S.') << '%06d ' % time.usec
64
+ end
65
+
66
+ def msg2str(msg)
67
+ case msg
68
+ when ::String
69
+ msg
70
+ when ::Exception
71
+ "#{ msg.message } (#{ msg.class })\n" << (msg.backtrace || []).join("\n")
72
+ else
73
+ msg.inspect
74
+ end
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,51 @@
1
+ class Object
2
+ # An object is blank if it's false, empty, or a whitespace string.
3
+ # For example, "", " ", +nil+, [], and {} are all blank.
4
+ #
5
+ # This simplifies:
6
+ #
7
+ # if address.nil? || address.empty?
8
+ #
9
+ # ...to:
10
+ #
11
+ # if address.blank?
12
+ def blank?
13
+ respond_to?(:empty?) ? empty? : !self
14
+ end
15
+
16
+ # An object is present if it's not <tt>blank?</tt>.
17
+ def present?
18
+ !blank?
19
+ end
20
+
21
+ # Returns object if it's <tt>present?</tt> otherwise returns +nil+.
22
+ # <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>.
23
+ #
24
+ # This is handy for any representation of objects where blank is the same
25
+ # as not present at all. For example, this simplifies a common check for
26
+ # HTTP POST/query parameters:
27
+ #
28
+ # state = params[:state] if params[:state].present?
29
+ # country = params[:country] if params[:country].present?
30
+ # region = state || country || 'US'
31
+ #
32
+ # ...becomes:
33
+ #
34
+ # region = params[:state].presence || params[:country].presence || 'US'
35
+ def presence
36
+ self if present?
37
+ end
38
+
39
+ def trim
40
+ respond_to?(:strip) ? self.strip : self
41
+ end
42
+ end
43
+
44
+ class Hash
45
+ def validate_required_strings!
46
+ self.each do |key, value|
47
+ raise MissingArgumentsError.new "Missing argument: #{key}." if value.blank?
48
+ raise InvalidArgumentsError.new "Invalid #{key} specified: #{value.inspect} must be a string or symbol." unless value.is_a?(String) || value.is_a?(Symbol)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module FinApps
2
+ VERSION = '0.0.14.pre'
3
+ end
data/lib/finapps.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'finapps/version' unless defined?(FinApps::VERSION)
2
+ require 'logger' unless defined?(::Logger)
3
+ require 'pp'
4
+
5
+ require 'faraday'
6
+ require 'faraday_middleware'
7
+ require 'typhoeus'
8
+ require 'typhoeus/adapters/faraday'
9
+
10
+ require 'finapps/rest/defaults'
11
+ require 'finapps/rest/errors'
12
+ require 'finapps/utils/logging'
13
+ require 'finapps/utils/utils'
14
+
15
+ require 'finapps/rest/resource'
16
+ require 'finapps/rest/resources'
17
+ require 'finapps/rest/users'
18
+ require 'finapps/rest/institutions'
19
+
20
+ require 'finapps/middleware/api_token'
21
+ require 'finapps/middleware/raise_http_exceptions'
22
+ require 'finapps/middleware/response_logger'
23
+
24
+ require 'finapps/rest/connection'
25
+ require 'finapps/rest/client'
@@ -0,0 +1,61 @@
1
+ require 'rspec'
2
+ require 'finapps'
3
+
4
+ module FinApps
5
+
6
+ describe FinApps::REST::Client do
7
+
8
+ before do
9
+ @client = FinApps::REST::Client.new :company_identifier, :company_token
10
+ end
11
+
12
+ describe '#new' do
13
+ context 'when company credentials are NOT provided' do
14
+ it 'should raise a MissingArgumentsError exception' do
15
+ expect { FinApps::REST::Client.new nil, nil }.to raise_error(FinApps::REST::MissingArgumentsError)
16
+ end
17
+ end
18
+
19
+ context 'when company credentials are of invalid type' do
20
+ it 'should raise an InvalidArgumentsError exception' do
21
+ expect{FinApps::REST::Client.new 1, 2}.to raise_error(FinApps::REST::InvalidArgumentsError)
22
+ end
23
+ end
24
+
25
+ context 'when company credentials are provided' do
26
+ it 'returns a client object' do
27
+ expect(@client).to be_an_instance_of(FinApps::REST::Client)
28
+ end
29
+ end
30
+ end
31
+
32
+ describe '#get' do
33
+ it 'responds to get' do
34
+ expect(@client).to respond_to(:get)
35
+ end
36
+ end
37
+
38
+ describe '#post' do
39
+ it 'responds to post' do
40
+ expect(@client).to respond_to(:post)
41
+ end
42
+ end
43
+
44
+ describe '.users' do
45
+ it 'returns a Users object' do
46
+ expect(@client.users).to be_an_instance_of(FinApps::REST::Users)
47
+ end
48
+ end
49
+
50
+ describe '#connection' do
51
+ it 'looks like Faraday connection' do
52
+ expect(@client.send(:connection)).to respond_to(:run_request)
53
+ end
54
+ it 'memoizes the connection' do
55
+ c1, c2 = @client.send(:connection), @client.send(:connection)
56
+ expect(c1.object_id).to eq(c2.object_id)
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,84 @@
1
+ require 'finapps'
2
+
3
+ # This file was generated by the `rspec --init` command. Conventionally, all
4
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
6
+ # file to always be loaded, without a need to explicitly require it in any files.
7
+ #
8
+ # Given that it is always loaded, you are encouraged to keep this file as
9
+ # light-weight as possible. Requiring heavyweight dependencies from this file
10
+ # will add to the boot time of your test suite on EVERY test run, even for an
11
+ # individual file that may not need all of that loaded. Instead, make a
12
+ # separate helper file that requires this one and then use it only in the specs
13
+ # that actually need it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+ RSpec.configure do |config|
20
+
21
+ # These two settings work together to allow you to limit a spec run
22
+ # to individual examples or groups you care about by tagging them with
23
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
24
+ # get run.
25
+ config.filter_run :focus
26
+ config.run_all_when_everything_filtered = true
27
+
28
+ # Many RSpec users commonly either run the entire suite or an individual
29
+ # file, and it's useful to allow more verbose output when running an
30
+ # individual spec file.
31
+ if config.files_to_run.one?
32
+ # Use the documentation formatter for detailed output,
33
+ # unless a formatter has already been configured
34
+ # (e.g. via a command-line flag).
35
+ config.default_formatter = 'doc'
36
+ end
37
+
38
+ # Run specs in random order to surface order dependencies. If you find an
39
+ # order dependency and want to debug it, you can fix the order by providing
40
+ # the seed, which is printed after each run.
41
+ # --seed 1234
42
+ config.order = :random
43
+
44
+ # The settings below are suggested to provide a good initial experience
45
+ # with RSpec, but feel free to customize to your heart's content.
46
+ =begin
47
+
48
+ # Print the 10 slowest examples and example groups at the
49
+ # end of the spec run, to help surface which specs are running
50
+ # particularly slow.
51
+ config.profile_examples = 10
52
+
53
+ # Seed global randomization in this process using the `--seed` CLI option.
54
+ # Setting this allows you to use `--seed` to deterministically reproduce
55
+ # test failures related to randomization by passing the same `--seed` value
56
+ # as the one that triggered the failure.
57
+ Kernel.srand config.seed
58
+
59
+ # rspec-expectations config goes here. You can use an alternate
60
+ # assertion/expectation library such as wrong or the stdlib/minitest
61
+ # assertions if you prefer.
62
+ config.expect_with :rspec do |expectations|
63
+ # Enable only the newer, non-monkey-patching expect syntax.
64
+ # For more details, see:
65
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
66
+ expectations.syntax = :expect
67
+ end
68
+
69
+ # rspec-mocks config goes here. You can use an alternate test double
70
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
71
+ config.mock_with :rspec do |mocks|
72
+ # Enable only the newer, non-monkey-patching expect syntax.
73
+ # For more details, see:
74
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
75
+ mocks.syntax = :expect
76
+
77
+ # Prevents you from mocking or stubbing a method that does not exist on
78
+ # a real object. This is generally recommended.
79
+ mocks.verify_partial_doubles = true
80
+ end
81
+ =end
82
+
83
+
84
+ end