Class: ShopifyAPI::GraphQL::Tiny

Inherits:
Object
  • Object
show all
Defined in:
lib/shopify_api/graphql/tiny.rb,
lib/shopify_api/graphql/tiny/version.rb

Overview

Lightweight, no-nonsense, Shopify GraphQL Admin/Storefront API client with built-in pagination and retry

Defined Under Namespace

Classes: GraphQLError, HTTPError

Constant Summary collapse

Error =
Class.new(StandardError)
ConnectionError =
Class.new(Error)
ERROR_CODE_THROTTLED =
"THROTTLED"
ERROR_CODE_TIMEOUT =
"TIMEOUT"
ERROR_CODE_SERVER_ERROR =
"INTERNAL_SERVER_ERROR"
RateLimitError =
Class.new(GraphQLError)
WarningError =
Class.new(GraphQLError)
USER_AGENT =
"ShopifyAPI::GraphQL::Tiny v#{VERSION} (Ruby v#{RUBY_VERSION})"
SHOPIFY_DOMAIN =
".myshopify.com"
ACCESS_TOKEN_HEADER =
"X-Shopify-Access-Token"
STOREFRONT_ACCESS_TOKEN_HEADER =
"X-Shopify-Storefront-Access-Token"
STOREFRONT_BUYER_IP_HEADER =
"X-Shopify-Storefront-Buyer-IP"
QUERY_COST_HEADER =
"X-GraphQL-Cost-Include-Fields"
DEFAULT_HEADERS =
{ "Content-Type" => "application/json" }.freeze
DEFAULT_BACKOFF_OPTIONS =
{
  :base_delay => 0.5,
  :jitter => true,
  :max_attempts => 10,
  :max_delay => 60,
  :multiplier => 2.0
}
DEFAULT_RETRY_ERRORS =
[
  "5XX",
  ERROR_CODE_SERVER_ERROR,
  ERROR_CODE_TIMEOUT,
  *NetHttpTimeoutErrors.all
]
ENDPOINT =

We omit the “/” after API for the case where there’s no version

"https://%s%s/api%s/graphql.json"
VERSION =
"1.1.0"

Instance Method Summary collapse

Constructor Details

#initialize(shop, token, options = nil) ⇒ Tiny

Create a new GraphQL client

Arguments

shop (String)

Shopify domain to make requests against

token (String)

Shopify Admin API or Storefront Access Token, depending on options

options (Hash)

Client options. Optional.

Options

:retry (Boolean|Array)

If false disable retries or an Array of errors to retry. Can be HTTP status codes, GraphQL errors, or exception classes.

:version (String)

Shopify API version to use. Defaults to the latest version.

:storefront (Boolean)

If true use the Storefront API instead of Admin API. Defaults to false.

:ip (String)

Optional buyer IP address for Storefront API (sets X-Shopify-Storefront-Buyer-IP header). Only used when :storefront is true.

:max_attempts (Integer)

Maximum number of retry attempts across all errors. Defaults to 10

:base_delay (Float)

Exponential backoff base delay. Defaults to 0.5

:jitter (Boolean)

Exponential backoff jitter (random delay added to backoff). Defaults to true

:multiplier (Float)

Exponential backoff multiplier. Defaults to 2.0

:debug (Boolean|IO)

Output the HTTP request/response to STDERR or to its value if it’s an IO. Defaults to false.

:raise_on_warnings (Boolean)

If true raise a WarningError when the GraphQL response contains warnings. Defaults to false.

Errors

ArgumentError if no shop or token is provided.

Raises:

  • (ArgumentError)


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/shopify_api/graphql/tiny.rb', line 99

def initialize(shop, token, options = nil)
  raise ArgumentError, "shop required" unless shop
  raise ArgumentError, "token required" unless token

  @domain = shopify_domain(shop)
  @options = options || {}

  @raise_on_warnings = @options[:raise_on_warnings]
  @storefront = !!@options[:storefront]

  @headers = DEFAULT_HEADERS.dup

  if @storefront
    @headers[STOREFRONT_ACCESS_TOKEN_HEADER] = token
    @headers[STOREFRONT_BUYER_IP_HEADER] = @options[:ip] if @options[:ip]
  else
    @headers[ACCESS_TOKEN_HEADER] = token
  end

  @headers[QUERY_COST_HEADER] = "true" unless @options[:retry] == false

  admin_path = @storefront ? "" : "/admin"
  @endpoint = URI(sprintf(ENDPOINT, @domain, admin_path, !@options[:version].to_s.strip.empty? ? "/#{@options[:version]}" : ""))
  @backoff_options = DEFAULT_BACKOFF_OPTIONS.merge(@options.slice(*DEFAULT_BACKOFF_OPTIONS.keys))

  if @options[:debug]
    @debug = @options[:debug].is_a?(IO) ? @options[:debug] : $stderr
  end

  case @options[:retry]
  when false
    @retryable = []
  when Array
    @retryable = @options[:retry]
  else
    @retryable = DEFAULT_RETRY_ERRORS
  end
end

Instance Method Details

#execute(q, variables = nil) ⇒ Object

Execute a GraphQL query or mutation

Arguments

q (String)

Query or mutation to execute

variables (Hash)

Optional. Variable to pass to the query or mutation given by q

Errors

ArgumentError, ConnectionError, HTTPError, RateLimitError, GraphQLError

Outside of ArgumentError these are raised after exhausing the configured retry.

  • An ShopifyAPI::GraphQL::Tiny::HTTPError is raised of the response does not have 200 status code

  • A ShopifyAPI::GraphQL::Tiny::RateLimitError is raised if rate-limited and retries are disabled or if still rate-limited after the configured number of retry attempts

  • A ShopifyAPI::GraphQL::Tiny::GraphQLError is raised if the response contains an errors property that is not a rate-limit error

  • A ShopifyAPI::GraphQL::Tiny::WarningError is raised if the :raise_on_warnings option is true and the response contains warnings

Returns

Hash

The GraphQL response. Unmodified.

Raises:

  • (ArgumentError)


164
165
166
167
168
169
170
# File 'lib/shopify_api/graphql/tiny.rb', line 164

def execute(q, variables = nil)
  raise ArgumentError, "query required" if q.nil? || q.to_s.strip.empty?

  @request_attempts = 0

  make_request(q, variables)
end

#paginate(*options) ⇒ Object

Create a pager to execute a paginated query:

pager = gql.paginate  # This is the same as gql.paginate(:after)
pager.execute(query, :id => id) do |page|
  page.dig("data", "product", "title")
end

The block is called for each page. If a block is not provided returns an instance of Enumerator::Lazy that will fetch the next page on each iteration.

Using pagination requires you to include the PageInfo object in your queries and wrap them in a function that accepts a page/cursor argument. See the README for more information.

Arguments

direction (Symbol)

The direction to paginate, either :after or :before. Optional, defaults to :after:

options (Hash)

Pagination options. Optional.

Options

:after (Array|Proc)

The location of PageInfo block.

An Array will be passed directly to Hash#dig. A TypeError resulting from the #dig call will be raised as an ArgumentError.

The "data" and "pageInfo" keys are automatically added if not provided.

A Proc must accept the GraphQL response Hash as its argument and must return the pageInfo block to use for pagination.

:before (Array|Proc)

See the :after option

:variable (String)

Name of the GraphQL variable to use as the “page” argument. Defaults to "before" or "after", depending on the pagination direction.

Returns

nil or ‘Enumerator::Lazy` if no block was provided

Errors

See #execute



219
220
221
# File 'lib/shopify_api/graphql/tiny.rb', line 219

def paginate(*options)
  Pager.new(self, options)
end