veryfi 3.0.0 → 4.0.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +1 -1
- data/.github/workflows/test.yml +1 -1
- data/.gitignore +4 -1
- data/.rubocop.yml +5 -1
- data/.ruby-version +1 -1
- data/.yardopts +10 -0
- data/Gemfile.lock +55 -56
- data/README.md +530 -2
- data/Rakefile +21 -0
- data/lib/veryfi/api/any_document.rb +123 -0
- data/lib/veryfi/api/bank_statement.rb +114 -0
- data/lib/veryfi/api/bank_statement_split.rb +66 -0
- data/lib/veryfi/api/business_card.rb +84 -0
- data/lib/veryfi/api/check.rb +127 -0
- data/lib/veryfi/api/classify.rb +53 -0
- data/lib/veryfi/api/document.rb +117 -0
- data/lib/veryfi/api/document_tag.rb +43 -0
- data/lib/veryfi/api/file_payload.rb +23 -0
- data/lib/veryfi/api/line_item.rb +55 -0
- data/lib/veryfi/api/pdf_split.rb +75 -0
- data/lib/veryfi/api/tag.rb +14 -0
- data/lib/veryfi/api/tag_operations.rb +63 -0
- data/lib/veryfi/api/tax_line.rb +71 -0
- data/lib/veryfi/api/w2.rb +90 -0
- data/lib/veryfi/api/w2_split.rb +68 -0
- data/lib/veryfi/api/w8.rb +90 -0
- data/lib/veryfi/api/w9.rb +92 -0
- data/lib/veryfi/client.rb +61 -17
- data/lib/veryfi/configuration.rb +28 -0
- data/lib/veryfi/error.rb +98 -12
- data/lib/veryfi/request.rb +32 -13
- data/lib/veryfi/resource.rb +102 -0
- data/lib/veryfi/version.rb +1 -1
- data/lib/veryfi.rb +64 -0
- data/veryfi.gemspec +24 -2
- metadata +40 -8
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Veryfi
|
|
4
|
+
module Api
|
|
5
|
+
# W-2 splitting endpoints (`/partner/w2s-set/`).
|
|
6
|
+
#
|
|
7
|
+
# Use these when you have a single file containing multiple W-2 forms.
|
|
8
|
+
# Veryfi will split it and process each W-2 separately; you receive a
|
|
9
|
+
# collection that references the individual {W2} ids it produced.
|
|
10
|
+
#
|
|
11
|
+
# @see https://docs.veryfi.com/api/split-and-process-a-w-2/
|
|
12
|
+
class W2Split
|
|
13
|
+
include FilePayload
|
|
14
|
+
|
|
15
|
+
ENDPOINT = "/partner/w2s-set/"
|
|
16
|
+
|
|
17
|
+
attr_reader :request
|
|
18
|
+
|
|
19
|
+
def initialize(request)
|
|
20
|
+
@request = request
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# List previously processed W-2 sets.
|
|
24
|
+
#
|
|
25
|
+
# @param params [Hash] optional query-string parameters
|
|
26
|
+
# @return [Veryfi::Resource]
|
|
27
|
+
def all(params = {})
|
|
28
|
+
request.get(ENDPOINT, params)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Fetch a single W-2 set by id.
|
|
32
|
+
#
|
|
33
|
+
# @param id [Integer]
|
|
34
|
+
# @param params [Hash] optional query-string parameters
|
|
35
|
+
# @return [Veryfi::Resource]
|
|
36
|
+
def get(id, params = {})
|
|
37
|
+
request.get("#{ENDPOINT}#{id}/", params)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Upload a multi-W-2 file and split-and-process it.
|
|
41
|
+
#
|
|
42
|
+
# @param raw_params [Hash]
|
|
43
|
+
# @option raw_params [String] :file_path **required.** Local path.
|
|
44
|
+
# @option raw_params [String] :file_name (basename of `:file_path`)
|
|
45
|
+
# @return [Veryfi::Resource]
|
|
46
|
+
def process(raw_params)
|
|
47
|
+
params = raw_params.transform_keys(&:to_sym)
|
|
48
|
+
file_path = params.delete(:file_path)
|
|
49
|
+
file_name = params.delete(:file_name)
|
|
50
|
+
|
|
51
|
+
payload = file_payload(file_path, file_name).merge(params)
|
|
52
|
+
|
|
53
|
+
request.post(ENDPOINT, payload)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# URL variant of {#process}.
|
|
57
|
+
#
|
|
58
|
+
# @param raw_params [Hash]
|
|
59
|
+
# @option raw_params [String] :file_url single URL
|
|
60
|
+
# @option raw_params [Array<String>] :file_urls list of URLs (alternative)
|
|
61
|
+
# @option raw_params [Integer] :max_pages_to_process (`nil` = all pages)
|
|
62
|
+
# @return [Veryfi::Resource]
|
|
63
|
+
def process_url(raw_params)
|
|
64
|
+
request.post(ENDPOINT, raw_params.transform_keys(&:to_sym))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Veryfi
|
|
4
|
+
module Api
|
|
5
|
+
# W-8 BEN-E endpoints (`/partner/w-8ben-e/`).
|
|
6
|
+
#
|
|
7
|
+
# @see https://docs.veryfi.com/api/w-8ben-e/
|
|
8
|
+
class W8
|
|
9
|
+
include FilePayload
|
|
10
|
+
include TagOperations
|
|
11
|
+
|
|
12
|
+
ENDPOINT = "/partner/w-8ben-e/"
|
|
13
|
+
|
|
14
|
+
attr_reader :request
|
|
15
|
+
|
|
16
|
+
def initialize(request)
|
|
17
|
+
@request = request
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# List previously processed W-8 BEN-E documents.
|
|
21
|
+
#
|
|
22
|
+
# @param params [Hash] optional query-string parameters
|
|
23
|
+
# @option params [String] :created_date__gt "YYYY-MM-DD HH:MM:SS" — strictly after
|
|
24
|
+
# @option params [String] :created_date__gte after or equal
|
|
25
|
+
# @option params [String] :created_date__lt strictly before
|
|
26
|
+
# @option params [String] :created_date__lte before or equal
|
|
27
|
+
# @option params [Integer] :page (1)
|
|
28
|
+
# @option params [Integer] :page_size (50)
|
|
29
|
+
# @return [Veryfi::Resource] `{ "documents" => [...] }`
|
|
30
|
+
def all(params = {})
|
|
31
|
+
request.get(ENDPOINT, params)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Fetch a single W-8 by id.
|
|
35
|
+
#
|
|
36
|
+
# @param id [Integer]
|
|
37
|
+
# @param params [Hash] optional query-string parameters
|
|
38
|
+
# @return [Veryfi::Resource]
|
|
39
|
+
def get(id, params = {})
|
|
40
|
+
request.get("#{ENDPOINT}#{id}/", params)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Upload a W-8 file and extract its fields.
|
|
44
|
+
#
|
|
45
|
+
# @param raw_params [Hash]
|
|
46
|
+
# @option raw_params [String] :file_path **required.** Local path.
|
|
47
|
+
# @option raw_params [String] :file_name (basename of `:file_path`)
|
|
48
|
+
# @return [Veryfi::Resource]
|
|
49
|
+
def process(raw_params)
|
|
50
|
+
params = raw_params.transform_keys(&:to_sym)
|
|
51
|
+
file_path = params.delete(:file_path)
|
|
52
|
+
file_name = params.delete(:file_name)
|
|
53
|
+
|
|
54
|
+
payload = file_payload(file_path, file_name).merge(params)
|
|
55
|
+
|
|
56
|
+
request.post(ENDPOINT, payload)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# URL variant of {#process}.
|
|
60
|
+
#
|
|
61
|
+
# @param raw_params [Hash]
|
|
62
|
+
# @option raw_params [String] :file_url **required.**
|
|
63
|
+
# @option raw_params [String] :file_name (basename of `:file_url`)
|
|
64
|
+
# @return [Veryfi::Resource]
|
|
65
|
+
def process_url(raw_params)
|
|
66
|
+
params = raw_params.transform_keys(&:to_sym)
|
|
67
|
+
params[:file_name] ||= File.basename(params[:file_url]) if params[:file_url]
|
|
68
|
+
|
|
69
|
+
request.post(ENDPOINT, params)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Update writable fields on a processed W-8.
|
|
73
|
+
#
|
|
74
|
+
# @param id [Integer]
|
|
75
|
+
# @param params [Hash]
|
|
76
|
+
# @return [Veryfi::Resource]
|
|
77
|
+
def update(id, params)
|
|
78
|
+
request.put("#{ENDPOINT}#{id}/", params)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Delete a W-8.
|
|
82
|
+
#
|
|
83
|
+
# @param id [Integer]
|
|
84
|
+
# @return [Veryfi::Resource]
|
|
85
|
+
def delete(id)
|
|
86
|
+
request.delete("#{ENDPOINT}#{id}/")
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Veryfi
|
|
4
|
+
module Api
|
|
5
|
+
# W-9 endpoints (`/partner/w9s/`).
|
|
6
|
+
#
|
|
7
|
+
# @see https://docs.veryfi.com/api/w9s/
|
|
8
|
+
class W9
|
|
9
|
+
include FilePayload
|
|
10
|
+
include TagOperations
|
|
11
|
+
|
|
12
|
+
ENDPOINT = "/partner/w9s/"
|
|
13
|
+
|
|
14
|
+
attr_reader :request
|
|
15
|
+
|
|
16
|
+
def initialize(request)
|
|
17
|
+
@request = request
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# List previously processed W-9 documents.
|
|
21
|
+
#
|
|
22
|
+
# @param params [Hash] optional query-string parameters
|
|
23
|
+
# @option params [String] :created_date__gt "YYYY-MM-DD HH:MM:SS" — strictly after
|
|
24
|
+
# @option params [String] :created_date__gte after or equal
|
|
25
|
+
# @option params [String] :created_date__lt strictly before
|
|
26
|
+
# @option params [String] :created_date__lte before or equal
|
|
27
|
+
# @option params [Integer] :page (1)
|
|
28
|
+
# @option params [Integer] :page_size (50)
|
|
29
|
+
# @return [Veryfi::Resource] `{ "documents" => [...] }`
|
|
30
|
+
def all(params = {})
|
|
31
|
+
request.get(ENDPOINT, params)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Fetch a single W-9 by id.
|
|
35
|
+
#
|
|
36
|
+
# @param id [Integer]
|
|
37
|
+
# @param params [Hash] optional query-string parameters
|
|
38
|
+
# @option params [Boolean] :bounding_boxes (`false`) Include bounding-box info.
|
|
39
|
+
# @option params [Boolean] :confidence_details (`false`) Include per-field confidence scores.
|
|
40
|
+
# @return [Veryfi::Resource]
|
|
41
|
+
def get(id, params = {})
|
|
42
|
+
request.get("#{ENDPOINT}#{id}/", params)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Upload a W-9 file and extract its fields.
|
|
46
|
+
#
|
|
47
|
+
# @param raw_params [Hash]
|
|
48
|
+
# @option raw_params [String] :file_path **required.** Local path.
|
|
49
|
+
# @option raw_params [String] :file_name (basename of `:file_path`)
|
|
50
|
+
# @return [Veryfi::Resource]
|
|
51
|
+
def process(raw_params)
|
|
52
|
+
params = raw_params.transform_keys(&:to_sym)
|
|
53
|
+
file_path = params.delete(:file_path)
|
|
54
|
+
file_name = params.delete(:file_name)
|
|
55
|
+
|
|
56
|
+
payload = file_payload(file_path, file_name).merge(params)
|
|
57
|
+
|
|
58
|
+
request.post(ENDPOINT, payload)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# URL variant of {#process}.
|
|
62
|
+
#
|
|
63
|
+
# @param raw_params [Hash]
|
|
64
|
+
# @option raw_params [String] :file_url **required.**
|
|
65
|
+
# @option raw_params [String] :file_name (basename of `:file_url`)
|
|
66
|
+
# @return [Veryfi::Resource]
|
|
67
|
+
def process_url(raw_params)
|
|
68
|
+
params = raw_params.transform_keys(&:to_sym)
|
|
69
|
+
params[:file_name] ||= File.basename(params[:file_url]) if params[:file_url]
|
|
70
|
+
|
|
71
|
+
request.post(ENDPOINT, params)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Update writable fields on a processed W-9.
|
|
75
|
+
#
|
|
76
|
+
# @param id [Integer]
|
|
77
|
+
# @param params [Hash]
|
|
78
|
+
# @return [Veryfi::Resource]
|
|
79
|
+
def update(id, params)
|
|
80
|
+
request.put("#{ENDPOINT}#{id}/", params)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Delete a W-9.
|
|
84
|
+
#
|
|
85
|
+
# @param id [Integer]
|
|
86
|
+
# @return [Veryfi::Resource]
|
|
87
|
+
def delete(id)
|
|
88
|
+
request.delete("#{ENDPOINT}#{id}/")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/veryfi/client.rb
CHANGED
|
@@ -1,7 +1,46 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Veryfi
|
|
4
|
+
# The user-facing entry point.
|
|
5
|
+
#
|
|
6
|
+
# @example Basic usage
|
|
7
|
+
# client = Veryfi::Client.new(
|
|
8
|
+
# client_id: ENV["VERYFI_CLIENT_ID"],
|
|
9
|
+
# client_secret: ENV["VERYFI_CLIENT_SECRET"],
|
|
10
|
+
# username: ENV["VERYFI_USERNAME"],
|
|
11
|
+
# api_key: ENV["VERYFI_API_KEY"]
|
|
12
|
+
# )
|
|
13
|
+
# client.document.process(file_path: "./receipt.jpg")
|
|
14
|
+
#
|
|
15
|
+
# @example Custom Faraday configuration (persistent connections + retries)
|
|
16
|
+
# client = Veryfi::Client.new(
|
|
17
|
+
# client_id: "…",
|
|
18
|
+
# client_secret: "…",
|
|
19
|
+
# username: "…",
|
|
20
|
+
# api_key: "…",
|
|
21
|
+
# faraday: ->(conn) {
|
|
22
|
+
# conn.request :retry, max: 3, interval: 0.5, backoff_factor: 2,
|
|
23
|
+
# retry_statuses: [429, 502, 503, 504]
|
|
24
|
+
# conn.response :logger, Rails.logger if defined?(Rails)
|
|
25
|
+
# conn.adapter :net_http_persistent
|
|
26
|
+
# }
|
|
27
|
+
# )
|
|
4
28
|
class Client
|
|
29
|
+
# DSL: declare an API namespace as a lazily-memoized reader.
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# api_namespace :document, Veryfi::Api::Document
|
|
33
|
+
#
|
|
34
|
+
# @param name [Symbol]
|
|
35
|
+
# @param klass [Class] API class accepting a `Veryfi::Request` in its constructor
|
|
36
|
+
# @return [void]
|
|
37
|
+
def self.api_namespace(name, klass)
|
|
38
|
+
ivar = :"@_#{name}"
|
|
39
|
+
define_method(name) do
|
|
40
|
+
instance_variable_get(ivar) || instance_variable_set(ivar, klass.new(request))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
5
44
|
attr_reader :request
|
|
6
45
|
|
|
7
46
|
def initialize(
|
|
@@ -11,26 +50,31 @@ module Veryfi
|
|
|
11
50
|
api_key:,
|
|
12
51
|
base_url: "https://api.veryfi.com/api/",
|
|
13
52
|
api_version: "v8",
|
|
14
|
-
timeout:
|
|
53
|
+
timeout: 30,
|
|
54
|
+
faraday: nil
|
|
15
55
|
)
|
|
16
|
-
@request = Veryfi::Request.new(
|
|
56
|
+
@request = Veryfi::Request.new(
|
|
57
|
+
client_id, client_secret, username, api_key,
|
|
58
|
+
base_url, api_version, timeout, faraday
|
|
59
|
+
)
|
|
17
60
|
end
|
|
18
61
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
62
|
+
api_namespace :document, Veryfi::Api::Document
|
|
63
|
+
api_namespace :line_item, Veryfi::Api::LineItem
|
|
64
|
+
api_namespace :tax_line, Veryfi::Api::TaxLine
|
|
65
|
+
api_namespace :tag, Veryfi::Api::Tag
|
|
66
|
+
api_namespace :document_tag, Veryfi::Api::DocumentTag
|
|
67
|
+
api_namespace :any_document, Veryfi::Api::AnyDocument
|
|
68
|
+
api_namespace :bank_statement, Veryfi::Api::BankStatement
|
|
69
|
+
api_namespace :bank_statement_split, Veryfi::Api::BankStatementSplit
|
|
70
|
+
api_namespace :business_card, Veryfi::Api::BusinessCard
|
|
71
|
+
api_namespace :check, Veryfi::Api::Check
|
|
72
|
+
api_namespace :classify, Veryfi::Api::Classify
|
|
73
|
+
api_namespace :pdf_split, Veryfi::Api::PdfSplit
|
|
74
|
+
api_namespace :w2, Veryfi::Api::W2
|
|
75
|
+
api_namespace :w2_split, Veryfi::Api::W2Split
|
|
76
|
+
api_namespace :w8, Veryfi::Api::W8
|
|
77
|
+
api_namespace :w9, Veryfi::Api::W9
|
|
34
78
|
|
|
35
79
|
def api_url
|
|
36
80
|
request.api_url
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Veryfi
|
|
4
|
+
# Process-wide settings used by {Veryfi.client} to build the shared
|
|
5
|
+
# singleton client. Mirrors the keyword arguments of
|
|
6
|
+
# {Veryfi::Client#initialize}; defaults match the client's defaults.
|
|
7
|
+
class Configuration
|
|
8
|
+
ATTRS = %i[
|
|
9
|
+
client_id client_secret username api_key
|
|
10
|
+
base_url api_version timeout faraday
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
attr_accessor(*ATTRS)
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@base_url = "https://api.veryfi.com/api/"
|
|
17
|
+
@api_version = "v8"
|
|
18
|
+
@timeout = 30
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Hash] the configuration as a keyword-arg-ready Hash. Keys
|
|
22
|
+
# with `nil` values are still included; {Veryfi.client} calls
|
|
23
|
+
# `.compact` before passing it to {Veryfi::Client#initialize}.
|
|
24
|
+
def to_h
|
|
25
|
+
ATTRS.to_h { |attr| [attr, public_send(attr)] }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/veryfi/error.rb
CHANGED
|
@@ -3,30 +3,116 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
|
|
5
5
|
module Veryfi
|
|
6
|
+
# Namespace + factory for every error raised by this SDK.
|
|
7
|
+
#
|
|
8
|
+
# All errors inherit from {VeryfiError}, so callers that only need to
|
|
9
|
+
# know "something went wrong with Veryfi" can keep using:
|
|
10
|
+
#
|
|
11
|
+
# begin
|
|
12
|
+
# client.document.process(file_path: path)
|
|
13
|
+
# rescue Veryfi::Error::VeryfiError => e
|
|
14
|
+
# # …
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# Callers that want to react differently per HTTP status can rescue a
|
|
18
|
+
# more specific subclass:
|
|
19
|
+
#
|
|
20
|
+
# begin
|
|
21
|
+
# client.document.process(file_path: path)
|
|
22
|
+
# rescue Veryfi::Error::Unauthorized then refresh_credentials!
|
|
23
|
+
# rescue Veryfi::Error::TooManyRequests then back_off
|
|
24
|
+
# rescue Veryfi::Error::ServerError then schedule_retry
|
|
25
|
+
# rescue Veryfi::Error::VeryfiError then log_and_raise
|
|
26
|
+
# end
|
|
6
27
|
class Error
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
end
|
|
14
|
-
|
|
28
|
+
# Base class for every Veryfi SDK error.
|
|
29
|
+
#
|
|
30
|
+
# `#message` returns the pretty-printed JSON error payload when one is
|
|
31
|
+
# available, otherwise the formatted `"<status>"` / `"<status>, <error>"`
|
|
32
|
+
# string. The `#status` and `#response` accessors give callers
|
|
33
|
+
# programmatic access to the same information.
|
|
15
34
|
class VeryfiError < StandardError
|
|
16
|
-
attr_reader :message
|
|
35
|
+
attr_reader :message, :status, :response
|
|
17
36
|
|
|
18
|
-
def initialize(message = "An error occurred", response = {})
|
|
19
|
-
@
|
|
37
|
+
def initialize(message = "An error occurred", response = {}, status = nil)
|
|
38
|
+
@status = status
|
|
39
|
+
@response = response
|
|
40
|
+
@message = if response.nil? || response.empty?
|
|
20
41
|
message
|
|
21
42
|
else
|
|
22
43
|
JSON.pretty_generate(response)
|
|
23
44
|
end
|
|
24
|
-
super(message)
|
|
45
|
+
super(@message)
|
|
25
46
|
end
|
|
26
47
|
|
|
27
48
|
def to_s
|
|
28
49
|
message
|
|
29
50
|
end
|
|
30
51
|
end
|
|
52
|
+
|
|
53
|
+
# 400 — request was malformed or failed server-side validation.
|
|
54
|
+
class BadRequest < VeryfiError; end
|
|
55
|
+
# 401 — credentials are missing, invalid, or expired.
|
|
56
|
+
class Unauthorized < VeryfiError; end
|
|
57
|
+
# 403 — credentials are valid but lack permission for this resource.
|
|
58
|
+
class AccessLimitReached < VeryfiError; end
|
|
59
|
+
# 404 — the resource id does not exist (or has been deleted).
|
|
60
|
+
class NotFound < VeryfiError; end
|
|
61
|
+
# 408 — the request timed out before Veryfi could respond.
|
|
62
|
+
class RequestTimeout < VeryfiError; end
|
|
63
|
+
# 409 — request conflicts with current resource state.
|
|
64
|
+
class Conflict < VeryfiError; end
|
|
65
|
+
# 415 — uploaded file type is not supported by the endpoint.
|
|
66
|
+
class UnsupportedMediaType < VeryfiError; end
|
|
67
|
+
# 429 — you've hit a rate limit. Back off and retry.
|
|
68
|
+
class TooManyRequests < VeryfiError; end
|
|
69
|
+
# Catch-all for any other 4xx the server returns.
|
|
70
|
+
class ClientError < VeryfiError; end
|
|
71
|
+
# 5xx — Veryfi reported an internal error. Retrying with backoff is usually safe.
|
|
72
|
+
class ServerError < VeryfiError; end
|
|
73
|
+
|
|
74
|
+
STATUS_MAP = {
|
|
75
|
+
400 => BadRequest,
|
|
76
|
+
401 => Unauthorized,
|
|
77
|
+
403 => AccessLimitReached,
|
|
78
|
+
404 => NotFound,
|
|
79
|
+
408 => RequestTimeout,
|
|
80
|
+
409 => Conflict,
|
|
81
|
+
415 => UnsupportedMediaType,
|
|
82
|
+
429 => TooManyRequests
|
|
83
|
+
}.freeze
|
|
84
|
+
private_constant :STATUS_MAP
|
|
85
|
+
|
|
86
|
+
# Build the right error subclass for the given HTTP status + response
|
|
87
|
+
# body. Always returns an instance of {VeryfiError} or one of its
|
|
88
|
+
# subclasses; never raises.
|
|
89
|
+
#
|
|
90
|
+
# @param status [Integer] HTTP status code
|
|
91
|
+
# @param response [Hash, Veryfi::Resource, nil] parsed JSON body
|
|
92
|
+
# @return [VeryfiError]
|
|
93
|
+
def self.from_response(status, response)
|
|
94
|
+
klass = error_class_for(status)
|
|
95
|
+
message = format_message(status, response)
|
|
96
|
+
|
|
97
|
+
klass.new(message, response, status)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def self.error_class_for(status)
|
|
101
|
+
return STATUS_MAP[status] if STATUS_MAP.key?(status)
|
|
102
|
+
return ServerError if status.between?(500, 599)
|
|
103
|
+
return ClientError if status.between?(400, 499)
|
|
104
|
+
|
|
105
|
+
VeryfiError
|
|
106
|
+
end
|
|
107
|
+
private_class_method :error_class_for
|
|
108
|
+
|
|
109
|
+
def self.format_message(status, response)
|
|
110
|
+
if response.nil? || response.empty?
|
|
111
|
+
format("%<code>d", code: status)
|
|
112
|
+
else
|
|
113
|
+
format("%<code>d, %<message>s", code: status, message: response["error"])
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
private_class_method :format_message
|
|
31
117
|
end
|
|
32
118
|
end
|
data/lib/veryfi/request.rb
CHANGED
|
@@ -5,8 +5,16 @@ require "faraday"
|
|
|
5
5
|
require "json"
|
|
6
6
|
|
|
7
7
|
module Veryfi
|
|
8
|
+
# Low-level HTTP layer used by every API class. You typically don't need
|
|
9
|
+
# to interact with this directly — go through {Veryfi::Client} instead.
|
|
10
|
+
#
|
|
11
|
+
# Custom Faraday configuration (adapter, retries, logging, persistent
|
|
12
|
+
# connections, …) can be supplied via the `faraday:` block when
|
|
13
|
+
# constructing the client. The block receives the `Faraday::Connection`
|
|
14
|
+
# before it's frozen, so you can attach any middleware you want.
|
|
8
15
|
class Request
|
|
9
|
-
attr_reader :client_id, :client_secret, :username, :api_key,
|
|
16
|
+
attr_reader :client_id, :client_secret, :username, :api_key,
|
|
17
|
+
:base_url, :api_version, :timeout, :faraday_block
|
|
10
18
|
|
|
11
19
|
VERBS_WITH_BODIES = %i[post put].freeze
|
|
12
20
|
|
|
@@ -17,7 +25,8 @@ module Veryfi
|
|
|
17
25
|
api_key,
|
|
18
26
|
base_url,
|
|
19
27
|
api_version,
|
|
20
|
-
timeout
|
|
28
|
+
timeout,
|
|
29
|
+
faraday_block = nil
|
|
21
30
|
)
|
|
22
31
|
@client_id = client_id
|
|
23
32
|
@client_secret = client_secret
|
|
@@ -26,6 +35,7 @@ module Veryfi
|
|
|
26
35
|
@base_url = base_url
|
|
27
36
|
@api_version = api_version
|
|
28
37
|
@timeout = timeout
|
|
38
|
+
@faraday_block = faraday_block
|
|
29
39
|
end
|
|
30
40
|
|
|
31
41
|
def get(path, params = {})
|
|
@@ -54,8 +64,7 @@ module Veryfi
|
|
|
54
64
|
url = [api_url, path].join
|
|
55
65
|
body = generate_body(http_verb, params)
|
|
56
66
|
headers = generate_headers(params)
|
|
57
|
-
|
|
58
|
-
response = conn.public_send(http_verb, url, body, headers)
|
|
67
|
+
response = attempt_request(http_verb, url, body, headers)
|
|
59
68
|
json_response = process_response(response)
|
|
60
69
|
|
|
61
70
|
if response.success?
|
|
@@ -65,9 +74,19 @@ module Veryfi
|
|
|
65
74
|
end
|
|
66
75
|
end
|
|
67
76
|
|
|
77
|
+
def attempt_request(http_verb, url, body, headers)
|
|
78
|
+
conn.public_send(http_verb, url, body, headers)
|
|
79
|
+
rescue Faraday::TimeoutError => e
|
|
80
|
+
raise unless e.message.include?("closed")
|
|
81
|
+
|
|
82
|
+
@_conn = nil
|
|
83
|
+
conn.public_send(http_verb, url, body, headers)
|
|
84
|
+
end
|
|
85
|
+
|
|
68
86
|
def conn
|
|
69
87
|
@_conn ||= Faraday.new do |conn|
|
|
70
88
|
conn.options.timeout = timeout
|
|
89
|
+
faraday_block&.call(conn)
|
|
71
90
|
end
|
|
72
91
|
end
|
|
73
92
|
|
|
@@ -84,18 +103,18 @@ module Veryfi
|
|
|
84
103
|
signature = generate_signature(params, timestamp)
|
|
85
104
|
|
|
86
105
|
default_headers.merge(
|
|
87
|
-
"X-Veryfi-Request-Timestamp"
|
|
88
|
-
"X-Veryfi-Request-Signature"
|
|
106
|
+
"X-Veryfi-Request-Timestamp" => timestamp,
|
|
107
|
+
"X-Veryfi-Request-Signature" => signature
|
|
89
108
|
)
|
|
90
109
|
end
|
|
91
110
|
|
|
92
111
|
def default_headers
|
|
93
112
|
{
|
|
94
|
-
"User-Agent"
|
|
95
|
-
Accept
|
|
96
|
-
"Content-Type"
|
|
97
|
-
"Client-Id"
|
|
98
|
-
Authorization
|
|
113
|
+
"User-Agent" => "Ruby Veryfi-Ruby/#{Veryfi::VERSION}",
|
|
114
|
+
"Accept" => "application/json",
|
|
115
|
+
"Content-Type" => "application/json",
|
|
116
|
+
"Client-Id" => client_id,
|
|
117
|
+
"Authorization" => "apikey #{username}:#{api_key}"
|
|
99
118
|
}
|
|
100
119
|
end
|
|
101
120
|
|
|
@@ -104,9 +123,9 @@ module Veryfi
|
|
|
104
123
|
end
|
|
105
124
|
|
|
106
125
|
def process_response(response)
|
|
107
|
-
return
|
|
126
|
+
return Veryfi::Resource.new if response.body.empty?
|
|
108
127
|
|
|
109
|
-
JSON.parse(response.body)
|
|
128
|
+
Veryfi::Resource.wrap(JSON.parse(response.body))
|
|
110
129
|
end
|
|
111
130
|
end
|
|
112
131
|
end
|