the_captain 3.0.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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8892d789d45a648b93fdf9ba321ea2ac7c35b6075d884377e8186c78ece65b55
4
+ data.tar.gz: 143e22fb874f94790f77b05a518703ce889b2319eaaf8631d58adcd4db369efb
5
+ SHA512:
6
+ metadata.gz: c6f4359e18b68ce119abf839978396cfb2b9c480bf3a69d28988bfc04b10ce07ad9117b3f1ba19d9061e6c7538aeebd34fc66d8449c4d1428d37c7f5e4ae3082
7
+ data.tar.gz: f6b9d44a64535eeb216986e24258d79efaca8b5d7172731acbc00f01ed04ad67d42bcc199be496edc29da3904f1d1579fb8800e7778dbb05bae2098a57a20890
@@ -0,0 +1,23 @@
1
+ # The Captain Ruby Bindings
2
+ [![Build Status](https://travis-ci.com/VianetManagement/the-captain-ruby.svg?token=HrNaBvyMMhTA1FpCeAJF&branch=master)](https://travis-ci.com/VianetManagement/the-captain-ruby)
3
+ [![Code Climate](https://codeclimate.com/github/BeatnikBranding/the-captain-ruby/badges/gpa.svg)](https://codeclimate.com/github/BeatnikBranding/the-captain-ruby)
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ gem 'the_captain', :git => 'git@github.com:VianetManagement/the-captain-ruby.git', tag: "v1.0.0-RC6"
9
+ ```
10
+
11
+ ## Examples
12
+ You can find more examples in `/examples` and at [our documentation site](https://captain.readme.io/docs)
13
+
14
+ ## Ruby support
15
+ The Captain's tests are currently passing for 2.2.3
16
+
17
+ ## Links
18
+ * [Source Code for The Captain](http://github.com/VianetManagement/the-captain)
19
+ * [Documenation for The Captain](https://captain.readme.io/docs)
20
+ * [Continuous Integration - Travis](https://travis-ci.com/VianetManagement/the-captain-ruby)
21
+
22
+ ## TODO
23
+ * Write More Documentation
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "http"
4
+ require "ostruct"
5
+ require "oj"
6
+
7
+ require "the_captain/version"
8
+
9
+ # Exception Classes Container
10
+ require "the_captain/error/exceptions"
11
+
12
+ # Utility Helper classes
13
+ require "the_captain/utility/helper"
14
+ require "the_captain/utility/configuration"
15
+
16
+ # Net and responses classes
17
+ require "the_captain/response/captain_vessel"
18
+ require "the_captain/response/captain_object"
19
+ require "the_captain/captain_client"
20
+
21
+ # Query Classes
22
+ require "the_captain/api_resource"
23
+ require "the_captain/collect"
24
+ require "the_captain/user"
25
+ require "the_captain/ip_address"
26
+ require "the_captain/content"
27
+ require "the_captain/credit_card"
28
+ require "the_captain/email_address"
29
+
30
+ module TheCaptain
31
+ @enabled = true
32
+ @write_timeout = 5
33
+ @read_timeout = 5
34
+ @connect_timeout = 5
35
+
36
+ class << self
37
+ attr_accessor :enabled, :write_timeout, :read_timeout, :connect_timeout
38
+
39
+ def enabled?
40
+ @enabled
41
+ end
42
+
43
+ def configure
44
+ yield configuration
45
+ end
46
+
47
+ def configuration
48
+ @configuration ||= Utility::Configuration.new
49
+ end
50
+
51
+ def api_key
52
+ @api_key ||= configuration.api_key.to_s.strip
53
+ end
54
+
55
+ def api_url
56
+ @api_url ||= configuration.api_url.to_s.strip.chomp("/")
57
+ end
58
+
59
+ def retry_attempts
60
+ @retry_attempts ||= configuration.retry_attempts.to_i
61
+ end
62
+
63
+ def raise_http_errors?
64
+ @raise_http_errors ||= configuration.raise_http_errors
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ class APIResource
5
+ def self.class_name
6
+ name.split("::")[-1]
7
+ end
8
+
9
+ def self.api_paths(**paths)
10
+ @api_paths ||= paths.tap { |hash| hash[:base] ||= "" }.freeze
11
+ end
12
+
13
+ def self.request(method, api_dest: :base, resource_id: nil, params: {})
14
+ return unless TheCaptain.enabled?
15
+
16
+ api_path = Utility::Helper.normalize_path(api_paths[api_dest], resource_id)
17
+ params = Utility::Helper.normalize_params(params)
18
+
19
+ CaptainClient.active_client.request(method, api_path, params).decode_response
20
+ end
21
+
22
+ private_class_method :request
23
+
24
+ def self.define_get_path_methods!
25
+ api_paths.each_key do |path_key|
26
+ if path_key == :base
27
+ define_singleton_method(:receive) do |resource_id, **params|
28
+ request(:get, resource_id: resource_id, params: params)
29
+ end
30
+ else
31
+ define_singleton_method("related_#{path_key}".to_sym) do |resource_id, **params|
32
+ request(:get, resource_id: resource_id, api_dest: path_key, params: params)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ private_class_method :define_get_path_methods!
39
+ end
40
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ class CaptainClient
5
+ attr_accessor :conn
6
+ attr_reader :response
7
+
8
+ REQUEST_METHODS = %i[get post].freeze
9
+
10
+ def self.active_client
11
+ Thread.current[:captain_client] || default_client
12
+ end
13
+
14
+ def self.default_client
15
+ Thread.current[:captain_default_client] ||= CaptainClient.new(default_conn)
16
+ end
17
+
18
+ def self.default_conn
19
+ Thread.current[:captain_default_conn] ||= begin
20
+ HTTP.headers("X-API-KEY" => TheCaptain.api_key)
21
+ .accept("application/json")
22
+ .timeout(
23
+ read: TheCaptain.read_timeout,
24
+ write: TheCaptain.write_timeout,
25
+ connect: TheCaptain.connect_timeout,
26
+ )
27
+ end
28
+ end
29
+
30
+ def initialize(conn = nil)
31
+ @conn = conn || self.class.default_conn
32
+ @captain_url = TheCaptain.api_url
33
+ end
34
+
35
+ def request(verb_method, path, params = {})
36
+ verify_api_key_header!
37
+ verify_request_method!(verb_method)
38
+ capture_response! { send(verb_method.to_sym, destination_url(path), params) }
39
+ raise_status_error! unless @response.status.success?
40
+ self
41
+ end
42
+
43
+ def decode_response
44
+ raise Error::ClientError.missing_response_object unless @response
45
+ Response::CaptainVessel.new(@response)
46
+ end
47
+
48
+ protected
49
+
50
+ def capture_response!(retry_count = TheCaptain.retry_attempts)
51
+ @response = yield
52
+ rescue StandardError
53
+ retry_count ||= 0
54
+ (retry_count -= 1).positive? ? retry : raise
55
+ end
56
+
57
+ def get(url, params = {})
58
+ @conn.get(url, params: params)
59
+ end
60
+
61
+ def post(url, params = {})
62
+ @conn.post(url, json: params)
63
+ end
64
+
65
+ private
66
+
67
+ def raise_status_error!
68
+ return unless TheCaptain.raise_http_errors?
69
+
70
+ case @response.status.code
71
+ when 401
72
+ raise Error::APIAuthorizationError.new("Authorization Error", @response)
73
+ when 500..502
74
+ raise Error::APIConnectionError.new("Problem with server response", @response)
75
+ else
76
+ false
77
+ end
78
+ end
79
+
80
+ def verify_api_key_header!
81
+ api_key = @conn.default_options.headers["X-API-KEY"]
82
+ return unless api_key.nil? || api_key.empty?
83
+ raise Error::ClientAuthenticationError.no_key_provided
84
+ end
85
+
86
+ def verify_request_method!(verb_method)
87
+ raise Error::ClientInvalidRequestError unless REQUEST_METHODS.include?(verb_method.to_sym)
88
+ end
89
+
90
+ def destination_url(path)
91
+ path = path.start_with?("/") || path.empty? ? path : "/#{path}"
92
+ "#{@captain_url}#{path}"
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ class Collect < APIResource
5
+ api_paths base: "collect"
6
+
7
+ def self.submit(**params)
8
+ request(:post, params: params)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ class Content < APIResource
5
+ api_paths base: "content/%<resource_id>s",
6
+ lists: "content/%<resource_id>s/related/lists",
7
+ payments: "content/%<resource_id>s/related/payments",
8
+ credit_cards: "content/%<resource_id>s/related/credit_cards",
9
+ email_addresses: "content/%<resource_id>s/related/email_addresses",
10
+ ip_addresses: "content/%<resource_id>s/related/ip_addresses"
11
+
12
+ define_get_path_methods!
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ class CreditCard < APIResource
5
+ api_paths base: "credit_cards/%<resource_id>s",
6
+ lists: "credit_cards/%<resource_id>s/related/lists",
7
+ payments: "credit_cards/%<resource_id>s/related/payments",
8
+ content: "credit_cards/%<resource_id>s/related/content",
9
+ ip_addresses: "credit_cards/%<resource_id>s/related/ip_addresses",
10
+ email_addresses: "credit_cards/%<resource_id>s/related/email_addresses"
11
+
12
+ define_get_path_methods!
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ class EmailAddress < APIResource
5
+ api_paths base: "email_addresses/%<resource_id>s",
6
+ lists: "email_addresses/%<resource_id>s/related/lists",
7
+ payments: "email_addresses/%<resource_id>s/related/payments",
8
+ content: "email_addresses/%<resource_id>s/related/content",
9
+ credit_cards: "email_addresses/%<resource_id>s/related/credit_cards"
10
+
11
+ define_get_path_methods!
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "the_captain/error/standard_exception"
4
+
5
+ module TheCaptain
6
+ module Error
7
+ class APIAuthorizationError < StandardException; end
8
+ class APIConnectionError < StandardException; end
9
+ class APIInvalidRequestError < StandardException; end
10
+
11
+ class ClientInvalidRequestError < StandardException; end
12
+ class ClientInvalidResourceError < StandardException; end
13
+
14
+ class ClientError < StandardException
15
+ def self.client_error(class_name, message)
16
+ new("It looks like our client raised an #{class_name} error with message: #{message}")
17
+ end
18
+
19
+ def self.missing_response_object
20
+ new("No response was provided prior to decoding."\
21
+ "Ensure that you make a request before attempting to decode the returning messages.")
22
+ end
23
+ end
24
+
25
+ class ClientAuthenticationError < StandardException
26
+ def self.no_key_provided
27
+ new("No API key provided. " \
28
+ 'Set your API key using "TheCaptain.api_key = <API-KEY>". ' \
29
+ "See https://thecaptain.elevatorup.com for details, or " \
30
+ "email support@thecaptain.elevatorup.com if you have any questions.")
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ module Error
5
+ class StandardException < StandardError
6
+ attr_reader :message, :http_status, :http_body, :http_headers
7
+
8
+ def initialize(message = "", http_response = nil)
9
+ @message = message
10
+
11
+ if http_response
12
+ @http_headers = http_response.headers
13
+ @http_status = "#{http_response.status.code} => #{http_response.status.reason}"
14
+ @http_body = http_response.to_s
15
+ end
16
+ end
17
+
18
+ def to_s
19
+ out_message = message
20
+ out_message += "\n(Status: #{http_status})" unless http_status.nil?
21
+ out_message += "\nRaw request body: #{http_body}" unless http_body.nil?
22
+ out_message += "\nResponse Headers: #{http_headers}\n" unless http_headers.nil?
23
+ out_message
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ class IPAddress < APIResource
5
+ api_paths base: "ip_addresses/%<resource_id>s",
6
+ lists: "ip_addresses/%<resource_id>s/related/lists",
7
+ payments: "ip_addresses/%<resource_id>s/related/payments",
8
+ content: "ip_addresses/%<resource_id>s/related/content",
9
+ credit_cards: "ip_addresses/%<resource_id>s/related/credit_cards",
10
+ email_addresses: "ip_addresses/%<resource_id>s/related/email_addresses"
11
+
12
+ define_get_path_methods!
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ module Response
5
+ class CaptainObject < ::OpenStruct
6
+ def method_missing(method_name, *args, &blk)
7
+ prefixed_method = method_name.to_s.chomp!("?")
8
+ prefixed_method ? @table.key?(prefixed_method.to_sym) : super
9
+ end
10
+
11
+ def respond_to_missing?(method_name, include_all)
12
+ method_name.to_s.end_with?("?") || super
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ module Response
5
+ class CaptainVessel
6
+ extend Forwardable
7
+ attr_reader :raw_response, :data, :status
8
+
9
+ def_delegators :@data, :dig, :method_missing
10
+
11
+ def initialize(captain_response)
12
+ raise Error::ClientInvalidResourceError unless captain_response.is_a?(HTTP::Response)
13
+ @raw_response = captain_response
14
+ @status = @raw_response.status
15
+ @data = Oj.sc_parse(CaptainObjectParser.new, @raw_response.to_s).freeze
16
+ freeze
17
+ end
18
+
19
+ def valid?
20
+ @status.success?
21
+ end
22
+
23
+ def invalid?
24
+ !valid?
25
+ end
26
+
27
+ def inspect
28
+ "#<#{self.class}:0x#{object_id.to_s(16)}> @status=#{@status.inspect} @data=#{@data.inspect}"
29
+ end
30
+ alias to_s inspect
31
+
32
+ class CaptainObjectParser < ::Oj::ScHandler
33
+ # OJ callback when a hash is initialized.
34
+ def hash_start
35
+ CaptainObject.new
36
+ end
37
+
38
+ # OJ callback when a Hash's key requires transformation.
39
+ def hash_key(key)
40
+ key.downcase
41
+ end
42
+
43
+ # OJ callback when a hash is setting a new key value.
44
+ def hash_set(hash, key, value)
45
+ hash[key] = value.freeze
46
+ end
47
+
48
+ # OJ callback when an Array is initialized.
49
+ def array_start
50
+ []
51
+ end
52
+
53
+ # OJ callback when an Array is being appended to.
54
+ def array_append(array, value)
55
+ array << value.freeze
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ class User < APIResource
5
+ api_paths base: "/users/%<resource_id>s",
6
+ payments: "/users/%<resource_id>s/related/payments",
7
+ content: "/users/%<resource_id>s/related/content",
8
+ ip_addresses: "/users/%<resource_id>s/related/ip_addresses",
9
+ credit_cards: "/users/%<resource_id>s/related/credit_cards",
10
+ email_addresses: "/users/%<resource_id>s/related/email_addresses"
11
+
12
+ define_get_path_methods!
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ module Utility
5
+ class Configuration
6
+ attr_accessor :api_url, :api_key, :retry_attempts, :raise_http_errors
7
+
8
+ def initialize
9
+ @api_key = ENV.fetch("CAPTAIN_API_KEY", "")
10
+ @api_url = ENV.fetch("CAPTAIN_API_URL", "https://api.trustcaptain.com/v3")
11
+ @retry_attempts = 0
12
+ @raise_http_errors = false
13
+ end
14
+
15
+ def to_h
16
+ {
17
+ api_key: api_key,
18
+ api_url: api_url,
19
+ api_version: api_version,
20
+ retry_attempts: retry_attempts,
21
+ raise_http_errors: raise_http_errors,
22
+ }.freeze
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi/util"
4
+
5
+ module TheCaptain
6
+ module Utility
7
+ module Helper
8
+ module_function
9
+
10
+ def normalize_params(params)
11
+ symbolize_names(params)
12
+ end
13
+
14
+ # Formats the destination path
15
+ # Ex: normalize_path("/foo/%<resource_id>s/bar", 101) #=> "/foo/101/bar"
16
+ # normalize_path("/foo/%<resource_id>s/bar", "user@example.com") #=> "/foo/user%40example.com/bar"
17
+ def normalize_path(api_path, resource_id)
18
+ return api_path unless resource_id
19
+ format(api_path, resource_id: CGI.escape(resource_id))
20
+ end
21
+
22
+ # Recursively traverses a Hash table to ensure keys are symbolized.
23
+ # A necessary evil to ensure data is handled correctly throughout the library processing.
24
+ def symbolize_names(object)
25
+ case object
26
+ when Hash
27
+ {}.tap do |new_hash|
28
+ object.each do |key, value|
29
+ key = begin
30
+ key.to_sym
31
+ rescue StandardError
32
+ key
33
+ end
34
+ new_hash[key] = symbolize_names(value)
35
+ end
36
+ end
37
+ when Array
38
+ object.map { |value| symbolize_names(value) }
39
+ else
40
+ object
41
+ end.freeze
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TheCaptain
4
+ VERSION = "3.0.0"
5
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: the_captain
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0
5
+ platform: ruby
6
+ authors:
7
+ - George J Protacio-Karaszi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-09-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: oj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-its
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.2'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.2'
83
+ description: The Captain will tell, talk, and taddle on those pesky fraudulent scalliwags.
84
+ email:
85
+ - george@elevatorup.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - README.md
91
+ - lib/the_captain.rb
92
+ - lib/the_captain/api_resource.rb
93
+ - lib/the_captain/captain_client.rb
94
+ - lib/the_captain/collect.rb
95
+ - lib/the_captain/content.rb
96
+ - lib/the_captain/credit_card.rb
97
+ - lib/the_captain/email_address.rb
98
+ - lib/the_captain/error/exceptions.rb
99
+ - lib/the_captain/error/standard_exception.rb
100
+ - lib/the_captain/ip_address.rb
101
+ - lib/the_captain/response/captain_object.rb
102
+ - lib/the_captain/response/captain_vessel.rb
103
+ - lib/the_captain/user.rb
104
+ - lib/the_captain/utility/configuration.rb
105
+ - lib/the_captain/utility/helper.rb
106
+ - lib/the_captain/version.rb
107
+ homepage: https://github.com/VianetManagement/the-captain-ruby
108
+ licenses:
109
+ - MIT
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.7.7
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Ruby bindings for the The Captain API
131
+ test_files: []