-
Notifications
You must be signed in to change notification settings - Fork 476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ApiVersion coercion_modes and fetch_known_versions from Shopify #600
Changes from all commits
1e75b47
c76e916
62f7a03
8192ad2
adaf51e
0ee8d7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,129 +1,195 @@ | ||
# frozen_string_literal: true | ||
module ShopifyAPI | ||
class ApiVersion | ||
class ApiVersionNotSetError < StandardError; end | ||
class UnknownVersion < StandardError; end | ||
class InvalidVersion < StandardError; end | ||
class ApiVersionNotSetError < StandardError; end | ||
include Comparable | ||
|
||
extend DefinedVersions | ||
HANDLE_FORMAT = /^\d{4}-\d{2}$/.freeze | ||
UNSTABLE_HANDLE = 'unstable' | ||
UNSTABLE_AS_DATE = Time.utc(3000, 1, 1) | ||
API_PREFIX = '/admin/api/' | ||
LOOKUP_MODES = [:raise_on_unknown, :define_on_unknown].freeze | ||
|
||
include Comparable | ||
class << self | ||
attr_reader :versions | ||
|
||
def self.coerce_to_version(version_or_name) | ||
return version_or_name if version_or_name.is_a?(ApiVersion) | ||
def version_lookup_mode | ||
@version_lookup_mode ||= :define_on_unknown | ||
end | ||
|
||
@versions ||= {} | ||
@versions.fetch(version_or_name.to_s) do | ||
raise UnknownVersion, "#{version_or_name} is not in the defined version set: #{@versions.keys.join(', ')}" | ||
def version_lookup_mode=(mode) | ||
raise ArgumentError, "Mode must be one of #{LOOKUP_MODES}" unless LOOKUP_MODES.include?(mode) | ||
sanitize_known_versions if mode == :raise_on_unknown | ||
@version_lookup_mode = mode | ||
end | ||
end | ||
|
||
def self.define_version(version) | ||
@versions ||= {} | ||
def find_version(version_or_handle) | ||
return version_or_handle if version_or_handle.is_a?(ApiVersion) | ||
handle = version_or_handle.to_s | ||
@versions ||= {} | ||
@versions.fetch(handle) do | ||
if @version_lookup_mode == :raise_on_unknown | ||
raise UnknownVersion, unknown_version_error_message(handle) | ||
else | ||
add_to_known_versions(ApiVersion.new(handle: handle)) | ||
end | ||
end | ||
end | ||
|
||
@versions[version.name] = version | ||
end | ||
def coerce_to_version(version_or_handle) | ||
warn( | ||
'[DEPRECATED] ShopifyAPI::ApiVersion.coerce_to_version be removed in a future version. ' \ | ||
'Use `find_version` instead.' | ||
) | ||
find_version(version_or_handle) | ||
end | ||
|
||
def fetch_known_versions | ||
@versions = Meta.admin_versions.map do |version| | ||
[version.handle, ApiVersion.new(version.attributes.merge(verified: version.persisted?))] | ||
end.to_h | ||
end | ||
|
||
def self.clear_defined_versions | ||
@versions = {} | ||
def define_known_versions | ||
warn( | ||
'[DEPRECATED] ShopifyAPI::ApiVersion.define_known_versions is deprecated and will be removed in a future version. ' \ | ||
'Use `fetch_known_versions` instead.' | ||
) | ||
fetch_known_versions | ||
end | ||
|
||
def add_to_known_versions(version) | ||
@versions[version.handle] = version | ||
end | ||
|
||
def clear_known_versions | ||
@versions = {} | ||
end | ||
|
||
def clear_defined_versions | ||
warn( | ||
'[DEPRECATED] ShopifyAPI::ApiVersion.clear_defined_versions is deprecated and will be removed in a future version. ' \ | ||
'Use `clear_known_versions` instead.' | ||
) | ||
clear_known_versions | ||
end | ||
|
||
def latest_stable_version | ||
warn( | ||
'[DEPRECATED] ShopifyAPI::ApiVersion.latest_stable_version is deprecated and will be removed in a future version.' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a recommended way to do this same thing after this method is removed? Is this just discouraged going forward? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we wanted to discourage anyone trying to set the version to the latest version automatically since it's likely to break apps. Bumping to the latest version should hopefully be intentional. It can still be computed however since each ApiVersion has a
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. latest_supported_version is used by the ShopifyApp gem https://github.com/Shopify/shopify_app/blob/0900ddcb2f268f6efe3e184739ee83eeab3a0020/lib/generators/shopify_app/install/install_generator.rb#L12 to run the generators. It can now use |
||
) | ||
versions.values.find(&:latest_supported?) | ||
end | ||
|
||
private | ||
|
||
def sanitize_known_versions | ||
return if @versions.nil? | ||
@versions = @versions.keys.map do |handle| | ||
next unless @versions[handle].verified? | ||
[handle, @versions[handle]] | ||
end.compact.to_h | ||
end | ||
|
||
def unknown_version_error_message(handle) | ||
msg = "ApiVersion.version_lookup_mode is set to `:raise_on_unknown`. \n" | ||
return msg + "No versions defined. You must call `ApiVersion.fetch_known_versions` first." if @versions.empty? | ||
msg + "`#{handle}` is not in the defined version set. Available versions: #{@versions.keys}" | ||
end | ||
end | ||
|
||
def self.latest_stable_version | ||
@versions.values.select(&:stable?).sort.last | ||
attr_reader :handle, :display_name, :supported, :latest_supported, :verified | ||
|
||
def initialize(attributes) | ||
attributes = ActiveSupport::HashWithIndifferentAccess.new(attributes) | ||
@handle = attributes[:handle].to_s | ||
@display_name = attributes.fetch(:display_name, attributes[:handle].to_s) | ||
@supported = attributes.fetch(:supported, false) | ||
@latest_supported = attributes.fetch(:latest_supported, false) | ||
@verified = attributes.fetch(:verified, false) | ||
end | ||
|
||
def to_s | ||
@version_name | ||
handle | ||
end | ||
alias_method :name, :to_s | ||
|
||
def inspect | ||
@version_name | ||
def latest_supported? | ||
latest_supported | ||
end | ||
|
||
def ==(other) | ||
other.class == self.class && to_s == other.to_s | ||
def supported? | ||
supported | ||
end | ||
|
||
def hash | ||
@version_name.hash | ||
def verified? | ||
verified | ||
end | ||
|
||
def <=>(other) | ||
numeric_version <=> other.numeric_version | ||
handle_as_date <=> other.handle_as_date | ||
end | ||
|
||
def stable? | ||
false | ||
def ==(other) | ||
other.class == self.class && handle == other.handle | ||
end | ||
|
||
def construct_api_path(_path) | ||
raise NotImplementedError | ||
def hash | ||
handle.hash | ||
end | ||
|
||
def construct_graphql_path | ||
raise NotImplementedError | ||
def construct_api_path(path) | ||
"#{API_PREFIX}#{handle}/#{path}" | ||
end | ||
|
||
protected | ||
|
||
attr_reader :numeric_version | ||
|
||
class Unstable < ApiVersion | ||
API_PREFIX = '/admin/api/unstable/' | ||
|
||
def initialize | ||
@version_name = "unstable" | ||
@url = API_PREFIX | ||
@numeric_version = 9_000_00 | ||
end | ||
|
||
def construct_api_path(path) | ||
"#{@url}#{path}" | ||
end | ||
|
||
def construct_graphql_path | ||
construct_api_path("graphql.json") | ||
end | ||
def construct_graphql_path | ||
construct_api_path('graphql.json') | ||
end | ||
|
||
class Release < ApiVersion | ||
FORMAT = /^\d{4}-\d{2}$/.freeze | ||
API_PREFIX = '/admin/api/' | ||
|
||
def initialize(version_number) | ||
raise InvalidVersion, version_number unless version_number.match(FORMAT) | ||
@version_name = version_number | ||
@url = "#{API_PREFIX}#{version_number}/" | ||
@numeric_version = version_number.tr('-', '').to_i | ||
end | ||
def name | ||
warn( | ||
'[DEPRECATED] ShopifyAPI::ApiVersion#name is deprecated and will be removed in a future version. ' \ | ||
'Use `handle` instead.' | ||
) | ||
handle | ||
end | ||
|
||
def stable? | ||
true | ||
end | ||
def stable? | ||
warn( | ||
'[DEPRECATED] ShopifyAPI::ApiVersion#stable? is deprecated and will be removed in a future version. ' \ | ||
'Use `supported?` instead.' | ||
) | ||
supported? | ||
end | ||
|
||
def construct_api_path(path) | ||
"#{@url}#{path}" | ||
end | ||
def unstable? | ||
handle == UNSTABLE_HANDLE | ||
end | ||
|
||
def construct_graphql_path | ||
construct_api_path('graphql.json') | ||
end | ||
def handle_as_date | ||
return UNSTABLE_AS_DATE if unstable? | ||
year, month, day = handle.split('-') | ||
Time.utc(year, month, day) | ||
end | ||
|
||
class NullVersion | ||
class << self | ||
def stable? | ||
raise ApiVersionNotSetError, "You must set ShopifyAPI::Base.api_version before making a request." | ||
end | ||
|
||
def construct_api_path(*_path) | ||
raise ApiVersionNotSetError, "You must set ShopifyAPI::Base.api_version before making a request." | ||
end | ||
|
||
def construct_graphql_path | ||
def raise_not_set_error(*_args) | ||
raise ApiVersionNotSetError, "You must set ShopifyAPI::Base.api_version before making a request." | ||
end | ||
alias_method :stable?, :raise_not_set_error | ||
alias_method :construct_api_path, :raise_not_set_error | ||
alias_method :construct_graphql_path, :raise_not_set_error | ||
alias_method :latest_supported?, :raise_not_set_error | ||
alias_method :supported?, :raise_not_set_error | ||
alias_method :verified?, :raise_not_set_error | ||
alias_method :unstable?, :raise_not_set_error | ||
alias_method :handle, :raise_not_set_error | ||
alias_method :display_name, :raise_not_set_error | ||
alias_method :supported, :raise_not_set_error | ||
alias_method :verified, :raise_not_set_error | ||
alias_method :latest_supported, :raise_not_set_error | ||
alias_method :name, :raise_not_set_error | ||
end | ||
end | ||
end | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
|
||
# frozen_string_literal: true | ||
module ShopifyAPI | ||
class Meta < ActiveResource::Base | ||
jtgrenz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.site = "https://app.shopify.com/services/" | ||
self.element_name = 'api' | ||
self.primary_key = :handle | ||
self.timeout = 5 | ||
|
||
def self.admin_versions | ||
all.find { |api| api.handle = :admin }.versions | ||
end | ||
end | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might call this
fetch_known_versions
to be explicit that it involves a remote lookupThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think I had that at first and at some point changed it back to minimize the number of changes or something? 🤔 It sorta made sense at the time, but I think you're right that fetch is a better choice.
Not sure if I should just rename this or alias it and issue a depreciation warning