finapps 0.0.14.pre

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,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