api_version.rb 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  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 ' \
  50. 'removed in a future version. 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 ' \
  63. 'removed in a future version. 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 ' \
  70. 'removed in a future version.'
  71. )
  72. versions.values.find(&:latest_supported?)
  73. end
  74. private
  75. def sanitize_known_versions
  76. return if @versions.nil?
  77. @versions = @versions.keys.map do |handle|
  78. next unless @versions[handle].verified?
  79. [handle, @versions[handle]]
  80. end.compact.to_h
  81. end
  82. def unknown_version_error_message(handle)
  83. msg = "ApiVersion.version_lookup_mode is set to `:raise_on_unknown`. \n"
  84. return msg + "No versions defined. You must call `ApiVersion.fetch_known_versions` first." if @versions.empty?
  85. msg + "`#{handle}` is not in the defined version set. Available versions: #{@versions.keys}"
  86. end
  87. end
  88. attr_reader :handle, :display_name, :supported, :latest_supported, :verified
  89. def initialize(attributes)
  90. attributes = ActiveSupport::HashWithIndifferentAccess.new(attributes)
  91. @handle = attributes[:handle].to_s
  92. @display_name = attributes.fetch(:display_name, attributes[:handle].to_s)
  93. @supported = attributes.fetch(:supported, false)
  94. @latest_supported = attributes.fetch(:latest_supported, false)
  95. @verified = attributes.fetch(:verified, false)
  96. end
  97. def to_s
  98. handle
  99. end
  100. def latest_supported?
  101. latest_supported
  102. end
  103. def supported?
  104. supported
  105. end
  106. def verified?
  107. verified
  108. end
  109. def <=>(other)
  110. handle_as_date <=> other.handle_as_date
  111. end
  112. def ==(other)
  113. other.class == self.class && handle == other.handle
  114. end
  115. def hash
  116. handle.hash
  117. end
  118. def construct_api_path(path)
  119. "#{API_PREFIX}#{handle}/#{path}"
  120. end
  121. def construct_graphql_path
  122. construct_api_path('graphql.json')
  123. end
  124. def name
  125. warn(
  126. '[DEPRECATED] ShopifyAPI::ApiVersion#name is deprecated and will be removed in a future version. ' \
  127. 'Use `handle` instead.'
  128. )
  129. handle
  130. end
  131. def stable?
  132. warn(
  133. '[DEPRECATED] ShopifyAPI::ApiVersion#stable? is deprecated and will be removed in a future version. ' \
  134. 'Use `supported?` instead.'
  135. )
  136. supported?
  137. end
  138. def unstable?
  139. handle == UNSTABLE_HANDLE
  140. end
  141. def handle_as_date
  142. return UNSTABLE_AS_DATE if unstable?
  143. year, month, day = handle.split('-')
  144. Time.utc(year, month, day)
  145. end
  146. class NullVersion
  147. class << self
  148. def new(*_args)
  149. raise NoMethodError, "NullVersion is an abstract class and cannot be instantiated."
  150. end
  151. def matches?(version)
  152. version.nil? || version == self
  153. end
  154. def raise_not_set_error(*_args)
  155. raise ApiVersionNotSetError, "You must set ShopifyAPI::Base.api_version before making a request."
  156. end
  157. alias_method :stable?, :raise_not_set_error
  158. alias_method :construct_api_path, :raise_not_set_error
  159. alias_method :construct_graphql_path, :raise_not_set_error
  160. alias_method :latest_supported?, :raise_not_set_error
  161. alias_method :supported?, :raise_not_set_error
  162. alias_method :verified?, :raise_not_set_error
  163. alias_method :unstable?, :raise_not_set_error
  164. alias_method :handle, :raise_not_set_error
  165. alias_method :display_name, :raise_not_set_error
  166. alias_method :supported, :raise_not_set_error
  167. alias_method :verified, :raise_not_set_error
  168. alias_method :latest_supported, :raise_not_set_error
  169. alias_method :name, :raise_not_set_error
  170. end
  171. end
  172. end
  173. end