api_version.rb 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. # frozen_string_literal: true
  2. module ShopifyAPI
  3. class ApiVersion
  4. class UnknownVersion < StandardError; end
  5. class ApiVersionNotSetError < StandardError; end
  6. include Comparable
  7. UNSTABLE_HANDLE = 'unstable'
  8. HANDLE_FORMAT = /((\d{4}-\d{2})|#{UNSTABLE_HANDLE})/.freeze
  9. UNSTABLE_AS_DATE = Time.utc(3000, 1, 1)
  10. API_PREFIX = '/admin/api/'
  11. LOOKUP_MODES = [:raise_on_unknown, :define_on_unknown].freeze
  12. class << self
  13. attr_reader :versions
  14. def version_lookup_mode
  15. @version_lookup_mode ||= :define_on_unknown
  16. end
  17. def version_lookup_mode=(mode)
  18. raise ArgumentError, "Mode must be one of #{LOOKUP_MODES}" unless LOOKUP_MODES.include?(mode)
  19. sanitize_known_versions if mode == :raise_on_unknown
  20. @version_lookup_mode = mode
  21. end
  22. def find_version(version_or_handle)
  23. raise ArgumentError, "NullVersion is not a valid version or version handle." if version_or_handle == NullVersion
  24. return version_or_handle if version_or_handle.is_a?(ApiVersion)
  25. handle = version_or_handle.to_s
  26. @versions ||= {}
  27. @versions.fetch(handle) do
  28. if @version_lookup_mode == :raise_on_unknown
  29. raise UnknownVersion, unknown_version_error_message(handle)
  30. else
  31. add_to_known_versions(ApiVersion.new(handle: handle))
  32. end
  33. end
  34. end
  35. def coerce_to_version(version_or_handle)
  36. warn(
  37. '[DEPRECATED] ShopifyAPI::ApiVersion.coerce_to_version be removed in a future version. ' \
  38. 'Use `find_version` instead.'
  39. )
  40. find_version(version_or_handle)
  41. end
  42. def fetch_known_versions
  43. @versions = Meta.admin_versions.map do |version|
  44. [version.handle, ApiVersion.new(version.attributes.merge(verified: version.persisted?))]
  45. end.to_h
  46. end
  47. def define_known_versions
  48. warn(
  49. '[DEPRECATED] ShopifyAPI::ApiVersion.define_known_versions is deprecated and will be removed in a future version. ' \
  50. 'Use `fetch_known_versions` instead.'
  51. )
  52. fetch_known_versions
  53. end
  54. def add_to_known_versions(version)
  55. @versions[version.handle] = version
  56. end
  57. def clear_known_versions
  58. @versions = {}
  59. end
  60. def clear_defined_versions
  61. warn(
  62. '[DEPRECATED] ShopifyAPI::ApiVersion.clear_defined_versions is deprecated and will be removed in a future version. ' \
  63. 'Use `clear_known_versions` instead.'
  64. )
  65. clear_known_versions
  66. end
  67. def latest_stable_version
  68. warn(
  69. '[DEPRECATED] ShopifyAPI::ApiVersion.latest_stable_version is deprecated and will be removed in a future version.'
  70. )
  71. versions.values.find(&:latest_supported?)
  72. end
  73. private
  74. def sanitize_known_versions
  75. return if @versions.nil?
  76. @versions = @versions.keys.map do |handle|
  77. next unless @versions[handle].verified?
  78. [handle, @versions[handle]]
  79. end.compact.to_h
  80. end
  81. def unknown_version_error_message(handle)
  82. msg = "ApiVersion.version_lookup_mode is set to `:raise_on_unknown`. \n"
  83. return msg + "No versions defined. You must call `ApiVersion.fetch_known_versions` first." if @versions.empty?
  84. msg + "`#{handle}` is not in the defined version set. Available versions: #{@versions.keys}"
  85. end
  86. end
  87. attr_reader :handle, :display_name, :supported, :latest_supported, :verified
  88. def initialize(attributes)
  89. attributes = ActiveSupport::HashWithIndifferentAccess.new(attributes)
  90. @handle = attributes[:handle].to_s
  91. @display_name = attributes.fetch(:display_name, attributes[:handle].to_s)
  92. @supported = attributes.fetch(:supported, false)
  93. @latest_supported = attributes.fetch(:latest_supported, false)
  94. @verified = attributes.fetch(:verified, false)
  95. end
  96. def to_s
  97. handle
  98. end
  99. def latest_supported?
  100. latest_supported
  101. end
  102. def supported?
  103. supported
  104. end
  105. def verified?
  106. verified
  107. end
  108. def <=>(other)
  109. handle_as_date <=> other.handle_as_date
  110. end
  111. def ==(other)
  112. other.class == self.class && handle == other.handle
  113. end
  114. def hash
  115. handle.hash
  116. end
  117. def construct_api_path(path)
  118. "#{API_PREFIX}#{handle}/#{path}"
  119. end
  120. def construct_graphql_path
  121. construct_api_path('graphql.json')
  122. end
  123. def name
  124. warn(
  125. '[DEPRECATED] ShopifyAPI::ApiVersion#name is deprecated and will be removed in a future version. ' \
  126. 'Use `handle` instead.'
  127. )
  128. handle
  129. end
  130. def stable?
  131. warn(
  132. '[DEPRECATED] ShopifyAPI::ApiVersion#stable? is deprecated and will be removed in a future version. ' \
  133. 'Use `supported?` instead.'
  134. )
  135. supported?
  136. end
  137. def unstable?
  138. handle == UNSTABLE_HANDLE
  139. end
  140. def handle_as_date
  141. return UNSTABLE_AS_DATE if unstable?
  142. year, month, day = handle.split('-')
  143. Time.utc(year, month, day)
  144. end
  145. class NullVersion
  146. class << self
  147. def new(*_args)
  148. raise NoMethodError, "NullVersion is an abstract class and cannot be instantiated."
  149. end
  150. def matches?(version)
  151. version.nil? || version == self
  152. end
  153. def raise_not_set_error(*_args)
  154. raise ApiVersionNotSetError, "You must set ShopifyAPI::Base.api_version before making a request."
  155. end
  156. alias_method :stable?, :raise_not_set_error
  157. alias_method :construct_api_path, :raise_not_set_error
  158. alias_method :construct_graphql_path, :raise_not_set_error
  159. alias_method :latest_supported?, :raise_not_set_error
  160. alias_method :supported?, :raise_not_set_error
  161. alias_method :verified?, :raise_not_set_error
  162. alias_method :unstable?, :raise_not_set_error
  163. alias_method :handle, :raise_not_set_error
  164. alias_method :display_name, :raise_not_set_error
  165. alias_method :supported, :raise_not_set_error
  166. alias_method :verified, :raise_not_set_error
  167. alias_method :latest_supported, :raise_not_set_error
  168. alias_method :name, :raise_not_set_error
  169. end
  170. end
  171. end
  172. end