shopify-client 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|