shopify-client 0.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 +7 -0
- data/README.md +342 -0
- data/lib/shopify-client.rb +49 -0
- data/lib/shopify-client/authorise.rb +38 -0
- data/lib/shopify-client/bulk_request.rb +219 -0
- data/lib/shopify-client/cache/redis_store.rb +28 -0
- data/lib/shopify-client/cache/store.rb +67 -0
- data/lib/shopify-client/cache/thread_local_store.rb +47 -0
- data/lib/shopify-client/cached_request.rb +69 -0
- data/lib/shopify-client/client.rb +126 -0
- data/lib/shopify-client/client/logging.rb +38 -0
- data/lib/shopify-client/client/normalise_path.rb +14 -0
- data/lib/shopify-client/cookieless/check_header.rb +28 -0
- data/lib/shopify-client/cookieless/decode_session_token.rb +43 -0
- data/lib/shopify-client/cookieless/middleware.rb +39 -0
- data/lib/shopify-client/create_all_webhooks.rb +23 -0
- data/lib/shopify-client/create_webhook.rb +21 -0
- data/lib/shopify-client/delete_all_webhooks.rb +22 -0
- data/lib/shopify-client/delete_webhook.rb +13 -0
- data/lib/shopify-client/error.rb +9 -0
- data/lib/shopify-client/parse_link_header.rb +33 -0
- data/lib/shopify-client/request.rb +40 -0
- data/lib/shopify-client/resource/base.rb +46 -0
- data/lib/shopify-client/resource/create.rb +31 -0
- data/lib/shopify-client/resource/delete.rb +29 -0
- data/lib/shopify-client/resource/read.rb +80 -0
- data/lib/shopify-client/resource/update.rb +30 -0
- data/lib/shopify-client/response.rb +201 -0
- data/lib/shopify-client/response_errors.rb +59 -0
- data/lib/shopify-client/response_user_errors.rb +42 -0
- data/lib/shopify-client/struct.rb +10 -0
- data/lib/shopify-client/throttling/redis_strategy.rb +62 -0
- data/lib/shopify-client/throttling/strategy.rb +50 -0
- data/lib/shopify-client/throttling/thread_local_strategy.rb +29 -0
- data/lib/shopify-client/verify_request.rb +51 -0
- data/lib/shopify-client/verify_webhook.rb +24 -0
- data/lib/shopify-client/version.rb +5 -0
- data/lib/shopify-client/webhook.rb +32 -0
- data/lib/shopify-client/webhook_list.rb +50 -0
- metadata +206 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module ShopifyClient
|
6
|
+
module Cookieless
|
7
|
+
# Rack middleware implementing cookieless authentication with App Bridge
|
8
|
+
# session tokens.
|
9
|
+
#
|
10
|
+
# Returns a 401 response if a request is unauthorised.
|
11
|
+
class Middleware
|
12
|
+
# @param app [#call]
|
13
|
+
# @param is_authenticated [#call]
|
14
|
+
# predicate for deciding when the request should be checked
|
15
|
+
def initialize(app, is_authenticated: ->(env) { true })
|
16
|
+
@app = app
|
17
|
+
|
18
|
+
@is_authenticated = is_authenticated
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param env [Hash]
|
22
|
+
#
|
23
|
+
# @param [Array<Integer, Hash{String => String}, Array<String>]
|
24
|
+
def call(env)
|
25
|
+
CheckHeader.new.(env) if @is_authenticated.(env)
|
26
|
+
|
27
|
+
@app.call(env)
|
28
|
+
rescue UnauthorisedError
|
29
|
+
Rack::Response.new do |response|
|
30
|
+
response.status = 401
|
31
|
+
response.set_header('Content-Type', 'application/json')
|
32
|
+
response.write({
|
33
|
+
error: 'Invalid session token',
|
34
|
+
}.to_json)
|
35
|
+
end.to_a
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyClient
|
4
|
+
class CreateAllWebhooks
|
5
|
+
# Create all registered webhooks for a shop.
|
6
|
+
#
|
7
|
+
# @param client [Client]
|
8
|
+
#
|
9
|
+
# @return [Array<Hash>] response data
|
10
|
+
def call(client)
|
11
|
+
create_webhook = CreateWebhook.new
|
12
|
+
|
13
|
+
ShopifyClient.webhooks.map do |topic|
|
14
|
+
Thread.new do
|
15
|
+
create_webhook.(client, {
|
16
|
+
topic: topic,
|
17
|
+
fields: topic[:fields],
|
18
|
+
})
|
19
|
+
end
|
20
|
+
end.map(&:value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyClient
|
4
|
+
class CreateWebhook
|
5
|
+
# @param client [Client]
|
6
|
+
# @param webhook [Hash]
|
7
|
+
# @option webhook [String] :topic
|
8
|
+
# @option webhook [Array<String>] :fields
|
9
|
+
#
|
10
|
+
# @return [Hash] response data
|
11
|
+
def call(client, webhook)
|
12
|
+
client.post_json(credentials, 'webhooks', webhook: webhook.merge(
|
13
|
+
address: ShopifyClient.config.webhook_uri,
|
14
|
+
))
|
15
|
+
rescue Response::Error => e
|
16
|
+
raise e unless e.response.errors.message?([
|
17
|
+
/has already been taken/,
|
18
|
+
])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyClient
|
4
|
+
class DeleteAllWebhooks
|
5
|
+
# Delete any existing webhooks.
|
6
|
+
#
|
7
|
+
# @param client [Client]
|
8
|
+
#
|
9
|
+
# @return [Array<Hash>] response data
|
10
|
+
def call(client)
|
11
|
+
webhooks = client.get(credentials, 'webhooks')['webhooks']
|
12
|
+
|
13
|
+
delete_webhook = DeleteWebhook.new
|
14
|
+
|
15
|
+
webhooks.map do |webhook|
|
16
|
+
Thread.new do
|
17
|
+
delete_webhook.(client, webhook['id'])
|
18
|
+
end
|
19
|
+
end.map(&:value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyClient
|
4
|
+
class DeleteWebhook
|
5
|
+
# @param client [Client]
|
6
|
+
# @param id [Integer]
|
7
|
+
#
|
8
|
+
# @return [Hash] response data
|
9
|
+
def call(client, id)
|
10
|
+
client.delete(credentials, "webhooks/#{id}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'addressable'
|
4
|
+
|
5
|
+
module ShopifyClient
|
6
|
+
class ParseLinkHeader
|
7
|
+
# Parse a Link header into query params for each pagination link (e.g.
|
8
|
+
# :next, :previous).
|
9
|
+
#
|
10
|
+
# @param link_header [String]
|
11
|
+
#
|
12
|
+
# @return [Hash]
|
13
|
+
def call(link_header)
|
14
|
+
link_header.split(',').map do |link|
|
15
|
+
url, rel = link.split(';') # rel should be the first param
|
16
|
+
url = url[/<(.*)>/, 1]
|
17
|
+
rel = rel[/rel="?(\w+)"?/, 1]
|
18
|
+
|
19
|
+
[
|
20
|
+
rel.to_sym,
|
21
|
+
params(url),
|
22
|
+
]
|
23
|
+
end.to_h
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param url [String]
|
27
|
+
#
|
28
|
+
# @return [Hash]
|
29
|
+
private def params(url)
|
30
|
+
Addressable::URI.parse(url).query_values
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyClient
|
4
|
+
# @!attribute [rw] myshopify_domain
|
5
|
+
# @return [String]
|
6
|
+
# @!attribute [rw] access_token
|
7
|
+
# @return [String, nil]
|
8
|
+
# @!attribute [rw] method
|
9
|
+
# @return [Symbol]
|
10
|
+
# @!attribute [rw] path
|
11
|
+
# @return [String]
|
12
|
+
# @!attribute [rw] params
|
13
|
+
# @return [Hash]
|
14
|
+
# @!attribute [rw] headers
|
15
|
+
# @return [Hash]
|
16
|
+
# @!attribute [rw] data
|
17
|
+
# @return [Hash, nil]
|
18
|
+
# @!attribute [rw] client
|
19
|
+
# @return [Client, nil]
|
20
|
+
Request = Struct.new(
|
21
|
+
:myshopify_domain,
|
22
|
+
:access_token,
|
23
|
+
:method,
|
24
|
+
:path,
|
25
|
+
:params,
|
26
|
+
:headers,
|
27
|
+
:data,
|
28
|
+
:client,
|
29
|
+
) do
|
30
|
+
# @return [Boolean]
|
31
|
+
def graphql?
|
32
|
+
path == '/graphql.json'
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String]
|
36
|
+
def inspect
|
37
|
+
"#<ShopifyClient::Request (#{myshopify_domain}#{path})>"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyClient
|
4
|
+
module Resource
|
5
|
+
module Base
|
6
|
+
module ClassMethods
|
7
|
+
# Set the remote API resource name for the subclass. If a singular
|
8
|
+
# is not provided, the plural will be used, without any trailing 's'.
|
9
|
+
#
|
10
|
+
# @param resource_plural [String, #to_s]
|
11
|
+
# @param resource_singular [String, #to_s, nil]
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# resource :orders
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# resource :orders, :order
|
18
|
+
def resource(name_plural, name_singular = nil)
|
19
|
+
define_method(:resource_name) { name_plural.to_s }
|
20
|
+
define_method(:resource_name_singular) do
|
21
|
+
name_singular ? name_singular.to_s : name_plural.to_s.sub(/s$/, '')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param base [Class]
|
27
|
+
def self.included(base)
|
28
|
+
base.extend(ClassMethods)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @abstract Use {ClassMethods#resource} to implement (required)
|
32
|
+
#
|
33
|
+
# @return [String]
|
34
|
+
def resource_name
|
35
|
+
raise NotImplementedError
|
36
|
+
end
|
37
|
+
|
38
|
+
# @abstract Use {ClassMethods#resource} to implement (required)
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
def resource_name_singular
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyClient
|
4
|
+
module Resource
|
5
|
+
module Create
|
6
|
+
# @param base [Class]
|
7
|
+
def self.included(base)
|
8
|
+
base.include(Base)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param client [Client]
|
12
|
+
# @param data [Hash]
|
13
|
+
#
|
14
|
+
# @return [Integer] the new result.id
|
15
|
+
def create(client, data)
|
16
|
+
result = client.post(resource_name, resource_name_singular => data).data[resource_name_singular]
|
17
|
+
|
18
|
+
result['id'].tap do |id|
|
19
|
+
ShopifyClient.config.logger({
|
20
|
+
source: 'shopify-client',
|
21
|
+
type: 'create',
|
22
|
+
info: {
|
23
|
+
resource: resource_name,
|
24
|
+
id: id,
|
25
|
+
},
|
26
|
+
}.to_json)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyClient
|
4
|
+
module Resource
|
5
|
+
module Delete
|
6
|
+
# @param base [Class]
|
7
|
+
def self.included(base)
|
8
|
+
base.include(Base)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param client [Client]
|
12
|
+
# @param id [Integer]
|
13
|
+
def delete(client, id)
|
14
|
+
client.delete("#{resource_name}/#{id}")
|
15
|
+
|
16
|
+
ShopifyClient.config.logger({
|
17
|
+
source: 'shopify-client',
|
18
|
+
type: 'delete',
|
19
|
+
info: {
|
20
|
+
resource: resource_name,
|
21
|
+
id: id,
|
22
|
+
},
|
23
|
+
}.to_json)
|
24
|
+
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyClient
|
4
|
+
module Resource
|
5
|
+
module Read
|
6
|
+
module ClassMethods
|
7
|
+
# Set the default query params. Note that 'fields' may be passed as an
|
8
|
+
# array of strings.
|
9
|
+
#
|
10
|
+
# @param params [Hash]
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# default_params fields: 'id,tags'
|
14
|
+
def default_params(params)
|
15
|
+
define_method(:default_params) { params }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param base [Class]
|
20
|
+
def self.included(base)
|
21
|
+
base.extend(ClassMethods)
|
22
|
+
|
23
|
+
base.include(Base)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @abstract Use {ClassMethods#default_params} to implement (optional)
|
27
|
+
#
|
28
|
+
# @return [Hash]
|
29
|
+
def default_params
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Find a single result.
|
34
|
+
#
|
35
|
+
# @param client [Client]
|
36
|
+
# @param id [Integer]
|
37
|
+
# @param params [Hash]
|
38
|
+
#
|
39
|
+
# @return [Hash]
|
40
|
+
def find_by_id(client, id, params = {})
|
41
|
+
params = default_params.merge(params)
|
42
|
+
|
43
|
+
client.get("#{resource_name}/#{id}", params).data[resource_name_singular]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Find all results.
|
47
|
+
#
|
48
|
+
# @param client [Client]
|
49
|
+
# @param params [Hash]
|
50
|
+
#
|
51
|
+
# @return [Enumerator<Hash>]
|
52
|
+
#
|
53
|
+
# @raise [ArgumentError] if 'fields' does not include 'id'
|
54
|
+
def all(client, params = {})
|
55
|
+
raise ArgumentError, 'missing id field' unless has_id?(params)
|
56
|
+
|
57
|
+
Enumerator.new do |yielder|
|
58
|
+
response = client.get(resource_name, params)
|
59
|
+
|
60
|
+
loop do
|
61
|
+
response.data[resource_name].each { |result| yielder << result }
|
62
|
+
|
63
|
+
response = response.next_page || break
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param params [Hash]
|
69
|
+
#
|
70
|
+
# @return [Boolean]
|
71
|
+
private def has_id?(params)
|
72
|
+
fields = params[:fields] || params['fields']
|
73
|
+
|
74
|
+
return true if fields.nil?
|
75
|
+
|
76
|
+
fields =~ /\bid\b/
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyClient
|
4
|
+
module Resource
|
5
|
+
module Update
|
6
|
+
# @param base [Class]
|
7
|
+
def self.included(base)
|
8
|
+
base.include(Base)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param client [Client]
|
12
|
+
# @param id [Integer]
|
13
|
+
# @param data [Hash]
|
14
|
+
def update(client, id, data)
|
15
|
+
client.put("#{resource_name}/#{id}", resource_name_singular => data)
|
16
|
+
|
17
|
+
ShopifyClient.config.logger({
|
18
|
+
source: 'shopify-client',
|
19
|
+
type: 'update',
|
20
|
+
info: {
|
21
|
+
resource: resource_name,
|
22
|
+
id: id,
|
23
|
+
},
|
24
|
+
}.to_json)
|
25
|
+
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'addressable'
|
4
|
+
|
5
|
+
module ShopifyClient
|
6
|
+
# @!attribute [rw] request
|
7
|
+
# @return [Request]
|
8
|
+
# @!attribute [rw] status_code
|
9
|
+
# @return [Integer]
|
10
|
+
# @!attribute [rw] headers
|
11
|
+
# @return [Hash]
|
12
|
+
# @!attribute [rw] data
|
13
|
+
# @return [Hash]
|
14
|
+
Response = Struct.new(:request, :status_code, :headers, :data)
|
15
|
+
|
16
|
+
# NOTE: Reopened for proper scoping of error classes.
|
17
|
+
class Response
|
18
|
+
class << self
|
19
|
+
# @param faraday_response [Faraday::Response]
|
20
|
+
# @param client [Client]
|
21
|
+
#
|
22
|
+
# @return [Response]
|
23
|
+
def from_faraday_response(faraday_response, client = nil)
|
24
|
+
uri = Addressable::URI.parse(faraday_response.env[:url])
|
25
|
+
|
26
|
+
new(
|
27
|
+
Request.new(
|
28
|
+
# Merchant myshopify.domain.
|
29
|
+
uri.host,
|
30
|
+
# Merchant access token.
|
31
|
+
faraday_response.env[:request_headers]['X-Shopify-Access-Token'],
|
32
|
+
# Request HTTP method.
|
33
|
+
faraday_response.env[:method],
|
34
|
+
# Request path.
|
35
|
+
uri.path,
|
36
|
+
# Request params.
|
37
|
+
uri.query_values,
|
38
|
+
# Request headers.
|
39
|
+
faraday_response.env[:request_headers],
|
40
|
+
# Request data.
|
41
|
+
faraday_response.env[:request_body],
|
42
|
+
# Client used for the request.
|
43
|
+
client,
|
44
|
+
),
|
45
|
+
faraday_response.status,
|
46
|
+
faraday_response.headers,
|
47
|
+
faraday_response.body || {},
|
48
|
+
).tap(&:assert!)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# @raise [ClientError] for status 4xx
|
53
|
+
# @raise [ServerError] for status 5xx
|
54
|
+
#
|
55
|
+
# @see https://shopify.dev/concepts/about-apis/response-codes
|
56
|
+
def assert!
|
57
|
+
case status_code
|
58
|
+
when 401
|
59
|
+
if errors.message?([/access token/i])
|
60
|
+
raise InvalidAccessTokenError.new(request, self), 'Invalid access token'
|
61
|
+
else
|
62
|
+
raise ClientError.new(request, self)
|
63
|
+
end
|
64
|
+
when 402
|
65
|
+
raise ShopError.new(request, self), 'Shop is frozen, awaiting payment'
|
66
|
+
when 403
|
67
|
+
# NOTE: Not sure what this one means (undocumented).
|
68
|
+
if errors.message?([/unavailable shop/i])
|
69
|
+
raise ShopError.new(request, self), 'Shop is unavailable'
|
70
|
+
else
|
71
|
+
raise ClientError.new(request, self)
|
72
|
+
end
|
73
|
+
when 423
|
74
|
+
raise ShopError.new(request, self), 'Shop is locked'
|
75
|
+
when 400..499
|
76
|
+
raise ClientError.new(request, self)
|
77
|
+
when 500..599
|
78
|
+
raise ServerError.new(request, self)
|
79
|
+
end
|
80
|
+
|
81
|
+
# GraphQL always has status 200.
|
82
|
+
if request.graphql? && (errors? || user_errors?)
|
83
|
+
raise GraphQLClientError.new(request, self)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Hash]
|
88
|
+
private def link
|
89
|
+
@link ||= ParseLinkHeader.new.(headers['Link'] || '')
|
90
|
+
end
|
91
|
+
|
92
|
+
# Request the next page for a GET request, if any.
|
93
|
+
#
|
94
|
+
# @param [Client]
|
95
|
+
#
|
96
|
+
# @return [Response, nil]
|
97
|
+
def next_page(client = request.client)
|
98
|
+
raise ArgumentError, 'missing client' if client.nil?
|
99
|
+
|
100
|
+
return nil unless link[:next]
|
101
|
+
|
102
|
+
client.get(request.path, link[:next])
|
103
|
+
end
|
104
|
+
|
105
|
+
# Request the next page for a GET request, if any.
|
106
|
+
#
|
107
|
+
# @param [Client]
|
108
|
+
#
|
109
|
+
# @return [Response, nil]
|
110
|
+
def previous_page(client = request.client)
|
111
|
+
raise ArgumentError, 'missing client' if client.nil?
|
112
|
+
|
113
|
+
return nil unless link[:previous]
|
114
|
+
|
115
|
+
client.get(request.path, link[:previous])
|
116
|
+
end
|
117
|
+
|
118
|
+
# Response errors (usually included with a 422 response).
|
119
|
+
#
|
120
|
+
# @return [ResponseErrors]
|
121
|
+
def errors
|
122
|
+
@errors ||= ResponseErrors.from_response_data(data)
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [Boolean]
|
126
|
+
def errors?
|
127
|
+
errors.any?
|
128
|
+
end
|
129
|
+
|
130
|
+
# GraphQL user errors (errors in mutation input).
|
131
|
+
#
|
132
|
+
# @return [ResponseUserErrors, nil]
|
133
|
+
def user_errors
|
134
|
+
return nil unless request.graphql?
|
135
|
+
|
136
|
+
@user_errors ||= ResponseUserErrors.from_response_data(data)
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [Boolean]
|
140
|
+
def user_errors?
|
141
|
+
return false unless request.graphql?
|
142
|
+
|
143
|
+
user_errors.any?
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return [String]
|
147
|
+
def inspect
|
148
|
+
"#<ShopifyClient::Response (#{status_code}, #{request.inspect})>"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class Response
|
153
|
+
# @!attribute [r] request
|
154
|
+
# @return [Request]
|
155
|
+
# @!attribute [r] response
|
156
|
+
# @return [Response]
|
157
|
+
class Error < Error
|
158
|
+
# @param request [Request]
|
159
|
+
# @param response [Response]
|
160
|
+
def initialize(request, response)
|
161
|
+
@request = request
|
162
|
+
@response = response
|
163
|
+
end
|
164
|
+
|
165
|
+
attr_reader :request
|
166
|
+
attr_reader :response
|
167
|
+
|
168
|
+
# @return [String]
|
169
|
+
def message
|
170
|
+
if response.errors?
|
171
|
+
"bad response (#{response.status_code}): #{response.errors.messages.first}"
|
172
|
+
else
|
173
|
+
"bad response (#{response.status_code})"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Client errors in the 4xx range.
|
179
|
+
ClientError = Class.new(Error)
|
180
|
+
# Server errors in the 5xx range.
|
181
|
+
ServerError = Class.new(Error)
|
182
|
+
# The access token was not accepted.
|
183
|
+
InvalidAccessTokenError = Class.new(ClientError)
|
184
|
+
# The shop is frozen/locked/unavailable.
|
185
|
+
ShopError = Class.new(ClientError)
|
186
|
+
|
187
|
+
# The GraphQL API always responds with a status code of 200.
|
188
|
+
GraphQLClientError = Class.new(ClientError) do
|
189
|
+
def message
|
190
|
+
case
|
191
|
+
when response.errors?
|
192
|
+
"bad response: #{response.errors.messages.first}"
|
193
|
+
when response.user_errors?
|
194
|
+
"bad response: #{response.user_errors.messages.first}"
|
195
|
+
else
|
196
|
+
"bad response"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|