Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Note: For changes to the API, see https://shopify.dev/changelog?filter=api
## Unreleased

- [#1272](https://github.com/Shopify/shopify-api-ruby/issues/1272) and [#1273](https://github.com/Shopify/shopify-api-ruby/issues/1273) Raise `NoMethodError` for unknown REST property getters and report dynamic properties consistently from `respond_to?`.

## 16.4.0 (2026-11-05)
- [#1481](https://github.com/Shopify/shopify-api-ruby/pull/1481) `ShopifyAPI::Auth::TokenExchange.exchange_token` now raises `ShopifyAPI::Errors::InvalidJwtTokenError` when token exchange rejects the session token with `invalid_identity_session`, matching the existing `invalid_subject_token` handling.
- [#1475](https://github.com/Shopify/shopify-api-ruby/pull/1475) Add an optional `client_assertion:` argument to `ShopifyAPI::Auth::ClientCredentials.client_credentials`. When supplied, the token request authenticates with a JWT client assertion and sends no client secret. Omitting it, or passing `nil`, keeps the existing client-secret behavior. See the [OAuth documentation](docs/usage/oauth.md#client-assertion-authentication).
Expand Down
41 changes: 33 additions & 8 deletions lib/shopify_api/rest/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ class Base
extend T::Helpers
abstract!

INTERNAL_PROPERTIES = T.let(
["session", "client", "custom_prefix", "forced_nils", "aliased_properties"].freeze,
T::Array[String],
)
private_constant :INTERNAL_PROPERTIES

@headers = T.let(nil, T.nilable(T::Hash[T.any(Symbol, String), String]))
@has_one = T.let({}, T::Hash[Symbol, T::Class[T.anything]])
@has_many = T.let({}, T::Hash[Symbol, T::Class[T.anything]])
Expand Down Expand Up @@ -185,7 +191,7 @@ def get_path(http_method:, operation:, entity: nil, ids: {})

url_ids = ids.transform_keys(&:to_sym)
path_ids.each do |id|
if url_ids[id].nil? && (entity_id = entity&.public_send(id))
if url_ids[id].nil? && (entity_id = entity&.send(:get_optional_property, id))
url_ids[id] = entity_id
end
end
Expand Down Expand Up @@ -315,16 +321,20 @@ def method_missing(meth_id, val = nil)
set_property(var, val)
@forced_nils[var] = val.nil?
else
return super unless dynamic_property_defined?(var)

get_property(var)
end
end

sig { params(meth_id: Symbol, args: T.untyped).void }
def respond_to_missing?(meth_id, *args)
sig { params(meth_id: Symbol, include_private: T::Boolean).returns(T::Boolean) }
def respond_to_missing?(meth_id, include_private = false)
str = meth_id.id2name
match = str.match(/([^=]+)=/)

match.nil? ? true : super
return true unless match.nil?

dynamic_property_defined?(str) || super(meth_id, include_private)
end

sig { params(saving: T::Boolean).returns(T::Hash[String, T.untyped]) }
Expand Down Expand Up @@ -430,14 +440,14 @@ def attributes_to_update
def build_required_attributes(http_method:)
required_attributes = {}

primary_key_value = send(self.class.primary_key)
primary_key_value = get_optional_property(self.class.primary_key, include_private: true)
unless primary_key_value.nil?
required_attributes[self.class.primary_key] = primary_key_value
end

path_ids = deduce_path_ids(http_method)
path_ids&.each do |path_id|
path_id_value = send(path_id)
path_id_value = get_optional_property(path_id, include_private: true)
required_attributes[path_id.to_s] = path_id_value unless path_id_value.nil?
end

Expand All @@ -446,7 +456,7 @@ def build_required_attributes(http_method:)

sig { returns(Symbol) }
def deduce_write_verb
send(self.class.primary_key) ? :put : :post
get_optional_property(self.class.primary_key, include_private: true) ? :put : :post
end

sig { params(method: Symbol).returns(T.nilable(String)) }
Expand Down Expand Up @@ -488,19 +498,34 @@ def set_property(key, val)
# variables. To work around that, we're cleaning them up here but keeping track of the properties that were
# aliased this way. When loading up the property, we can map back from the "invalid" field so that it is
# transparent to outside callers
clean_key = key.to_s.gsub(/[\?\s]/, "")
clean_key = key.to_s.gsub(/[?\s]/, "")
@aliased_properties[key.to_s] = clean_key if clean_key != key

instance_variable_set("@#{clean_key}", val)
end

sig { params(key: T.any(String, Symbol), include_private: T::Boolean).returns(T.untyped) }
def get_optional_property(key, include_private: false)
return nil unless respond_to?(key, include_private)

include_private ? send(key) : public_send(key)
end

sig { params(key: T.any(String, Symbol)).returns(T.untyped) }
def get_property(key)
clean_key = @aliased_properties.key?(key.to_s) ? @aliased_properties[key.to_s] : key

instance_variable_get("@#{clean_key}")
end

sig { params(key: String).returns(T::Boolean) }
def dynamic_property_defined?(key)
property = @aliased_properties[key] || key
!INTERNAL_PROPERTIES.include?(property) && instance_variable_defined?("@#{property}")
rescue NameError
false
end

sig do
params(
element: T.nilable(T.any(T::Hash[String, T.untyped], ShopifyAPI::Rest::Base)),
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2022_07/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2022_10/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2023_01/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2023_04/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2023_07/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2023_10/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2024_01/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2024_04/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2024_07/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2024_10/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2025_01/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2025_04/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2025_07/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
4 changes: 2 additions & 2 deletions lib/shopify_api/rest/resources/2025_10/dispute_evidence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
@fulfillments = T.let(nil, T.nilable(T::Array[T.untyped]))
@id = T.let(nil, T.nilable(Integer))
@payments_dispute_id = T.let(nil, T.nilable(Integer))
@product_description = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
@product_description = T.let(nil, T.nilable(String))
@refund_policy_disclosure = T.let(nil, T.nilable(String))
@refund_refusal_explanation = T.let(nil, T.nilable(String))
@shipping_address = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped]))
Expand Down Expand Up @@ -74,7 +74,7 @@ def initialize(session: ShopifyAPI::Context.active_session, from_hash: nil)
attr_reader :id
sig { returns(T.nilable(Integer)) }
attr_reader :payments_dispute_id
sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
sig { returns(T.nilable(String)) }
attr_reader :product_description
sig { returns(T.nilable(String)) }
attr_reader :refund_policy_disclosure
Expand Down
Loading
Loading