e_plat 1.3.0 → 1.5.0
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/lib/e_plat/resource/collection.rb +9 -2
- data/lib/e_plat/resource/concerns/graph_q_lable.rb +69 -1
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2026_04/mutation/product/variant.rb +12 -0
- data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2026_04/mutation/product.rb +8 -0
- data/lib/e_plat/version.rb +1 -1
- data/lib/e_plat.rb +58 -58
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f97875697b04e12d0fd6514ae9198d03022199c064d0c752ffa60a7f024c13e
|
|
4
|
+
data.tar.gz: e8ebc93252048ff24b128d3f46125d6709fccf66dc95578ab5267bb842d0011c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0405d140e821f967cd7266006fd79881b8da73c6fe9bba9e82f337326d5f604392627e7c77e9e399a3a4b278aa8daee2590ca667b2834344e740799acce462a0
|
|
7
|
+
data.tar.gz: 4305d78ed5fb650cd21034bd450c7fca9ed3cdf4af8432202b2dfc888fc1ee2f9ef6c2d97e3a74f7044d110fffc03cd7255e2ac9abdad09b8fbdf7e65e94c2a6
|
|
@@ -8,6 +8,7 @@ module EPlat
|
|
|
8
8
|
EPlat::Shopify::Product
|
|
9
9
|
EPlat::Shopify::Product::Variant
|
|
10
10
|
)
|
|
11
|
+
GRAPHQL_PAGING_PARAMS = %w(after before first last limit).freeze
|
|
11
12
|
|
|
12
13
|
def initialize(parsed = [])
|
|
13
14
|
super parsed
|
|
@@ -56,8 +57,8 @@ module EPlat
|
|
|
56
57
|
url = "#{ resource_class.collection_path }#{ url }" if client.bigcommerce?
|
|
57
58
|
|
|
58
59
|
if paginates_via_graphql?
|
|
59
|
-
|
|
60
|
-
resource_class.find(:all, params:
|
|
60
|
+
cursor_param = (url == @next) ? "after" : "before"
|
|
61
|
+
resource_class.find(:all, params: params_for_another_graphql_page(cursor_param, url))
|
|
61
62
|
elsif client.woocommerce?
|
|
62
63
|
page_number = extract_page_from_url(url)
|
|
63
64
|
resource_class.find(:all, params: {page: page_number})
|
|
@@ -66,6 +67,12 @@ module EPlat
|
|
|
66
67
|
end
|
|
67
68
|
end
|
|
68
69
|
|
|
70
|
+
def params_for_another_graphql_page(cursor_param, cursor)
|
|
71
|
+
filter_params = (original_params || {}).to_h.reject { |key, _value| GRAPHQL_PAGING_PARAMS.include?(key.to_s) }
|
|
72
|
+
|
|
73
|
+
filter_params.merge(cursor_param => cursor, first: 100)
|
|
74
|
+
end
|
|
75
|
+
|
|
69
76
|
def pagination_links
|
|
70
77
|
return pagination_cursors if paginates_via_graphql?
|
|
71
78
|
|
|
@@ -10,6 +10,7 @@ module EPlat
|
|
|
10
10
|
|
|
11
11
|
FILTER_ARGS = [:id, :after, :before, :last, :first, :sort_key, :reverse].freeze
|
|
12
12
|
QUERY_ARG_ARGS = [:available_for_sale, :created_at, :product_type, :product_id, :tag, :tag_not, :title, :updated_at, :vendor].freeze
|
|
13
|
+
UserErrorsResponse = Struct.new(:body)
|
|
13
14
|
|
|
14
15
|
class_methods do
|
|
15
16
|
|
|
@@ -60,11 +61,15 @@ module EPlat
|
|
|
60
61
|
else
|
|
61
62
|
case action
|
|
62
63
|
when :find_one, :find_single
|
|
64
|
+
raise_resource_not_found! if resource_missing_from?(response)
|
|
65
|
+
|
|
63
66
|
instantiate_record resources_parsed_from(response)
|
|
64
67
|
else
|
|
65
|
-
instantiate_collection resources_parsed_from(response)
|
|
68
|
+
instantiate_collection resources_parsed_from(response), args
|
|
66
69
|
end
|
|
67
70
|
end
|
|
71
|
+
rescue ActiveResource::ResourceNotFound
|
|
72
|
+
raise
|
|
68
73
|
rescue StandardError => e
|
|
69
74
|
raise EPlat::GraphqlError.new(e.message)
|
|
70
75
|
end
|
|
@@ -79,6 +84,17 @@ module EPlat
|
|
|
79
84
|
).body
|
|
80
85
|
end
|
|
81
86
|
|
|
87
|
+
# a 200 with a null resource is Shopify graphql's version of a 404
|
|
88
|
+
def resource_missing_from?(response)
|
|
89
|
+
data = response["data"].is_a?(Hash) ? response["data"] : response
|
|
90
|
+
|
|
91
|
+
data.key?(graphql_element_name) && data[graphql_element_name].nil?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def raise_resource_not_found!
|
|
95
|
+
raise ActiveResource::ResourceNotFound.new(nil, "#{name} not found")
|
|
96
|
+
end
|
|
97
|
+
|
|
82
98
|
#utils
|
|
83
99
|
|
|
84
100
|
def scope_with_options_hash(args, action)
|
|
@@ -214,6 +230,13 @@ module EPlat
|
|
|
214
230
|
|
|
215
231
|
end
|
|
216
232
|
|
|
233
|
+
def save!
|
|
234
|
+
return true if save
|
|
235
|
+
|
|
236
|
+
invalid_response = UserErrorsResponse.new({message: errors.full_messages.join(", ")}.to_json)
|
|
237
|
+
raise ActiveResource::ResourceInvalid.new(invalid_response)
|
|
238
|
+
end
|
|
239
|
+
|
|
217
240
|
def mutate_graphql(action, graphql_proc_constant, *args)
|
|
218
241
|
callback_name = (action == :delete) ? :destroy : action
|
|
219
242
|
|
|
@@ -224,6 +247,9 @@ module EPlat
|
|
|
224
247
|
raise EPlat::GraphqlError.new(response.first["message"])
|
|
225
248
|
elsif response["errors"]
|
|
226
249
|
raise EPlat::GraphqlError.new(response["errors"].first["message"])
|
|
250
|
+
elsif (user_errors_by_mutation = user_errors_by_mutation_from(response)).any?
|
|
251
|
+
log_user_errors(user_errors_by_mutation)
|
|
252
|
+
raise_resource_invalid_with(user_errors_by_mutation.values.flatten)
|
|
227
253
|
else
|
|
228
254
|
response = remove_mutation_root_from(response, graphql_proc_constant)
|
|
229
255
|
parsed_response = self.class.resources_parsed_from(response)
|
|
@@ -238,6 +264,8 @@ module EPlat
|
|
|
238
264
|
changed_attributes.clear
|
|
239
265
|
end
|
|
240
266
|
end
|
|
267
|
+
rescue ActiveResource::ResourceInvalid
|
|
268
|
+
raise
|
|
241
269
|
rescue StandardError => e
|
|
242
270
|
raise EPlat::GraphqlError.new(e.message)
|
|
243
271
|
end
|
|
@@ -251,6 +279,46 @@ module EPlat
|
|
|
251
279
|
mutation_proc.call(input, additional_graphql:)
|
|
252
280
|
end
|
|
253
281
|
|
|
282
|
+
# one request can carry several mutation roots, e.g. productUpdate plus productVariantsBulkUpdate
|
|
283
|
+
def user_errors_by_mutation_from(response)
|
|
284
|
+
data = response["data"].is_a?(Hash) ? response["data"] : response
|
|
285
|
+
mutation_payloads = data.select { |_mutation_name, payload| payload.is_a?(Hash) }
|
|
286
|
+
|
|
287
|
+
mutation_payloads
|
|
288
|
+
.transform_values { |payload| payload["userErrors"] || [] }
|
|
289
|
+
.reject { |_mutation_name, user_errors| user_errors.empty? }
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def log_user_errors(user_errors_by_mutation)
|
|
293
|
+
user_errors_by_mutation.each do |mutation_name, user_errors|
|
|
294
|
+
user_errors_logger&.warn(
|
|
295
|
+
"[EPlat] #{mutation_name} userErrors on #{self.class.name} #{id.inspect}: #{user_errors_message(user_errors)}"
|
|
296
|
+
)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def user_errors_logger
|
|
301
|
+
defined?(Rails) && Rails.logger ? Rails.logger : self.class.logger
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def user_errors_message(user_errors)
|
|
305
|
+
user_errors.map do |user_error|
|
|
306
|
+
field = Array(user_error["field"]).join(".")
|
|
307
|
+
|
|
308
|
+
[field.presence, user_error["message"]].compact.join(": ")
|
|
309
|
+
end.join(", ")
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# ActiveResource's save rescues ResourceInvalid into errors and returns false; save! re-raises it
|
|
313
|
+
def raise_resource_invalid_with(user_errors)
|
|
314
|
+
error_messages = user_errors.map { |user_error| user_error["message"] }
|
|
315
|
+
invalid_response = UserErrorsResponse.new(
|
|
316
|
+
{errors: {base: error_messages}, message: error_messages.join(", ")}.to_json
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
raise ActiveResource::ResourceInvalid.new(invalid_response)
|
|
320
|
+
end
|
|
321
|
+
|
|
254
322
|
def remove_mutation_root_from(data, graphql_proc_constant)
|
|
255
323
|
mutation_root = graphql_proc_constant.split(".").last.camelcase(:lower)
|
|
256
324
|
(mutation_root && data[mutation_root]) ? data[mutation_root] : data
|
data/lib/e_plat/resource/platform_specific/shopify/graph_q_l/v_2026_04/mutation/product/variant.rb
CHANGED
|
@@ -47,6 +47,10 @@ module EPlat::Shopify::GraphQL::V202604::Mutation::Product::Variant
|
|
|
47
47
|
mutation {
|
|
48
48
|
#{additional_graphql}
|
|
49
49
|
productVariantsBulkDelete(#{args}) {
|
|
50
|
+
userErrors {
|
|
51
|
+
field
|
|
52
|
+
message
|
|
53
|
+
}
|
|
50
54
|
product {
|
|
51
55
|
id
|
|
52
56
|
}
|
|
@@ -61,6 +65,10 @@ module EPlat::Shopify::GraphQL::V202604::Mutation::Product::Variant
|
|
|
61
65
|
<<~GRAPHQL
|
|
62
66
|
#{additional_graphql}
|
|
63
67
|
productVariantsBulkCreate(#{args}) {
|
|
68
|
+
userErrors {
|
|
69
|
+
field
|
|
70
|
+
message
|
|
71
|
+
}
|
|
64
72
|
productVariants {
|
|
65
73
|
#{EPlat::Shopify::Product::Variant.graphql_fields}
|
|
66
74
|
}
|
|
@@ -74,6 +82,10 @@ module EPlat::Shopify::GraphQL::V202604::Mutation::Product::Variant
|
|
|
74
82
|
<<~GRAPHQL
|
|
75
83
|
#{additional_graphql}
|
|
76
84
|
productVariantsBulkUpdate(#{args}) {
|
|
85
|
+
userErrors {
|
|
86
|
+
field
|
|
87
|
+
message
|
|
88
|
+
}
|
|
77
89
|
productVariants {
|
|
78
90
|
#{EPlat::Shopify::Product::Variant.graphql_fields}
|
|
79
91
|
}
|
|
@@ -10,6 +10,10 @@ module EPlat::Shopify::GraphQL::V202604::Mutation::Product
|
|
|
10
10
|
mutation {
|
|
11
11
|
#{additional_graphql}
|
|
12
12
|
productCreate(#{args}) {
|
|
13
|
+
userErrors {
|
|
14
|
+
field
|
|
15
|
+
message
|
|
16
|
+
}
|
|
13
17
|
product {
|
|
14
18
|
#{EPlat::Shopify::Product.graphql_fields}
|
|
15
19
|
}
|
|
@@ -25,6 +29,10 @@ module EPlat::Shopify::GraphQL::V202604::Mutation::Product
|
|
|
25
29
|
mutation {
|
|
26
30
|
#{additional_graphql}
|
|
27
31
|
productUpdate(#{args}) {
|
|
32
|
+
userErrors {
|
|
33
|
+
field
|
|
34
|
+
message
|
|
35
|
+
}
|
|
28
36
|
product {
|
|
29
37
|
#{EPlat::Shopify::Product.graphql_fields}
|
|
30
38
|
}
|
data/lib/e_plat/version.rb
CHANGED
data/lib/e_plat.rb
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
require "e_plat/engine"
|
|
3
|
-
require "zeitwerk"
|
|
1
|
+
# frozen_string_literal: true
|
|
4
2
|
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
3
|
+
require 'e_plat/version'
|
|
4
|
+
require 'e_plat/engine'
|
|
5
|
+
require 'zeitwerk'
|
|
6
|
+
|
|
7
|
+
require 'active_support'
|
|
8
|
+
require 'activeresource'
|
|
9
|
+
require 'activeresource-response'
|
|
8
10
|
require 'dry-types'
|
|
9
11
|
require 'active_resource/connection_error'
|
|
10
|
-
require_relative
|
|
12
|
+
require_relative 'hash'
|
|
11
13
|
# require "e_plat/railtie" if defined?(Rails::Railtie)
|
|
12
14
|
|
|
13
15
|
Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
|
@@ -16,66 +18,64 @@ loader.collapse("#{__dir__}/e_plat/resource") # So can call EPlat::Product
|
|
|
16
18
|
loader.collapse("#{__dir__}/e_plat/resource/shopify_only")
|
|
17
19
|
loader.collapse("#{__dir__}/e_plat/resource/platform_specific")
|
|
18
20
|
loader.collapse("#{__dir__}/e_plat/errors")
|
|
19
|
-
loader.setup
|
|
21
|
+
loader.setup
|
|
20
22
|
|
|
23
|
+
module EPlat
|
|
24
|
+
extend Dry::Configurable
|
|
25
|
+
SUPPORTED_SHOPIFY_API_VERSIONS = %w[2024_01 2024_07 2025_01 2025_10 2026_04].freeze
|
|
26
|
+
SUPPORTED_BIGCOMMERCE_API_VERSIONS = ['3'].freeze
|
|
27
|
+
SUPPORTED_WOOCOMMERCE_API_VERSIONS = ['3'].freeze
|
|
28
|
+
SUPPORTED_PLATFORMS = %w[shopify bigcommerce woocommerce].freeze
|
|
29
|
+
SUPPORTED_RESOURCES = [
|
|
30
|
+
'shop',
|
|
31
|
+
'product',
|
|
32
|
+
'product/variant',
|
|
33
|
+
'product/image',
|
|
34
|
+
'product/option',
|
|
35
|
+
'product/image',
|
|
36
|
+
'order',
|
|
37
|
+
'order/line_item',
|
|
38
|
+
'order/shipping_address',
|
|
39
|
+
'order/billing_address',
|
|
40
|
+
'script_tag',
|
|
41
|
+
'webhook',
|
|
42
|
+
'metafield'
|
|
43
|
+
].freeze
|
|
44
|
+
PROTECTED_PREFIX = '_protected_'
|
|
21
45
|
|
|
46
|
+
setting(:shopify_api_version, default: ENV['EPLAT_SHOPIFY_API_VERSION'] || '2026_04', constructor: lambda do |value|
|
|
47
|
+
SUPPORTED_SHOPIFY_API_VERSIONS.include?(value.underscore) ? value.underscore : raise(ArgumentError, "Shopify API version #{value} is not supported")
|
|
48
|
+
end)
|
|
22
49
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
SUPPORTED_WOOCOMMERCE_API_VERSIONS = ['3']
|
|
28
|
-
SUPPORTED_PLATFORMS = ["shopify", "bigcommerce", "woocommerce"]
|
|
29
|
-
SUPPORTED_RESOURCES = [
|
|
30
|
-
"shop",
|
|
31
|
-
"product",
|
|
32
|
-
"product/variant",
|
|
33
|
-
"product/image",
|
|
34
|
-
"product/option",
|
|
35
|
-
"product/image",
|
|
36
|
-
"order",
|
|
37
|
-
"order/line_item",
|
|
38
|
-
"order/shipping_address",
|
|
39
|
-
"order/billing_address",
|
|
40
|
-
"script_tag",
|
|
41
|
-
"webhook",
|
|
42
|
-
"metafield"
|
|
43
|
-
]
|
|
44
|
-
PROTECTED_PREFIX = "_protected_"
|
|
45
|
-
|
|
46
|
-
setting(:shopify_api_version, default: ENV['EPLAT_SHOPIFY_API_VERSION'] || "2026_04", constructor: ->(value) do
|
|
47
|
-
SUPPORTED_SHOPIFY_API_VERSIONS.include?(value.underscore) ? value.underscore : raise(ArgumentError, "Shopify API version #{value} is not supported")
|
|
48
|
-
end)
|
|
50
|
+
setting(:bigcommerce_api_version, default: ENV['EPLAT_BIGCOMMERCE_API_VERSION'] || 'v3', constructor: lambda do |value|
|
|
51
|
+
v = value.downcase.gsub('v', '')
|
|
52
|
+
SUPPORTED_BIGCOMMERCE_API_VERSIONS.include?(v) ? v : raise(ArgumentError, "Bigcommerce API version #{value} is not supported")
|
|
53
|
+
end)
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
setting(:woocommerce_api_version, default: ENV['EPLAT_WOOCOMMERCE_API_VERSION'] || "v3", constructor: ->(value) do
|
|
56
|
-
v = value.downcase.gsub("v", "")
|
|
57
|
-
SUPPORTED_WOOCOMMERCE_API_VERSIONS.include?(v) ? v : raise(ArgumentError, "WooCommerce API version #{value} is not supported")
|
|
58
|
-
end)
|
|
55
|
+
setting(:woocommerce_api_version, default: ENV['EPLAT_WOOCOMMERCE_API_VERSION'] || 'v3', constructor: lambda do |value|
|
|
56
|
+
v = value.downcase.gsub('v', '')
|
|
57
|
+
SUPPORTED_WOOCOMMERCE_API_VERSIONS.include?(v) ? v : raise(ArgumentError, "WooCommerce API version #{value} is not supported")
|
|
58
|
+
end)
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
setting(:print_graphql_requests, default: ENV['EPLAT_PRINT_GRAPHQL_REQUESTS'] || false)
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
setting(:mute_woocommerce_variant_warnings, default: ENV['EPLAT_MUTE_WOOCOMMERCE_VARIANT_WARNINGS'] || false)
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
def self.api_display_name
|
|
65
|
+
apis = Struct.new(:shopify, :bigcommerce, :woocommerce)
|
|
66
|
+
apis.new(
|
|
67
|
+
shopify: config.shopify_api_version&.dasherize, # "2024-01"
|
|
68
|
+
bigcommerce: "v#{config.bigcommerce_api_version}", # "v3"
|
|
69
|
+
woocommerce: "v#{config.woocommerce_api_version}" # "v3"
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
def self.shopify_graphql_version
|
|
74
|
+
"V#{EPlat.config.shopify_api_version.gsub('_', '')}"
|
|
75
|
+
end
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
class Error < StandardError; end
|
|
78
|
+
class GraphqlError < Error; end
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
EPlat::Initializer.check_required_classes!
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: e_plat
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- oliwoodsuk
|
|
@@ -503,7 +503,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
503
503
|
- !ruby/object:Gem::Version
|
|
504
504
|
version: '0'
|
|
505
505
|
requirements: []
|
|
506
|
-
rubygems_version: 4.0.
|
|
506
|
+
rubygems_version: 4.0.10
|
|
507
507
|
specification_version: 4
|
|
508
508
|
summary: Creates a single interface for integrating with ecommerce platform APIs.
|
|
509
509
|
test_files: []
|