class ShopifyAPI::GraphQL::Tiny

Client to make Shopify GraphQL Admin API requests with built-in retries.

Constants

ACCESS_TOKEN_HEADER
ConnectionError
DEFAULT_HEADERS
DEFAULT_RETRY_OPTIONS
ENDPOINT
Error
GraphQLError
QUERY_COST_HEADER
SHOPIFY_DOMAIN
VERSION

Public Class Methods

new(shop, token, options = nil) click to toggle source

Create a new GraphQL client.

Arguments

shop (String)

Shopify domain to make requests against

token (String)

Shopify Admin API GraphQL token

options (Hash)

Client options. Optional.

Options

:retry (Boolean|Hash)

Hash can be retry config options. For the format see ShopifyAPIRetry. Defaults to true

:version (String)

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

:debug (Boolean)

Output the HTTP request/response to STDERR. Defaults to false.

Errors

ArgumentError if no shop or token is provided.

# File lib/shopify_api/graphql/tiny.rb, line 65
def initialize(shop, token, options = nil)
  raise ArgumentError, "shop required" unless shop
  raise ArgumentError, "token required" unless token

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

  @headers = DEFAULT_HEADERS.dup
  @headers[ACCESS_TOKEN_HEADER] = token
  @headers[QUERY_COST_HEADER] = "true" if retry?

  @endpoint = URI(sprintf(ENDPOINT, @domain, !@options[:version].to_s.strip.empty? ? "/#{@options[:version]}" : ""))
end

Public Instance Methods

execute(q, variables = nil) click to toggle source

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

ConnectionError, HTTPError, RateLimitError, GraphQLError

  • An HTTPError is raised of the response does not have 200 status code

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

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

Returns

Hash

The GraphQL response. Unmodified.

# File lib/shopify_api/graphql/tiny.rb, line 99
def execute(q, variables = nil)
  config = retry? ? @options[:retry] || DEFAULT_RETRY_OPTIONS : {}
  ShopifyAPIRetry::GraphQL.retry(config) { post(q, variables) }
end

Private Instance Methods

post(query, variables = nil) click to toggle source
# File lib/shopify_api/graphql/tiny.rb, line 116
def post(query, variables = nil)
  begin
    # Newer versions of Ruby
    # response = Net::HTTP.post(@endpoint, query, @headers)
    params = { :query => query }
    params[:variables] = variables if variables

    post = Net::HTTP::Post.new(@endpoint.path)
    post.body = params.to_json

    @headers.each { |k,v| post[k] = v }

    request = Net::HTTP.new(@endpoint.host, @endpoint.port)
    request.use_ssl = true
    request.set_debug_output($stderr) if @options[:debug]

    response = request.start { |http| http.request(post) }
  rescue => e
    raise ConnectionError, "request to #@endpoint failed: #{e}"
  end

  # TODO: Even if non-200 check if JSON. See: https://shopify.dev/api/admin-graphql
  prefix = "failed to execute query for #@domain: "
  raise HTTPError.new("#{prefix}#{response.body}", response.code) if response.code != "200"

  json = JSON.parse(response.body)
  return json unless json.include?("errors")

  errors = json["errors"].map { |e| e["message"] }.join(", ")
  if json.dig("errors", 0, "extensions", "code") == "THROTTLED"
    raise RateLimitError.new(errors, json) unless retry?
    return json
  end

  raise GraphQLError, prefix + errors
end
retry?() click to toggle source
# File lib/shopify_api/graphql/tiny.rb, line 106
def retry?
  @options[:retry] != false
end
shopify_domain(host) click to toggle source
# File lib/shopify_api/graphql/tiny.rb, line 110
def shopify_domain(host)
  domain = host.sub(%r{\Ahttps?://}i, "")
  domain << SHOPIFY_DOMAIN unless domain.end_with?(SHOPIFY_DOMAIN)
  domain
end